diff options
Diffstat (limited to 'git-submodule.sh')
-rwxr-xr-x | git-submodule.sh | 293 |
1 files changed, 233 insertions, 60 deletions
diff --git a/git-submodule.sh b/git-submodule.sh index 22ec5b63b4..896f1c9b82 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -8,15 +8,19 @@ 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] 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>...]" 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= @@ -31,6 +35,7 @@ nofetch= update= prefix= custom_name= +depth= # The function takes at most 2 arguments. The first argument is the # URL that navigates to the submodule origin repo. When relative, this URL @@ -105,14 +110,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 ' @@ -120,6 +161,7 @@ module_list() my ($null_sha1) = ("0" x 40); my @out = (); my $unmatched = 0; + $/ = "\0"; while (<STDIN>) { if (/^unmatched pathspec/) { $unmatched = 1; @@ -210,6 +252,7 @@ module_clone() name=$2 url=$3 reference="$4" + depth="$5" quiet= if test -n "$GIT_QUIET" then @@ -232,7 +275,7 @@ module_clone() mkdir -p "$gitdir_base" ( clear_local_git_env - git clone $quiet -n ${reference:+"$reference"} \ + git clone $quiet ${depth:+"$depth"} -n ${reference:+"$reference"} \ --separate-git-dir "$gitdir" "$url" "$sm_path" ) || die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")" @@ -266,6 +309,11 @@ module_clone() (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b") } +isnumber() +{ + n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1" +} + # # Add a new submodule to the working tree, .gitmodules and the index # @@ -276,6 +324,7 @@ module_clone() cmd_add() { # parse $args after "submodule ... add". + reference_path= while test $# -ne 0 do case "$1" in @@ -292,17 +341,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 @@ -317,6 +374,14 @@ 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 sm_path=$2 @@ -329,9 +394,14 @@ cmd_add() 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 ;; @@ -399,7 +469,7 @@ Use -f if you really want to add it." >&2 echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")" fi fi - module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" || exit + module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" "$depth" || exit ( clear_local_git_env cd "$sm_path" && @@ -465,21 +535,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 } @@ -518,12 +590,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 @@ -532,9 +606,9 @@ cmd_init() ;; 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 @@ -542,7 +616,85 @@ cmd_init() 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 +} + +# +# 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 + + 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 '\$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 } @@ -594,6 +746,14 @@ cmd_update() --checkout) update="checkout" ;; + --depth) + case "$2" in '') usage ;; esac + depth="--depth=$2" + shift + ;; + --depth=*) + depth=$1 + ;; --) shift break @@ -622,7 +782,7 @@ cmd_update() 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 @@ -635,9 +795,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 '$sm_path'" + echo "Skipping submodule '$displaypath'" continue fi @@ -646,20 +808,20 @@ cmd_update() # 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 '\$displaypath' not initialized Maybe you want to use 'update --init'?")" continue fi if ! test -d "$sm_path"/.git -o -f "$sm_path"/.git then - module_clone "$sm_path" "$name" "$url" "$reference" || exit + module_clone "$sm_path" "$name" "$url" "$reference" "$depth" || exit cloned_modules="$cloned_modules;$name" subsha1= 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 '\$displaypath'")" fi if test -n "$remote" @@ -692,7 +854,7 @@ Maybe you want to use 'update --init'?")" (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 '\$displaypath'")" fi # Is this something we just cloned? @@ -706,20 +868,26 @@ Maybe you want to use 'update --init'?")" 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 '\$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 '\$sm_path'")" - say_msg="$(eval_gettext "Submodule path '\$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="${update_module#!}" + die_msg="$(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$prefix\$sm_path'")" + say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': '\$command \$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 '\$displaypath'")" + say_msg="$(eval_gettext "Submodule path '\$displaypath': checked out '\$sha1'")" ;; esac @@ -737,11 +905,16 @@ Maybe you want to use 'update --init'?")" 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 '\$displaypath'")" if test $res -eq 1 then err="${err};$die_msg" @@ -809,14 +982,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 @@ -855,16 +1028,24 @@ 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' | - 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 + test $status = D -o $status = T && echo "$sm_path" && continue + # Respect the ignore setting for --for-status. + if test -n "$for_status" + then + name=$(module_name "$sm_path") + ignore_config=$(get_submodule_config "$name" ignore none) + test $status != A -a $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 && + echo "$sm_path" done ) @@ -904,16 +1085,18 @@ cmd_summary() { ! 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= @@ -942,12 +1125,12 @@ cmd_summary() { 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 @@ -973,18 +1156,7 @@ cmd_summary() { echo fi echo - done | - if test -n "$for_status"; then - if [ -n "$files" ]; then - gettextln "# Submodules changed but not updated:" - else - gettextln "# Submodule changes to be committed:" - fi - echo "#" - sed -e 's|^|# |' -e 's|^# $|#|' - else - cat - fi + done } # # List all submodules, prefixed with: @@ -1031,7 +1203,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" @@ -1042,16 +1214,16 @@ cmd_status() 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 @@ -1126,7 +1298,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 @@ -1157,7 +1330,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) |