{"thread":{"id":"32827","subject":"[PATCH v3] submodule: add 'deinit' command","startedAt":"2013-02-04T20:09:30Z","lastAt":"2013-02-04T20:55:25Z","messageCount":2,"participants":["Jens Lehmann","Junio C Hamano"],"isPatch":true,"patchVersion":3,"patchTotal":null},"messages":[{"id":"208641","messageId":"5110157A.2040402@web.de","threadId":"32827","inReplyTo":null,"subject":"[PATCH v3] submodule: add 'deinit' command","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-02-04T20:09:30Z","receivedAt":"2013-02-04T20:09:30Z","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\nOk, here is the reroll following the discussion in $gmane/212884.\n\nChanges since v2 are:\n- deinit always needs an argument; if the user wants to deinit all initialized\n  submodules he can use \".\" (and we tell him that when failing without any\n  arguments).\n- We also remove the work tree of the submodule. When it contains local changes\n  (tested with \"git rm -n\") this fails unless forced.\n\n\n Documentation/git-rm.txt        |  4 +++\n Documentation/git-submodule.txt | 18 ++++++++++-\n git-submodule.sh                | 70 ++++++++++++++++++++++++++++++++++++++++-\n t/t7400-submodule-basic.sh      | 24 ++++++++++++++\n 4 files changed, 114 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt\nindex 262436b..8ae72f7 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 that use linkgit:git-submodule[1] `deinit`\n+instead.\n+\n EXAMPLES\n --------\n `git rm Documentation/\\*.txt`::\ndiff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt\nindex b1996f1..9a20e1d 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 22ec5b6..e01f6b5 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,73 @@ 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\t# Only mention uninitialized submodules when its\n+\t\t\t# path have been specified\n+\t\t\ttest \"$@\" != \".\" &&\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\n+\t\tif test -z $force\n+\t\tthen\n+\t\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+\t\tfi\n+\t\trm -rf \"$sm_path\"\n+\t\tmkdir \"$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 +1225,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..34d8274 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -757,4 +757,28 @@ test_expect_success 'submodule add with an existing name fails unless forced' '\n \t)\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 submodule deinit init &&\n+\ttest -z \"$(git config submodule.example.url)\" &&\n+\ttest -z \"$(git config submodule.example.foo)\"\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+\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+'\n+\n+test_expect_success 'submodule deinit complains when explicitly 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+'\n+\n test_done\n-- \n1.8.1.2.460.gae7a4e0\n"},{"id":"208645","messageId":"7vlib32402.fsf@alter.siamese.dyndns.org","threadId":"32827","inReplyTo":"5110157A.2040402@web.de","subject":"Re: [PATCH v3] submodule: add 'deinit' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-02-04T20:55:25Z","receivedAt":"2013-02-04T20:55:25Z","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> 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> Ok, here is the reroll following the discussion in $gmane/212884.\n>\n> Changes since v2 are:\n> - deinit always needs an argument; if the user wants to deinit all initialized\n>   submodules he can use \".\" (and we tell him that when failing without any\n>   arguments).\n> - We also remove the work tree of the submodule. When it contains local changes\n>   (tested with \"git rm -n\") this fails unless forced.\n>\n>\n>  Documentation/git-rm.txt        |  4 +++\n>  Documentation/git-submodule.txt | 18 ++++++++++-\n>  git-submodule.sh                | 70 ++++++++++++++++++++++++++++++++++++++++-\n>  t/t7400-submodule-basic.sh      | 24 ++++++++++++++\n>  4 files changed, 114 insertions(+), 2 deletions(-)\n>\n> diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt\n> index 262436b..8ae72f7 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 that use linkgit:git-submodule[1] `deinit`\n> +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 b1996f1..9a20e1d 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\n> diff --git a/git-submodule.sh b/git-submodule.sh\n> index 22ec5b6..e01f6b5 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,73 @@ 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\nNo need to quote either of these (imitate what you did above with\n\"while\"); also use single \"=\" for string comparison.\n\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\t# Only mention uninitialized submodules when its\n> +\t\t\t# path have been specified\n> +\t\t\ttest \"$@\" != \".\" &&\n\nIf your $# is not 1, \"test\" will fail with \"too many arguments\"\nhere, I think.\n\nI would probably set a variable before running module_list,\n\n\tneeds_uninitialized_warning=\n        if $# = 1 || $1 != .\n\tthen\n\t\tneeds_uninitialized_warning=yes\n\tfi\n\nand check that variable here, perhaps.\n\nBut stepping back a bit, why does it warn only for non '.' case?\n\nShouldn't\n\n\tgit submodule deinit 'foo*'\n\nbehave the same way between foo1 and foo2 when it sees foo1 has been\ninitialized but foo2 hasn't?\n\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\n> +\t\tif test -z $force\n\nAs $force is initialized to an empty string, this without dq around\nit will become a syntax error, feeding \"test\" with a single argument\n\"-z\".\n\n> +\t\tthen\n> +\t\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> +\t\tfi\n> +\t\trm -rf \"$sm_path\"\n\nDo we want to make sure that $sm_path/.git is a gitfile here?  I do\nnot think it matters that much, but the user may have local commits\nthat he has not pushed out, in which case it would be nicer to give\na way to preserve them.\n\n> +\t\tmkdir \"$sm_path\"\n\nAlso can these rm -rf or mkdir fail, and what would we want to do\nwhen they do?\n\nI think it is preferrable to run \"config --remove-section\"\nregardless, but we may want to tell the user what happened (i.e. the\nsubmodule has been unregistered as far as Git is concerned, but the\nworktree may have some cruft we couldn't remove).\n\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 +1225,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)\n"}]}