diff options
Diffstat (limited to 'git-submodule.sh')
-rwxr-xr-x | git-submodule.sh | 459 |
1 files changed, 363 insertions, 96 deletions
diff --git a/git-submodule.sh b/git-submodule.sh index ad9fe628fd..b40f876a2c 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -4,16 +4,15 @@ # # Copyright (c) 2007 Lars Hjemli -USAGE='[--quiet] [--cached] [add <repo> [-b branch]|status|init|update] [--] [<path>...]' +USAGE="[--quiet] [--cached] \ +[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \ +[--] [<path>...]" OPTIONS_SPEC= . git-sh-setup require_work_tree -add= +command= branch= -init= -update= -status= quiet= cached= @@ -28,26 +27,14 @@ say() fi } -# NEEDSWORK: identical function exists in get_repo_base in clone.sh -get_repo_base() { - ( - cd "`/bin/pwd`" && - cd "$1" || cd "$1.git" && - { - cd .git - pwd - } - ) 2>/dev/null -} - # Resolve relative url by appending to parent's url resolve_relative_url () { branch="$(git symbolic-ref HEAD 2>/dev/null)" remote="$(git config branch.${branch#refs/heads/}.remote)" remote="${remote:-origin}" - remoteurl="$(git config remote.$remote.url)" || - die "remote ($remote) does not have a url in .git/config" + remoteurl=$(git config "remote.$remote.url") || + die "remote ($remote) does not have a url defined in .git/config" url="$1" while test -n "$url" do @@ -74,9 +61,8 @@ resolve_relative_url () module_name() { # Do we have "submodule.<something>.path = $1" defined in .gitmodules file? - re=$(printf '%s' "$1" | sed -e 's/[].[^$\\*]/\\&/g') - name=$( GIT_CONFIG=.gitmodules \ - git config --get-regexp '^submodule\..*\.path$' | + 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'" @@ -86,9 +72,9 @@ module_name() # # Clone a submodule # -# Prior to calling, modules_update checks that a possibly existing +# Prior to calling, cmd_update checks that a possibly existing # path is not a git repository. -# Likewise, module_add checks that path does not exist at all, +# Likewise, cmd_add checks that path does not exist at all, # since it is the location of a new submodule. # module_clone() @@ -117,54 +103,97 @@ module_clone() # # Add a new submodule to the working tree, .gitmodules and the index # -# $@ = repo [path] +# $@ = repo path # # optional branch is stored in global branch variable # -module_add() +cmd_add() { + # parse $args after "submodule ... add". + while test $# -ne 0 + do + case "$1" in + -b | --branch) + case "$2" in '') usage ;; esac + branch=$2 + shift + ;; + -q|--quiet) + quiet=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + repo=$1 path=$2 - if test -z "$repo"; then + if test -z "$repo" -o -z "$path"; then usage fi + # assure repo is absolute or relative to parent case "$repo" in ./*|../*) # dereference source url relative to parent's url - realrepo="$(resolve_relative_url $repo)" ;; - *) - # Turn the source into an absolute path if - # it is local - if base=$(get_repo_base "$repo"); then - repo="$base" - fi + realrepo=$(resolve_relative_url "$repo") || exit + ;; + *:*|/*) + # absolute url realrepo=$repo ;; + *) + die "repo URL: '$repo' must be absolute or begin with ./|../" + ;; esac - # Guess path from repo if not specified or strip trailing slashes - if test -z "$path"; then - path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') - else - path=$(echo "$path" | sed -e 's|/*$||') - fi - - test -e "$path" && - die "'$path' already exists" + # strip trailing slashes from path + path=$(echo "$path" | sed -e 's|/*$||') git ls-files --error-unmatch "$path" > /dev/null 2>&1 && die "'$path' already exists in the index" - module_clone "$path" "$realrepo" || exit - (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || - die "Unable to checkout submodule '$path'" + # perhaps the path exists and is already a git repo, else clone it + if test -e "$path" + then + if test -d "$path"/.git -o -f "$path"/.git + then + echo "Adding existing repo at '$path' to the index" + else + die "'$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" || exit + (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) || + die "Unable to checkout submodule '$path'" + fi + git add "$path" || die "Failed to add submodule '$path'" - GIT_CONFIG=.gitmodules git config submodule."$path".path "$path" && - GIT_CONFIG=.gitmodules git config submodule."$path".url "$repo" && + git config -f .gitmodules submodule."$path".path "$path" && + git config -f .gitmodules submodule."$path".url "$repo" && git add .gitmodules || die "Failed to register submodule '$path'" } @@ -174,9 +203,30 @@ module_add() # # $@ = requested paths (default to all) # -modules_init() +cmd_init() { - git ls-files --stage -- "$@" | grep -e '^160000 ' | + # parse $args after "submodule ... init". + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + quiet=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + git ls-files --stage -- "$@" | grep '^160000 ' | while read mode sha1 stage path do # Skip already registered paths @@ -184,14 +234,14 @@ modules_init() url=$(git config submodule."$name".url) test -z "$url" || continue - url=$(GIT_CONFIG=.gitmodules git config submodule."$name".url) + url=$(git config -f .gitmodules submodule."$name".url) test -z "$url" && die "No url found for submodule path '$path' in .gitmodules" # Possibly a url relative to parent case "$url" in ./*|../*) - url="$(resolve_relative_url "$url")" + url=$(resolve_relative_url "$url") || exit ;; esac @@ -207,9 +257,34 @@ modules_init() # # $@ = requested paths (default to all) # -modules_update() +cmd_update() { - git ls-files --stage -- "$@" | grep -e '^160000 ' | + # parse $args after "submodule ... update". + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + shift + quiet=1 + ;; + -i|--init) + shift + cmd_init "$@" || return + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + done + + git ls-files --stage -- "$@" | grep '^160000 ' | while read mode sha1 stage path do name=$(module_name "$path") || exit @@ -220,10 +295,11 @@ modules_update() # path have been specified test "$#" != "0" && say "Submodule path '$path' not initialized" + say "Maybe you want to use 'update --init'?" continue fi - if ! test -d "$path"/.git + if ! test -d "$path"/.git -o -f "$path"/.git then module_clone "$path" "$url" || exit subsha1= @@ -250,12 +326,193 @@ set_name_rev () { cd "$1" && { git describe "$2" 2>/dev/null || git describe --tags "$2" 2>/dev/null || - git describe --contains --tags "$2" + git describe --contains "$2" 2>/dev/null || + git describe --all --always "$2" } ) ) test -z "$revname" || revname=" ($revname)" } +# +# Show commit summary for submodules in index or working tree +# +# If '--cached' is given, show summary between index and given commit, +# or between working tree and given commit +# +# $@ = [commit (default 'HEAD'),] requested paths (default all) +# +cmd_summary() { + summary_limit=-1 + for_status= + # parse $args after "submodule ... summary". + while test $# -ne 0 + do + case "$1" in + --cached) + cached="$1" + ;; + --for-status) + for_status="$1" + ;; + -n|--summary-limit) + if summary_limit=$(($2 + 0)) 2>/dev/null && test "$summary_limit" = "$2" + then + : + else + usage + fi + shift + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + test $summary_limit = 0 && return + + if rev=$(git rev-parse --verify "$1^0" 2>/dev/null) + then + head=$rev + shift + else + head=HEAD + fi + + cd_to_toplevel + # Get modified modules cared by user + modules=$(git diff-index $cached --raw $head -- "$@" | + grep -e '^:160000' -e '^:[0-7]* 160000' | + while read mod_src mod_dst sha1_src sha1_dst status name + do + # Always show modules deleted or type-changed (blob<->module) + test $status = D -o $status = T && echo "$name" && continue + # 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" + done + ) + + test -z "$modules" && return + + git diff-index $cached --raw $head -- $modules | + grep -e '^:160000' -e '^:[0-7]* 160000' | + cut -c2- | + while read mod_src mod_dst sha1_src sha1_dst status name + do + if test -z "$cached" && + test $sha1_dst = 0000000000000000000000000000000000000000 + then + case "$mod_dst" in + 160000) + sha1_dst=$(GIT_DIR="$name/.git" git rev-parse HEAD) + ;; + 100644 | 100755 | 120000) + sha1_dst=$(git hash-object $name) + ;; + 000000) + ;; # removed + *) + # unexpected type + echo >&2 "unexpected mode $mod_dst" + continue ;; + esac + fi + missing_src= + missing_dst= + + test $mod_src = 160000 && + ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_src^0 >/dev/null 2>&1 && + missing_src=t + + test $mod_dst = 160000 && + ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_dst^0 >/dev/null 2>&1 && + missing_dst=t + + total_commits= + case "$missing_src,$missing_dst" in + t,) + errmsg=" Warn: $name doesn't contain commit $sha1_src" + ;; + ,t) + errmsg=" Warn: $name doesn't contain commit $sha1_dst" + ;; + t,t) + errmsg=" Warn: $name doesn't contain commits $sha1_src and $sha1_dst" + ;; + *) + errmsg= + total_commits=$( + if test $mod_src = 160000 -a $mod_dst = 160000 + then + range="$sha1_src...$sha1_dst" + elif test $mod_src = 160000 + then + range=$sha1_src + else + range=$sha1_dst + fi + GIT_DIR="$name/.git" \ + git log --pretty=oneline --first-parent $range | wc -l + ) + total_commits=" ($(($total_commits + 0)))" + ;; + esac + + sha1_abbr_src=$(echo $sha1_src | cut -c1-7) + sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7) + if test $status = T + then + if test $mod_dst = 160000 + then + echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:" + else + echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:" + fi + else + echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:" + fi + if test -n "$errmsg" + then + # Don't give error msg for modification whose dst is not submodule + # i.e. deleted or changed to blob + test $mod_dst = 160000 && echo "$errmsg" + else + if test $mod_src = 160000 -a $mod_dst = 160000 + then + limit= + test $summary_limit -gt 0 && limit="-$summary_limit" + GIT_DIR="$name/.git" \ + git log $limit --pretty='format: %m %s' \ + --first-parent $sha1_src...$sha1_dst + elif test $mod_dst = 160000 + then + GIT_DIR="$name/.git" \ + git log --pretty='format: > %s' -1 $sha1_dst + else + GIT_DIR="$name/.git" \ + git log --pretty='format: < %s' -1 $sha1_src + fi + echo + fi + echo + done | + if test -n "$for_status"; then + echo "# Modified submodules:" + echo "#" + sed -e 's|^|# |' -e 's|^# $|#|' + else + cat + fi +} # # List all submodules, prefixed with: # - submodule not initialized @@ -266,14 +523,38 @@ set_name_rev () { # # $@ = requested paths (default to all) # -modules_list() +cmd_status() { - git ls-files --stage -- "$@" | grep -e '^160000 ' | + # parse $args after "submodule ... status". + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + quiet=1 + ;; + --cached) + cached=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + git ls-files --stage -- "$@" | grep '^160000 ' | while read mode sha1 stage path do name=$(module_name "$path") || exit url=$(git config submodule."$name".url) - if test -z "url" || ! test -d "$path"/.git + if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git then say "-$sha1 $path" continue; @@ -293,20 +574,17 @@ modules_list() done } -while test $# != 0 +# This loop parses the command line arguments to find the +# subcommand name to dispatch. Parsing of the subcommand specific +# options are primarily done by the subcommand implementations. +# Subcommand specific options such as --branch and --cached are +# parsed here as well, for backward compatibility. + +while test $# != 0 && test -z "$command" do case "$1" in - add) - add=1 - ;; - init) - init=1 - ;; - update) - update=1 - ;; - status) - status=1 + add | init | update | status | summary) + command=$1 ;; -q|--quiet) quiet=1 @@ -320,7 +598,7 @@ do branch="$2"; shift ;; --cached) - cached=1 + cached="$1" ;; --) break @@ -335,30 +613,19 @@ do shift done -case "$add,$branch" in -1,*) - ;; -,) - ;; -,*) +# No command word defaults to "status" +test -n "$command" || command=status + +# "-b branch" is accepted only by "add" +if test -n "$branch" && test "$command" != add +then usage - ;; -esac +fi -case "$add,$init,$update,$status,$cached" in -1,,,,) - module_add "$@" - ;; -,1,,,) - modules_init "$@" - ;; -,,1,,) - modules_update "$@" - ;; -,,,*,*) - modules_list "$@" - ;; -*) +# "--cached" is accepted only by "status" and "summary" +if test -n "$cached" && test "$command" != status -a "$command" != summary +then usage - ;; -esac +fi + +"cmd_$command" "$@" |