Skip to content

Commit cf41982

Browse files
jlehmanngitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f94c325 commit cf41982

File tree

5 files changed

+198
-3
lines changed

5 files changed

+198
-3
lines changed

Documentation/git-rm.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ files that aren't ignored are present in the submodules work tree.
149149
Ignored files are deemed expendable and won't stop a submodule's work
150150
tree from being removed.
151151

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+
152156
EXAMPLES
153157
--------
154158
`git rm Documentation/\*.txt`::

Documentation/git-submodule.txt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ SYNOPSIS
1313
[--reference <repository>] [--] <repository> [<path>]
1414
'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
1515
'git submodule' [--quiet] init [--] [<path>...]
16+
'git submodule' [--quiet] deinit [-f|--force] [--] <path>...
1617
'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]
1718
[--reference <repository>] [--merge] [--recursive] [--] [<path>...]
1819
'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]
@@ -134,6 +135,19 @@ init::
134135
the explicit 'init' step if you do not intend to customize
135136
any submodule locations.
136137

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+
137151
update::
138152
Update the registered submodules, i.e. clone missing submodules and
139153
checkout the commit specified in the index of the containing repository.
@@ -211,8 +225,10 @@ OPTIONS
211225

212226
-f::
213227
--force::
214-
This option is only valid for add and update commands.
228+
This option is only valid for add, deinit and update commands.
215229
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.
216232
When running update, throw away local changes in submodules when
217233
switching to a different commit; and always run a checkout operation
218234
in the submodule, even if the commit listed in the index of the

contrib/completion/git-completion.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2176,7 +2176,7 @@ _git_submodule ()
21762176
{
21772177
__git_has_doubledash && return
21782178

2179-
local subcommands="add status init update summary foreach sync"
2179+
local subcommands="add status init deinit update summary foreach sync"
21802180
if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
21812181
case "$cur" in
21822182
--*)

git-submodule.sh

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ dashless=$(basename "$0" | sed -e 's/-/ /')
88
USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]
99
or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
1010
or: $dashless [--quiet] init [--] [<path>...]
11+
or: $dashless [--quiet] deinit [-f|--force] [--] <path>...
1112
or: $dashless [--quiet] update [--init] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
1213
or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
1314
or: $dashless [--quiet] foreach [--recursive] <command>
@@ -515,6 +516,80 @@ cmd_init()
515516
done
516517
}
517518

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+
518593
#
519594
# Update each submodule path to correct revision, using clone and checkout as needed
520595
#
@@ -1108,7 +1183,7 @@ cmd_sync()
11081183
while test $# != 0 && test -z "$command"
11091184
do
11101185
case "$1" in
1111-
add | foreach | init | update | status | summary | sync)
1186+
add | foreach | init | deinit | update | status | summary | sync)
11121187
command=$1
11131188
;;
11141189
-q|--quiet)

t/t7400-submodule-basic.sh

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,4 +756,104 @@ test_expect_success 'submodule add with an existing name fails unless forced' '
756756
)
757757
'
758758

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+
759859
test_done

0 commit comments

Comments
 (0)