Merge branch 'jl/submodule-deinit'
authorJunio C Hamano <gitster@pobox.com>
Mon, 25 Mar 2013 21:00:29 +0000 (14:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 25 Mar 2013 21:00:29 +0000 (14:00 -0700)
There was no Porcelain way to say "I no longer am interested in
this submodule", once you express your interest in a submodule with
"submodule init". "submodule deinit" is the way to do so.

* jl/submodule-deinit:
submodule: add 'deinit' command

1  2 
Documentation/git-rm.txt
Documentation/git-submodule.txt
contrib/completion/git-completion.bash
git-submodule.sh
t/t7400-submodule-basic.sh
diff --combined Documentation/git-rm.txt
index 92bac27e05fca2f532fd2ae309dbc9f7b160c3d2,dbf41621ae29da22a047b40fa0ce9bceeaecff7c..1d876c2619a414159a121868c5dbaab48e55a708
@@@ -28,7 -28,7 +28,7 @@@ OPTION
  -------
  <file>...::
        Files to remove.  Fileglobs (e.g. `*.c`) can be given to
 -      remove all matching files.  If you want git to expand
 +      remove all matching files.  If you want Git to expand
        file glob characters, you may need to shell-escape them.
        A leading directory name
        (e.g. `dir` to remove `dir/file1` and `dir/file2`) can be
@@@ -74,8 -74,8 +74,8 @@@ DISCUSSIO
  
  The <file> list given to the command can be exact pathnames,
  file glob patterns, or leading directory names.  The command
 -removes only the paths that are known to git.  Giving the name of
 -a file that you have not told git about does not remove that file.
 +removes only the paths that are known to Git.  Giving the name of
 +a file that you have not told Git about does not remove that file.
  
  File globbing matches across directory boundaries.  Thus, given
  two directories `d` and `d2`, there is a difference between
@@@ -137,7 -137,7 +137,7 @@@ git diff --name-only --diff-filter=D -
  Submodules
  ~~~~~~~~~~
  Only submodules using a gitfile (which means they were cloned
 -with a git version 1.7.8 or newer) will be removed from the work
 +with a Git version 1.7.8 or newer) will be removed from the work
  tree, as their repository lives inside the .git directory of the
  superproject. If a submodule (or one of those nested inside it)
  still uses a .git directory, `git rm` will fail - no matter if forced
@@@ -149,6 -149,10 +149,10 @@@ files that aren't ignored are present i
  Ignored files are deemed expendable and won't stop a submodule's work
  tree from being removed.
  
+ If you only want to remove the local checkout of a submodule from your
+ work tree without committing the removal,
+ use linkgit:git-submodule[1] `deinit` instead.
  EXAMPLES
  --------
  `git rm Documentation/\*.txt`::
        `Documentation` directory and any of its subdirectories.
  +
  Note that the asterisk `*` is quoted from the shell in this
 -example; this lets git, and not the shell, expand the pathnames
 +example; this lets Git, and not the shell, expand the pathnames
  of files and subdirectories under the `Documentation/` directory.
  
  `git rm -f git-*.sh`::
