diff options
Diffstat (limited to 'git-submodule.sh')
-rwxr-xr-x | git-submodule.sh | 798 |
1 files changed, 538 insertions, 260 deletions
diff --git a/git-submodule.sh b/git-submodule.sh index 170186f494..7fe8a511b3 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -1,21 +1,35 @@ #!/bin/sh # -# git-submodules.sh: add, init, update or list git submodules +# git-submodule.sh: add, init, update or list git submodules # # Copyright (c) 2007 Lars Hjemli dashless=$(basename "$0" | sed -e 's/-/ /') -USAGE="[--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <repository> [<path>] +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] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...] + or: $dashless [--quiet] deinit [-f|--force] (--all| [--] <path>...) + or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--reference <repository>] [--recursive] [--] [<path>...] or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] or: $dashless [--quiet] foreach [--recursive] <command> - or: $dashless [--quiet] sync [--] [<path>...]" + 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 + +# Restrict ourselves to a vanilla subset of protocols; the URLs +# we get are under control of a remote repository, and we do not +# want them kicking off arbitrary git-remote-* programs. +# +# If the user has already specified a set of allowed protocols, +# we assume they know what they're doing and use that instead. +: ${GIT_ALLOW_PROTOCOL=file:git:http:https:ssh} +export GIT_ALLOW_PROTOCOL command= branch= @@ -25,95 +39,60 @@ cached= recursive= init= files= +remote= nofetch= update= prefix= +custom_name= +depth= -# Resolve relative url by appending to parent's url -resolve_relative_url () -{ - remote=$(get_default_remote) - remoteurl=$(git config "remote.$remote.url") || - die "remote ($remote) does not have a url defined in .git/config" - url="$1" - remoteurl=${remoteurl%/} - while test -n "$url" - do - case "$url" in - ../*) - url="${url#../}" - remoteurl="${remoteurl%/*}" - ;; - ./*) - url="${url#./}" - ;; - *) - break;; - esac - done - echo "$remoteurl/${url%/}" -} - -# -# Get submodule info for registered submodules -# $@ = path to limit submodule list -# -module_list() +die_if_unmatched () { - git ls-files --error-unmatch --stage -- "$@" | sane_grep '^160000 ' + if test "$1" = "#unmatched" + then + exit 1 + fi } # -# Map submodule path to submodule name +# Print a submodule configuration setting # -# $1 = path +# $1 = submodule name +# $2 = option name +# $3 = default value # -module_name() -{ - # Do we have "submodule.<something>.path = $1" defined in .gitmodules file? - re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g') - name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | - sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' ) - test -z "$name" && - die "No submodule mapping found in .gitmodules for path '$path'" - echo "$name" -} - +# 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. # -# Clone a submodule -# -# Prior to calling, cmd_update checks that a possibly existing -# path is not a git repository. -# Likewise, cmd_add checks that path does not exist at all, -# since it is the location of a new submodule. -# -module_clone() -{ - path=$1 - url=$2 - reference="$3" - - # If there already is a directory at the submodule path, - # expect it to be empty (since that is the default checkout - # action) and try to remove it. - # Note: if $path is a symlink to a directory the test will - # succeed but the rmdir will fail. We might want to fix this. - if test -d "$path" +get_submodule_config () { + name="$1" + option="$2" + default="$3" + value=$(git config submodule."$name"."$option") + if test -z "$value" then - rmdir "$path" 2>/dev/null || - die "Directory '$path' exists, but is neither empty nor a git repository" + value=$(git config -f .gitmodules submodule."$name"."$option") fi + printf '%s' "${value:-$default}" +} - test -e "$path" && - die "A file already exist at path '$path'" +isnumber() +{ + n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1" +} - if test -n "$reference" - then - git-clone "$reference" -n "$url" "$path" - else - git-clone -n "$url" "$path" - fi || - die "Clone of '$url' into submodule path '$path' failed" +# Sanitize the local git environment for use within a submodule. We +# can't simply use clear_local_git_env since we want to preserve some +# of the settings from GIT_CONFIG_PARAMETERS. +sanitize_submodule_env() +{ + save_config=$GIT_CONFIG_PARAMETERS + clear_local_git_env + GIT_CONFIG_PARAMETERS=$save_config + export GIT_CONFIG_PARAMETERS } # @@ -126,6 +105,7 @@ module_clone() cmd_add() { # parse $args after "submodule ... add". + reference_path= while test $# -ne 0 do case "$1" in @@ -142,13 +122,25 @@ cmd_add() ;; --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 + custom_name=$2 shift ;; + --depth) + case "$2" in '') usage ;; esac + depth="--depth=$2" + shift + ;; + --depth=*) + depth=$1 + ;; --) shift break @@ -163,96 +155,125 @@ cmd_add() 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 - path=$2 + sm_path=$2 - if test -z "$path"; then - path=$(echo "$repo" | + if test -z "$sm_path"; then + sm_path=$(printf '%s\n' "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') fi - if test -z "$repo" -o -z "$path"; then + if test -z "$repo" || test -z "$sm_path"; then 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 + realrepo=$(git submodule--helper resolve-relative-url "$repo") || exit ;; *:*|/*) # absolute url realrepo=$repo ;; *) - die "repo URL: '$repo' must be absolute or begin with ./|../" + die "$(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")" ;; esac # normalize path: # multiple //; leading ./; /./; /../; trailing / - path=$(printf '%s/\n' "$path" | + sm_path=$(printf '%s/\n' "$sm_path" | sed -e ' s|//*|/|g s|^\(\./\)*|| - s|/\./|/|g + s|/\(\./\)*|/|g :start s|\([^/]*\)/\.\./|| tstart s|/*$|| ') - git ls-files --error-unmatch "$path" > /dev/null 2>&1 && - die "'$path' already exists in the index" + git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 && + die "$(eval_gettext "'\$sm_path' already exists in the index")" - if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1 + if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1 then - echo >&2 "The following path is ignored by one of your .gitignore files:" && - echo >&2 $path && - echo >&2 "Use -f if you really want to add it." + eval_gettextln "The following path is ignored by one of your .gitignore files: +\$sm_path +Use -f if you really want to add it." >&2 exit 1 fi + if test -n "$custom_name" + then + sm_name="$custom_name" + else + sm_name="$sm_path" + fi + # perhaps the path exists and is already a git repo, else clone it - if test -e "$path" + if test -e "$sm_path" then - if test -d "$path"/.git -o -f "$path"/.git + if test -d "$sm_path"/.git || test -f "$sm_path"/.git then - echo "Adding existing repo at '$path' to the index" + eval_gettextln "Adding existing repo at '\$sm_path' to the index" else - die "'$path' already exists and is not a valid git repo" + die "$(eval_gettext "'\$sm_path' already exists and is not a valid git repo")" fi - case "$repo" in - ./*|../*) - url=$(resolve_relative_url "$repo") || exit - ;; - *) - url="$repo" - ;; - esac - git config submodule."$path".url "$url" else - - module_clone "$path" "$realrepo" "$reference" || exit + if test -d ".git/modules/$sm_name" + then + if test -z "$force" + then + echo >&2 "$(eval_gettext "A git directory for '\$sm_name' is found locally with remote(s):")" + GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^," ", -e s,' (fetch)',, >&2 + echo >&2 "$(eval_gettext "If you want to reuse this local git directory instead of cloning again from")" + echo >&2 " $realrepo" + echo >&2 "$(eval_gettext "use the '--force' option. If the local git directory is not the correct repo")" + die "$(eval_gettext "or you are unsure what this means choose another name with the '--name' option.")" + else + echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")" + fi + fi + git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${depth:+"$depth"} || exit ( - clear_local_git_env - cd "$path" && + sanitize_submodule_env + cd "$sm_path" && # ash fails to wordsplit ${branch:+-b "$branch"...} case "$branch" in '') git checkout -f -q ;; - ?*) git checkout -f -q -b "$branch" "origin/$branch" ;; + ?*) git checkout -f -q -B "$branch" "origin/$branch" ;; esac - ) || die "Unable to checkout submodule '$path'" + ) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")" fi + git config submodule."$sm_name".url "$realrepo" - git add $force "$path" || - die "Failed to add submodule '$path'" + git add $force "$sm_path" || + die "$(eval_gettext "Failed to add submodule '\$sm_path'")" - git config -f .gitmodules submodule."$path".path "$path" && - git config -f .gitmodules submodule."$path".url "$repo" && + 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 "Failed to register submodule '$path'" + die "$(eval_gettext "Failed to register submodule '\$sm_path'")" } # @@ -285,24 +306,41 @@ cmd_foreach() toplevel=$(pwd) - module_list | - while read mode sha1 stage path + # dup stdin so that it can be restored when running the external + # command in the subshell (and a recursive call to this function) + exec 3<&0 + + { + git submodule--helper list --prefix "$wt_prefix" || + echo "#unmatched" + } | + while read mode sha1 stage sm_path do - if test -e "$path"/.git + die_if_unmatched "$mode" + if test -e "$sm_path"/.git then - say "Entering '$prefix$path'" - name=$(module_name "$path") + displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix") + say "$(eval_gettext "Entering '\$displaypath'")" + name=$(git submodule--helper name "$sm_path") ( - prefix="$prefix$path/" - clear_local_git_env - cd "$path" && - eval "$@" && + prefix="$prefix$sm_path/" + sanitize_submodule_env + cd "$sm_path" && + sm_path=$(git submodule--helper relative-path "$sm_path" "$wt_prefix") && + # we make $path available to scripts ... + path=$sm_path && + if test $# -eq 1 + then + eval "$1" + else + "$@" + fi && if test -n "$recursive" then cmd_foreach "--recursive" "$@" fi - ) || - die "Stopping at '$path'; script returned non-zero status." + ) <&3 3<&- || + die "$(eval_gettext "Stopping at '\$displaypath'; script returned non-zero status.")" fi done } @@ -335,37 +373,117 @@ cmd_init() shift done - module_list "$@" | - while read mode sha1 stage path - do - # Skip already registered paths - name=$(module_name "$path") || exit - url=$(git config submodule."$name".url) - test -z "$url" || continue - - url=$(git config -f .gitmodules submodule."$name".url) - test -z "$url" && - die "No url found for submodule path '$path' in .gitmodules" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper init ${GIT_QUIET:+--quiet} ${prefix:+--prefix "$prefix"} "$@" +} - # Possibly a url relative to parent - case "$url" in - ./*|../*) - url=$(resolve_relative_url "$url") || exit +# +# Unregister submodules from .git/config and remove their work tree +# +# $@ = requested paths (use '.' to deinit all submodules) +# +cmd_deinit() +{ + # parse $args after "submodule ... deinit". + deinit_all= + while test $# -ne 0 + do + case "$1" in + -f|--force) + force=$1 + ;; + -q|--quiet) + GIT_QUIET=1 + ;; + --all) + deinit_all=t + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break ;; esac + shift + done - git config submodule."$name".url "$url" || - die "Failed to register url for submodule path '$path'" + if test -n "$deinit_all" && test "$#" -ne 0 + then + echo >&2 "$(eval_gettext "pathspec and --all are incompatible")" + usage + fi + if test $# = 0 && test -z "$deinit_all" + then + die "$(eval_gettext "Use '--all' if you really want to deinitialize all submodules")" + fi - upd="$(git config -f .gitmodules submodule."$name".update)" - test -z "$upd" || - git config submodule."$name".update "$upd" || - die "Failed to register update mode for submodule path '$path'" + { + git submodule--helper list --prefix "$wt_prefix" "$@" || + echo "#unmatched" + } | + while read mode sha1 stage sm_path + do + die_if_unmatched "$mode" + name=$(git submodule--helper name "$sm_path") || exit - say "Submodule '$name' ($url) registered for path '$path'" + displaypath=$(git submodule--helper relative-path "$sm_path" "$wt_prefix") + + # 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 '\$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 '\$displaypath' contains local modifications; use '-f' to discard them")" + fi + rm -rf "$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 '\$displaypath'")" + + # 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 '\$displaypath'")" + fi done } +is_tip_reachable () ( + sanitize_submodule_env && + cd "$1" && + rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) && + test -z "$rev" +) + +fetch_in_submodule () ( + sanitize_submodule_env && + cd "$1" && + case "$2" in + '') + git fetch ;; + *) + git fetch $(get_default_remote) "$2" ;; + esac +) + # # Update each submodule path to correct revision, using clone and checkout as needed # @@ -374,43 +492,60 @@ cmd_init() cmd_update() { # parse $args after "submodule ... update". - orig_args="$@" while test $# -ne 0 do case "$1" in -q|--quiet) - shift GIT_QUIET=1 ;; -i|--init) init=1 - shift + ;; + --remote) + remote=1 ;; -N|--no-fetch) - shift nofetch=1 ;; + -f|--force) + force=$1 + ;; -r|--rebase) - shift update="rebase" ;; --reference) case "$2" in '') usage ;; esac reference="--reference=$2" - shift 2 + shift ;; --reference=*) reference="$1" - shift ;; -m|--merge) - shift update="merge" ;; --recursive) - shift recursive=1 ;; + --checkout) + update="checkout" + ;; + --depth) + case "$2" in '') usage ;; esac + depth="--depth=$2" + shift + ;; + --depth=*) + depth=$1 + ;; + -j|--jobs) + case "$2" in '') usage ;; esac + jobs="--jobs=$2" + shift + ;; + --jobs=*) + jobs=$1 + ;; --) shift break @@ -422,6 +557,7 @@ cmd_update() break ;; esac + shift done if test -n "$init" @@ -429,86 +565,170 @@ cmd_update() cmd_init "--" "$@" || return fi - module_list "$@" | - while read mode sha1 stage path + { + git submodule--helper update-clone ${GIT_QUIET:+--quiet} \ + ${wt_prefix:+--prefix "$wt_prefix"} \ + ${prefix:+--recursive-prefix "$prefix"} \ + ${update:+--update "$update"} \ + ${reference:+--reference "$reference"} \ + ${depth:+--depth "$depth"} \ + ${jobs:+$jobs} \ + "$@" || echo "#unmatched" + } | { + err= + while read mode sha1 stage just_cloned sm_path do - name=$(module_name "$path") || exit + die_if_unmatched "$mode" + + name=$(git submodule--helper name "$sm_path") || exit url=$(git config submodule."$name".url) - update_module=$(git config submodule."$name".update) - if test -z "$url" + branch=$(get_submodule_config "$name" branch master) + if ! test -z "$update" then - # Only mention uninitialized submodules when its - # path have been specified - test "$#" != "0" && - say "Submodule path '$path' not initialized" && - say "Maybe you want to use 'update --init'?" - continue + update_module=$update + else + update_module=$(git config submodule."$name".update) + if test -z "$update_module" + then + update_module="checkout" + fi fi - if ! test -d "$path"/.git -o -f "$path"/.git + displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix") + + if test $just_cloned -eq 1 then - module_clone "$path" "$url" "$reference"|| exit subsha1= + update_module=checkout else - subsha1=$(clear_local_git_env; cd "$path" && + subsha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD) || - die "Unable to find current revision in submodule path '$path'" + die "$(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")" fi - if ! test -z "$update" + if test -n "$remote" then - update_module=$update + if test -z "$nofetch" + then + # Fetch remote before determining tracking $sha1 + (sanitize_submodule_env; cd "$sm_path" && git-fetch) || + die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")" + fi + remote_name=$(sanitize_submodule_env; cd "$sm_path" && get_default_remote) + sha1=$(sanitize_submodule_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" + if test "$subsha1" != "$sha1" || test -n "$force" then - force= - if test -z "$subsha1" + subforce=$force + # If we don't already have a -f flag and the submodule has never been checked out + if test -z "$subsha1" && test -z "$force" then - force="-f" + subforce="-f" fi if test -z "$nofetch" then - (clear_local_git_env; cd "$path" && - git-fetch) || - die "Unable to fetch in submodule path '$path'" + # Run fetch only if $sha1 isn't present or it + # is not reachable from a ref. + is_tip_reachable "$sm_path" "$sha1" || + fetch_in_submodule "$sm_path" || + die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")" + + # Now we tried the usual fetch, but $sha1 may + # not be reachable from any of the refs + is_tip_reachable "$sm_path" "$sha1" || + fetch_in_submodule "$sm_path" "$sha1" || + die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain $sha1. Direct fetching of that commit failed.")" fi + must_die_on_failure= case "$update_module" in + checkout) + command="git checkout $subforce -q" + die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$displaypath'")" + say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")" + ;; rebase) command="git rebase" - action="rebase" - msg="rebased onto" + 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" - action="merge" - msg="merged in" + 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 $force -q" - action="checkout" - msg="checked out" + !*) + command="${update_module#!}" + die_msg="$(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$displaypath'")" + say_msg="$(eval_gettext "Submodule path '\$displaypath': '\$command \$sha1'")" + must_die_on_failure=yes ;; + *) + die "$(eval_gettext "Invalid update mode '$update_module' for submodule '$name'")" esac - (clear_local_git_env; cd "$path" && $command "$sha1") || - die "Unable to $action '$sha1' in submodule path '$path'" - say "Submodule path '$path': $msg '$sha1'" + if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1") + then + say "$say_msg" + elif test -n "$must_die_on_failure" + then + die_with_status 2 "$die_msg" + else + err="${err};$die_msg" + continue + fi fi if test -n "$recursive" then - (clear_local_git_env; cd "$path" && cmd_update $orig_args) || - die "Failed to recurse into submodule path '$path'" + ( + prefix=$(git submodule--helper relative-path "$prefix$sm_path/" "$wt_prefix") + wt_prefix= + sanitize_submodule_env + cd "$sm_path" && + eval cmd_update + ) + res=$? + if test $res -gt 0 + then + die_msg="$(eval_gettext "Failed to recurse into submodule path '\$displaypath'")" + if test $res -eq 1 + then + err="${err};$die_msg" + continue + else + die_with_status $res "$die_msg" + fi + fi fi done + + if test -n "$err" + then + OIFS=$IFS + IFS=';' + for e in $err + do + if test -n "$e" + then + echo >&2 "$e" + fi + done + IFS=$OIFS + exit 1 + fi + } } set_name_rev () { revname=$( ( - clear_local_git_env + sanitize_submodule_env cd "$1" && { git describe "$2" 2>/dev/null || git describe --tags "$2" 2>/dev/null || @@ -545,14 +765,14 @@ cmd_summary() { for_status="$1" ;; -n|--summary-limit) - if summary_limit=$(($2 + 0)) 2>/dev/null && test "$summary_limit" = "$2" - then - : - else - usage - fi + summary_limit="$2" + isnumber "$summary_limit" || usage shift ;; + --summary-limit=*) + summary_limit="${1#--summary-limit=}" + isnumber "$summary_limit" || usage + ;; --) shift break @@ -573,7 +793,7 @@ cmd_summary() { then head=$rev test $# = 0 || shift - elif test -z "$1" -o "$1" = "HEAD" + elif test -z "$1" || test "$1" = "HEAD" then # before the first commit: compare with an empty tree head=$(git hash-object -w -t tree --stdin </dev/null) @@ -585,22 +805,34 @@ cmd_summary() { if [ -n "$files" ] then test -n "$cached" && - die "--cached cannot be used with --files" + die "$(gettext "The --cached option cannot be used with the --files option")" diff_cmd=diff-files head= 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' | - while read mod_src mod_dst sha1_src sha1_dst status name + while read mod_src mod_dst sha1_src sha1_dst status sm_path do # Always show modules deleted or type-changed (blob<->module) - test $status = D -o $status = T && echo "$name" && continue + if test "$status" = D || test "$status" = T + then + printf '%s\n' "$sm_path" + continue + fi + # Respect the ignore setting for --for-status. + if test -n "$for_status" + then + name=$(git submodule--helper name "$sm_path") + ignore_config=$(get_submodule_config "$name" ignore none) + test $status != A && test $ignore_config = all && continue + fi # Also show added or modified modules which are checked out - GIT_DIR="$name/.git" git-rev-parse --git-dir >/dev/null 2>&1 && - echo "$name" + GIT_DIR="$sm_path/.git" git-rev-parse --git-dir >/dev/null 2>&1 && + printf '%s\n' "$sm_path" done ) @@ -625,7 +857,7 @@ cmd_summary() { ;; # removed *) # unexpected type - echo >&2 "unexpected mode $mod_dst" + eval_gettextln "unexpected mode \$mod_dst" >&2 continue ;; esac fi @@ -640,21 +872,23 @@ cmd_summary() { ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null && missing_dst=t + display_name=$(git submodule--helper relative-path "$name" "$wt_prefix") + total_commits= case "$missing_src,$missing_dst" in t,) - errmsg=" Warn: $name doesn't contain commit $sha1_src" + errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_src")" ;; ,t) - errmsg=" Warn: $name doesn't contain commit $sha1_dst" + errmsg="$(eval_gettext " Warn: \$display_name doesn't contain commit \$sha1_dst")" ;; t,t) - errmsg=" 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= total_commits=$( - if test $mod_src = 160000 -a $mod_dst = 160000 + if test $mod_src = 160000 && test $mod_dst = 160000 then range="$sha1_src...$sha1_dst" elif test $mod_src = 160000 @@ -674,14 +908,16 @@ cmd_summary() { sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7) if test $status = T then + blob="$(gettext "blob")" + 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 @@ -689,7 +925,7 @@ cmd_summary() { # i.e. deleted or changed to blob test $mod_dst = 160000 && echo "$errmsg" else - if test $mod_src = 160000 -a $mod_dst = 160000 + if test $mod_src = 160000 && test $mod_dst = 160000 then limit= test $summary_limit -gt 0 && limit="-$summary_limit" @@ -707,18 +943,7 @@ cmd_summary() { echo fi echo - done | - if test -n "$for_status"; then - if [ -n "$files" ]; then - echo "# Submodules changed but not updated:" - else - echo "# Submodule changes to be committed:" - fi - echo "#" - sed -e 's|^|# |' -e 's|^# $|#|' - else - cat - fi + done } # # List all submodules, prefixed with: @@ -733,7 +958,6 @@ cmd_summary() { cmd_status() { # parse $args after "submodule ... status". - orig_args="$@" while test $# -ne 0 do case "$1" in @@ -760,27 +984,40 @@ cmd_status() shift done - module_list "$@" | - while read mode sha1 stage path + { + git submodule--helper list --prefix "$wt_prefix" "$@" || + echo "#unmatched" + } | + while read mode sha1 stage sm_path do - name=$(module_name "$path") || exit + die_if_unmatched "$mode" + name=$(git submodule--helper name "$sm_path") || exit url=$(git config submodule."$name".url) - displaypath="$prefix$path" - if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git + displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix") + if test "$stage" = U + then + say "U$sha1 $displaypath" + continue + fi + if test -z "$url" || + { + ! test -d "$sm_path"/.git && + ! test -f "$sm_path"/.git + } then say "-$sha1 $displaypath" continue; fi - set_name_rev "$path" "$sha1" - if git diff-files --ignore-submodules=dirty --quiet -- "$path" + 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 "$path" && git rev-parse --verify HEAD) - set_name_rev "$path" "$sha1" + sha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD) fi + set_name_rev "$sm_path" "$sha1" say "+$sha1 $displaypath$revname" fi @@ -788,11 +1025,12 @@ cmd_status() then ( prefix="$displaypath/" - clear_local_git_env - cd "$path" && - cmd_status $orig_args + sanitize_submodule_env + wt_prefix= + cd "$sm_path" && + eval cmd_status ) || - die "Failed to recurse into submodule path '$path'" + die "$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")" fi done } @@ -810,6 +1048,10 @@ cmd_sync() GIT_QUIET=1 shift ;; + --recursive) + recursive=1 + shift + ;; --) shift break @@ -823,28 +1065,56 @@ cmd_sync() esac done cd_to_toplevel - module_list "$@" | - while read mode sha1 stage path + { + git submodule--helper list --prefix "$wt_prefix" "$@" || + echo "#unmatched" + } | + while read mode sha1 stage sm_path do - name=$(module_name "$path") + die_if_unmatched "$mode" + name=$(git submodule--helper name "$sm_path") url=$(git config -f .gitmodules --get submodule."$name".url) # Possibly a url relative to parent case "$url" in ./*|../*) - url=$(resolve_relative_url "$url") || exit + # rewrite foo/bar as ../.. to find path from + # submodule work tree to superproject work tree + up_path="$(printf '%s\n' "$sm_path" | sed "s/[^/][^/]*/../g")" && + # guarantee a trailing / + up_path=${up_path%/}/ && + # path from submodule work tree to submodule origin repo + sub_origin_url=$(git submodule--helper resolve-relative-url "$url" "$up_path") && + # path from superproject work tree to submodule origin repo + super_config_url=$(git submodule--helper resolve-relative-url "$url") || exit + ;; + *) + sub_origin_url="$url" + super_config_url="$url" ;; esac - if test -e "$path"/.git + if git config "submodule.$name.url" >/dev/null 2>/dev/null then - ( - clear_local_git_env - cd "$path" - remote=$(get_default_remote) - say "Synchronizing submodule url for '$name'" - git config remote."$remote".url "$url" - ) + displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix") + say "$(eval_gettext "Synchronizing submodule url for '\$displaypath'")" + git config submodule."$name".url "$super_config_url" + + if test -e "$sm_path"/.git + then + ( + sanitize_submodule_env + cd "$sm_path" + remote=$(get_default_remote) + git config remote."$remote".url "$sub_origin_url" + + if test -n "$recursive" + then + prefix="$prefix$sm_path/" + eval cmd_sync + fi + ) + fi fi done } @@ -858,7 +1128,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) @@ -889,7 +1159,15 @@ do done # No command word defaults to "status" -test -n "$command" || command=status +if test -z "$command" +then + if test $# = 0 + then + command=status + else + usage + fi +fi # "-b branch" is accepted only by "add" if test -n "$branch" && test "$command" != add @@ -898,7 +1176,7 @@ then fi # "--cached" is accepted only by "status" and "summary" -if test -n "$cached" && test "$command" != status -a "$command" != summary +if test -n "$cached" && test "$command" != status && test "$command" != summary then usage fi |