submodule: add 'deinit' command

With "git submodule init" the user is able to tell git he cares about one or more submodules and wants to have it populated on the next call to "git submodule update". But currently there is no easy way he could tell git he does not care about a submodule anymore and wants to get rid of his local work tree (except he knows a lot about submodule internals and removes the "submodule.$name.url" setting from .git/config together with the work tree himself). Help those users by providing a 'deinit' command. This removes the whole submodule.<name> section from .git/config (either for the given submodule(s) or for all those which have been initialized if '.' is used) together with their work tree. Fail if the current work tree contains modifications (unless forced), but don't complain when either the work tree is already removed or no settings are found in .git/config. Add tests and link the man pages of "git submodule deinit" and "git rm" to assist the user in deciding whether removing or unregistering the submodule is the right thing to do for him. Also add the deinit subcommand to the completion list. Signed-off-by: Jens Lehmann <Jens.Lehmann@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jens Lehmann committed Mar 4, 2013 at 22:20 UTC cf419828064d4f22a2c3134e7b46a7719462b1dc
5 files changed +198 -3
Documentation/git-rm.txt
+4
@@ -149,6 +149,10 @@ files that aren't ignored are present in the submodules work tree.
149 Ignored files are deemed expendable and won't stop a submodule's work
150 tree from being removed.
151
152 +If you only want to remove the local checkout of a submodule from your
153 +work tree without committing the removal,
154 +use linkgit:git-submodule[1] `deinit` instead.
155 +
156 EXAMPLES
157 --------
158 `git rm Documentation/\*.txt`::
Documentation/git-submodule.txt
+17 -1
@@ -13,6 +13,7 @@ SYNOPSIS
13 [--reference <repository>] [--] <repository> [<path>]
14 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
15 'git submodule' [--quiet] init [--] [<path>...]
16 +'git submodule' [--quiet] deinit [-f|--force] [--] <path>...
17 'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]
18 [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
19 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]
@@ -134,6 +135,19 @@ init::
135 the explicit 'init' step if you do not intend to customize
136 any submodule locations.
137
138 +deinit::
139 + Unregister the given submodules, i.e. remove the whole
140 + `submodule.$name` section from .git/config together with their work
141 + tree. Further calls to `git submodule update`, `git submodule foreach`
142 + and `git submodule sync` will skip any unregistered submodules until
143 + they are initialized again, so use this command if you don't want to
144 + have a local checkout of the submodule in your work tree anymore. If
145 + you really want to remove a submodule from the repository and commit
146 + that use linkgit:git-rm[1] instead.
147 ++
148 +If `--force` is specified, the submodule's work tree will be removed even if
149 +it contains local modifications.
150 +
151 update::
152 Update the registered submodules, i.e. clone missing submodules and
153 checkout the commit specified in the index of the containing repository.
@@ -211,8 +225,10 @@ OPTIONS
225
226 -f::
227 --force::
214 - This option is only valid for add and update commands.
228 + This option is only valid for add, deinit and update commands.
229 When running add, allow adding an otherwise ignored submodule path.
230 + When running deinit the submodule work trees will be removed even if
231 + they contain local changes.
232 When running update, throw away local changes in submodules when
233 switching to a different commit; and always run a checkout operation
234 in the submodule, even if the commit listed in the index of the
contrib/completion/git-completion.bash
+1 -1
@@ -2176,7 +2176,7 @@ _git_submodule ()
2176 {
2177 __git_has_doubledash && return
2178
2179 - local subcommands="add status init update summary foreach sync"
2179 + local subcommands="add status init deinit update summary foreach sync"
2180 if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
2181 case "$cur" in
2182 --*)
git-submodule.sh
+76 -1
@@ -8,6 +8,7 @@ dashless=$(basename "$0" | sed -e 's/-/ /')
8 USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]
9 or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
10 or: $dashless [--quiet] init [--] [<path>...]
11 + or: $dashless [--quiet] deinit [-f|--force] [--] <path>...
12 or: $dashless [--quiet] update [--init] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
13 or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
14 or: $dashless [--quiet] foreach [--recursive] <command>
@@ -515,6 +516,80 @@ cmd_init()
516 done
517 }
518
519 +#
520 +# Unregister submodules from .git/config and remove their work tree
521 +#
522 +# $@ = requested paths (use '.' to deinit all submodules)
523 +#
524 +cmd_deinit()
525 +{
526 + # parse $args after "submodule ... deinit".
527 + while test $# -ne 0
528 + do
529 + case "$1" in
530 + -f|--force)
531 + force=$1
532 + ;;
533 + -q|--quiet)
534 + GIT_QUIET=1
535 + ;;
536 + --)
537 + shift
538 + break
539 + ;;
540 + -*)
541 + usage
542 + ;;
543 + *)
544 + break
545 + ;;
546 + esac
547 + shift
548 + done
549 +
550 + if test $# = 0
551 + then
552 + die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")"
553 + fi
554 +
555 + module_list "$@" |
556 + while read mode sha1 stage sm_path
557 + do
558 + die_if_unmatched "$mode"
559 + name=$(module_name "$sm_path") || exit
560 +
561 + # Remove the submodule work tree (unless the user already did it)
562 + if test -d "$sm_path"
563 + then
564 + # Protect submodules containing a .git directory
565 + if test -d "$sm_path/.git"
566 + then
567 + echo >&2 "$(eval_gettext "Submodule work tree '\$sm_path' contains a .git directory")"
568 + die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")"
569 + fi
570 +
571 + if test -z "$force"
572 + then
573 + git rm -n "$sm_path" ||
574 + die "$(eval_gettext "Submodule work tree '\$sm_path' contains local modifications; use '-f' to discard them")"
575 + fi
576 + rm -rf "$sm_path" || say "$(eval_gettext "Could not remove submodule work tree '\$sm_path'")"
577 + fi
578 +
579 + mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$sm_path'")"
580 +
581 + # Remove the .git/config entries (unless the user already did it)
582 + if test -n "$(git config --get-regexp submodule."$name\.")"
583 + then
584 + # Remove the whole section so we have a clean state when
585 + # the user later decides to init this submodule again
586 + url=$(git config submodule."$name".url)
587 + git config --remove-section submodule."$name" 2>/dev/null &&
588 + say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$sm_path'")"
589 + fi
590 + done
591 +}
592 +
593 #
594 # Update each submodule path to correct revision, using clone and checkout as needed
595 #
@@ -1108,7 +1183,7 @@ cmd_sync()
1183 while test $# != 0 && test -z "$command"
1184 do
1185 case "$1" in
1111 - add | foreach | init | update | status | summary | sync)
1186 + add | foreach | init | deinit | update | status | summary | sync)
1187 command=$1
1188 ;;
1189 -q|--quiet)
t/t7400-submodule-basic.sh
+100
@@ -756,4 +756,104 @@ test_expect_success 'submodule add with an existing name fails unless forced' '
756 )
757 '
758
759 +test_expect_success 'set up a second submodule' '
760 + git submodule add ./init2 example2 &&
761 + git commit -m "submodule example2 added"
762 +'
763 +
764 +test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' '
765 + git config submodule.example.foo bar &&
766 + git config submodule.example2.frotz nitfol &&
767 + git submodule deinit init &&
768 + test -z "$(git config --get-regexp "submodule\.example\.")" &&
769 + test -n "$(git config --get-regexp "submodule\.example2\.")" &&
770 + test -f example2/.git &&
771 + rmdir init
772 +'
773 +
774 +test_expect_success 'submodule deinit . deinits all initialized submodules' '
775 + git submodule update --init &&
776 + git config submodule.example.foo bar &&
777 + git config submodule.example2.frotz nitfol &&
778 + test_must_fail git submodule deinit &&
779 + git submodule deinit . &&
780 + test -z "$(git config --get-regexp "submodule\.example\.")" &&
781 + test -z "$(git config --get-regexp "submodule\.example2\.")" &&
782 + rmdir init example2
783 +'
784 +
785 +test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' '
786 + git submodule update --init &&
787 + rm -rf init example2/* example2/.git &&
788 + git submodule deinit init example2 &&
789 + test -z "$(git config --get-regexp "submodule\.example\.")" &&
790 + test -z "$(git config --get-regexp "submodule\.example2\.")" &&
791 + rmdir init
792 +'
793 +
794 +test_expect_success 'submodule deinit fails when the submodule contains modifications unless forced' '
795 + git submodule update --init &&
796 + echo X >>init/s &&
797 + test_must_fail git submodule deinit init &&
798 + test -n "$(git config --get-regexp "submodule\.example\.")" &&
799 + test -f example2/.git &&
800 + git submodule deinit -f init &&
801 + test -z "$(git config --get-regexp "submodule\.example\.")" &&
802 + rmdir init
803 +'
804 +
805 +test_expect_success 'submodule deinit fails when the submodule contains untracked files unless forced' '
806 + git submodule update --init &&
807 + echo X >>init/untracked &&
808 + test_must_fail git submodule deinit init &&
809 + test -n "$(git config --get-regexp "submodule\.example\.")" &&
810 + test -f example2/.git &&
811 + git submodule deinit -f init &&
812 + test -z "$(git config --get-regexp "submodule\.example\.")" &&
813 + rmdir init
814 +'
815 +
816 +test_expect_success 'submodule deinit fails when the submodule HEAD does not match unless forced' '
817 + git submodule update --init &&
818 + (
819 + cd init &&
820 + git checkout HEAD^
821 + ) &&
822 + test_must_fail git submodule deinit init &&
823 + test -n "$(git config --get-regexp "submodule\.example\.")" &&
824 + test -f example2/.git &&
825 + git submodule deinit -f init &&
826 + test -z "$(git config --get-regexp "submodule\.example\.")" &&
827 + rmdir init
828 +'
829 +
830 +test_expect_success 'submodule deinit is silent when used on an uninitialized submodule' '
831 + git submodule update --init &&
832 + git submodule deinit init >actual &&
833 + test_i18ngrep "Submodule .example. (.*) unregistered for path .init" actual &&
834 + git submodule deinit init >actual &&
835 + test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
836 + git submodule deinit . >actual &&
837 + test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
838 + test_i18ngrep "Submodule .example2. (.*) unregistered for path .example2" actual &&
839 + git submodule deinit . >actual &&
840 + test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
841 + test_i18ngrep ! "Submodule .example2. (.*) unregistered for path .example2" actual &&
842 + rmdir init example2
843 +'
844 +
845 +test_expect_success 'submodule deinit fails when submodule has a .git directory even when forced' '
846 + git submodule update --init &&
847 + (
848 + cd init &&
849 + rm .git &&
850 + cp -R ../.git/modules/example .git &&
851 + GIT_WORK_TREE=. git config --unset core.worktree
852 + ) &&
853 + test_must_fail git submodule deinit init &&
854 + test_must_fail git submodule deinit -f init &&
855 + test -d init/.git &&
856 + test -n "$(git config --get-regexp "submodule\.example\.")"
857 +'
858 +
859 test_done