git-submodule foreach: export .gitmodules settings as variables
authorW. Trevor King <wking@tremily.us>
Thu, 8 Nov 2012 23:47:18 +0000 (18:47 -0500)
committerW. Trevor King <wking@tremily.us>
Fri, 9 Nov 2012 03:27:50 +0000 (22:27 -0500)
This makes it easy to access per-submodule variables.  For example,

  git submodule foreach 'git checkout $(git config --file $toplevel/.gitmodules submodule.$name.branch) && git pull'

can now be reduced to

  git submodule foreach 'git checkout $submodule_branch && git pull'

Every submodule.<name>.<opt> setting from .gitmodules is available as
a $submodule_<sanitized-opt> variable.  These variables are not
propagated recursively into nested submodules.

Signed-off-by: W. Trevor King <wking@tremily.us>
Based-on-patch-by: Phil Hord <phil.hord@gmail.com>
Documentation/git-submodule.txt
git-sh-setup.sh [changed mode: 0644->0755]
git-submodule.sh
t/t7407-submodule-foreach.sh

index cbec363a4b71c88d2500d4e6bf4e0b7b0502d6c4..9a99826d5c7bd1aadb86b41c993c48cb22f182c2 100644 (file)
@@ -175,6 +175,9 @@ foreach::
        $path is the name of the submodule directory relative to the
        superproject, $sha1 is the commit as recorded in the superproject,
        and $toplevel is the absolute path to the top-level of the superproject.
+       In addition, every submodule.<name>.<opt> setting from .gitmodules
+       is available as the variable $submodule_<sanitized_opt>.  These
+       variables are not propagated recursively into nested submodules.
        Any submodules defined in the superproject but not checked out are
        ignored by this command. Unless given `--quiet`, foreach prints the name
        of each submodule before evaluating the command.
old mode 100644 (file)
new mode 100755 (executable)
index ee0e0bc..179a920
@@ -222,6 +222,26 @@ clear_local_git_env() {
        unset $(git rev-parse --local-env-vars)
 }
 
+# Remove any suspect characters from a user-generated variable name.
+sanitize_variable_name() {
+       VAR_NAME="$1"
+       printf '%s' "$VAR_NAME" |
+       sed -e 's/^[^a-zA-Z]/_/' -e 's/[^a-zA-Z0-9]/_/g'
+}
+
+# Return a command for setting a new variable.
+# Neither the variable name nor the variable value passed to this
+# function need to be sanitized.  You need to eval the returned
+# string, because new variables set by the function itself don't
+# effect the calling process.
+set_user_variable() {
+       VAR_NAME="$1"
+       VAR_VALUE="$2"
+       VAR_NAME=$(sanitize_variable_name "$VAR_NAME")
+       VAR_VALUE=$(printf '%s' "$VAR_VALUE" |
+               sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
+       printf '%s=%s;\n' "$VAR_NAME" "\"$VAR_VALUE\""
+}
 
 # Platform specific tweaks to work around some commands
 case $(uname -s) in
index bc33112ab59adf14a319f81f826930a02f0b6bba..e4d26f9a9cabfb74f536e4cc0155794360b86289 100755 (executable)
@@ -434,8 +434,24 @@ cmd_foreach()
                                clear_local_git_env
                                # we make $path available to scripts ...
                                path=$sm_path
+
+                               # make all submodule variables available to scripts
+                               eval $(
+                                       git config -f .gitmodules --get-regexp "^submodule\.${name}\..*" |
+                                       sed -e "s|^submodule\.${name}\.||" |
+                                       while read VAR_NAME VAR_VALUE ; do
+                                               VAR_NAME=$(printf '%s' "$VAR_NAME" | tr A-Z a-z)
+                                               set_user_variable "submodule_${VAR_NAME}" "$VAR_VALUE"
+                                       done)
+                               UNSET_CMD=$(set |
+                                       sed -n -e 's|^\(submodule_[a-z_]*\)=.*$|\1|p' |
+                                       while read VAR_NAME ; do
+                                               printf 'unset %s;\n' "$VAR_NAME"
+                                       done)
+
                                cd "$sm_path" &&
                                eval "$@" &&
+                               eval "$UNSET_CMD" &&
                                if test -n "$recursive"
                                then
                                        cmd_foreach "--recursive" "$@"
index 9b69fe2e1470e406b005e629a6f7759f8e564ddf..46ac74611315e669ccab80e5887f2f1e8843d979 100755 (executable)
@@ -313,4 +313,33 @@ test_expect_success 'command passed to foreach --recursive retains notion of std
        test_cmp expected actual
 '
 
+cat > expect <<EOF
+Entering 'nested1'
+nested1 nested1 wonky"value
+Entering 'nested1/nested2'
+nested2 nested2 another wonky"value
+Entering 'nested1/nested2/nested3'
+nested3 nested3
+Entering 'nested1/nested2/nested3/submodule'
+submodule submodule
+Entering 'sub1'
+sub1 sub1
+Entering 'sub2'
+sub2 sub2
+Entering 'sub3'
+sub3 sub3
+EOF
+
+test_expect_success 'test foreach environment variables' '
+       (
+               cd clone2 &&
+               git config -f .gitmodules submodule.nested1.wonky-var "wonky\"value" &&
+               git config -f nested1/.gitmodules submodule.nested2.wonky-var "another wonky\"value" &&
+               git submodule foreach --recursive "echo \$path \$submodule_path \$submodule_wonky_var" > ../actual
+       ) &&
+       test_i18ncmp expect actual
+'
+#
+#"echo \$toplevel-\$name-\$submodule_path-\$submodule_url"
+
 test_done