index c99d795618f59bf98969b551f2a931680534f18e,27cd57a7e4e9efe7a79e562c955ff9d4f07a3d23..74d5bdc59d58f743bc6f5cb5f7f9ee24d384d5c8
@@@ -13,9 -13,9 +13,10 @@@ SYNOPSI
              [--reference <repository>] [--] <repository> [<path>]
  'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
  'git submodule' [--quiet] init [--] [<path>...]
 -'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]
 -            [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
+ 'git submodule' [--quiet] deinit [-f|--force] [--] <path>...
 +'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch]
 +            [-f|--force] [--rebase] [--reference <repository>]
 +            [--merge] [--recursive] [--] [<path>...]
  'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]
              [commit] [--] [<path>...]
  'git submodule' [--quiet] foreach [--recursive] <command>
@@@ -92,7 -92,7 +93,7 @@@ working directory is used instead
  <path> is the relative location for the cloned submodule to
  exist in the superproject. If <path> does not exist, then the
  submodule is created by cloning from the named URL. If <path> does
 -exist and is already a valid git repository, then this is added
 +exist and is already a valid Git repository, then this is added
  to the changeset without cloning. This second form is provided
  to ease creating a new submodule from scratch, and presumes
  the user will later push the submodule to the given URL.
@@@ -135,6 -135,19 +136,19 @@@ init:
        the explicit 'init' step if you do not intend to customize
        any submodule locations.
  
+ deinit::
+       Unregister the given submodules, i.e. remove the whole
+       `submodule.$name` section from .git/config together with their work
+       tree. Further calls to `git submodule update`, `git submodule foreach`
+       and `git submodule sync` will skip any unregistered submodules until
+       they are initialized again, so use this command if you don't want to
+       have a local checkout of the submodule in your work tree anymore. If
+       you really want to remove a submodule from the repository and commit
+       that use linkgit:git-rm[1] instead.
+ +
+ If `--force` is specified, the submodule's work tree will be removed even if
+ it contains local modifications.
  update::
        Update the registered submodules, i.e. clone missing submodules and
        checkout the commit specified in the index of the containing repository.
@@@ -209,13 -222,13 +223,15 @@@ OPTION
  -b::
  --branch::
        Branch of repository to add as submodule.
 +      The name of the branch is recorded as `submodule.<path>.branch` in
 +      `.gitmodules` for `update --remote`.
  
  -f::
  --force::
-       This option is only valid for add and update commands.
+       This option is only valid for add, deinit and update commands.
        When running add, allow adding an otherwise ignored submodule path.
+       When running deinit the submodule work trees will be removed even if
+       they contain local changes.
        When running update, throw away local changes in submodules when
        switching to a different commit; and always run a checkout operation
        in the submodule, even if the commit listed in the index of the
        (the default). This limit only applies to modified submodules. The
        size is always limited to 1 for added/deleted/typechanged submodules.
  
 +--remote::
 +      This option is only valid for the update command.  Instead of using
 +      the superproject's recorded SHA-1 to update the submodule, use the
 +      status of the submodule's remote tracking branch.  The remote used
 +      is branch's remote (`branch.<name>.remote`), defaulting to `origin`.
 +      The remote branch used defaults to `master`, but the branch name may
 +      be overridden by setting the `submodule.<name>.branch` option in
 +      either `.gitmodules` or `.git/config` (with `.git/config` taking
 +      precedence).
 ++
 +This works for any of the supported update procedures (`--checkout`,
 +`--rebase`, etc.).  The only change is the source of the target SHA-1.
 +For example, `submodule update --remote --merge` will merge upstream
 +submodule changes into the submodules, while `submodule update
 +--merge` will merge superproject gitlink changes into the submodules.
 ++
 +In order to ensure a current tracking branch state, `update --remote`
 +fetches the submodule's remote repository before calculating the
 +SHA-1.  If you don't want to fetch, you should use `submodule update
 +--remote --no-fetch`.
 +
  -N::
  --no-fetch::
        This option is only valid for the update command.
index 93eba467502282db2697e1cf6c93a6909d35ee5c,f3c53cc8fb99288910e9600b1c54d111df3f6959..2ba1461422c4e6c16e6c85ed3914536e1f4aa56e
@@@ -13,7 -13,6 +13,7 @@@
  #    *) .git/remotes file names
  #    *) git 'subcommands'
  #    *) tree paths within 'ref:path/to/file' expressions
 +#    *) file paths within current working directory and index
  #    *) common --long-options
  #
  # To use these routines:
@@@ -234,124 -233,6 +234,124 @@@ __gitcomp_nl (
        COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}"))
  }
  
 +# Generates completion reply with compgen from newline-separated possible
 +# completion filenames.
 +# It accepts 1 to 3 arguments:
 +# 1: List of possible completion filenames, separated by a single newline.
 +# 2: A directory prefix to be added to each possible completion filename
 +#    (optional).
 +# 3: Generate possible completion matches for this word (optional).
 +__gitcomp_file ()
 +{
 +      local IFS=$'\n'
 +
 +      # XXX does not work when the directory prefix contains a tilde,
 +      # since tilde expansion is not applied.
 +      # This means that COMPREPLY will be empty and Bash default
 +      # completion will be used.
 +      COMPREPLY=($(compgen -P "${2-}" -W "$1" -- "${3-$cur}"))
 +
 +      # Tell Bash that compspec generates filenames.
 +      compopt -o filenames 2>/dev/null
 +}
 +
 +__git_index_file_list_filter_compat ()
 +{
 +      local path
 +
 +      while read -r path; do
 +              case "$path" in
 +              ?*/*) echo "${path%%/*}/" ;;
 +              *) echo "$path" ;;
 +              esac
 +      done
 +}
 +
 +__git_index_file_list_filter_bash ()
 +{
 +      local path
 +
 +      while read -r path; do
 +              case "$path" in
 +              ?*/*)
 +                      # XXX if we append a slash to directory names when using
 +                      # `compopt -o filenames`, Bash will append another slash.
 +                      # This is pretty stupid, and this the reason why we have to
 +                      # define a compatible version for this function.
 +                      echo "${path%%/*}" ;;
 +              *)
 +                      echo "$path" ;;
 +              esac
 +      done
 +}
 +
 +# Process path list returned by "ls-files" and "diff-index --name-only"
 +# commands, in order to list only file names relative to a specified
 +# directory, and append a slash to directory names.
 +__git_index_file_list_filter ()
 +{
 +      # Default to Bash >= 4.x
 +      __git_index_file_list_filter_bash
 +}
 +
 +# Execute git ls-files, returning paths relative to the directory
 +# specified in the first argument, and using the options specified in
 +# the second argument.
 +__git_ls_files_helper ()
 +{
 +      (
 +              test -n "${CDPATH+set}" && unset CDPATH
 +              # NOTE: $2 is not quoted in order to support multiple options
 +              cd "$1" && git ls-files --exclude-standard $2
 +      ) 2>/dev/null
 +}
 +
 +
 +# Execute git diff-index, returning paths relative to the directory
 +# specified in the first argument, and using the tree object id
 +# specified in the second argument.
 +__git_diff_index_helper ()
 +{
 +      (
 +              test -n "${CDPATH+set}" && unset CDPATH
 +              cd "$1" && git diff-index --name-only --relative "$2"
 +      ) 2>/dev/null
 +}
 +
 +# __git_index_files accepts 1 or 2 arguments:
 +# 1: Options to pass to ls-files (required).
 +#    Supported options are --cached, --modified, --deleted, --others,
 +#    and --directory.
 +# 2: A directory path (optional).
 +#    If provided, only files within the specified directory are listed.
 +#    Sub directories are never recursed.  Path must have a trailing
 +#    slash.
 +__git_index_files ()
 +{
 +      local dir="$(__gitdir)" root="${2-.}"
 +
 +      if [ -d "$dir" ]; then
 +              __git_ls_files_helper "$root" "$1" | __git_index_file_list_filter |
 +                      sort | uniq
 +      fi
 +}
 +
 +# __git_diff_index_files accepts 1 or 2 arguments:
 +# 1) The id of a tree object.
 +# 2) A directory path (optional).
 +#    If provided, only files within the specified directory are listed.
 +#    Sub directories are never recursed.  Path must have a trailing
 +#    slash.
 +__git_diff_index_files ()
 +{
 +      local dir="$(__gitdir)" root="${2-.}"
 +
 +      if [ -d "$dir" ]; then
 +              __git_diff_index_helper "$root" "$1" | __git_index_file_list_filter |
 +                      sort | uniq
 +      fi
 +}
 +
  __git_heads ()
  {
        local dir="$(__gitdir)"
@@@ -516,7 -397,7 +516,7 @@@ __git_complete_revlist_file (
                *)   pfx="$ref:$pfx" ;;
                esac
  
 -              __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
 +              __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \
                                | sed '/^100... blob /{
                                           s,^.*        ,,
                                           s,$, ,
  }
  
  
 +# __git_complete_index_file requires 1 argument: the options to pass to
 +# ls-file
 +__git_complete_index_file ()
 +{
 +      local pfx cur_="$cur"
 +
 +      case "$cur_" in
 +      ?*/*)
 +              pfx="${cur_%/*}"
 +              cur_="${cur_##*/}"
 +              pfx="${pfx}/"
 +
 +              __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_"
 +              ;;
 +      *)
 +              __gitcomp_file "$(__git_index_files "$1")" "" "$cur_"
 +              ;;
 +      esac
 +}
 +
 +# __git_complete_diff_index_file requires 1 argument: the id of a tree
 +# object
 +__git_complete_diff_index_file ()
 +{
 +      local pfx cur_="$cur"
 +
 +      case "$cur_" in
 +      ?*/*)
 +              pfx="${cur_%/*}"
 +              cur_="${cur_##*/}"
 +              pfx="${pfx}/"
 +
 +              __gitcomp_file "$(__git_diff_index_files "$1" "$pfx")" "$pfx" "$cur_"
 +              ;;
 +      *)
 +              __gitcomp_file "$(__git_diff_index_files "$1")" "" "$cur_"
 +              ;;
 +      esac
 +}
 +
  __git_complete_file ()
  {
        __git_complete_revlist_file
@@@ -690,19 -531,10 +690,19 @@@ __git_complete_strategy (
        return 1
  }
  
 +__git_commands () {
 +      if test -n "${GIT_TESTING_COMMAND_COMPLETION:-}"
 +      then
 +              printf "%s" "${GIT_TESTING_COMMAND_COMPLETION}"
 +      else
 +              git help -a|egrep '^  [a-zA-Z0-9]'
 +      fi
 +}
 +
  __git_list_all_commands ()
  {
        local i IFS=" "$'\n'
 -      for i in $(git help -a|egrep '^  [a-zA-Z0-9]')
 +      for i in $(__git_commands)
        do
                case $i in
                *--*)             : helper pattern;;
@@@ -731,7 -563,6 +731,7 @@@ __git_list_porcelain_commands (
                archimport)       : import;;
                cat-file)         : plumbing;;
                check-attr)       : plumbing;;
 +              check-ignore)     : plumbing;;
                check-ref-format) : plumbing;;
                checkout-index)   : plumbing;;
                commit-tree)      : plumbing;;
@@@ -891,43 -722,6 +891,43 @@@ __git_has_doubledash (
        return 1
  }
  
 +# Try to count non option arguments passed on the command line for the
 +# specified git command.
 +# When options are used, it is necessary to use the special -- option to
 +# tell the implementation were non option arguments begin.
 +# XXX this can not be improved, since options can appear everywhere, as
 +# an example:
 +#     git mv x -n y
 +#
 +# __git_count_arguments requires 1 argument: the git command executed.
 +__git_count_arguments ()
 +{
 +      local word i c=0
 +
 +      # Skip "git" (first argument)
 +      for ((i=1; i < ${#words[@]}; i++)); do
 +              word="${words[i]}"
 +
 +              case "$word" in
 +                      --)
 +                              # Good; we can assume that the following are only non
 +                              # option arguments.
 +                              ((c = 0))
 +                              ;;
 +                      "$1")
 +                              # Skip the specified git command and discard git
 +                              # main options
 +                              ((c = 0))
 +                              ;;
 +                      ?*)
 +                              ((c++))
 +                              ;;
 +              esac
 +      done
 +
 +      printf "%d" $c
 +}
 +
  __git_whitespacelist="nowarn warn error error-all fix"
  
  _git_am ()
@@@ -976,6 -770,8 +976,6 @@@ _git_apply (
  
  _git_add ()
  {
 -      __git_has_doubledash && return
 -
        case "$cur" in
        --*)
                __gitcomp "
                        "
                return
        esac
 -      COMPREPLY=()
 +
 +      # XXX should we check for --update and --all options ?
 +      __git_complete_index_file "--others --modified"
  }
  
  _git_archive ()
@@@ -1136,15 -930,15 +1136,15 @@@ _git_cherry_pick (
  
  _git_clean ()
  {
 -      __git_has_doubledash && return
 -
        case "$cur" in
        --*)
                __gitcomp "--dry-run --quiet"
                return
                ;;
        esac
 -      COMPREPLY=()
 +
 +      # XXX should we check for -x option ?
 +      __git_complete_index_file "--others"
  }
  
  _git_clone ()
  
  _git_commit ()
  {
 -      __git_has_doubledash && return
 +      case "$prev" in
 +      -c|-C)
 +              __gitcomp_nl "$(__git_refs)" "" "${cur}"
 +              return
 +              ;;
 +      esac
 +
 +      case "$prev" in
 +      -c|-C)
 +              __gitcomp_nl "$(__git_refs)" "" "${cur}"
 +              return
 +              ;;
 +      esac
  
        case "$cur" in
        --cleanup=*)
                        "
                return
        esac
 -      COMPREPLY=()
 +
 +      if git rev-parse --verify --quiet HEAD >/dev/null; then
 +              __git_complete_diff_index_file "HEAD"
 +      else
 +              # This is the first commit
 +              __git_complete_index_file "--cached"
 +      fi
  }
  
  _git_describe ()
        __gitcomp_nl "$(__git_refs)"
  }
  
 +__git_diff_algorithms="myers minimal patience histogram"
 +
  __git_diff_common_options="--stat --numstat --shortstat --summary
                        --patch-with-stat --name-only --name-status --color
                        --no-color --color-words --no-renames --check
                        --no-ext-diff
                        --no-prefix --src-prefix= --dst-prefix=
                        --inter-hunk-context=
 -                      --patience
 +                      --patience --histogram --minimal
                        --raw
                        --dirstat --dirstat= --dirstat-by-file
                        --dirstat-by-file= --cumulative
 +                      --diff-algorithm=
  "
  
  _git_diff ()
        __git_has_doubledash && return
  
        case "$cur" in
 +      --diff-algorithm=*)
 +              __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
 +              return
 +              ;;
        --*)
                __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
                        --base --ours --theirs --no-index
@@@ -1447,6 -1216,8 +1447,6 @@@ _git_init (
  
  _git_ls_files ()
  {
 -      __git_has_doubledash && return
 -
        case "$cur" in
        --*)
                __gitcomp "--cached --deleted --modified --others --ignored
                return
                ;;
        esac
 -      COMPREPLY=()
 +
 +      # XXX ignore options like --modified and always suggest all cached
 +      # files.
 +      __git_complete_index_file "--cached"
  }
  
  _git_ls_remote ()
@@@ -1594,14 -1362,7 +1594,14 @@@ _git_mv (
                return
                ;;
        esac
 -      COMPREPLY=()
 +
 +      if [ $(__git_count_arguments "mv") -gt 0 ]; then
 +              # We need to show both cached and untracked files (including
 +              # empty directories) since this may not be the last argument.
 +              __git_complete_index_file "--cached --others --directory"
 +      else
 +              __git_complete_index_file "--cached"
 +      fi
  }
  
  _git_name_rev ()
@@@ -1792,7 -1553,7 +1792,7 @@@ __git_config_get_set_variables (
        while [ $c -gt 1 ]; do
                word="${words[c]}"
                case "$word" in
 -              --global|--system|--file=*)
 +              --system|--global|--local|--file=*)
                        config_file="$word"
                        break
                        ;;
@@@ -1898,7 -1659,7 +1898,7 @@@ _git_config (
        case "$cur" in
        --*)
                __gitcomp "
 -                      --global --system --file=
 +                      --system --global --local --file=
                        --list --replace-all
                        --get --get-all --get-regexp
                        --add --unset --unset-all
                diff.suppressBlankEmpty
                diff.tool
                diff.wordRegex
 +              diff.algorithm
                difftool.
                difftool.prompt
                fetch.recurseSubmodules
@@@ -2308,14 -2068,15 +2308,14 @@@ _git_revert (
  
  _git_rm ()
  {
 -      __git_has_doubledash && return
 -
        case "$cur" in
        --*)
                __gitcomp "--cached --dry-run --ignore-unmatch --quiet"
                return
                ;;
        esac
 -      COMPREPLY=()
 +
 +      __git_complete_index_file "--cached"
  }
  
  _git_shortlog ()
@@@ -2345,10 -2106,6 +2345,10 @@@ _git_show (
                        " "" "${cur#*=}"
                return
                ;;
 +      --diff-algorithm=*)
 +              __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
 +              return
 +              ;;
        --*)
                __gitcomp "--pretty= --format= --abbrev-commit --oneline
                        $__git_diff_common_options
@@@ -2419,7 -2176,7 +2419,7 @@@ _git_submodule (
  {
        __git_has_doubledash && return
  
-       local subcommands="add status init update summary foreach sync"
+       local subcommands="add status init deinit update summary foreach sync"
        if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
                case "$cur" in
                --*)
@@@ -2667,7 -2424,7 +2667,7 @@@ if [[ -n ${ZSH_VERSION-} ]]; the
                                --*=*|*.) ;;
                                *) c="$c " ;;
                                esac
 -                              array+=("$c")
 +                              array[$#array+1]="$c"
                        done
                        compset -P '*[=:]'
                        compadd -Q -S '' -p "${2-}" -a -- array && _ret=0
                compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
        }
  
 +      __gitcomp_file ()
 +      {
 +              emulate -L zsh
 +
 +              local IFS=$'\n'
 +              compset -P '*[=:]'
 +              compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
 +      }
 +
        __git_zsh_helper ()
        {
                emulate -L ksh
  
        compdef _git git gitk
        return
 +elif [[ -n ${BASH_VERSION-} ]]; then
 +      if ((${BASH_VERSINFO[0]} < 4)); then
 +              # compopt is not supported
 +              __git_index_file_list_filter ()
 +              {
 +                      __git_index_file_list_filter_compat
 +              }
 +      fi
  fi
  
  __git_func_wrap ()
diff --combined git-submodule.sh
index ab29bfeb738d1b8f452c558ba6f6d7aa4e854b3d,c1a3202d410ef7cd62f5580e1b9d05743b9387bc..204bc78a9fdffcd11c397a85f94636bb17529b91
@@@ -8,7 -8,8 +8,8 @@@ dashless=$(basename "$0" | sed -e 's/-
  USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]
     or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
     or: $dashless [--quiet] init [--] [<path>...]
 -   or: $dashless [--quiet] update [--init] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
+    or: $dashless [--quiet] deinit [-f|--force] [--] <path>...
 +   or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
     or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
     or: $dashless [--quiet] foreach [--recursive] <command>
     or: $dashless [--quiet] sync [--recursive] [--] [<path>...]"
@@@ -26,7 -27,6 +27,7 @@@ cached
  recursive=
  init=
  files=
 +remote=
  nofetch=
  update=
  prefix=
@@@ -153,32 -153,6 +154,32 @@@ die_if_unmatched (
        fi
  }
  
 +#
 +# Print a submodule configuration setting
 +#
 +# $1 = submodule name
 +# $2 = option name
 +# $3 = default value
 +#
 +# Checks in the usual git-config places first (for overrides),
 +# otherwise it falls back on .gitmodules.  This allows you to
 +# distribute project-wide defaults in .gitmodules, while still
 +# customizing individual repositories if necessary.  If the option is
 +# not in .gitmodules either, print a default value.
 +#
 +get_submodule_config () {
 +      name="$1"
 +      option="$2"
 +      default="$3"
 +      value=$(git config submodule."$name"."$option")
 +      if test -z "$value"
 +      then
 +              value=$(git config -f .gitmodules submodule."$name"."$option")
 +      fi
 +      printf '%s' "${value:-$default}"
 +}
 +
 +
  #
  # Map submodule path to submodule name
  #
@@@ -417,10 -391,6 +418,10 @@@ Use -f if you really want to add it." >
  
        git config -f .gitmodules submodule."$sm_name".path "$sm_path" &&
        git config -f .gitmodules submodule."$sm_name".url "$repo" &&
 +      if test -n "$branch"
 +      then
 +              git config -f .gitmodules submodule."$sm_name".branch "$branch"
 +      fi &&
        git add --force .gitmodules ||
        die "$(eval_gettext "Failed to register submodule '\$sm_path'")"
  }
@@@ -546,6 -516,80 +547,80 @@@ cmd_init(
        done
  }
  
+ #
+ # Unregister submodules from .git/config and remove their work tree
+ #
+ # $@ = requested paths (use '.' to deinit all submodules)
+ #
+ cmd_deinit()
+ {
+       # parse $args after "submodule ... deinit".
+       while test $# -ne 0
+       do
+               case "$1" in
+               -f|--force)
+                       force=$1
+                       ;;
+               -q|--quiet)
+                       GIT_QUIET=1
+                       ;;
+               --)
+                       shift
+                       break
+                       ;;
+               -*)
+                       usage
+                       ;;
+               *)
+                       break
+                       ;;
+               esac
+               shift
+       done
+       if test $# = 0
+       then
+               die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")"
+       fi
+       module_list "$@" |
+       while read mode sha1 stage sm_path
+       do
+               die_if_unmatched "$mode"
+               name=$(module_name "$sm_path") || exit
+               # Remove the submodule work tree (unless the user already did it)
+               if test -d "$sm_path"
+               then
+                       # Protect submodules containing a .git directory
+                       if test -d "$sm_path/.git"
+                       then
+                               echo >&2 "$(eval_gettext "Submodule work tree '\$sm_path' contains a .git directory")"
+                               die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")"
+                       fi
+                       if test -z "$force"
+                       then
+                               git rm -n "$sm_path" ||
+                               die "$(eval_gettext "Submodule work tree '\$sm_path' contains local modifications; use '-f' to discard them")"
+                       fi
+                       rm -rf "$sm_path" || say "$(eval_gettext "Could not remove submodule work tree '\$sm_path'")"
+               fi
+               mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$sm_path'")"
+               # Remove the .git/config entries (unless the user already did it)
+               if test -n "$(git config --get-regexp submodule."$name\.")"
+               then
+                       # Remove the whole section so we have a clean state when
+                       # the user later decides to init this submodule again
+                       url=$(git config submodule."$name".url)
+                       git config --remove-section submodule."$name" 2>/dev/null &&
+                       say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$sm_path'")"
+               fi
+       done
+ }
  #
  # Update each submodule path to correct revision, using clone and checkout as needed
  #
@@@ -564,9 -608,6 +639,9 @@@ cmd_update(
                -i|--init)
                        init=1
                        ;;
 +              --remote)
 +                      remote=1
 +                      ;;
                -N|--no-fetch)
                        nofetch=1
                        ;;
                die_if_unmatched "$mode"
                if test "$stage" = U
                then
 -                      echo >&2 "Skipping unmerged submodule $sm_path"
 +                      echo >&2 "Skipping unmerged submodule $prefix$sm_path"
                        continue
                fi
                name=$(module_name "$sm_path") || exit
                url=$(git config submodule."$name".url)
 +              branch=$(get_submodule_config "$name" branch master)
                if ! test -z "$update"
                then
                        update_module=$update
  
                if test "$update_module" = "none"
                then
 -                      echo "Skipping submodule '$sm_path'"
 +                      echo "Skipping submodule '$prefix$sm_path'"
                        continue
                fi
  
                        # Only mention uninitialized submodules when its
                        # path have been specified
                        test "$#" != "0" &&
 -                      say "$(eval_gettext "Submodule path '\$sm_path' not initialized
 +                      say "$(eval_gettext "Submodule path '\$prefix\$sm_path' not initialized
  Maybe you want to use 'update --init'?")"
                        continue
                fi
                else
                        subsha1=$(clear_local_git_env; cd "$sm_path" &&
                                git rev-parse --verify HEAD) ||
 -                      die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
 +                      die "$(eval_gettext "Unable to find current revision in submodule path '\$prefix\$sm_path'")"
 +              fi
 +
 +              if test -n "$remote"
 +              then
 +                      if test -z "$nofetch"
 +                      then
 +                              # Fetch remote before determining tracking $sha1
 +                              (clear_local_git_env; cd "$sm_path" && git-fetch) ||
 +                              die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
 +                      fi
 +                      remote_name=$(clear_local_git_env; cd "$sm_path" && get_default_remote)
 +                      sha1=$(clear_local_git_env; cd "$sm_path" &&
 +                              git rev-parse --verify "${remote_name}/${branch}") ||
 +                      die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")"
                fi
  
                if test "$subsha1" != "$sha1" -o -n "$force"
                                (clear_local_git_env; cd "$sm_path" &&
                                        ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
                                         test -z "$rev") || git-fetch)) ||
 -                              die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
 +                              die "$(eval_gettext "Unable to fetch in submodule path '\$prefix\$sm_path'")"
                        fi
  
                        # Is this something we just cloned?
                        case "$update_module" in
                        rebase)
                                command="git rebase"
 -                              die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$sm_path'")"
 -                              say_msg="$(eval_gettext "Submodule path '\$sm_path': rebased into '\$sha1'")"
 +                              die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$prefix\$sm_path'")"
 +                              say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': rebased into '\$sha1'")"
                                must_die_on_failure=yes
                                ;;
                        merge)
                                command="git merge"
 -                              die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$sm_path'")"
 -                              say_msg="$(eval_gettext "Submodule path '\$sm_path': merged in '\$sha1'")"
 +                              die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$prefix\$sm_path'")"
 +                              say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': merged in '\$sha1'")"
                                must_die_on_failure=yes
                                ;;
                        *)
                                command="git checkout $subforce -q"
 -                              die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$sm_path'")"
 -                              say_msg="$(eval_gettext "Submodule path '\$sm_path': checked out '\$sha1'")"
 +                              die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$prefix\$sm_path'")"
 +                              say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': checked out '\$sha1'")"
                                ;;
                        esac
  
  
                if test -n "$recursive"
                then
 -                      (clear_local_git_env; cd "$sm_path" && eval cmd_update "$orig_flags")
 +                      (
 +                              prefix="$prefix$sm_path/"
 +                              clear_local_git_env
 +                              cd "$sm_path" &&
 +                              eval cmd_update "$orig_flags"
 +                      )
                        res=$?
                        if test $res -gt 0
                        then
 -                              die_msg="$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
 +                              die_msg="$(eval_gettext "Failed to recurse into submodule path '\$prefix\$sm_path'")"
                                if test $res -eq 1
                                then
                                        err="${err};$die_msg"
@@@ -981,12 -1002,12 +1056,12 @@@ cmd_summary() 
        done |
        if test -n "$for_status"; then
                if [ -n "$files" ]; then
 -                      gettextln "# Submodules changed but not updated:"
 +                      gettextln "Submodules changed but not updated:" | git stripspace -c
                else
 -                      gettextln "# Submodule changes to be committed:"
 +                      gettextln "Submodule changes to be committed:" | git stripspace -c
                fi
 -              echo "#"
 -              sed -e 's|^|# |' -e 's|^# $|#|'
 +              printf "\n" | git stripspace -c
 +              git stripspace -c
        else
                cat
        fi
@@@ -1162,7 -1183,7 +1237,7 @@@ cmd_sync(
  while test $# != 0 && test -z "$command"
  do
        case "$1" in
-       add | foreach | init | update | status | summary | sync)
+       add | foreach | init | deinit | update | status | summary | sync)
                command=$1
                ;;
        -q|--quiet)
index 2683cba7e3f0ba7fbb7ea1752a39a6068fc91718,825c8b94572981ff896e584973938eacc231fd99..5030f1f1bcc2442482ca923d03454fbc69f1a7ca
@@@ -133,7 -133,6 +133,7 @@@ test_expect_success 'submodule add --br
        (
                cd addtest &&
                git submodule add -b initial "$submodurl" submod-branch &&
 +              test "initial" = "$(git config -f .gitmodules submodule.submod-branch.branch)" &&
                git submodule init
        ) &&
  
@@@ -757,4 -756,104 +757,104 @@@ test_expect_success 'submodule add wit
        )
  '
  
+ test_expect_success 'set up a second submodule' '
+       git submodule add ./init2 example2 &&
+       git commit -m "submodule example2 added"
+ '
+ test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' '
+       git config submodule.example.foo bar &&
+       git config submodule.example2.frotz nitfol &&
+       git submodule deinit init &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       test -n "$(git config --get-regexp "submodule\.example2\.")" &&
+       test -f example2/.git &&
+       rmdir init
+ '
+ test_expect_success 'submodule deinit . deinits all initialized submodules' '
+       git submodule update --init &&
+       git config submodule.example.foo bar &&
+       git config submodule.example2.frotz nitfol &&
+       test_must_fail git submodule deinit &&
+       git submodule deinit . &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       test -z "$(git config --get-regexp "submodule\.example2\.")" &&
+       rmdir init example2
+ '
+ test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' '
+       git submodule update --init &&
+       rm -rf init example2/* example2/.git &&
+       git submodule deinit init example2 &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       test -z "$(git config --get-regexp "submodule\.example2\.")" &&
+       rmdir init
+ '
+ test_expect_success 'submodule deinit fails when the submodule contains modifications unless forced' '
+       git submodule update --init &&
+       echo X >>init/s &&
+       test_must_fail git submodule deinit init &&
+       test -n "$(git config --get-regexp "submodule\.example\.")" &&
+       test -f example2/.git &&
+       git submodule deinit -f init &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       rmdir init
+ '
+ test_expect_success 'submodule deinit fails when the submodule contains untracked files unless forced' '
+       git submodule update --init &&
+       echo X >>init/untracked &&
+       test_must_fail git submodule deinit init &&
+       test -n "$(git config --get-regexp "submodule\.example\.")" &&
+       test -f example2/.git &&
+       git submodule deinit -f init &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       rmdir init
+ '
+ test_expect_success 'submodule deinit fails when the submodule HEAD does not match unless forced' '
+       git submodule update --init &&
+       (
+               cd init &&
+               git checkout HEAD^
+       ) &&
+       test_must_fail git submodule deinit init &&
+       test -n "$(git config --get-regexp "submodule\.example\.")" &&
+       test -f example2/.git &&
+       git submodule deinit -f init &&
+       test -z "$(git config --get-regexp "submodule\.example\.")" &&
+       rmdir init
+ '
+ test_expect_success 'submodule deinit is silent when used on an uninitialized submodule' '
+       git submodule update --init &&
+       git submodule deinit init >actual &&
+       test_i18ngrep "Submodule .example. (.*) unregistered for path .init" actual &&
+       git submodule deinit init >actual &&
+       test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
+       git submodule deinit . >actual &&
+       test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
+       test_i18ngrep "Submodule .example2. (.*) unregistered for path .example2" actual &&
+       git submodule deinit . >actual &&
+       test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
+       test_i18ngrep ! "Submodule .example2. (.*) unregistered for path .example2" actual &&
+       rmdir init example2
+ '
+ test_expect_success 'submodule deinit fails when submodule has a .git directory even when forced' '
+       git submodule update --init &&
+       (
+               cd init &&
+               rm .git &&
+               cp -R ../.git/modules/example .git &&
+               GIT_WORK_TREE=. git config --unset core.worktree
+       ) &&
+       test_must_fail git submodule deinit init &&
+       test_must_fail git submodule deinit -f init &&
+       test -d init/.git &&
+       test -n "$(git config --get-regexp "submodule\.example\.")"
+ '
  test_done