{"thread":{"id":"32845","subject":"[PATCH v4] submodule: add 'deinit' command","startedAt":"2013-02-06T21:11:18Z","lastAt":"2013-04-17T20:30:30Z","messageCount":21,"participants":["Jens Lehmann","Phil Hord","Junio C Hamano","Heiko Voigt"],"isPatch":true,"patchVersion":4,"patchTotal":null},"messages":[{"id":"208865","messageId":"5112C6F6.4030607@web.de","threadId":"32845","inReplyTo":null,"subject":"[PATCH v4] submodule: add 'deinit' command","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-02-06T21:11:18Z","receivedAt":"2013-02-06T21:11:18Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"With \"git submodule init\" the user is able to tell git he cares about one\nor more submodules and wants to have it populated on the next call to \"git\nsubmodule update\". But currently there is no easy way he could tell git he\ndoes not care about a submodule anymore and wants to get rid of his local\nwork tree (except he knows a lot about submodule internals and removes the\n\"submodule.$name.url\" setting from .git/config together with the work tree\nhimself).\n\nHelp those users by providing a 'deinit' command. This removes the whole\nsubmodule.<name> section from .git/config either for the given\nsubmodule(s) or for all those which have been initialized if '.' is\ngiven. Fail if the current work tree contains modifications unless\nforced. Complain when for a submodule given on the command line the url\nsetting can't be found in .git/config, but nonetheless don't fail.\n\nAdd tests and link the man pages of \"git submodule deinit\" and \"git rm\"\nto assist the user in deciding whether removing or unregistering the\nsubmodule is the right thing to do for him.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\nChanges since v3:\n- Add deinit to the --force documentation of \"git submodule\"\n- Never remove submodules containing a .git dir, even when forced\n- Diagnostic output when \"rm -rf\" or \"mkdir\" fails\n- More test cases\n\n\n Documentation/git-rm.txt        |   4 ++\n Documentation/git-submodule.txt |  18 +++++++-\n git-submodule.sh                |  78 ++++++++++++++++++++++++++++++-\n t/t7400-submodule-basic.sh      | 100 ++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 198 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt\nindex 92bac27..1d876c2 100644\n--- a/Documentation/git-rm.txt\n+++ b/Documentation/git-rm.txt\n@@ -149,6 +149,10 @@ files that aren't ignored are present in the submodules work tree.\n Ignored files are deemed expendable and won't stop a submodule's work\n tree from being removed.\n\n+If you only want to remove the local checkout of a submodule from your\n+work tree without committing the removal,\n+use linkgit:git-submodule[1] `deinit` instead.\n+\n EXAMPLES\n --------\n `git rm Documentation/\\*.txt`::\ndiff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt\nindex a0c9df8..bc06159 100644\n--- a/Documentation/git-submodule.txt\n+++ b/Documentation/git-submodule.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t      [--reference <repository>] [--] <repository> [<path>]\n 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]\n 'git submodule' [--quiet] init [--] [<path>...]\n+'git submodule' [--quiet] deinit [-f|--force] [--] <path>...\n 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] [--rebase]\n \t      [--reference <repository>] [--merge] [--recursive] [--] [<path>...]\n 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]\n@@ -134,6 +135,19 @@ init::\n \tthe explicit 'init' step if you do not intend to customize\n \tany submodule locations.\n\n+deinit::\n+\tUnregister the given submodules, i.e. remove the whole\n+\t`submodule.$name` section from .git/config together with their work\n+\ttree. Further calls to `git submodule update`, `git submodule foreach`\n+\tand `git submodule sync` will skip any unregistered submodules until\n+\tthey are initialized again, so use this command if you don't want to\n+\thave a local checkout of the submodule in your work tree anymore. If\n+\tyou really want to remove a submodule from the repository and commit\n+\tthat use linkgit:git-rm[1] instead.\n++\n+If `--force` is specified, the submodule's work tree will be removed even if\n+it contains local modifications.\n+\n update::\n \tUpdate the registered submodules, i.e. clone missing submodules and\n \tcheckout the commit specified in the index of the containing repository.\n@@ -213,8 +227,10 @@ OPTIONS\n\n -f::\n --force::\n-\tThis option is only valid for add and update commands.\n+\tThis option is only valid for add, deinit and update commands.\n \tWhen running add, allow adding an otherwise ignored submodule path.\n+\tWhen running deinit the submodule work trees will be removed even if\n+\tthey contain local changes.\n \tWhen running update, throw away local changes in submodules when\n \tswitching to a different commit; and always run a checkout operation\n \tin the submodule, even if the commit listed in the index of the\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 004c034..f1b552f 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -8,6 +8,7 @@ dashless=$(basename \"$0\" | sed -e 's/-/ /')\n USAGE=\"[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]\n    or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]\n    or: $dashless [--quiet] init [--] [<path>...]\n+   or: $dashless [--quiet] deinit [-f|--force] [--] <path>...\n    or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]\n    or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]\n    or: $dashless [--quiet] foreach [--recursive] <command>\n@@ -547,6 +548,81 @@ cmd_init()\n }\n\n #\n+# Unregister submodules from .git/config and remove their work tree\n+#\n+# $@ = requested paths (use '.' to deinit all submodules)\n+#\n+cmd_deinit()\n+{\n+\t# parse $args after \"submodule ... init\".\n+\twhile test $# -ne 0\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t-f|--force)\n+\t\t\tforce=$1\n+\t\t\t;;\n+\t\t-q|--quiet)\n+\t\t\tGIT_QUIET=1\n+\t\t\t;;\n+\t\t--)\n+\t\t\tshift\n+\t\t\tbreak\n+\t\t\t;;\n+\t\t-*)\n+\t\t\tusage\n+\t\t\t;;\n+\t\t*)\n+\t\t\tbreak\n+\t\t\t;;\n+\t\tesac\n+\t\tshift\n+\tdone\n+\n+\tif test $# = 0\n+\tthen\n+\t\tdie \"$(eval_gettext \"Use '.' if you really want to deinitialize all submodules\")\"\n+\tfi\n+\n+\tmodule_list \"$@\" |\n+\twhile read mode sha1 stage sm_path\n+\tdo\n+\t\tdie_if_unmatched \"$mode\"\n+\t\tname=$(module_name \"$sm_path\") || exit\n+\t\turl=$(git config submodule.\"$name\".url)\n+\t\tif test -z \"$url\"\n+\t\tthen\n+\t\t\tsay \"$(eval_gettext \"No url found for submodule path '\\$sm_path' in .git/config\")\"\n+\t\t\tcontinue\n+\t\tfi\n+\n+\t\t# Remove the submodule work tree (unless the user already did it)\n+\t\tif test -d \"$sm_path\"\n+\t\tthen\n+\t\t\t# Protect submodules containing a .git directory\n+\t\t\tif test -d \"$sm_path/.git\"\n+\t\t\tthen\n+\t\t\t\techo >&2 \"$(eval_gettext \"Submodule work tree $sm_path contains a .git directory\")\"\n+\t\t\t\tdie \"$(eval_gettext \"(use 'rm -rf' if you really want to remove it including all of its history)\")\"\n+\t\t\tfi\n+\n+\t\t\tif test -z \"$force\"\n+\t\t\tthen\n+\t\t\t\tgit rm -n \"$sm_path\" ||\n+\t\t\t\tdie \"$(eval_gettext \"Submodule work tree $sm_path contains local modifications, use '-f' to discard them\")\"\n+\t\t\tfi\n+\t\t\trm -rf \"$sm_path\" || say \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n+\t\tfi\n+\n+\t\tmkdir \"$sm_path\" || say \"$(eval_gettext \"Could not create empty submodule directory '\\$sm_path'\")\"\n+\n+\t\t# Remove the whole section so we have a clean state when the\n+\t\t# user later decides to init this submodule again\n+\t\tgit config --remove-section submodule.\"$name\" &&\n+\t\tsay \"$(eval_gettext \"Submodule '\\$name' (\\$url) unregistered for path '\\$sm_path'\")\"\n+\tdone\n+}\n+\n+#\n # Update each submodule path to correct revision, using clone and checkout as needed\n #\n # $@ = requested paths (default to all)\n@@ -1157,7 +1233,7 @@ cmd_sync()\n while test $# != 0 && test -z \"$command\"\n do\n \tcase \"$1\" in\n-\tadd | foreach | init | update | status | summary | sync)\n+\tadd | foreach | init | deinit | update | status | summary | sync)\n \t\tcommand=$1\n \t\t;;\n \t-q|--quiet)\ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex 2683cba..f54a40d 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -757,4 +757,104 @@ test_expect_success 'submodule add with an existing name fails unless forced' '\n \t)\n '\n\n+test_expect_success 'set up a second submodule' '\n+\tgit submodule add ./init2 example2 &&\n+\tgit commit -m \"submodle example2 added\"\n+'\n+\n+test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' '\n+\tgit config submodule.example.foo bar &&\n+\tgit config submodule.example2.frotz nitfol &&\n+\tgit submodule deinit init &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\ttest -z \"$(git config submodule.example.foo)\" &&\n+\ttest -n \"$(git config submodule.example2.url)\" &&\n+\ttest -n \"$(git config submodule.example2.frotz)\" &&\n+\ttest -f example2/.git &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit . deinits all initialized submodules' '\n+\tgit submodule update --init &&\n+\tgit config submodule.example.foo bar &&\n+\tgit config submodule.example2.frotz nitfol &&\n+\ttest_must_fail git submodule deinit &&\n+\tgit submodule deinit . &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\ttest -z \"$(git config submodule.example.foo)\" &&\n+\ttest -z \"$(git config submodule.example2.url)\" &&\n+\ttest -z \"$(git config submodule.example2.frotz)\" &&\n+\trmdir init example2\n+'\n+\n+test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' '\n+\tgit submodule update --init &&\n+\trm -rf init example2/* example2/.git &&\n+\tgit submodule deinit init example2 &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\ttest -z \"$(git config submodule.example2.url)\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit fails when the submodule contains modifications unless forced' '\n+\tgit submodule update --init &&\n+\techo X >>init/s &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest -n \"$(git config submodule.example.url)\" &&\n+\ttest -f example2/.git &&\n+\tgit submodule deinit -f init &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit fails when the submodule contains untracked files unless forced' '\n+\tgit submodule update --init &&\n+\techo X >>init/untracked &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest -n \"$(git config submodule.example.url)\" &&\n+\ttest -f example2/.git &&\n+\tgit submodule deinit -f init &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit fails when the submodule HEAD does not match unless forced' '\n+\tgit submodule update --init &&\n+\t(\n+\t\tcd init &&\n+\t\tgit checkout HEAD^\n+\t) &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest -n \"$(git config submodule.example.url)\" &&\n+\ttest -f example2/.git &&\n+\tgit submodule deinit -f init &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit complains but does not fail when used on an uninitialized submodule' '\n+\tgit submodule update --init &&\n+\tgit submodule deinit init >actual &&\n+\ttest_i18ngrep \"Submodule .example. (.*) unregistered for path .init\" actual\n+\tgit submodule deinit init >actual &&\n+\ttest_i18ngrep \"No url found for submodule path .init. in .git/config\" actual &&\n+\tgit submodule deinit . >actual &&\n+\ttest_i18ngrep \"Submodule .example2. (.*) unregistered for path .example2\" actual\n+\trmdir init example2\n+'\n+\n+test_expect_success 'submodule deinit fails when submodule has a .git directory even when forced' '\n+\tgit submodule update --init &&\n+\t(\n+\t\tcd init &&\n+\t\trm .git &&\n+\t\tcp -R ../.git/modules/example .git &&\n+\t\tGIT_WORK_TREE=. git config --unset core.worktree\n+\t) &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest_must_fail git submodule deinit -f init &&\n+\ttest -d init/.git &&\n+\ttest -n \"$(git config submodule.example.url)\"\n+'\n+\n test_done\n-- \n1.8.1.2.546.gea155c0\n"},{"id":"209376","messageId":"CABURp0oQcPotK20QcqCG1pGQPVoa4RnN2nDA=iQoKS99gnPEAQ@mail.gmail.com","threadId":"32845","inReplyTo":"5112C6F6.4030607@web.de","subject":"Re: [PATCH v4] submodule: add 'deinit' command","fromName":"Phil Hord","fromEmail":"phil.hord@gmail.com","sentAt":"2013-02-12T17:11:48Z","receivedAt":"2013-02-12T17:11:48Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"I haven't tried it yet, but I have some comments.\n\nOn Wed, Feb 6, 2013 at 4:11 PM, Jens Lehmann <Jens.Lehmann@web.de> wrote:\n> With \"git submodule init\" the user is able to tell git he cares about one\n> or more submodules and wants to have it populated on the next call to \"git\n> submodule update\". But currently there is no easy way he could tell git he\n> does not care about a submodule anymore and wants to get rid of his local\n> work tree (except he knows a lot about submodule internals and removes the\n> \"submodule.$name.url\" setting from .git/config together with the work tree\n> himself).\n>\n> Help those users by providing a 'deinit' command. This removes the whole\n> submodule.<name> section from .git/config either for the given\n> submodule(s) or for all those which have been initialized if '.' is\n> given. Fail if the current work tree contains modifications unless\n> forced. Complain when for a submodule given on the command line the url\n> setting can't be found in .git/config, but nonetheless don't fail.\n>\n> Add tests and link the man pages of \"git submodule deinit\" and \"git rm\"\n> to assist the user in deciding whether removing or unregistering the\n> submodule is the right thing to do for him.\n>\n> Signed-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n> ---\n>\n> Changes since v3:\n> - Add deinit to the --force documentation of \"git submodule\"\n> - Never remove submodules containing a .git dir, even when forced\n> - Diagnostic output when \"rm -rf\" or \"mkdir\" fails\n> - More test cases\n>\n>\n>  Documentation/git-rm.txt        |   4 ++\n>  Documentation/git-submodule.txt |  18 +++++++-\n>  git-submodule.sh                |  78 ++++++++++++++++++++++++++++++-\n>  t/t7400-submodule-basic.sh      | 100 ++++++++++++++++++++++++++++++++++++++++\n>  4 files changed, 198 insertions(+), 2 deletions(-)\n>\n> diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt\n> index 92bac27..1d876c2 100644\n> --- a/Documentation/git-rm.txt\n> +++ b/Documentation/git-rm.txt\n> @@ -149,6 +149,10 @@ files that aren't ignored are present in the submodules work tree.\n>  Ignored files are deemed expendable and won't stop a submodule's work\n>  tree from being removed.\n>\n> +If you only want to remove the local checkout of a submodule from your\n> +work tree without committing the removal,\n> +use linkgit:git-submodule[1] `deinit` instead.\n> +\n>  EXAMPLES\n>  --------\n>  `git rm Documentation/\\*.txt`::\n> diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt\n> index a0c9df8..bc06159 100644\n> --- a/Documentation/git-submodule.txt\n> +++ b/Documentation/git-submodule.txt\n> @@ -13,6 +13,7 @@ SYNOPSIS\n>               [--reference <repository>] [--] <repository> [<path>]\n>  'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]\n>  'git submodule' [--quiet] init [--] [<path>...]\n> +'git submodule' [--quiet] deinit [-f|--force] [--] <path>...\n>  'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] [--rebase]\n>               [--reference <repository>] [--merge] [--recursive] [--] [<path>...]\n>  'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]\n> @@ -134,6 +135,19 @@ init::\n>         the explicit 'init' step if you do not intend to customize\n>         any submodule locations.\n>\n> +deinit::\n> +       Unregister the given submodules, i.e. remove the whole\n> +       `submodule.$name` section from .git/config together with their work\n> +       tree. Further calls to `git submodule update`, `git submodule foreach`\n> +       and `git submodule sync` will skip any unregistered submodules until\n> +       they are initialized again, so use this command if you don't want to\n> +       have a local checkout of the submodule in your work tree anymore. If\n> +       you really want to remove a submodule from the repository and commit\n> +       that use linkgit:git-rm[1] instead.\n> ++\n> +If `--force` is specified, the submodule's work tree will be removed even if\n> +it contains local modifications.\n> +\n>  update::\n>         Update the registered submodules, i.e. clone missing submodules and\n>         checkout the commit specified in the index of the containing repository.\n> @@ -213,8 +227,10 @@ OPTIONS\n>\n>  -f::\n>  --force::\n> -       This option is only valid for add and update commands.\n> +       This option is only valid for add, deinit and update commands.\n>         When running add, allow adding an otherwise ignored submodule path.\n> +       When running deinit the submodule work trees will be removed even if\n> +       they contain local changes.\n>         When running update, throw away local changes in submodules when\n>         switching to a different commit; and always run a checkout operation\n>         in the submodule, even if the commit listed in the index of the\n> diff --git a/git-submodule.sh b/git-submodule.sh\n> index 004c034..f1b552f 100755\n> --- a/git-submodule.sh\n> +++ b/git-submodule.sh\n> @@ -8,6 +8,7 @@ dashless=$(basename \"$0\" | sed -e 's/-/ /')\n>  USAGE=\"[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]\n>     or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]\n>     or: $dashless [--quiet] init [--] [<path>...]\n> +   or: $dashless [--quiet] deinit [-f|--force] [--] <path>...\n>     or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]\n>     or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]\n>     or: $dashless [--quiet] foreach [--recursive] <command>\n> @@ -547,6 +548,81 @@ cmd_init()\n>  }\n>\n>  #\n> +# Unregister submodules from .git/config and remove their work tree\n> +#\n> +# $@ = requested paths (use '.' to deinit all submodules)\n> +#\n> +cmd_deinit()\n> +{\n> +       # parse $args after \"submodule ... init\".\n> +       while test $# -ne 0\n> +       do\n> +               case \"$1\" in\n> +               -f|--force)\n> +                       force=$1\n> +                       ;;\n> +               -q|--quiet)\n> +                       GIT_QUIET=1\n> +                       ;;\n> +               --)\n> +                       shift\n> +                       break\n> +                       ;;\n> +               -*)\n> +                       usage\n> +                       ;;\n> +               *)\n> +                       break\n> +                       ;;\n> +               esac\n> +               shift\n> +       done\n> +\n> +       if test $# = 0\n> +       then\n> +               die \"$(eval_gettext \"Use '.' if you really want to deinitialize all submodules\")\"\n> +       fi\n> +\n> +       module_list \"$@\" |\n> +       while read mode sha1 stage sm_path\n> +       do\n> +               die_if_unmatched \"$mode\"\n> +               name=$(module_name \"$sm_path\") || exit\n> +               url=$(git config submodule.\"$name\".url)\n> +               if test -z \"$url\"\n> +               then\n> +                       say \"$(eval_gettext \"No url found for submodule path '\\$sm_path' in .git/config\")\"\n\nIs it safe to shelter the user a little bit more from the git\ninternals here and say instead:\n\n   Submodule '\\$sm_path' is not initialized.\n\n\nAlso, I think this code will show this message for each submodule on\n'git submodule deinit .'  But I think I would prefer to suppress it in\nthat case.  If I have not explicitly stated which submodules to\ndeinit, then I do not think git should complain that some of them are\nnot initialized.\n\n> +                       continue\n> +               fi\n> +\n> +               # Remove the submodule work tree (unless the user already did it)\n> +               if test -d \"$sm_path\"\n> +               then\n> +                       # Protect submodules containing a .git directory\n> +                       if test -d \"$sm_path/.git\"\n> +                       then\n> +                               echo >&2 \"$(eval_gettext \"Submodule work tree $sm_path contains a .git directory\")\"\n> +                               die \"$(eval_gettext \"(use 'rm -rf' if you really want to remove it including all of its history)\")\"\n\nI expect this is the right thing to do for now.  But I wonder if we\ncan also move $sm_path/.git to $GIT_DIR/modules/$sm_path in this case\n(though I think I am not spelling this path correctly).  Would that be\nok?  What extra work is needed to relocate the .git dir like this?\n\n> +                       fi\n> +\n> +                       if test -z \"$force\"\n> +                       then\n> +                               git rm -n \"$sm_path\" ||\n> +                               die \"$(eval_gettext \"Submodule work tree $sm_path contains local modifications, use '-f' to discard them\")\"\n\nNit, grammar: use a semicolon instead of a comma.\n\n> +                       fi\n> +                       rm -rf \"$sm_path\" || say \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n> +               fi\n> +\n> +               mkdir \"$sm_path\" || say \"$(eval_gettext \"Could not create empty submodule directory '\\$sm_path'\")\"\n> +\n> +               # Remove the whole section so we have a clean state when the\n> +               # user later decides to init this submodule again\n> +               git config --remove-section submodule.\"$name\" &&\n> +               say \"$(eval_gettext \"Submodule '\\$name' (\\$url) unregistered for path '\\$sm_path'\")\"\n> +       done\n> +}\n> +\n> +#\n>  # Update each submodule path to correct revision, using clone and checkout as needed\n>  #\n>  # $@ = requested paths (default to all)\n> @@ -1157,7 +1233,7 @@ cmd_sync()\n>  while test $# != 0 && test -z \"$command\"\n>  do\n>         case \"$1\" in\n> -       add | foreach | init | update | status | summary | sync)\n> +       add | foreach | init | deinit | update | status | summary | sync)\n>                 command=$1\n>                 ;;\n>         -q|--quiet)\n> diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\n> index 2683cba..f54a40d 100755\n> --- a/t/t7400-submodule-basic.sh\n> +++ b/t/t7400-submodule-basic.sh\n> @@ -757,4 +757,104 @@ test_expect_success 'submodule add with an existing name fails unless forced' '\n>         )\n>  '\n>\n> +test_expect_success 'set up a second submodule' '\n> +       git submodule add ./init2 example2 &&\n> +       git commit -m \"submodle example2 added\"\n\nNit: submodule is misspelled\n\n> +'\n> +\n> +test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' '\n> +       git config submodule.example.foo bar &&\n> +       git config submodule.example2.frotz nitfol &&\n> +       git submodule deinit init &&\n> +       test -z \"$(git config submodule.example.url)\" &&\n> +       test -z \"$(git config submodule.example.foo)\" &&\n> +       test -n \"$(git config submodule.example2.url)\" &&\n> +       test -n \"$(git config submodule.example2.frotz)\" &&\n> +       test -f example2/.git &&\n> +       rmdir init\n> +'\n> +\n> +test_expect_success 'submodule deinit . deinits all initialized submodules' '\n> +       git submodule update --init &&\n> +       git config submodule.example.foo bar &&\n> +       git config submodule.example2.frotz nitfol &&\n> +       test_must_fail git submodule deinit &&\n> +       git submodule deinit . &&\n> +       test -z \"$(git config submodule.example.url)\" &&\n> +       test -z \"$(git config submodule.example.foo)\" &&\n> +       test -z \"$(git config submodule.example2.url)\" &&\n> +       test -z \"$(git config submodule.example2.frotz)\" &&\n> +       rmdir init example2\n> +'\n> +\n> +test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' '\n> +       git submodule update --init &&\n> +       rm -rf init example2/* example2/.git &&\n> +       git submodule deinit init example2 &&\n> +       test -z \"$(git config submodule.example.url)\" &&\n> +       test -z \"$(git config submodule.example2.url)\" &&\n> +       rmdir init\n> +'\n> +\n> +test_expect_success 'submodule deinit fails when the submodule contains modifications unless forced' '\n> +       git submodule update --init &&\n> +       echo X >>init/s &&\n> +       test_must_fail git submodule deinit init &&\n> +       test -n \"$(git config submodule.example.url)\" &&\n> +       test -f example2/.git &&\n> +       git submodule deinit -f init &&\n> +       test -z \"$(git config submodule.example.url)\" &&\n> +       rmdir init\n> +'\n> +\n> +test_expect_success 'submodule deinit fails when the submodule contains untracked files unless forced' '\n> +       git submodule update --init &&\n> +       echo X >>init/untracked &&\n> +       test_must_fail git submodule deinit init &&\n> +       test -n \"$(git config submodule.example.url)\" &&\n> +       test -f example2/.git &&\n> +       git submodule deinit -f init &&\n> +       test -z \"$(git config submodule.example.url)\" &&\n> +       rmdir init\n> +'\n> +\n> +test_expect_success 'submodule deinit fails when the submodule HEAD does not match unless forced' '\n> +       git submodule update --init &&\n> +       (\n> +               cd init &&\n> +               git checkout HEAD^\n> +       ) &&\n> +       test_must_fail git submodule deinit init &&\n> +       test -n \"$(git config submodule.example.url)\" &&\n> +       test -f example2/.git &&\n> +       git submodule deinit -f init &&\n> +       test -z \"$(git config submodule.example.url)\" &&\n> +       rmdir init\n> +'\n> +\n> +test_expect_success 'submodule deinit complains but does not fail when used on an uninitialized submodule' '\n> +       git submodule update --init &&\n> +       git submodule deinit init >actual &&\n> +       test_i18ngrep \"Submodule .example. (.*) unregistered for path .init\" actual\n> +       git submodule deinit init >actual &&\n> +       test_i18ngrep \"No url found for submodule path .init. in .git/config\" actual &&\n> +       git submodule deinit . >actual &&\n> +       test_i18ngrep \"Submodule .example2. (.*) unregistered for path .example2\" actual\n> +       rmdir init example2\n> +'\n> +\n> +test_expect_success 'submodule deinit fails when submodule has a .git directory even when forced' '\n> +       git submodule update --init &&\n> +       (\n> +               cd init &&\n> +               rm .git &&\n> +               cp -R ../.git/modules/example .git &&\n> +               GIT_WORK_TREE=. git config --unset core.worktree\n> +       ) &&\n> +       test_must_fail git submodule deinit init &&\n> +       test_must_fail git submodule deinit -f init &&\n> +       test -d init/.git &&\n> +       test -n \"$(git config submodule.example.url)\"\n> +'\n> +\n>  test_done\n> --\n> 1.8.1.2.546.gea155c0\n>\n>\n"},{"id":"209382","messageId":"7vr4klh0k7.fsf@alter.siamese.dyndns.org","threadId":"32845","inReplyTo":"CABURp0oQcPotK20QcqCG1pGQPVoa4RnN2nDA=iQoKS99gnPEAQ@mail.gmail.com","subject":"Re: [PATCH v4] submodule: add 'deinit' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-02-12T18:03:20Z","receivedAt":"2013-02-12T18:03:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phil Hord <phil.hord@gmail.com> writes:\n\n>> +       if test $# = 0\n>> +       then\n>> +               die \"$(eval_gettext \"Use '.' if you really want to deinitialize all submodules\")\"\n>> +       fi\n>> +\n>> +       module_list \"$@\" |\n>> +       while read mode sha1 stage sm_path\n>> +       do\n>> +               die_if_unmatched \"$mode\"\n>> +               name=$(module_name \"$sm_path\") || exit\n>> +               url=$(git config submodule.\"$name\".url)\n>> +               if test -z \"$url\"\n>> +               then\n>> +                       say \"$(eval_gettext \"No url found for submodule path '\\$sm_path' in .git/config\")\"\n>\n> Is it safe to shelter the user a little bit more from the git\n> internals here and say instead:\n>\n>    Submodule '\\$sm_path' is not initialized.\n\nSounds like a sensible suggestion.\n\n> Also, I think this code will show this message for each submodule on\n> 'git submodule deinit .'  But I think I would prefer to suppress it in\n> that case.  If I have not explicitly stated which submodules to\n> deinit,...\n\nBut isn't it the way to explicitly say \"everything under the sun\"?\nAfter all, what does the message say to \"git submodule deinit\"?\n"},{"id":"209486","messageId":"511BEA75.6000002@web.de","threadId":"32845","inReplyTo":"CABURp0oQcPotK20QcqCG1pGQPVoa4RnN2nDA=iQoKS99gnPEAQ@mail.gmail.com","subject":"Re: [PATCH v4] submodule: add 'deinit' command","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-02-13T19:33:09Z","receivedAt":"2013-02-13T19:33:09Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Am 12.02.2013 18:11, schrieb Phil Hord:\n> On Wed, Feb 6, 2013 at 4:11 PM, Jens Lehmann <Jens.Lehmann@web.de> wrote:\n> +               die_if_unmatched \"$mode\"\n>> +               name=$(module_name \"$sm_path\") || exit\n>> +               url=$(git config submodule.\"$name\".url)\n>> +               if test -z \"$url\"\n>> +               then\n>> +                       say \"$(eval_gettext \"No url found for submodule path '\\$sm_path' in .git/config\")\"\n> \n> Is it safe to shelter the user a little bit more from the git\n> internals here and say instead:\n> \n>    Submodule '\\$sm_path' is not initialized.\n\nYeah, that makes much more sense. But I'd rather use the name too:\n\n   Submodule '\\$name' is not initialized for path '\\$sm_path'\n\n> Also, I think this code will show this message for each submodule on\n> 'git submodule deinit .'  But I think I would prefer to suppress it in\n> that case.  If I have not explicitly stated which submodules to\n> deinit, then I do not think git should complain that some of them are\n> not initialized.\n\nYes, that message gets printed for each uninitialized submodule. We\ncould easily suppress that for '.', but it would be really hard to\nget that right for other wildcards like 'foo*'. (And e.g. running a\n\"submodule update\" also lists all submodules it skips because of an\n\"update=none\" setting, so I'm not sure if it's that important here)\n\nBut if people really want that, I'd suppress that for the '.' case.\nFurther opinions?\n\n>> +                       continue\n>> +               fi\n>> +\n>> +               # Remove the submodule work tree (unless the user already did it)\n>> +               if test -d \"$sm_path\"\n>> +               then\n>> +                       # Protect submodules containing a .git directory\n>> +                       if test -d \"$sm_path/.git\"\n>> +                       then\n>> +                               echo >&2 \"$(eval_gettext \"Submodule work tree $sm_path contains a .git directory\")\"\n>> +                               die \"$(eval_gettext \"(use 'rm -rf' if you really want to remove it including all of its history)\")\"\n> \n> I expect this is the right thing to do for now.  But I wonder if we\n> can also move $sm_path/.git to $GIT_DIR/modules/$sm_path in this case\n> (though I think I am not spelling this path correctly).  Would that be\n> ok?  What extra work is needed to relocate the .git dir like this?\n\nHmm, I'm a bit torn about automagically moving the repo somewhere\nelse. While I think it is a sane solution for most users I suspect\nsome users might hate us for doing that without asking (and with no\noption to turn that off). What about adding a separate \"git submodule\nto-gitfile\" command which does that and hinting that here? However I\ndo have the feeling that this should be done in another commit.\n\n>> +                               die \"$(eval_gettext \"Submodule work tree $sm_path contains local modifications, use '-f' to discard them\")\"\n> \n> Nit, grammar: use a semicolon instead of a comma.\n\nOk. And while looking at it I noticed that \"$sm_path\" should be\n\"'\\$sm_path'\" here, same a few lines above ... sigh\n\n>> +test_expect_success 'set up a second submodule' '\n>> +       git submodule add ./init2 example2 &&\n>> +       git commit -m \"submodle example2 added\"\n> \n> Nit: submodule is misspelled\n\nThanks.\n\nJunio, this looks like a we have v5 as soon as we decide what to do\nwith the \"not initialized\" messages when '.' is used, right?\n\nMy current diff to v4 looks like this:\n\n-------------8<-------------\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex f1b552f..27f8e12 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -591,7 +591,7 @@ cmd_deinit()\n \t\turl=$(git config submodule.\"$name\".url)\n \t\tif test -z \"$url\"\n \t\tthen\n-\t\t\tsay \"$(eval_gettext \"No url found for submodule path '\\$sm_path' in .git/config\")\"\n+\t\t\tsay \"$(eval_gettext \"Submodule '\\$name' is not initialized for path '\\$sm_path'\")\"\n \t\t\tcontinue\n \t\tfi\n\n@@ -601,14 +601,14 @@ cmd_deinit()\n \t\t\t# Protect submodules containing a .git directory\n \t\t\tif test -d \"$sm_path/.git\"\n \t\t\tthen\n-\t\t\t\techo >&2 \"$(eval_gettext \"Submodule work tree $sm_path contains a .git directory\")\"\n+\t\t\t\techo >&2 \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains a .git directory\")\"\n \t\t\t\tdie \"$(eval_gettext \"(use 'rm -rf' if you really want to remove it including all of its history)\")\"\n \t\t\tfi\n\n \t\t\tif test -z \"$force\"\n \t\t\tthen\n \t\t\t\tgit rm -n \"$sm_path\" ||\n-\t\t\t\tdie \"$(eval_gettext \"Submodule work tree $sm_path contains local modifications, use '-f' to discard them\")\"\n+\t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n \t\t\tfi\n \t\t\trm -rf \"$sm_path\" || say \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n \t\tfi\ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex f54a40d..e4b0a59 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -759,7 +759,7 @@ test_expect_success 'submodule add with an existing name fails unless forced' '\n\n test_expect_success 'set up a second submodule' '\n \tgit submodule add ./init2 example2 &&\n-\tgit commit -m \"submodle example2 added\"\n+\tgit commit -m \"submodule example2 added\"\n '\n\n test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' '\n@@ -837,7 +837,7 @@ test_expect_success 'submodule deinit complains but does not fail when used on a\n \tgit submodule deinit init >actual &&\n \ttest_i18ngrep \"Submodule .example. (.*) unregistered for path .init\" actual\n \tgit submodule deinit init >actual &&\n-\ttest_i18ngrep \"No url found for submodule path .init. in .git/config\" actual &&\n+\ttest_i18ngrep \"Submodule .example. is not initialized for path .init\" actual &&\n \tgit submodule deinit . >actual &&\n \ttest_i18ngrep \"Submodule .example2. (.*) unregistered for path .example2\" actual\n \trmdir init example2\n"},{"id":"209490","messageId":"7vip5w6l8s.fsf@alter.siamese.dyndns.org","threadId":"32845","inReplyTo":"511BEA75.6000002@web.de","subject":"Re: [PATCH v4] submodule: add 'deinit' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-02-13T19:56:35Z","receivedAt":"2013-02-13T19:56:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jens Lehmann <Jens.Lehmann@web.de> writes:\n\n> Junio, this looks like a we have v5 as soon as we decide what to do\n> with the \"not initialized\" messages when '.' is used, right?\n\nOK.  I myself do not deeply care if we end up special casing \".\" or\nnot; I'll leave it up to you and other submodule folks.\n\nThanks.\n"},{"id":"209612","messageId":"5121384B.10009@web.de","threadId":"32845","inReplyTo":"7vip5w6l8s.fsf@alter.siamese.dyndns.org","subject":"[PATCH v5] submodule: add 'deinit' command","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-02-17T20:06:35Z","receivedAt":"2013-02-17T20:06:35Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"With \"git submodule init\" the user is able to tell git he cares about one\nor more submodules and wants to have it populated on the next call to \"git\nsubmodule update\". But currently there is no easy way he could tell git he\ndoes not care about a submodule anymore and wants to get rid of his local\nwork tree (except he knows a lot about submodule internals and removes the\n\"submodule.$name.url\" setting from .git/config together with the work tree\nhimself).\n\nHelp those users by providing a 'deinit' command. This removes the whole\nsubmodule.<name> section from .git/config either for the given\nsubmodule(s) or for all those which have been initialized if '.' is\ngiven. Fail if the current work tree contains modifications unless\nforced. Complain when for a submodule given on the command line the url\nsetting can't be found in .git/config, but nonetheless don't fail.\n\nAdd tests and link the man pages of \"git submodule deinit\" and \"git rm\"\nto assist the user in deciding whether removing or unregistering the\nsubmodule is the right thing to do for him. Also add the deinit subcommand\nto the completion list.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\nAm 13.02.2013 20:56, schrieb Junio C Hamano:\n> Jens Lehmann <Jens.Lehmann@web.de> writes:\n> \n>> Junio, this looks like a we have v5 as soon as we decide what to do\n>> with the \"not initialized\" messages when '.' is used, right?\n> \n> OK.  I myself do not deeply care if we end up special casing \".\" or\n> not; I'll leave it up to you and other submodule folks.\n\nHere we go, changes to v4 are:\n\n- I decided to do the proposed special casing for \".\"; no messages\n  about uninitialized submodules will show up anymore (this is also\n  tested)\n- The spelling fixes and better 'uninitialized' message Phil proposed\n- Added the missing quotation for $sm_path in output strings\n- \"deinit\" is added to the submodule completion list\n- Added two missing \"&&\" in t7400\n\n\n\n Documentation/git-rm.txt               |   4 ++\n Documentation/git-submodule.txt        |  18 +++++-\n contrib/completion/git-completion.bash |   2 +-\n git-submodule.sh                       |  79 +++++++++++++++++++++++++-\n t/t7400-submodule-basic.sh             | 101 +++++++++++++++++++++++++++++++++\n 5 files changed, 201 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt\nindex 92bac27..1d876c2 100644\n--- a/Documentation/git-rm.txt\n+++ b/Documentation/git-rm.txt\n@@ -149,6 +149,10 @@ files that aren't ignored are present in the submodules work tree.\n Ignored files are deemed expendable and won't stop a submodule's work\n tree from being removed.\n\n+If you only want to remove the local checkout of a submodule from your\n+work tree without committing the removal,\n+use linkgit:git-submodule[1] `deinit` instead.\n+\n EXAMPLES\n --------\n `git rm Documentation/\\*.txt`::\ndiff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt\nindex a0c9df8..bc06159 100644\n--- a/Documentation/git-submodule.txt\n+++ b/Documentation/git-submodule.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t      [--reference <repository>] [--] <repository> [<path>]\n 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]\n 'git submodule' [--quiet] init [--] [<path>...]\n+'git submodule' [--quiet] deinit [-f|--force] [--] <path>...\n 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] [--rebase]\n \t      [--reference <repository>] [--merge] [--recursive] [--] [<path>...]\n 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]\n@@ -134,6 +135,19 @@ init::\n \tthe explicit 'init' step if you do not intend to customize\n \tany submodule locations.\n\n+deinit::\n+\tUnregister the given submodules, i.e. remove the whole\n+\t`submodule.$name` section from .git/config together with their work\n+\ttree. Further calls to `git submodule update`, `git submodule foreach`\n+\tand `git submodule sync` will skip any unregistered submodules until\n+\tthey are initialized again, so use this command if you don't want to\n+\thave a local checkout of the submodule in your work tree anymore. If\n+\tyou really want to remove a submodule from the repository and commit\n+\tthat use linkgit:git-rm[1] instead.\n++\n+If `--force` is specified, the submodule's work tree will be removed even if\n+it contains local modifications.\n+\n update::\n \tUpdate the registered submodules, i.e. clone missing submodules and\n \tcheckout the commit specified in the index of the containing repository.\n@@ -213,8 +227,10 @@ OPTIONS\n\n -f::\n --force::\n-\tThis option is only valid for add and update commands.\n+\tThis option is only valid for add, deinit and update commands.\n \tWhen running add, allow adding an otherwise ignored submodule path.\n+\tWhen running deinit the submodule work trees will be removed even if\n+\tthey contain local changes.\n \tWhen running update, throw away local changes in submodules when\n \tswitching to a different commit; and always run a checkout operation\n \tin the submodule, even if the commit listed in the index of the\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 059ba9d..7cee9bd 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2401,7 +2401,7 @@ _git_submodule ()\n {\n \t__git_has_doubledash && return\n\n-\tlocal subcommands=\"add status init update summary foreach sync\"\n+\tlocal subcommands=\"add status init deinit update summary foreach sync\"\n \tif [ -z \"$(__git_find_on_cmdline \"$subcommands\")\" ]; then\n \t\tcase \"$cur\" in\n \t\t--*)\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 004c034..0fb6ee0 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -8,6 +8,7 @@ dashless=$(basename \"$0\" | sed -e 's/-/ /')\n USAGE=\"[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]\n    or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]\n    or: $dashless [--quiet] init [--] [<path>...]\n+   or: $dashless [--quiet] deinit [-f|--force] [--] <path>...\n    or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]\n    or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]\n    or: $dashless [--quiet] foreach [--recursive] <command>\n@@ -547,6 +548,82 @@ cmd_init()\n }\n\n #\n+# Unregister submodules from .git/config and remove their work tree\n+#\n+# $@ = requested paths (use '.' to deinit all submodules)\n+#\n+cmd_deinit()\n+{\n+\t# parse $args after \"submodule ... init\".\n+\twhile test $# -ne 0\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t-f|--force)\n+\t\t\tforce=$1\n+\t\t\t;;\n+\t\t-q|--quiet)\n+\t\t\tGIT_QUIET=1\n+\t\t\t;;\n+\t\t--)\n+\t\t\tshift\n+\t\t\tbreak\n+\t\t\t;;\n+\t\t-*)\n+\t\t\tusage\n+\t\t\t;;\n+\t\t*)\n+\t\t\tbreak\n+\t\t\t;;\n+\t\tesac\n+\t\tshift\n+\tdone\n+\n+\tif test $# = 0\n+\tthen\n+\t\tdie \"$(eval_gettext \"Use '.' if you really want to deinitialize all submodules\")\"\n+\tfi\n+\n+\tmodule_list \"$@\" |\n+\twhile read mode sha1 stage sm_path\n+\tdo\n+\t\tdie_if_unmatched \"$mode\"\n+\t\tname=$(module_name \"$sm_path\") || exit\n+\t\turl=$(git config submodule.\"$name\".url)\n+\t\tif test -z \"$url\"\n+\t\tthen\n+\t\t\ttest $# -ne 1 || test \"$@\" = \".\" ||\n+\t\t\tsay \"$(eval_gettext \"Submodule '\\$name' is not initialized for path '\\$sm_path'\")\"\n+\t\t\tcontinue\n+\t\tfi\n+\n+\t\t# Remove the submodule work tree (unless the user already did it)\n+\t\tif test -d \"$sm_path\"\n+\t\tthen\n+\t\t\t# Protect submodules containing a .git directory\n+\t\t\tif test -d \"$sm_path/.git\"\n+\t\t\tthen\n+\t\t\t\techo >&2 \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains a .git directory\")\"\n+\t\t\t\tdie \"$(eval_gettext \"(use 'rm -rf' if you really want to remove it including all of its history)\")\"\n+\t\t\tfi\n+\n+\t\t\tif test -z \"$force\"\n+\t\t\tthen\n+\t\t\t\tgit rm -n \"$sm_path\" ||\n+\t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n+\t\t\tfi\n+\t\t\trm -rf \"$sm_path\" || say \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n+\t\tfi\n+\n+\t\tmkdir \"$sm_path\" || say \"$(eval_gettext \"Could not create empty submodule directory '\\$sm_path'\")\"\n+\n+\t\t# Remove the whole section so we have a clean state when the\n+\t\t# user later decides to init this submodule again\n+\t\tgit config --remove-section submodule.\"$name\" &&\n+\t\tsay \"$(eval_gettext \"Submodule '\\$name' (\\$url) unregistered for path '\\$sm_path'\")\"\n+\tdone\n+}\n+\n+#\n # Update each submodule path to correct revision, using clone and checkout as needed\n #\n # $@ = requested paths (default to all)\n@@ -1157,7 +1234,7 @@ cmd_sync()\n while test $# != 0 && test -z \"$command\"\n do\n \tcase \"$1\" in\n-\tadd | foreach | init | update | status | summary | sync)\n+\tadd | foreach | init | deinit | update | status | summary | sync)\n \t\tcommand=$1\n \t\t;;\n \t-q|--quiet)\ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex 2683cba..e32b62b 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -757,4 +757,105 @@ test_expect_success 'submodule add with an existing name fails unless forced' '\n \t)\n '\n\n+test_expect_success 'set up a second submodule' '\n+\tgit submodule add ./init2 example2 &&\n+\tgit commit -m \"submodule example2 added\"\n+'\n+\n+test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' '\n+\tgit config submodule.example.foo bar &&\n+\tgit config submodule.example2.frotz nitfol &&\n+\tgit submodule deinit init &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\ttest -z \"$(git config submodule.example.foo)\" &&\n+\ttest -n \"$(git config submodule.example2.url)\" &&\n+\ttest -n \"$(git config submodule.example2.frotz)\" &&\n+\ttest -f example2/.git &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit . deinits all initialized submodules' '\n+\tgit submodule update --init &&\n+\tgit config submodule.example.foo bar &&\n+\tgit config submodule.example2.frotz nitfol &&\n+\ttest_must_fail git submodule deinit &&\n+\tgit submodule deinit . &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\ttest -z \"$(git config submodule.example.foo)\" &&\n+\ttest -z \"$(git config submodule.example2.url)\" &&\n+\ttest -z \"$(git config submodule.example2.frotz)\" &&\n+\trmdir init example2\n+'\n+\n+test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' '\n+\tgit submodule update --init &&\n+\trm -rf init example2/* example2/.git &&\n+\tgit submodule deinit init example2 &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\ttest -z \"$(git config submodule.example2.url)\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit fails when the submodule contains modifications unless forced' '\n+\tgit submodule update --init &&\n+\techo X >>init/s &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest -n \"$(git config submodule.example.url)\" &&\n+\ttest -f example2/.git &&\n+\tgit submodule deinit -f init &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit fails when the submodule contains untracked files unless forced' '\n+\tgit submodule update --init &&\n+\techo X >>init/untracked &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest -n \"$(git config submodule.example.url)\" &&\n+\ttest -f example2/.git &&\n+\tgit submodule deinit -f init &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit fails when the submodule HEAD does not match unless forced' '\n+\tgit submodule update --init &&\n+\t(\n+\t\tcd init &&\n+\t\tgit checkout HEAD^\n+\t) &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest -n \"$(git config submodule.example.url)\" &&\n+\ttest -f example2/.git &&\n+\tgit submodule deinit -f init &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit complains but does not fail when used on an uninitialized submodule' '\n+\tgit submodule update --init &&\n+\tgit submodule deinit init >actual &&\n+\ttest_i18ngrep \"Submodule .example. (.*) unregistered for path .init\" actual &&\n+\tgit submodule deinit init >actual &&\n+\ttest_i18ngrep \"Submodule .example. is not initialized for path .init\" actual &&\n+\tgit submodule deinit . >actual &&\n+\ttest_i18ngrep \"Submodule .example2. (.*) unregistered for path .example2\" actual &&\n+\ttest_i18ngrep ! \"Submodule .example. is not initialized for path .init\" actual &&\n+\trmdir init example2\n+'\n+\n+test_expect_success 'submodule deinit fails when submodule has a .git directory even when forced' '\n+\tgit submodule update --init &&\n+\t(\n+\t\tcd init &&\n+\t\trm .git &&\n+\t\tcp -R ../.git/modules/example .git &&\n+\t\tGIT_WORK_TREE=. git config --unset core.worktree\n+\t) &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest_must_fail git submodule deinit -f init &&\n+\ttest -d init/.git &&\n+\ttest -n \"$(git config submodule.example.url)\"\n+'\n+\n test_done\n-- \n1.8.1.2.677.ga1bd48d\n"},{"id":"209613","messageId":"7v38wufu5t.fsf@alter.siamese.dyndns.org","threadId":"32845","inReplyTo":"5121384B.10009@web.de","subject":"Re: [PATCH v5] submodule: add 'deinit' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-02-17T22:32:46Z","receivedAt":"2013-02-17T22:32:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jens Lehmann <Jens.Lehmann@web.de> writes:\n\n> Here we go, changes to v4 are:\n>\n> - I decided to do the proposed special casing for \".\"; no messages\n>   about uninitialized submodules will show up anymore (this is also\n>   tested)\n> - The spelling fixes and better 'uninitialized' message Phil proposed\n> - Added the missing quotation for $sm_path in output strings\n> - \"deinit\" is added to the submodule completion list\n> - Added two missing \"&&\" in t7400\n\nThanks for being thorough. I honestly did not expect this topic to\ntake this many cycles before becoming 'next-ready'.\n\n> diff --git a/git-submodule.sh b/git-submodule.sh\n> index 004c034..0fb6ee0 100755\n> --- a/git-submodule.sh\n> +++ b/git-submodule.sh\n> @@ -547,6 +548,82 @@ cmd_init()\n>  }\n>\n>  #\n> +# Unregister submodules from .git/config and remove their work tree\n> +#\n> +# $@ = requested paths (use '.' to deinit all submodules)\n> +#\n> +cmd_deinit()\n> +{\n> +\t# parse $args after \"submodule ... init\".\n> +\twhile test $# -ne 0\n> +\tdo\n> ..\n> +\tdone\n> +\n> +\tif test $# = 0\n> +\tthen\n> +\t\tdie \"$(eval_gettext \"Use '.' if you really want to deinitialize all submodules\")\"\n\nI do not think I saw anybody mentioned this so far, but how is\n\"deinit\" supposed to work inside a subdirectory of a superproject?\nIf the answer is to work on submodules appear in that subdirectory,\n'.' should probably not mean \"all in the superproject\" I think?\n\n> +\tmodule_list \"$@\" |\n> +\twhile read mode sha1 stage sm_path\n> +\tdo\n> +\t\tdie_if_unmatched \"$mode\"\n> +\t\tname=$(module_name \"$sm_path\") || exit\n> +\t\turl=$(git config submodule.\"$name\".url)\n> +\t\tif test -z \"$url\"\n> +\t\tthen\n> +\t\t\ttest $# -ne 1 || test \"$@\" = \".\" ||\n> +\t\t\tsay \"$(eval_gettext \"Submodule '\\$name' is not initialized for path '\\$sm_path'\")\"\n> +\t\t\tcontinue\n> +\t\tfi\n\nThis 'test \"$@\" = \".\"' makes readers feel uneasy.  This particular\ninvocation happens to be safe only because it is protected with\n\"test $# -ne 1 ||\", but for all other values of $# this will result\nin a syntax error.  'test \"$1\" = \".\"' would make the intention of\nthe check more clear.\n\nBut stepping back a bit, is the condition this test is trying to\nwarn against really worth warning?\n\nIt seems that this \"warn if user told us to deinitialize a submodule\nthat hasn't been initialized\" is from the very initial round of this\nseries, and not something other people asked for during the review.\nIf somebody did\n\n\tgit submodule deinit foo bar\n\nand then later said:\n\n\tgit submodule deinit foo\n\nwould it a mistake that the user wants to be warned about?\n\nPerhaps the user did not mean to deinitialize foo (e.g. wanted to\n*initialize* foo instead, or wanted to deinitialize *foz* instead)\nand that is worth warning about?  I am not sure, but I have a\nfeeling that we can do without this check.\n\nAlso the value of submodule.$name.url is not used in the later\ncodepath to ensure that the named submodule is in the pristine state\nin the superproject's working tree (i.e. no submodule.$name section\nin the local configuration, no working tree for that submodule), so\nit may be even a good change to remove the \"does submodule.$name.url\nexist\" check and always do the \"deinitialize\" process.  That would\ngive the users a way to recover from a state where a submodule is\nonly half initialized for some reason safely, no?\n"},{"id":"209702","messageId":"51227EFE.6030908@web.de","threadId":"32845","inReplyTo":"7v38wufu5t.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v5] submodule: add 'deinit' command","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-02-18T19:20:30Z","receivedAt":"2013-02-18T19:20:30Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Am 17.02.2013 23:32, schrieb Junio C Hamano:\n> Jens Lehmann <Jens.Lehmann@web.de> writes:\n>> diff --git a/git-submodule.sh b/git-submodule.sh\n>> index 004c034..0fb6ee0 100755\n>> --- a/git-submodule.sh\n>> +++ b/git-submodule.sh\n>> @@ -547,6 +548,82 @@ cmd_init()\n>>  }\n>>\n>>  #\n>> +# Unregister submodules from .git/config and remove their work tree\n>> +#\n>> +# $@ = requested paths (use '.' to deinit all submodules)\n>> +#\n>> +cmd_deinit()\n>> +{\n>> +\t# parse $args after \"submodule ... init\".\n>> +\twhile test $# -ne 0\n>> +\tdo\n>> ..\n>> +\tdone\n>> +\n>> +\tif test $# = 0\n>> +\tthen\n>> +\t\tdie \"$(eval_gettext \"Use '.' if you really want to deinitialize all submodules\")\"\n> \n> I do not think I saw anybody mentioned this so far, but how is\n> \"deinit\" supposed to work inside a subdirectory of a superproject?\n> If the answer is to work on submodules appear in that subdirectory,\n> '.' should probably not mean \"all in the superproject\" I think?\n\n\"git submodule\" fails when not run in the top level directory.\n\n>> +\tmodule_list \"$@\" |\n>> +\twhile read mode sha1 stage sm_path\n>> +\tdo\n>> +\t\tdie_if_unmatched \"$mode\"\n>> +\t\tname=$(module_name \"$sm_path\") || exit\n>> +\t\turl=$(git config submodule.\"$name\".url)\n>> +\t\tif test -z \"$url\"\n>> +\t\tthen\n>> +\t\t\ttest $# -ne 1 || test \"$@\" = \".\" ||\n>> +\t\t\tsay \"$(eval_gettext \"Submodule '\\$name' is not initialized for path '\\$sm_path'\")\"\n>> +\t\t\tcontinue\n>> +\t\tfi\n> \n> This 'test \"$@\" = \".\"' makes readers feel uneasy.  This particular\n> invocation happens to be safe only because it is protected with\n> \"test $# -ne 1 ||\", but for all other values of $# this will result\n> in a syntax error.  'test \"$1\" = \".\"' would make the intention of\n> the check more clear.\n\nI used $@ here because it makes the code more robust against\nsomeone accidentally removing the \"test $# -ne 1 ||\" (as it then\nwill fail when $# > 1 in one of the tests). But looking at it\nagain I agree that \"$1\" might better show the intention here and\nthe \"$@\" can easily make people suspicious.\n\n> But stepping back a bit, is the condition this test is trying to\n> warn against really worth warning?\n> \n> It seems that this \"warn if user told us to deinitialize a submodule\n> that hasn't been initialized\" is from the very initial round of this\n> series, and not something other people asked for during the review.\n> If somebody did\n> \n> \tgit submodule deinit foo bar\n> \n> and then later said:\n> \n> \tgit submodule deinit foo\n> \n> would it a mistake that the user wants to be warned about?\n> \n> Perhaps the user did not mean to deinitialize foo (e.g. wanted to\n> *initialize* foo instead, or wanted to deinitialize *foz* instead)\n> and that is worth warning about?  I am not sure, but I have a\n> feeling that we can do without this check.\n\nHmm, if you would replace \"submodule deinit\" with \"rm\" in the above\nexample, the \"rm\" would not only warn but error out the second time.\nBut on the other hand doing a \"git submodule init\" again on already\ninitialized submodules will silently do nothing, which seems to be\nthe better analogy here. So yes, it looks like we can do without.\n\nOk, unless someone speaks up and argues in favor of this message\nI'll remove it in v6.\n\n> Also the value of submodule.$name.url is not used in the later\n> codepath to ensure that the named submodule is in the pristine state\n> in the superproject's working tree (i.e. no submodule.$name section\n> in the local configuration, no working tree for that submodule), so\n> it may be even a good change to remove the \"does submodule.$name.url\n> exist\" check and always do the \"deinitialize\" process.  That would\n> give the users a way to recover from a state where a submodule is\n> only half initialized for some reason safely, no?\n\nThat is a very good point. It makes sense that if we don't nuke the\nwhole test to at least remove the \"continue\" there (in case someone\nfiddled with .git/config but wants to get rid of the work tree in a\nsafe manner later).\n"},{"id":"210589","messageId":"51351018.20301@web.de","threadId":"32845","inReplyTo":"51227EFE.6030908@web.de","subject":"[PATCH v6] submodule: add 'deinit' command","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-03-04T21:20:24Z","receivedAt":"2013-03-04T21:20:24Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"With \"git submodule init\" the user is able to tell git he cares about one\nor more submodules and wants to have it populated on the next call to \"git\nsubmodule update\". But currently there is no easy way he could tell git he\ndoes not care about a submodule anymore and wants to get rid of his local\nwork tree (except he knows a lot about submodule internals and removes the\n\"submodule.$name.url\" setting from .git/config together with the work tree\nhimself).\n\nHelp those users by providing a 'deinit' command. This removes the\nwhole submodule.<name> section from .git/config (either for the given\nsubmodule(s) or for all those which have been initialized if '.' is used)\ntogether with their work tree. Fail if the current work tree contains\nmodifications (unless forced), but don't complain when either the work\ntree is already removed or no settings are found in .git/config.\n\nAdd tests and link the man pages of \"git submodule deinit\" and \"git rm\"\nto assist the user in deciding whether removing or unregistering the\nsubmodule is the right thing to do for him. Also add the deinit subcommand\nto the completion list.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\nAm 18.02.2013 20:20, schrieb Jens Lehmann:\n> Am 17.02.2013 23:32, schrieb Junio C Hamano:\n>> It seems that this \"warn if user told us to deinitialize a submodule\n>> that hasn't been initialized\" is from the very initial round of this\n>> series, and not something other people asked for during the review.\n>> If somebody did\n>>\n>> \tgit submodule deinit foo bar\n>>\n>> and then later said:\n>>\n>> \tgit submodule deinit foo\n>>\n>> would it a mistake that the user wants to be warned about?\n>>\n>> Perhaps the user did not mean to deinitialize foo (e.g. wanted to\n>> *initialize* foo instead, or wanted to deinitialize *foz* instead)\n>> and that is worth warning about?  I am not sure, but I have a\n>> feeling that we can do without this check.\n> \n> Hmm, if you would replace \"submodule deinit\" with \"rm\" in the above\n> example, the \"rm\" would not only warn but error out the second time.\n> But on the other hand doing a \"git submodule init\" again on already\n> initialized submodules will silently do nothing, which seems to be\n> the better analogy here. So yes, it looks like we can do without.\n> \n> Ok, unless someone speaks up and argues in favor of this message\n> I'll remove it in v6.\n\nNobody spoke up, so here we go.\n\nChanges since v5:\n\n- No warning message when the url setting is not found\n- Reword second commit message paragraph accordingly\n- Use 'git config --get-regexp \"submodule\\.example\\.\"' in tests\n- Fix a copied comment still talking about \"init\"\n\nHopefully this is the final version ;-)\n\n\n Documentation/git-rm.txt               |   4 ++\n Documentation/git-submodule.txt        |  18 +++++-\n contrib/completion/git-completion.bash |   2 +-\n git-submodule.sh                       |  77 ++++++++++++++++++++++++-\n t/t7400-submodule-basic.sh             | 100 +++++++++++++++++++++++++++++++++\n 5 files changed, 198 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt\nindex 92bac27..1d876c2 100644\n--- a/Documentation/git-rm.txt\n+++ b/Documentation/git-rm.txt\n@@ -149,6 +149,10 @@ files that aren't ignored are present in the submodules work tree.\n Ignored files are deemed expendable and won't stop a submodule's work\n tree from being removed.\n\n+If you only want to remove the local checkout of a submodule from your\n+work tree without committing the removal,\n+use linkgit:git-submodule[1] `deinit` instead.\n+\n EXAMPLES\n --------\n `git rm Documentation/\\*.txt`::\ndiff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt\nindex c99d795..74d5bdc 100644\n--- a/Documentation/git-submodule.txt\n+++ b/Documentation/git-submodule.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t      [--reference <repository>] [--] <repository> [<path>]\n 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]\n 'git submodule' [--quiet] init [--] [<path>...]\n+'git submodule' [--quiet] deinit [-f|--force] [--] <path>...\n 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch]\n \t      [-f|--force] [--rebase] [--reference <repository>]\n \t      [--merge] [--recursive] [--] [<path>...]\n@@ -135,6 +136,19 @@ init::\n \tthe explicit 'init' step if you do not intend to customize\n \tany submodule locations.\n\n+deinit::\n+\tUnregister the given submodules, i.e. remove the whole\n+\t`submodule.$name` section from .git/config together with their work\n+\ttree. Further calls to `git submodule update`, `git submodule foreach`\n+\tand `git submodule sync` will skip any unregistered submodules until\n+\tthey are initialized again, so use this command if you don't want to\n+\thave a local checkout of the submodule in your work tree anymore. If\n+\tyou really want to remove a submodule from the repository and commit\n+\tthat use linkgit:git-rm[1] instead.\n++\n+If `--force` is specified, the submodule's work tree will be removed even if\n+it contains local modifications.\n+\n update::\n \tUpdate the registered submodules, i.e. clone missing submodules and\n \tcheckout the commit specified in the index of the containing repository.\n@@ -214,8 +228,10 @@ OPTIONS\n\n -f::\n --force::\n-\tThis option is only valid for add and update commands.\n+\tThis option is only valid for add, deinit and update commands.\n \tWhen running add, allow adding an otherwise ignored submodule path.\n+\tWhen running deinit the submodule work trees will be removed even if\n+\tthey contain local changes.\n \tWhen running update, throw away local changes in submodules when\n \tswitching to a different commit; and always run a checkout operation\n \tin the submodule, even if the commit listed in the index of the\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex b62bec0..6ab5752 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2413,7 +2413,7 @@ _git_submodule ()\n {\n \t__git_has_doubledash && return\n\n-\tlocal subcommands=\"add status init update summary foreach sync\"\n+\tlocal subcommands=\"add status init deinit update summary foreach sync\"\n \tif [ -z \"$(__git_find_on_cmdline \"$subcommands\")\" ]; then\n \t\tcase \"$cur\" in\n \t\t--*)\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 004c034..44f70c4 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -8,6 +8,7 @@ dashless=$(basename \"$0\" | sed -e 's/-/ /')\n USAGE=\"[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]\n    or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]\n    or: $dashless [--quiet] init [--] [<path>...]\n+   or: $dashless [--quiet] deinit [-f|--force] [--] <path>...\n    or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]\n    or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]\n    or: $dashless [--quiet] foreach [--recursive] <command>\n@@ -547,6 +548,80 @@ cmd_init()\n }\n\n #\n+# Unregister submodules from .git/config and remove their work tree\n+#\n+# $@ = requested paths (use '.' to deinit all submodules)\n+#\n+cmd_deinit()\n+{\n+\t# parse $args after \"submodule ... deinit\".\n+\twhile test $# -ne 0\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t-f|--force)\n+\t\t\tforce=$1\n+\t\t\t;;\n+\t\t-q|--quiet)\n+\t\t\tGIT_QUIET=1\n+\t\t\t;;\n+\t\t--)\n+\t\t\tshift\n+\t\t\tbreak\n+\t\t\t;;\n+\t\t-*)\n+\t\t\tusage\n+\t\t\t;;\n+\t\t*)\n+\t\t\tbreak\n+\t\t\t;;\n+\t\tesac\n+\t\tshift\n+\tdone\n+\n+\tif test $# = 0\n+\tthen\n+\t\tdie \"$(eval_gettext \"Use '.' if you really want to deinitialize all submodules\")\"\n+\tfi\n+\n+\tmodule_list \"$@\" |\n+\twhile read mode sha1 stage sm_path\n+\tdo\n+\t\tdie_if_unmatched \"$mode\"\n+\t\tname=$(module_name \"$sm_path\") || exit\n+\n+\t\t# Remove the submodule work tree (unless the user already did it)\n+\t\tif test -d \"$sm_path\"\n+\t\tthen\n+\t\t\t# Protect submodules containing a .git directory\n+\t\t\tif test -d \"$sm_path/.git\"\n+\t\t\tthen\n+\t\t\t\techo >&2 \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains a .git directory\")\"\n+\t\t\t\tdie \"$(eval_gettext \"(use 'rm -rf' if you really want to remove it including all of its history)\")\"\n+\t\t\tfi\n+\n+\t\t\tif test -z \"$force\"\n+\t\t\tthen\n+\t\t\t\tgit rm -n \"$sm_path\" ||\n+\t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n+\t\t\tfi\n+\t\t\trm -rf \"$sm_path\" || say \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n+\t\tfi\n+\n+\t\tmkdir \"$sm_path\" || say \"$(eval_gettext \"Could not create empty submodule directory '\\$sm_path'\")\"\n+\n+\t\t# Remove the .git/config entries (unless the user already did it)\n+\t\tif test -n \"$(git config --get-regexp submodule.\"$name\\.\")\"\n+\t\tthen\n+\t\t\t# Remove the whole section so we have a clean state when\n+\t\t\t# the user later decides to init this submodule again\n+\t\t\turl=$(git config submodule.\"$name\".url)\n+\t\t\tgit config --remove-section submodule.\"$name\" 2>/dev/null &&\n+\t\t\tsay \"$(eval_gettext \"Submodule '\\$name' (\\$url) unregistered for path '\\$sm_path'\")\"\n+\t\tfi\n+\tdone\n+}\n+\n+#\n # Update each submodule path to correct revision, using clone and checkout as needed\n #\n # $@ = requested paths (default to all)\n@@ -1157,7 +1232,7 @@ cmd_sync()\n while test $# != 0 && test -z \"$command\"\n do\n \tcase \"$1\" in\n-\tadd | foreach | init | update | status | summary | sync)\n+\tadd | foreach | init | deinit | update | status | summary | sync)\n \t\tcommand=$1\n \t\t;;\n \t-q|--quiet)\ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex 2683cba..5030f1f 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -757,4 +757,104 @@ test_expect_success 'submodule add with an existing name fails unless forced' '\n \t)\n '\n\n+test_expect_success 'set up a second submodule' '\n+\tgit submodule add ./init2 example2 &&\n+\tgit commit -m \"submodule example2 added\"\n+'\n+\n+test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' '\n+\tgit config submodule.example.foo bar &&\n+\tgit config submodule.example2.frotz nitfol &&\n+\tgit submodule deinit init &&\n+\ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\ttest -n \"$(git config --get-regexp \"submodule\\.example2\\.\")\" &&\n+\ttest -f example2/.git &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit . deinits all initialized submodules' '\n+\tgit submodule update --init &&\n+\tgit config submodule.example.foo bar &&\n+\tgit config submodule.example2.frotz nitfol &&\n+\ttest_must_fail git submodule deinit &&\n+\tgit submodule deinit . &&\n+\ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\ttest -z \"$(git config --get-regexp \"submodule\\.example2\\.\")\" &&\n+\trmdir init example2\n+'\n+\n+test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' '\n+\tgit submodule update --init &&\n+\trm -rf init example2/* example2/.git &&\n+\tgit submodule deinit init example2 &&\n+\ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\ttest -z \"$(git config --get-regexp \"submodule\\.example2\\.\")\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit fails when the submodule contains modifications unless forced' '\n+\tgit submodule update --init &&\n+\techo X >>init/s &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest -n \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\ttest -f example2/.git &&\n+\tgit submodule deinit -f init &&\n+\ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit fails when the submodule contains untracked files unless forced' '\n+\tgit submodule update --init &&\n+\techo X >>init/untracked &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest -n \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\ttest -f example2/.git &&\n+\tgit submodule deinit -f init &&\n+\ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit fails when the submodule HEAD does not match unless forced' '\n+\tgit submodule update --init &&\n+\t(\n+\t\tcd init &&\n+\t\tgit checkout HEAD^\n+\t) &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest -n \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\ttest -f example2/.git &&\n+\tgit submodule deinit -f init &&\n+\ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\trmdir init\n+'\n+\n+test_expect_success 'submodule deinit is silent when used on an uninitialized submodule' '\n+\tgit submodule update --init &&\n+\tgit submodule deinit init >actual &&\n+\ttest_i18ngrep \"Submodule .example. (.*) unregistered for path .init\" actual &&\n+\tgit submodule deinit init >actual &&\n+\ttest_i18ngrep ! \"Submodule .example. (.*) unregistered for path .init\" actual &&\n+\tgit submodule deinit . >actual &&\n+\ttest_i18ngrep ! \"Submodule .example. (.*) unregistered for path .init\" actual &&\n+\ttest_i18ngrep \"Submodule .example2. (.*) unregistered for path .example2\" actual &&\n+\tgit submodule deinit . >actual &&\n+\ttest_i18ngrep ! \"Submodule .example. (.*) unregistered for path .init\" actual &&\n+\ttest_i18ngrep ! \"Submodule .example2. (.*) unregistered for path .example2\" actual &&\n+\trmdir init example2\n+'\n+\n+test_expect_success 'submodule deinit fails when submodule has a .git directory even when forced' '\n+\tgit submodule update --init &&\n+\t(\n+\t\tcd init &&\n+\t\trm .git &&\n+\t\tcp -R ../.git/modules/example .git &&\n+\t\tGIT_WORK_TREE=. git config --unset core.worktree\n+\t) &&\n+\ttest_must_fail git submodule deinit init &&\n+\ttest_must_fail git submodule deinit -f init &&\n+\ttest -d init/.git &&\n+\ttest -n \"$(git config --get-regexp \"submodule\\.example\\.\")\"\n+'\n+\n test_done\n-- \n1.8.2.rc2.5.geb8e692\n"},{"id":"210610","messageId":"20130305072944.GA4677@sandbox-ub","threadId":"32845","inReplyTo":"51351018.20301@web.de","subject":"Re: [PATCH v6] submodule: add 'deinit' command","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2013-03-05T07:29:46Z","receivedAt":"2013-03-05T07:29:46Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"Hi,\n\nOn Mon, Mar 04, 2013 at 10:20:24PM +0100, Jens Lehmann wrote:\n> diff --git a/git-submodule.sh b/git-submodule.sh\n> index 004c034..44f70c4 100755\n> --- a/git-submodule.sh\n> +++ b/git-submodule.sh\n> @@ -547,6 +548,80 @@ cmd_init()\n>  }\n> \n[...]\n> +\n> +\tmodule_list \"$@\" |\n> +\twhile read mode sha1 stage sm_path\n> +\tdo\n> +\t\tdie_if_unmatched \"$mode\"\n> +\t\tname=$(module_name \"$sm_path\") || exit\n> +\n> +\t\t# Remove the submodule work tree (unless the user already did it)\n> +\t\tif test -d \"$sm_path\"\n> +\t\tthen\n> +\t\t\t# Protect submodules containing a .git directory\n> +\t\t\tif test -d \"$sm_path/.git\"\n> +\t\t\tthen\n> +\t\t\t\techo >&2 \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains a .git directory\")\"\n> +\t\t\t\tdie \"$(eval_gettext \"(use 'rm -rf' if you really want to remove it including all of its history)\")\"\n> +\t\t\tfi\n> +\n> +\t\t\tif test -z \"$force\"\n> +\t\t\tthen\n> +\t\t\t\tgit rm -n \"$sm_path\" ||\n> +\t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n\nMinor nit. IMO, there is an indentation for the || missing here. Maybe\nJunio can squash that in on his side?\n\n> +\t\t\tfi\n> +\t\t\trm -rf \"$sm_path\" || say \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n> +\t\tfi\n> +\n> +\t\tmkdir \"$sm_path\" || say \"$(eval_gettext \"Could not create empty submodule directory '\\$sm_path'\")\"\n\n[...]\n\nEverything else looks good to me.\n\nCheers Heiko\n"},{"id":"210632","messageId":"7vppzdvoj1.fsf@alter.siamese.dyndns.org","threadId":"32845","inReplyTo":"20130305072944.GA4677@sandbox-ub","subject":"Re: [PATCH v6] submodule: add 'deinit' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-05T15:45:22Z","receivedAt":"2013-03-05T15:45:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Heiko Voigt <hvoigt@hvoigt.net> writes:\n\n>> +\t\t\tif test -z \"$force\"\n>> +\t\t\tthen\n>> +\t\t\t\tgit rm -n \"$sm_path\" ||\n>> +\t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n>\n> Minor nit. IMO, there is an indentation for the || missing here. Maybe\n> Junio can squash that in on his side?\n\nSorry, but I do not see an indentation nit here.  The format looks\nperfectly sane to me and in fact any other indentation would be\nwrong.\n\nPuzzled...\n"},{"id":"210672","messageId":"20130306065246.GB4064@sandbox-ub","threadId":"32845","inReplyTo":"7vppzdvoj1.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v6] submodule: add 'deinit' command","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2013-03-06T06:52:46Z","receivedAt":"2013-03-06T06:52:46Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"On Tue, Mar 05, 2013 at 07:45:22AM -0800, Junio C Hamano wrote:\n> Heiko Voigt <hvoigt@hvoigt.net> writes:\n> \n> >> +\t\t\tif test -z \"$force\"\n> >> +\t\t\tthen\n> >> +\t\t\t\tgit rm -n \"$sm_path\" ||\n> >> +\t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n> >\n> > Minor nit. IMO, there is an indentation for the || missing here. Maybe\n> > Junio can squash that in on his side?\n> \n> Sorry, but I do not see an indentation nit here.  The format looks\n> perfectly sane to me and in fact any other indentation would be\n> wrong.\n> \n> Puzzled...\n\nWouldn't you write this code snippet like this to make clear that there\nis another conditional?\n\n\tif test -z \"$force\"\n\tthen\n\t\tgit rm -n \"$sm_path\" ||\n\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n\nIt seems it is not clear in the code base either. Some places do\nsome do not indent:\n\n  git grep -A 1 '||' *.sh\n\nExcept for the testsuite I just assumed that this style was common,\nbecause for me it makes the code much easier to read.\n\nIt seems it is not, so forget my comment.\n\nCheers Heiko\n"},{"id":"211134","messageId":"CABURp0pC2FELxM5aUwxuTqS1roZm+fwkCQA+BoXjrd0+yQMmbg@mail.gmail.com","threadId":"32845","inReplyTo":"5112C6F6.4030607@web.de","subject":"Re: [PATCH v4] submodule: add 'deinit' command","fromName":"Phil Hord","fromEmail":"phil.hord@gmail.com","sentAt":"2013-03-12T15:39:40Z","receivedAt":"2013-03-12T15:39:40Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"On Wed, Feb 6, 2013 at 4:11 PM, Jens Lehmann <Jens.Lehmann@web.de> wrote:\n> With \"git submodule init\" the user is able to tell git he cares about one\n> or more submodules and wants to have it populated on the next call to \"git\n> submodule update\". But currently there is no easy way he could tell git he\n> does not care about a submodule anymore and wants to get rid of his local\n> work tree (except he knows a lot about submodule internals and removes the\n> \"submodule.$name.url\" setting from .git/config together with the work tree\n> himself).\n>\n> Help those users by providing a 'deinit' command. This removes the whole\n> submodule.<name> section from .git/config either for the given\n> submodule(s) or for all those which have been initialized if '.' is\n> given. Fail if the current work tree contains modifications unless\n> forced. Complain when for a submodule given on the command line the url\n> setting can't be found in .git/config, but nonetheless don't fail.\n>\n> Add tests and link the man pages of \"git submodule deinit\" and \"git rm\"\n> to assist the user in deciding whether removing or unregistering the\n> submodule is the right thing to do for him.\n>\n> Signed-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n\n\nProbably because I was new to this command, I was confused by this output.\n\n  $ git submodule deinit submodule\n  rm 'submodule'\n  Submodule 'submodule' (gerrit:foo/submodule) unregistered for path 'submodule'\n\n  $ git rm submodule\n  rm 'submodule'\n\n\nThis line is confusing to me in the deinit command:\n\n  rm 'submodule'\n\nIt doesn't mean what git usually means when it says this to me.  See\nhow the 'git rm' command says the same thing but means something\ndifferent in the next command.\n\nIn the deinit case, git removes the workdir contents of 'submodule'\nand it reports \"rm 'submodule'\".  In the rm case, git removes the\nsubmodule link from the tree and rmdirs the empty 'submodule'\ndirectory.\n\nI think this would be clearer if 'git deinit' said\n\n    rm 'submodule/*'\n\nor maybe\n\n    Removed workdir for 'submodule'\n\nIs it just me?\n\nPhil\n"},{"id":"211140","messageId":"7v1ubk8u6o.fsf@alter.siamese.dyndns.org","threadId":"32845","inReplyTo":"CABURp0pC2FELxM5aUwxuTqS1roZm+fwkCQA+BoXjrd0+yQMmbg@mail.gmail.com","subject":"Re: [PATCH v4] submodule: add 'deinit' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-12T16:22:07Z","receivedAt":"2013-03-12T16:22:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phil Hord <phil.hord@gmail.com> writes:\n\n> I think this would be clearer if 'git deinit' said\n>\n>     rm 'submodule/*'\n>\n> or maybe\n>\n>     Removed workdir for 'submodule'\n>\n> Is it just me?\n\nThe latter may probably be better.  \n\nAfter cloning the superproject, you show interest in individual\nsubmodules by saying \"git submodule init <that submodule>\" and until\nthen your .git/config in the superproject does not indicate that you\nare interested in that submodule, and you won't have a submodule\ncheckout.\n\n\"deinit\" is a way to revert back to that original state; recording\nthat you are no longer interested in the submodule is the primary\neffect, and removal of its checkout is a mere side effect of it.\n"},{"id":"211603","messageId":"51477EFF.2010505@web.de","threadId":"32845","inReplyTo":"7v1ubk8u6o.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v4] submodule: add 'deinit' command","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-03-18T20:54:23Z","receivedAt":"2013-03-18T20:54:23Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Am 12.03.2013 17:22, schrieb Junio C Hamano:\n> Phil Hord <phil.hord@gmail.com> writes:\n> \n>> I think this would be clearer if 'git deinit' said\n>>\n>>     rm 'submodule/*'\n>>\n>> or maybe\n>>\n>>     Removed workdir for 'submodule'\n>>\n>> Is it just me?\n> \n> The latter may probably be better.  \n\nHmm, it doesn't really remove the directory but only empties it\n(it recreates it a few lines after removing it together with its\ncontents). So what about\n\n    Cleared directory 'submodule'\n\nThe attached interdiff suppresses the \"rm 'submodule'\" message\nand issues the \"Cleared ...\" message after it successfully removed\nthe work tree. (But please note that it also prints this message\neven if the submodule work tree is already empty, e.g. when you\ndeinit a submodule the second time)\n\n----------------8<-------------------------\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 204bc78..d003e8a 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -601,10 +601,12 @@ cmd_deinit()\n\n \t\t\tif test -z \"$force\"\n \t\t\tthen\n-\t\t\t\tgit rm -n \"$sm_path\" ||\n+\t\t\t\tgit rm -qn \"$sm_path\" ||\n \t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n \t\t\tfi\n-\t\t\trm -rf \"$sm_path\" || say \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n+\t\t\trm -rf \"$sm_path\" &&\n+\t\t\tsay \"$(eval_gettext \"Cleared directory '\\$sm_path'\")\" ||\n+\t\t\tsay \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n \t\tfi\n\n \t\tmkdir \"$sm_path\" || say \"$(eval_gettext \"Could not create empty submodule directory '\\$sm_path'\")\"\n"},{"id":"211621","messageId":"7v7gl4mabf.fsf@alter.siamese.dyndns.org","threadId":"32845","inReplyTo":"51477EFF.2010505@web.de","subject":"Re: [PATCH v4] submodule: add 'deinit' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-19T01:45:40Z","receivedAt":"2013-03-19T01:45:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jens Lehmann <Jens.Lehmann@web.de> writes:\n\n> Am 12.03.2013 17:22, schrieb Junio C Hamano:\n>> Phil Hord <phil.hord@gmail.com> writes:\n>> \n>>> I think this would be clearer if 'git deinit' said\n>>>\n>>>     rm 'submodule/*'\n>>>\n>>> or maybe\n>>>\n>>>     Removed workdir for 'submodule'\n>>>\n>>> Is it just me?\n>> \n>> The latter may probably be better.  \n>\n> Hmm, it doesn't really remove the directory but only empties it\n> (it recreates it a few lines after removing it together with its\n> contents). So what about\n>\n>     Cleared directory 'submodule'\n\nSounds the cleanest among the suggested phrasing so far.\n"},{"id":"212810","messageId":"5159D9A8.30901@web.de","threadId":"32845","inReplyTo":"7v7gl4mabf.fsf@alter.siamese.dyndns.org","subject":"[PATCH] submodule deinit: clarify work tree removal message","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-04-01T19:02:00Z","receivedAt":"2013-04-01T19:02:00Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"The output of \"git submodule deinit sub\" of a populated submodule prints\n\n  rm 'sub'\n\nas the first line unless used with the -f option.\n\nThe \"rm 'sub'\" line is exactly the same output the user gets when using\n\"git rm sub\" (because that command is used with the --dry-run option under\nthe hood to determine if the submodule is clean), which can easily lead to\nthe false impression that the submodule would be permanently removed. Also\nusers might be confused that the \"rm 'submodule'\" line won't show up when\nthe -f option is used, as the test is skipped in this case.\n\nSilence the \"rm 'submodule'\" output by using the --quiet option for \"git\nrm\" and always print\n\n  Cleared directory 'submodule'\n\ninstead as the first output line. This line is printed as long as the\ndirectory exists, no matter if empty or not.\n\nAlso extend the tests in t7400 to make sure the \"Cleared directory\" line\nis printed correctly.\n\nReported-by: Phil Hord <phil.hord@gmail.com>\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\n\nAm 19.03.2013 02:45, schrieb Junio C Hamano:\n> Jens Lehmann <Jens.Lehmann@web.de> writes:\n>> Am 12.03.2013 17:22, schrieb Junio C Hamano:\n>>> Phil Hord <phil.hord@gmail.com> writes:\n>>>\n>>>> I think this would be clearer if 'git deinit' said\n>>>>\n>>>>     rm 'submodule/*'\n>>>>\n>>>> or maybe\n>>>>\n>>>>     Removed workdir for 'submodule'\n>>>>\n>>>> Is it just me?\n>>>\n>>> The latter may probably be better.  \n>>\n>> Hmm, it doesn't really remove the directory but only empties it\n>> (it recreates it a few lines after removing it together with its\n>> contents). So what about\n>>\n>>     Cleared directory 'submodule'\n> \n> Sounds the cleanest among the suggested phrasing so far.\n\nOkay, so here is the patch for that. If someone could point out\na portable and efficient way to check if a directory is already\nempty I would be happy to use that to silence the \"Cleaned\ndirectory\" message currently printed also when deinit is run on\nan already empty directory. Technically that is correct, as the\n\"rm -rf\" is also executed, but I think it would be better to\nskip the whole if block containing the \"rm -rf\" and \"mkdir\"\ncommands in that case as there is really nothing to do. Calling\n\"find\" to see if there is anything inside the directory sounds\ntoo expensive to me, as the directory will contain a lot of\nfiles sometimes. But maybe this should be done in another patch.\n\n\n git-submodule.sh           |  6 ++++--\n t/t7400-submodule-basic.sh | 21 ++++++++++++++++-----\n 2 files changed, 20 insertions(+), 7 deletions(-)\n\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 204bc78..d003e8a 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -601,10 +601,12 @@ cmd_deinit()\n\n \t\t\tif test -z \"$force\"\n \t\t\tthen\n-\t\t\t\tgit rm -n \"$sm_path\" ||\n+\t\t\t\tgit rm -qn \"$sm_path\" ||\n \t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n \t\t\tfi\n-\t\t\trm -rf \"$sm_path\" || say \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n+\t\t\trm -rf \"$sm_path\" &&\n+\t\t\tsay \"$(eval_gettext \"Cleared directory '\\$sm_path'\")\" ||\n+\t\t\tsay \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n \t\tfi\n\n \t\tmkdir \"$sm_path\" || say \"$(eval_gettext \"Could not create empty submodule directory '\\$sm_path'\")\"\ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex 5030f1f..ff26535 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -777,18 +777,22 @@ test_expect_success 'submodule deinit . deinits all initialized submodules' '\n \tgit config submodule.example.foo bar &&\n \tgit config submodule.example2.frotz nitfol &&\n \ttest_must_fail git submodule deinit &&\n-\tgit submodule deinit . &&\n+\tgit submodule deinit . >actual &&\n \ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n \ttest -z \"$(git config --get-regexp \"submodule\\.example2\\.\")\" &&\n+\ttest_i18ngrep \"Cleared directory .init\" actual &&\n+\ttest_i18ngrep \"Cleared directory .example2\" actual &&\n \trmdir init example2\n '\n\n test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' '\n \tgit submodule update --init &&\n \trm -rf init example2/* example2/.git &&\n-\tgit submodule deinit init example2 &&\n+\tgit submodule deinit init example2 >actual &&\n \ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n \ttest -z \"$(git config --get-regexp \"submodule\\.example2\\.\")\" &&\n+\ttest_i18ngrep ! \"Cleared directory .init\" actual &&\n+\ttest_i18ngrep \"Cleared directory .example2\" actual &&\n \trmdir init\n '\n\n@@ -798,8 +802,9 @@ test_expect_success 'submodule deinit fails when the submodule contains modifica\n \ttest_must_fail git submodule deinit init &&\n \ttest -n \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n \ttest -f example2/.git &&\n-\tgit submodule deinit -f init &&\n+\tgit submodule deinit -f init >actual &&\n \ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\ttest_i18ngrep \"Cleared directory .init\" actual &&\n \trmdir init\n '\n\n@@ -809,8 +814,9 @@ test_expect_success 'submodule deinit fails when the submodule contains untracke\n \ttest_must_fail git submodule deinit init &&\n \ttest -n \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n \ttest -f example2/.git &&\n-\tgit submodule deinit -f init &&\n+\tgit submodule deinit -f init >actual &&\n \ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\ttest_i18ngrep \"Cleared directory .init\" actual &&\n \trmdir init\n '\n\n@@ -823,8 +829,9 @@ test_expect_success 'submodule deinit fails when the submodule HEAD does not mat\n \ttest_must_fail git submodule deinit init &&\n \ttest -n \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n \ttest -f example2/.git &&\n-\tgit submodule deinit -f init &&\n+\tgit submodule deinit -f init >actual &&\n \ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n+\ttest_i18ngrep \"Cleared directory .init\" actual &&\n \trmdir init\n '\n\n@@ -832,14 +839,18 @@ test_expect_success 'submodule deinit is silent when used on an uninitialized su\n \tgit submodule update --init &&\n \tgit submodule deinit init >actual &&\n \ttest_i18ngrep \"Submodule .example. (.*) unregistered for path .init\" actual &&\n+\ttest_i18ngrep \"Cleared directory .init\" actual &&\n \tgit submodule deinit init >actual &&\n \ttest_i18ngrep ! \"Submodule .example. (.*) unregistered for path .init\" actual &&\n+\ttest_i18ngrep \"Cleared directory .init\" actual &&\n \tgit submodule deinit . >actual &&\n \ttest_i18ngrep ! \"Submodule .example. (.*) unregistered for path .init\" actual &&\n \ttest_i18ngrep \"Submodule .example2. (.*) unregistered for path .example2\" actual &&\n+\ttest_i18ngrep \"Cleared directory .init\" actual &&\n \tgit submodule deinit . >actual &&\n \ttest_i18ngrep ! \"Submodule .example. (.*) unregistered for path .init\" actual &&\n \ttest_i18ngrep ! \"Submodule .example2. (.*) unregistered for path .example2\" actual &&\n+\ttest_i18ngrep \"Cleared directory .init\" actual &&\n \trmdir init example2\n '\n\n-- \n1.8.2.358.g5c06bcf\n"},{"id":"214485","messageId":"CABURp0rkeYc7K0COhc1+96Q2Ox2TaNRpMSmQYOiwBpgPJbsafA@mail.gmail.com","threadId":"32845","inReplyTo":"5159D9A8.30901@web.de","subject":"Re: [PATCH] submodule deinit: clarify work tree removal message","fromName":"Phil Hord","fromEmail":"phil.hord@gmail.com","sentAt":"2013-04-16T13:32:21Z","receivedAt":"2013-04-16T13:32:21Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"On Mon, Apr 1, 2013 at 3:02 PM, Jens Lehmann <Jens.Lehmann@web.de> wrote:\n> Okay, so here is the patch for that. If someone could point out\n> a portable and efficient way to check if a directory is already\n> empty I would be happy to use that to silence the \"Cleaned\n> directory\" message currently printed also when deinit is run on\n> an already empty directory.\n\n   isemptydir() {\n        test -d \"$(find $1 -maxdepth 0 -empty)\"\n   }\n\nSorry for the late reply.  I see this patch is already in master\n(which is fine with me).\n\nThanks,\nPhil\n"},{"id":"214538","messageId":"516DB8CE.6050109@web.de","threadId":"32845","inReplyTo":"CABURp0rkeYc7K0COhc1+96Q2Ox2TaNRpMSmQYOiwBpgPJbsafA@mail.gmail.com","subject":"[PATCH] submodule deinit: don't output \"Cleared directory\" when directory is empty","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-04-16T20:47:10Z","receivedAt":"2013-04-16T20:47:10Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Currently a \"submodule deinit\" run on a non-populated submodule will still\nprint the \"Cleared directory\" message even though the directory is already\nempty. While that is technically correct (as the directory is removed and\ncreated again), it is rather surprising to see this message for an empty\nsubmodule directory where nothing is to be cleared.\n\nFix that by using 'test ! -d \"$(find \"$sm_path\" -maxdepth 0 -empty)\"' to\ntest for the directory being not empty before removing and recreating it.\n\nThanks-to: Phil Hord <phil.hord@gmail.com>\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\nAm 16.04.2013 15:32, schrieb Phil Hord:\n> On Mon, Apr 1, 2013 at 3:02 PM, Jens Lehmann <Jens.Lehmann@web.de> wrote:\n>> Okay, so here is the patch for that. If someone could point out\n>> a portable and efficient way to check if a directory is already\n>> empty I would be happy to use that to silence the \"Cleaned\n>> directory\" message currently printed also when deinit is run on\n>> an already empty directory.\n> \n>    isemptydir() {\n>         test -d \"$(find $1 -maxdepth 0 -empty)\"\n>    }\n> \n> Sorry for the late reply.  I see this patch is already in master\n> (which is fine with me).\n\nThanks, I managed to miss that solution when googling for it.\n\n\n git-submodule.sh           | 35 +++++++++++++++++++----------------\n t/t7400-submodule-basic.sh |  8 ++++----\n 2 files changed, 23 insertions(+), 20 deletions(-)\n\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 79bfaac..52ecbf1 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -594,27 +594,30 @@ cmd_deinit()\n \t\tdie_if_unmatched \"$mode\"\n \t\tname=$(module_name \"$sm_path\") || exit\n\n-\t\t# Remove the submodule work tree (unless the user already did it)\n-\t\tif test -d \"$sm_path\"\n+\t\t# Remove the submodule work tree (unless the user already did it or it is empty)\n+\t\tif test ! -d \"$(find \"$sm_path\" -maxdepth 0 -empty)\"\n \t\tthen\n-\t\t\t# Protect submodules containing a .git directory\n-\t\t\tif test -d \"$sm_path/.git\"\n+\t\t\tif test -d \"$sm_path\"\n \t\t\tthen\n-\t\t\t\techo >&2 \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains a .git directory\")\"\n-\t\t\t\tdie \"$(eval_gettext \"(use 'rm -rf' if you really want to remove it including all of its history)\")\"\n-\t\t\tfi\n+\t\t\t\t# Protect submodules containing a .git directory\n+\t\t\t\tif test -d \"$sm_path/.git\"\n+\t\t\t\tthen\n+\t\t\t\t\techo >&2 \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains a .git directory\")\"\n+\t\t\t\t\tdie \"$(eval_gettext \"(use 'rm -rf' if you really want to remove it including all of its history)\")\"\n+\t\t\t\tfi\n\n-\t\t\tif test -z \"$force\"\n-\t\t\tthen\n-\t\t\t\tgit rm -qn \"$sm_path\" ||\n-\t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n+\t\t\t\tif test -z \"$force\"\n+\t\t\t\tthen\n+\t\t\t\t\tgit rm -qn \"$sm_path\" ||\n+\t\t\t\t\tdie \"$(eval_gettext \"Submodule work tree '\\$sm_path' contains local modifications; use '-f' to discard them\")\"\n+\t\t\t\tfi\n+\t\t\t\trm -rf \"$sm_path\" &&\n+\t\t\t\tsay \"$(eval_gettext \"Cleared directory '\\$sm_path'\")\" ||\n+\t\t\t\tsay \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n \t\t\tfi\n-\t\t\trm -rf \"$sm_path\" &&\n-\t\t\tsay \"$(eval_gettext \"Cleared directory '\\$sm_path'\")\" ||\n-\t\t\tsay \"$(eval_gettext \"Could not remove submodule work tree '\\$sm_path'\")\"\n-\t\tfi\n\n-\t\tmkdir \"$sm_path\" || say \"$(eval_gettext \"Could not create empty submodule directory '\\$sm_path'\")\"\n+\t\t\tmkdir \"$sm_path\" || say \"$(eval_gettext \"Could not create empty submodule directory '\\$sm_path'\")\"\n+\t\tfi\n\n \t\t# Remove the .git/config entries (unless the user already did it)\n \t\tif test -n \"$(git config --get-regexp submodule.\"$name\\.\")\"\ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex ff26535..b56e4a5 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -792,7 +792,7 @@ test_expect_success 'submodule deinit deinits a submodule when its work tree is\n \ttest -z \"$(git config --get-regexp \"submodule\\.example\\.\")\" &&\n \ttest -z \"$(git config --get-regexp \"submodule\\.example2\\.\")\" &&\n \ttest_i18ngrep ! \"Cleared directory .init\" actual &&\n-\ttest_i18ngrep \"Cleared directory .example2\" actual &&\n+\ttest_i18ngrep ! \"Cleared directory .example2\" actual &&\n \trmdir init\n '\n\n@@ -842,15 +842,15 @@ test_expect_success 'submodule deinit is silent when used on an uninitialized su\n \ttest_i18ngrep \"Cleared directory .init\" actual &&\n \tgit submodule deinit init >actual &&\n \ttest_i18ngrep ! \"Submodule .example. (.*) unregistered for path .init\" actual &&\n-\ttest_i18ngrep \"Cleared directory .init\" actual &&\n+\ttest_i18ngrep ! \"Cleared directory .init\" actual &&\n \tgit submodule deinit . >actual &&\n \ttest_i18ngrep ! \"Submodule .example. (.*) unregistered for path .init\" actual &&\n \ttest_i18ngrep \"Submodule .example2. (.*) unregistered for path .example2\" actual &&\n-\ttest_i18ngrep \"Cleared directory .init\" actual &&\n+\ttest_i18ngrep ! \"Cleared directory .init\" actual &&\n \tgit submodule deinit . >actual &&\n \ttest_i18ngrep ! \"Submodule .example. (.*) unregistered for path .init\" actual &&\n \ttest_i18ngrep ! \"Submodule .example2. (.*) unregistered for path .example2\" actual &&\n-\ttest_i18ngrep \"Cleared directory .init\" actual &&\n+\ttest_i18ngrep ! \"Cleared directory .init\" actual &&\n \trmdir init example2\n '\n\n-- \n1.8.2.1.419.g33f67ba\n"},{"id":"214574","messageId":"7vppxthh44.fsf@alter.siamese.dyndns.org","threadId":"32845","inReplyTo":"CABURp0rkeYc7K0COhc1+96Q2Ox2TaNRpMSmQYOiwBpgPJbsafA@mail.gmail.com","subject":"Re: [PATCH] submodule deinit: clarify work tree removal message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-04-17T05:16:11Z","receivedAt":"2013-04-17T05:16:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phil Hord <phil.hord@gmail.com> writes:\n\n> On Mon, Apr 1, 2013 at 3:02 PM, Jens Lehmann <Jens.Lehmann@web.de> wrote:\n>> Okay, so here is the patch for that. If someone could point out\n>> a portable and efficient way to check if a directory is already\n>> empty I would be happy to use that to silence the \"Cleaned\n>> directory\" message currently printed also when deinit is run on\n>> an already empty directory.\n>\n>    isemptydir() {\n>         test -d \"$(find $1 -maxdepth 0 -empty)\"\n>    }\n\nHrm, -maxdepth and -empty are not even in POSIX.  Folks on GNU\nplatforms and BSDs (I checked NetBSD 6 and OpenBSD 5.2) should be\nfine, but it makes other platforms unhappy.\n\nWhat is this check used for?  To avoid running \"rmdir\" on non-empty\nones?  Saying \"cleaning foo/\" (or \"cleaned foo/\") when foo/ is\nalready empty is not a crime; not omitting an empty one may actually\nbe a better behaviour from the point of view of repeatability and\nuniformity.\n"},{"id":"214638","messageId":"516F0666.5080308@web.de","threadId":"32845","inReplyTo":"7vppxthh44.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] submodule deinit: clarify work tree removal message","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-04-17T20:30:30Z","receivedAt":"2013-04-17T20:30:30Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Am 17.04.2013 07:16, schrieb Junio C Hamano:\n> Phil Hord <phil.hord@gmail.com> writes:\n> \n>> On Mon, Apr 1, 2013 at 3:02 PM, Jens Lehmann <Jens.Lehmann@web.de> wrote:\n>>> Okay, so here is the patch for that. If someone could point out\n>>> a portable and efficient way to check if a directory is already\n>>> empty I would be happy to use that to silence the \"Cleaned\n>>> directory\" message currently printed also when deinit is run on\n>>> an already empty directory.\n>>\n>>    isemptydir() {\n>>         test -d \"$(find $1 -maxdepth 0 -empty)\"\n>>    }\n> \n> Hrm, -maxdepth and -empty are not even in POSIX.  Folks on GNU\n> platforms and BSDs (I checked NetBSD 6 and OpenBSD 5.2) should be\n> fine, but it makes other platforms unhappy.\n\nOk, that is not acceptable.\n\n> What is this check used for?  To avoid running \"rmdir\" on non-empty\n> ones?  Saying \"cleaning foo/\" (or \"cleaned foo/\") when foo/ is\n> already empty is not a crime; not omitting an empty one may actually\n> be a better behaviour from the point of view of repeatability and\n> uniformity.\n\nIt's no big issue, but 'init' issues the \"Submodule ... registered\nfor path ...\" message only once and is quiet on subsequent calls,\n'deinit' does the same with \"Submodule ... unregistered for path\n...\", only the \"Cleared directory ...\" message appears each time\n'deinit' is called, which makes it stand out. I do not believe\nthis little inconsistency is important enough to write a helper in\nC (to have a portable way to see if the directory has already been\ncleared), but this simple additional \"if\" looked easy enough. That\nshould have made me suspicious ;-)\n"}]}