Merge branch 'jk/submodule-subdirectory-ok'
authorJunio C Hamano <gitster@pobox.com>
Sun, 30 Jun 2013 22:39:35 +0000 (15:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 30 Jun 2013 22:39:35 +0000 (15:39 -0700)
Allow various subcommands of "git submodule" to be run not from the
top of the working tree of the superproject.

* jk/submodule-subdirectory-ok:
submodule: drop the top-level requirement
rev-parse: add --prefix option
submodule: show full path in error message
t7403: add missing && chaining
t7403: modernize style
t7401: make indentation consistent

1  2 
git-submodule.sh
t/t7400-submodule-basic.sh
diff --combined git-submodule.sh
index eb58c8e89d28626fed56604533ffb4b87991a6af,7756d813c3d1fed237519c12fed50c6647292724..945e296d304a1d1f108571946a6d50fbf238302f
@@@ -14,10 -14,13 +14,13 @@@ USAGE="[--quiet] add [-b <branch>] [-f|
     or: $dashless [--quiet] foreach [--recursive] <command>
     or: $dashless [--quiet] sync [--recursive] [--] [<path>...]"
  OPTIONS_SPEC=
+ SUBDIRECTORY_OK=Yes
  . git-sh-setup
  . git-sh-i18n
  . git-parse-remote
  require_work_tree
+ wt_prefix=$(git rev-parse --show-prefix)
+ cd_to_toplevel
  
  command=
  branch=
@@@ -106,14 -109,50 +109,50 @@@ resolve_relative_url (
        echo "${is_relative:+${up_path}}${remoteurl#./}"
  }
  
+ # Resolve a path to be relative to another path.  This is intended for
+ # converting submodule paths when git-submodule is run in a subdirectory
+ # and only handles paths where the directory separator is '/'.
+ #
+ # The output is the first argument as a path relative to the second argument,
+ # which defaults to $wt_prefix if it is omitted.
+ relative_path ()
+ {
+       local target curdir result
+       target=$1
+       curdir=${2-$wt_prefix}
+       curdir=${curdir%/}
+       result=
+       while test -n "$curdir"
+       do
+               case "$target" in
+               "$curdir/"*)
+                       target=${target#"$curdir"/}
+                       break
+                       ;;
+               esac
+               result="${result}../"
+               if test "$curdir" = "${curdir%/*}"
+               then
+                       curdir=
+               else
+                       curdir="${curdir%/*}"
+               fi
+       done
+       echo "$result$target"
+ }
  #
  # Get submodule info for registered submodules
  # $@ = path to limit submodule list
  #
  module_list()
  {
+       eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
        (
 -              git ls-files --error-unmatch --stage -- "$@" ||
 +              git ls-files -z --error-unmatch --stage -- "$@" ||
                echo "unmatched pathspec exists"
        ) |
        perl -e '
        my ($null_sha1) = ("0" x 40);
        my @out = ();
        my $unmatched = 0;
 +      $/ = "\0";
        while (<STDIN>) {
                if (/^unmatched pathspec/) {
                        $unmatched = 1;
@@@ -283,6 -321,7 +322,7 @@@ isnumber(
  cmd_add()
  {
        # parse $args after "submodule ... add".
+       reference_path=
        while test $# -ne 0
        do
                case "$1" in
                        ;;
                --reference)
                        case "$2" in '') usage ;; esac
-                       reference="--reference=$2"
+                       reference_path=$2
                        shift
                        ;;
                --reference=*)
-                       reference="$1"
+                       reference_path="${1#--reference=}"
                        ;;
                --name)
                        case "$2" in '') usage ;; esac
                shift
        done
  
+       if test -n "$reference_path"
+       then
+               is_absolute_path "$reference_path" ||
+               reference_path="$wt_prefix$reference_path"
+               reference="--reference=$reference_path"
+       fi
        repo=$1
        sm_path=$2
  
                usage
        fi
  
+       is_absolute_path "$sm_path" || sm_path="$wt_prefix$sm_path"
        # assure repo is absolute or relative to parent
        case "$repo" in
        ./*|../*)
+               test -z "$wt_prefix" ||
+               die "$(gettext "Relative path can only be used from the toplevel of the working tree")"
                # dereference source url relative to parent's url
                realrepo=$(resolve_relative_url "$repo") || exit
                ;;
@@@ -472,21 -524,23 +525,23 @@@ cmd_foreach(
                die_if_unmatched "$mode"
                if test -e "$sm_path"/.git
                then
-                       say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
+                       displaypath=$(relative_path "$sm_path")
+                       say "$(eval_gettext "Entering '\$prefix\$displaypath'")"
                        name=$(module_name "$sm_path")
                        (
                                prefix="$prefix$sm_path/"
                                clear_local_git_env
-                               # we make $path available to scripts ...
-                               path=$sm_path
                                cd "$sm_path" &&
+                               sm_path=$(relative_path "$sm_path") &&
+                               # we make $path available to scripts ...
+                               path=$sm_path &&
                                eval "$@" &&
                                if test -n "$recursive"
                                then
                                        cmd_foreach "--recursive" "$@"
                                fi
                        ) <&3 3<&- ||
-                       die "$(eval_gettext "Stopping at '\$sm_path'; script returned non-zero status.")"
+                       die "$(eval_gettext "Stopping at '\$prefix\$displaypath'; script returned non-zero status.")"
                fi
        done
  }
@@@ -525,12 -579,14 +580,14 @@@ cmd_init(
                die_if_unmatched "$mode"
                name=$(module_name "$sm_path") || exit
  
+               displaypath=$(relative_path "$sm_path")
                # Copy url setting when it is not set yet
                if test -z "$(git config "submodule.$name.url")"
                then
                        url=$(git config -f .gitmodules submodule."$name".url)
                        test -z "$url" &&
-                       die "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")"
+                       die "$(eval_gettext "No url found for submodule path '\$displaypath' in .gitmodules")"
  
                        # Possibly a url relative to parent
                        case "$url" in
                                ;;
                        esac
                        git config submodule."$name".url "$url" ||
-                       die "$(eval_gettext "Failed to register url for submodule path '\$sm_path'")"
+                       die "$(eval_gettext "Failed to register url for submodule path '\$displaypath'")"
  
-                       say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$sm_path'")"
+                       say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$displaypath'")"
                fi
  
                # Copy "update" setting when it is not set yet
                test -z "$upd" ||
                test -n "$(git config submodule."$name".update)" ||
                git config submodule."$name".update "$upd" ||
-               die "$(eval_gettext "Failed to register update mode for submodule path '\$sm_path'")"
+               die "$(eval_gettext "Failed to register update mode for submodule path '\$displaypath'")"
        done
  }
  
@@@ -595,27 -651,29 +652,29 @@@ cmd_deinit(
                die_if_unmatched "$mode"
                name=$(module_name "$sm_path") || exit
  
+               displaypath=$(relative_path "$sm_path")
                # 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")"
+                               echo >&2 "$(eval_gettext "Submodule work tree '\$displaypath' 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 -qn "$sm_path" ||
-                               die "$(eval_gettext "Submodule work tree '\$sm_path' contains local modifications; use '-f' to discard them")"
+                               die "$(eval_gettext "Submodule work tree '\$displaypath' contains local modifications; use '-f' to discard them")"
                        fi
                        rm -rf "$sm_path" &&
-                       say "$(eval_gettext "Cleared directory '\$sm_path'")" ||
-                       say "$(eval_gettext "Could not remove submodule work tree '\$sm_path'")"
+                       say "$(eval_gettext "Cleared directory '\$displaypath'")" ||
+                       say "$(eval_gettext "Could not remove submodule work tree '\$displaypath'")"
                fi
  
-               mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$sm_path'")"
+               mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$displaypath'")"
  
                # Remove the .git/config entries (unless the user already did it)
                if test -n "$(git config --get-regexp submodule."$name\.")"
                        # 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'")"
+                       say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$displaypath'")"
                fi
        done
  }
@@@ -718,9 -776,11 +777,11 @@@ cmd_update(
                        update_module=$(git config submodule."$name".update)
                fi
  
+               displaypath=$(relative_path "$prefix$sm_path")
                if test "$update_module" = "none"
                then
-                       echo "Skipping submodule '$prefix$sm_path'"
+                       echo "Skipping submodule '$displaypath'"
                        continue
                fi
  
                        # Only mention uninitialized submodules when its
                        # path have been specified
                        test "$#" != "0" &&
-                       say "$(eval_gettext "Submodule path '\$prefix\$sm_path' not initialized
+                       say "$(eval_gettext "Submodule path '\$displaypath' 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 '\$prefix\$sm_path'")"
+                       die "$(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
                fi
  
                if test -n "$remote"
                                (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 '\$prefix\$sm_path'")"
+                               die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
                        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 '\$prefix\$sm_path'")"
-                               say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': rebased into '\$sha1'")"
+                               die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$displaypath'")"
+                               say_msg="$(eval_gettext "Submodule path '\$displaypath': rebased into '\$sha1'")"
                                must_die_on_failure=yes
                                ;;
                        merge)
                                command="git merge"
-                               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'")"
+                               die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$displaypath'")"
+                               say_msg="$(eval_gettext "Submodule path '\$displaypath': merged in '\$sha1'")"
                                must_die_on_failure=yes
                                ;;
                        *)
                                command="git checkout $subforce -q"
-                               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'")"
+                               die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'")"
+                               say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")"
                                ;;
                        esac
  
                        res=$?
                        if test $res -gt 0
                        then
-                               die_msg="$(eval_gettext "Failed to recurse into submodule path '\$prefix\$sm_path'")"
+                               die_msg="$(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
                                if test $res -eq 1
                                then
                                        err="${err};$die_msg"
@@@ -943,6 -1003,7 +1004,7 @@@ cmd_summary() 
        fi
  
        cd_to_toplevel
+       eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
        # Get modified modules cared by user
        modules=$(git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- "$@" |
                sane_egrep '^:([0-7]* )?160000' |
                ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null &&
                missing_dst=t
  
+               display_name=$(relative_path "$name")
                total_commits=
                case "$missing_src,$missing_dst" in
                t,)
-                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commit \$sha1_src")"
+                       errmsg="$(eval_gettext "  Warn: \$display_name doesn't contain commit \$sha1_src")"
                        ;;
                ,t)
-                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commit \$sha1_dst")"
+                       errmsg="$(eval_gettext "  Warn: \$display_name doesn't contain commit \$sha1_dst")"
                        ;;
                t,t)
-                       errmsg="$(eval_gettext "  Warn: \$name doesn't contain commits \$sha1_src and \$sha1_dst")"
+                       errmsg="$(eval_gettext "  Warn: \$display_name doesn't contain commits \$sha1_src and \$sha1_dst")"
                        ;;
                *)
                        errmsg=
                        submodule="$(gettext "submodule")"
                        if test $mod_dst = 160000
                        then
-                               echo "* $name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
+                               echo "* $display_name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
                        else
-                               echo "* $name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
+                               echo "* $display_name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
                        fi
                else
-                       echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
+                       echo "* $display_name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
                fi
                if test -n "$errmsg"
                then
@@@ -1119,7 -1182,7 +1183,7 @@@ cmd_status(
                die_if_unmatched "$mode"
                name=$(module_name "$sm_path") || exit
                url=$(git config submodule."$name".url)
-               displaypath="$prefix$sm_path"
+               displaypath=$(relative_path "$prefix$sm_path")
                if test "$stage" = U
                then
                        say "U$sha1 $displaypath"
                        say "-$sha1 $displaypath"
                        continue;
                fi
 -              set_name_rev "$sm_path" "$sha1"
                if git diff-files --ignore-submodules=dirty --quiet -- "$sm_path"
                then
 +                      set_name_rev "$sm_path" "$sha1"
                        say " $sha1 $displaypath$revname"
                else
                        if test -z "$cached"
                        then
                                sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD)
 -                              set_name_rev "$sm_path" "$sha1"
                        fi
 +                      set_name_rev "$sm_path" "$sha1"
                        say "+$sha1 $displaypath$revname"
                fi
  
@@@ -1214,7 -1277,8 +1278,8 @@@ cmd_sync(
  
                if git config "submodule.$name.url" >/dev/null 2>/dev/null
                then
-                       say "$(eval_gettext "Synchronizing submodule url for '\$prefix\$sm_path'")"
+                       displaypath=$(relative_path "$prefix$sm_path")
+                       say "$(eval_gettext "Synchronizing submodule url for '\$displaypath'")"
                        git config submodule."$name".url "$super_config_url"
  
                        if test -e "$sm_path"/.git
index 71a42f00865944339187995bee63bab508f03b9c,a38fd9208e9a52d951b0ec70740e1c2a46677e64..50e6ad7458c48842408e2afc20f84c117120c262
@@@ -78,7 -78,7 +78,7 @@@ test_expect_success 'submodule add' 
        (
                cd addtest &&
                git submodule add -q "$submodurl" submod >actual &&
 -              test ! -s actual &&
 +              test_must_be_empty actual &&
                echo "gitdir: ../.git/modules/submod" >expect &&
                test_cmp expect submod/.git &&
                (
@@@ -212,6 -212,32 +212,32 @@@ test_expect_success 'submodule add wit
        test_cmp empty untracked
  '
  
+ test_expect_success 'submodule add in subdirectory' '
+       echo "refs/heads/master" >expect &&
+       >empty &&
+       mkdir addtest/sub &&
+       (
+               cd addtest/sub &&
+               git submodule add "$submodurl" ../realsubmod3 &&
+               git submodule init
+       ) &&
+       rm -f heads head untracked &&
+       inspect addtest/realsubmod3 ../.. &&
+       test_cmp expect heads &&
+       test_cmp expect head &&
+       test_cmp empty untracked
+ '
+ test_expect_success 'submodule add in subdirectory with relative path should fail' '
+       (
+               cd addtest/sub &&
+               test_must_fail git submodule add ../../ submod3 2>../../output.err
+       ) &&
+       test_i18ngrep toplevel output.err
+ '
  test_expect_success 'setup - add an example entry to .gitmodules' '
        GIT_CONFIG=.gitmodules \
        git config submodule.example.url git://example.com/init.git
@@@ -308,7 -334,7 +334,7 @@@ test_expect_success 'update should wor
  
        mkdir init &&
        git submodule update -q >update.out &&
 -      test ! -s update.out &&
 +      test_must_be_empty update.out &&
  
        inspect init &&
        test_cmp expect head-sha1
@@@ -319,6 -345,26 +345,26 @@@ test_expect_success 'status should be "
        grep "^ $rev1" list
  '
  
+ test_expect_success 'status "up-to-date" from subdirectory' '
+       mkdir -p sub &&
+       (
+               cd sub &&
+               git submodule status >../list
+       ) &&
+       grep "^ $rev1" list &&
+       grep "\\.\\./init" list
+ '
+ test_expect_success 'status "up-to-date" from subdirectory with path' '
+       mkdir -p sub &&
+       (
+               cd sub &&
+               git submodule status ../init >../list
+       ) &&
+       grep "^ $rev1" list &&
+       grep "\\.\\./init" list
+ '
  test_expect_success 'status should be "modified" after submodule commit' '
        (
                cd init &&
@@@ -399,6 -445,25 +445,25 @@@ test_expect_success 'update --init' 
        git rev-parse --resolve-git-dir init/.git
  '
  
+ test_expect_success 'update --init from subdirectory' '
+       mv init init2 &&
+       git config -f .gitmodules submodule.example.url "$(pwd)/init2" &&
+       git config --remove-section submodule.example &&
+       test_must_fail git config submodule.example.url &&
+       mkdir -p sub &&
+       (
+               cd sub &&
+               git submodule update ../init >update.out &&
+               cat update.out &&
+               test_i18ngrep "not initialized" update.out &&
+               test_must_fail git rev-parse --resolve-git-dir ../init/.git &&
+               git submodule update --init ../init
+       ) &&
+       git rev-parse --resolve-git-dir init/.git
+ '
  test_expect_success 'do not add files from a submodule' '
  
        git reset --hard &&
@@@ -696,7 -761,7 +761,7 @@@ test_expect_success 'submodule add --na
                rm -rf repo &&
                git rm repo &&
                git submodule add -q --name repo_new "$submodurl/bare.git" repo >actual &&
 -              test ! -s actual &&
 +              test_must_be_empty actual &&
                echo "gitdir: ../.git/modules/submod" >expect &&
                test_cmp expect submod/.git &&
                (
@@@ -772,6 -837,21 +837,21 @@@ test_expect_success 'submodule deinit s
        rmdir init
  '
  
+ test_expect_success 'submodule deinit from subdirectory' '
+       git submodule update --init &&
+       git config submodule.example.foo bar &&
+       mkdir -p sub &&
+       (
+               cd sub &&
+               git submodule deinit ../init >../output
+       ) &&
+       grep "\\.\\./init" output &&
+       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 &&
@@@ -868,19 -948,4 +948,19 @@@ test_expect_success 'submodule deinit f
        test -n "$(git config --get-regexp "submodule\.example\.")"
  '
  
 +test_expect_success 'submodule with UTF-8 name' '
 +      svname=$(printf "\303\245 \303\244\303\266") &&
 +      mkdir "$svname" &&
 +      (
 +              cd "$svname" &&
 +              git init &&
 +              >sub &&
 +              git add sub &&
 +              git commit -m "init sub"
 +      ) &&
 +      test_config core.precomposeunicode true &&
 +      git submodule add ./"$svname" &&
 +      git submodule >&2 &&
 +      test -n "$(git submodule | grep "$svname")"
 +'
  test_done