summaryrefslogtreecommitdiff
path: root/git-submodule.sh
diff options
context:
space:
mode:
Diffstat (limited to 'git-submodule.sh')
-rwxr-xr-xgit-submodule.sh428
1 files changed, 352 insertions, 76 deletions
diff --git a/git-submodule.sh b/git-submodule.sh
index 97e4d9a1ef..3a13397e05 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -4,44 +4,57 @@
#
# Copyright (c) 2007 Lars Hjemli
-USAGE="[--quiet] [--cached] \
-[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
-[--] [<path>...]"
+dashless=$(basename "$0" | sed -e 's/-/ /')
+USAGE="[--quiet] add [-b branch] [-f|--force] [--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] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
+ or: $dashless [--quiet] foreach [--recursive] <command>
+ or: $dashless [--quiet] sync [--] [<path>...]"
OPTIONS_SPEC=
. git-sh-setup
+. git-parse-remote
require_work_tree
command=
branch=
-quiet=
+force=
+reference=
cached=
-
-#
-# print stuff on stdout unless -q was specified
-#
-say()
-{
- if test -z "$quiet"
- then
- echo "$@"
- fi
-}
+recursive=
+init=
+files=
+nofetch=
+update=
+prefix=
# 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}"
+ 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%/}
+ sep=/
while test -n "$url"
do
case "$url" in
../*)
url="${url#../}"
- remoteurl="${remoteurl%/*}"
+ case "$remoteurl" in
+ */*)
+ remoteurl="${remoteurl%/*}"
+ ;;
+ *:*)
+ remoteurl="${remoteurl%:*}"
+ sep=:
+ ;;
+ *)
+ die "cannot strip one component off url '$remoteurl'"
+ ;;
+ esac
;;
./*)
url="${url#./}"
@@ -50,7 +63,16 @@ resolve_relative_url ()
break;;
esac
done
- echo "$remoteurl/$url"
+ echo "$remoteurl$sep${url%/}"
+}
+
+#
+# Get submodule info for registered submodules
+# $@ = path to limit submodule list
+#
+module_list()
+{
+ git ls-files --error-unmatch --stage -- "$@" | sane_grep '^160000 '
}
#
@@ -81,22 +103,14 @@ 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"
+ if test -n "$reference"
then
- rmdir "$path" 2>/dev/null ||
- die "Directory '$path' exist, but is neither empty nor a git repository"
- fi
-
- test -e "$path" &&
- die "A file already exist at path '$path'"
-
- git-clone -n "$url" "$path" ||
+ git-clone "$reference" -n "$url" "$path"
+ else
+ git-clone -n "$url" "$path"
+ fi ||
die "Clone of '$url' into submodule path '$path' failed"
}
@@ -118,8 +132,20 @@ cmd_add()
branch=$2
shift
;;
+ -f | --force)
+ force=$1
+ ;;
-q|--quiet)
- quiet=1
+ GIT_QUIET=1
+ ;;
+ --reference)
+ case "$2" in '') usage ;; esac
+ reference="--reference=$2"
+ shift
+ ;;
+ --reference=*)
+ reference="$1"
+ shift
;;
--)
shift
@@ -138,6 +164,11 @@ cmd_add()
repo=$1
path=$2
+ if test -z "$path"; then
+ path=$(echo "$repo" |
+ sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+ fi
+
if test -z "$repo" -o -z "$path"; then
usage
fi
@@ -157,12 +188,29 @@ cmd_add()
;;
esac
- # strip trailing slashes from path
- path=$(echo "$path" | sed -e 's|/*$||')
-
+ # normalize path:
+ # multiple //; leading ./; /./; /../; trailing /
+ path=$(printf '%s/\n' "$path" |
+ sed -e '
+ s|//*|/|g
+ s|^\(\./\)*||
+ s|/\./|/|g
+ :start
+ s|\([^/]*\)/\.\./||
+ tstart
+ s|/*$||
+ ')
git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
die "'$path' already exists in the index"
+ if test -z "$force" && ! git add --dry-run --ignore-missing "$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."
+ exit 1
+ fi
+
# perhaps the path exists and is already a git repo, else clone it
if test -e "$path"
then
@@ -184,21 +232,80 @@ cmd_add()
git config submodule."$path".url "$url"
else
- module_clone "$path" "$realrepo" || exit
- (unset GIT_DIR; cd "$path" && git checkout -f -q ${branch:+-b "$branch" "origin/$branch"}) ||
- die "Unable to checkout submodule '$path'"
+ module_clone "$path" "$realrepo" "$reference" || exit
+ (
+ clear_local_git_env
+ cd "$path" &&
+ # ash fails to wordsplit ${branch:+-b "$branch"...}
+ case "$branch" in
+ '') git checkout -f -q ;;
+ ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
+ esac
+ ) || die "Unable to checkout submodule '$path'"
fi
- git add "$path" ||
+ git add $force "$path" ||
die "Failed to add submodule '$path'"
git config -f .gitmodules submodule."$path".path "$path" &&
git config -f .gitmodules submodule."$path".url "$repo" &&
- git add .gitmodules ||
+ git add --force .gitmodules ||
die "Failed to register submodule '$path'"
}
#
+# Execute an arbitrary command sequence in each checked out
+# submodule
+#
+# $@ = command to execute
+#
+cmd_foreach()
+{
+ # parse $args after "submodule ... foreach".
+ while test $# -ne 0
+ do
+ case "$1" in
+ -q|--quiet)
+ GIT_QUIET=1
+ ;;
+ --recursive)
+ recursive=1
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done
+
+ toplevel=$(pwd)
+
+ module_list |
+ while read mode sha1 stage path
+ do
+ if test -e "$path"/.git
+ then
+ say "Entering '$prefix$path'"
+ name=$(module_name "$path")
+ (
+ prefix="$prefix$path/"
+ clear_local_git_env
+ cd "$path" &&
+ eval "$@" &&
+ if test -n "$recursive"
+ then
+ cmd_foreach "--recursive" "$@"
+ fi
+ ) ||
+ die "Stopping at '$path'; script returned non-zero status."
+ fi
+ done
+}
+
+#
# Register submodules in .git/config
#
# $@ = requested paths (default to all)
@@ -210,7 +317,7 @@ cmd_init()
do
case "$1" in
-q|--quiet)
- quiet=1
+ GIT_QUIET=1
;;
--)
shift
@@ -226,7 +333,7 @@ cmd_init()
shift
done
- git ls-files --stage -- "$@" | grep '^160000 ' |
+ module_list "$@" |
while read mode sha1 stage path
do
# Skip already registered paths
@@ -248,6 +355,11 @@ cmd_init()
git config submodule."$name".url "$url" ||
die "Failed to register url for submodule path '$path'"
+ 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'"
+
say "Submodule '$name' ($url) registered for path '$path'"
done
}
@@ -260,16 +372,36 @@ cmd_init()
cmd_update()
{
# parse $args after "submodule ... update".
+ orig_flags=
while test $# -ne 0
do
case "$1" in
-q|--quiet)
- shift
- quiet=1
+ GIT_QUIET=1
;;
-i|--init)
+ init=1
+ ;;
+ -N|--no-fetch)
+ nofetch=1
+ ;;
+ -r|--rebase)
+ update="rebase"
+ ;;
+ --reference)
+ case "$2" in '') usage ;; esac
+ reference="--reference=$2"
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
shift
- cmd_init "$@" || return
+ ;;
+ --reference=*)
+ reference="$1"
+ ;;
+ -m|--merge)
+ update="merge"
+ ;;
+ --recursive)
+ recursive=1
;;
--)
shift
@@ -282,13 +414,22 @@ cmd_update()
break
;;
esac
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
+ shift
done
- git ls-files --stage -- "$@" | grep '^160000 ' |
+ if test -n "$init"
+ then
+ cmd_init "--" "$@" || return
+ fi
+
+ cloned_modules=
+ module_list "$@" |
while read mode sha1 stage path
do
name=$(module_name "$path") || exit
url=$(git config submodule."$name".url)
+ update_module=$(git config submodule."$name".update)
if test -z "$url"
then
# Only mention uninitialized submodules when its
@@ -301,14 +442,20 @@ cmd_update()
if ! test -d "$path"/.git -o -f "$path"/.git
then
- module_clone "$path" "$url" || exit
+ module_clone "$path" "$url" "$reference"|| exit
+ cloned_modules="$cloned_modules;$name"
subsha1=
else
- subsha1=$(unset GIT_DIR; cd "$path" &&
+ subsha1=$(clear_local_git_env; cd "$path" &&
git rev-parse --verify HEAD) ||
die "Unable to find current revision in submodule path '$path'"
fi
+ if ! test -z "$update"
+ then
+ update_module=$update
+ fi
+
if test "$subsha1" != "$sha1"
then
force=
@@ -316,18 +463,55 @@ cmd_update()
then
force="-f"
fi
- (unset GIT_DIR; cd "$path" && git-fetch &&
- git-checkout $force -q "$sha1") ||
- die "Unable to checkout '$sha1' in submodule path '$path'"
- say "Submodule path '$path': checked out '$sha1'"
+ if test -z "$nofetch"
+ then
+ (clear_local_git_env; cd "$path" &&
+ git-fetch) ||
+ die "Unable to fetch in submodule path '$path'"
+ fi
+
+ # Is this something we just cloned?
+ case ";$cloned_modules;" in
+ *";$name;"*)
+ # then there is no local change to integrate
+ update_module= ;;
+ esac
+
+ case "$update_module" in
+ rebase)
+ command="git rebase"
+ action="rebase"
+ msg="rebased onto"
+ ;;
+ merge)
+ command="git merge"
+ action="merge"
+ msg="merged in"
+ ;;
+ *)
+ command="git checkout $force -q"
+ action="checkout"
+ msg="checked out"
+ ;;
+ esac
+
+ (clear_local_git_env; cd "$path" && $command "$sha1") ||
+ die "Unable to $action '$sha1' in submodule path '$path'"
+ say "Submodule path '$path': $msg '$sha1'"
+ fi
+
+ if test -n "$recursive"
+ then
+ (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags") ||
+ die "Failed to recurse into submodule path '$path'"
fi
done
}
set_name_rev () {
revname=$( (
- unset GIT_DIR
+ clear_local_git_env
cd "$1" && {
git describe "$2" 2>/dev/null ||
git describe --tags "$2" 2>/dev/null ||
@@ -348,6 +532,7 @@ set_name_rev () {
cmd_summary() {
summary_limit=-1
for_status=
+ diff_cmd=diff-index
# parse $args after "submodule ... summary".
while test $# -ne 0
@@ -356,6 +541,9 @@ cmd_summary() {
--cached)
cached="$1"
;;
+ --files)
+ files="$1"
+ ;;
--for-status)
for_status="$1"
;;
@@ -384,18 +572,31 @@ cmd_summary() {
test $summary_limit = 0 && return
- if rev=$(git rev-parse --verify "$1^0" 2>/dev/null)
+ if rev=$(git rev-parse -q --verify --default HEAD ${1+"$1"})
then
head=$rev
- shift
+ test $# = 0 || shift
+ elif test -z "$1" -o "$1" = "HEAD"
+ then
+ # before the first commit: compare with an empty tree
+ head=$(git hash-object -w -t tree --stdin </dev/null)
+ test -z "$1" || shift
else
- head=HEAD
+ head="HEAD"
+ fi
+
+ if [ -n "$files" ]
+ then
+ test -n "$cached" &&
+ die "--cached cannot be used with --files"
+ diff_cmd=diff-files
+ 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' |
+ 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
do
# Always show modules deleted or type-changed (blob<->module)
@@ -408,8 +609,8 @@ cmd_summary() {
test -z "$modules" && return
- git diff-index $cached --raw $head -- $modules |
- grep -e '^:160000' -e '^:[0-7]* 160000' |
+ git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- $modules |
+ sane_egrep '^:([0-7]* )?160000' |
cut -c2- |
while read mod_src mod_dst sha1_src sha1_dst status name
do
@@ -435,11 +636,11 @@ cmd_summary() {
missing_dst=
test $mod_src = 160000 &&
- ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_src^0 >/dev/null 2>&1 &&
+ ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_src^0 >/dev/null &&
missing_src=t
test $mod_dst = 160000 &&
- ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_dst^0 >/dev/null 2>&1 &&
+ ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null &&
missing_dst=t
total_commits=
@@ -466,7 +667,7 @@ cmd_summary() {
range=$sha1_dst
fi
GIT_DIR="$name/.git" \
- git log --pretty=oneline --first-parent $range | wc -l
+ git rev-list --first-parent $range -- | wc -l
)
total_commits=" ($(($total_commits + 0)))"
;;
@@ -511,7 +712,11 @@ cmd_summary() {
echo
done |
if test -n "$for_status"; then
- echo "# Modified submodules:"
+ 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
@@ -531,15 +736,19 @@ cmd_summary() {
cmd_status()
{
# parse $args after "submodule ... status".
+ orig_flags=
while test $# -ne 0
do
case "$1" in
-q|--quiet)
- quiet=1
+ GIT_QUIET=1
;;
--cached)
cached=1
;;
+ --recursive)
+ recursive=1
+ ;;
--)
shift
break
@@ -551,30 +760,97 @@ cmd_status()
break
;;
esac
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
shift
done
- git ls-files --stage -- "$@" | grep '^160000 ' |
+ module_list "$@" |
while read mode sha1 stage path
do
name=$(module_name "$path") || exit
url=$(git config submodule."$name".url)
+ displaypath="$prefix$path"
if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
then
- say "-$sha1 $path"
+ say "-$sha1 $displaypath"
continue;
fi
set_name_rev "$path" "$sha1"
- if git diff-files --quiet -- "$path"
+ if git diff-files --ignore-submodules=dirty --quiet -- "$path"
then
- say " $sha1 $path$revname"
+ say " $sha1 $displaypath$revname"
else
if test -z "$cached"
then
- sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD)
+ sha1=$(clear_local_git_env; cd "$path" && git rev-parse --verify HEAD)
set_name_rev "$path" "$sha1"
fi
- say "+$sha1 $path$revname"
+ say "+$sha1 $displaypath$revname"
+ fi
+
+ if test -n "$recursive"
+ then
+ (
+ prefix="$displaypath/"
+ clear_local_git_env
+ cd "$path" &&
+ eval cmd_status "$orig_args"
+ ) ||
+ die "Failed to recurse into submodule path '$path'"
+ fi
+ done
+}
+#
+# Sync remote urls for submodules
+# This makes the value for remote.$remote.url match the value
+# specified in .gitmodules.
+#
+cmd_sync()
+{
+ while test $# -ne 0
+ do
+ case "$1" in
+ -q|--quiet)
+ GIT_QUIET=1
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+ cd_to_toplevel
+ module_list "$@" |
+ while read mode sha1 stage path
+ do
+ name=$(module_name "$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
+ ;;
+ esac
+
+ say "Synchronizing submodule url for '$name'"
+ git config submodule."$name".url "$url"
+
+ if test -e "$path"/.git
+ then
+ (
+ clear_local_git_env
+ cd "$path"
+ remote=$(get_default_remote)
+ git config remote."$remote".url "$url"
+ )
fi
done
}
@@ -588,11 +864,11 @@ cmd_status()
while test $# != 0 && test -z "$command"
do
case "$1" in
- add | init | update | status | summary)
+ add | foreach | init | update | status | summary | sync)
command=$1
;;
-q|--quiet)
- quiet=1
+ GIT_QUIET=1
;;
-b|--branch)
case "$2" in