#!/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
update=
prefix=
-# Resolve relative url by appending to parent's url
+# The function takes at most 2 arguments. The first argument is the
+# URL that navigates to the submodule origin repo. When relative, this URL
+# is relative to the superproject origin URL repo. The second up_path
+# argument, if specified, is the relative path that navigates
+# from the submodule working tree to the superproject working tree.
+#
+# The output of the function is the origin URL of the submodule.
+#
+# The output will either be an absolute URL or filesystem path (if the
+# superproject origin URL is an absolute URL or filesystem path,
+# respectively) or a relative file system path (if the superproject
+# origin URL is a relative file system path).
+#
+# When the output is a relative file system path, the path is either
+# relative to the submodule working tree, if up_path is specified, or to
+# the superproject working tree otherwise.
resolve_relative_url ()
{
remote=$(get_default_remote)
url="$1"
remoteurl=${remoteurl%/}
sep=/
+ up_path="$2"
+
+ case "$remoteurl" in
+ *:*|/*)
+ is_relative=
+ ;;
+ ./*|../*)
+ is_relative=t
+ ;;
+ *)
+ is_relative=t
+ remoteurl="./$remoteurl"
+ ;;
+ esac
+
while test -n "$url"
do
case "$url" in
sep=:
;;
*)
- die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
+ if test -z "$is_relative" || test "." = "$remoteurl"
+ then
+ die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
+ else
+ remoteurl=.
+ fi
;;
esac
;;
break;;
esac
done
- echo "$remoteurl$sep${url%/}"
+ remoteurl="$remoteurl$sep${url%/}"
+ echo "${is_relative:+${up_path}}${remoteurl#./}"
}
#
#
module_list()
{
- git ls-files --error-unmatch --stage -- "$@" |
+ (
+ git ls-files --error-unmatch --stage -- "$@" ||
+ echo "unmatched pathspec exists"
+ ) |
perl -e '
my %unmerged = ();
my ($null_sha1) = ("0" x 40);
+ my @out = ();
+ my $unmatched = 0;
while (<STDIN>) {
+ if (/^unmatched pathspec/) {
+ $unmatched = 1;
+ next;
+ }
chomp;
my ($mode, $sha1, $stage, $path) =
/^([0-7]+) ([0-9a-f]{40}) ([0-3])\t(.*)$/;
next unless $mode eq "160000";
if ($stage ne "0") {
if (!$unmerged{$path}++) {
- print "$mode $null_sha1 U\t$path\n";
+ push @out, "$mode $null_sha1 U\t$path\n";
}
next;
}
- print "$_\n";
+ push @out, "$_\n";
+ }
+ if ($unmatched) {
+ print "#unmatched\n";
+ } else {
+ print for (@out);
}
'
}
+die_if_unmatched ()
+{
+ if test "$1" = "#unmatched"
+ then
+ exit 1
+ fi
+}
+
#
# Map submodule path to submodule name
#
rm -f "$gitdir/index"
else
mkdir -p "$gitdir_base"
- git clone $quiet -n ${reference:+"$reference"} \
- --separate-git-dir "$gitdir" "$url" "$sm_path" ||
+ (
+ clear_local_git_env
+ git clone $quiet -n ${reference:+"$reference"} \
+ --separate-git-dir "$gitdir" "$url" "$sm_path"
+ ) ||
die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
fi
- a=$(cd "$gitdir" && pwd)/
- b=$(cd "$sm_path" && pwd)/
+ # We already are at the root of the work tree but cd_to_toplevel will
+ # resolve any symlinks that might be present in $PWD
+ a=$(cd_to_toplevel && cd "$gitdir" && pwd)/
+ b=$(cd_to_toplevel && cd "$sm_path" && pwd)/
# normalize Windows-style absolute paths to POSIX-style absolute paths
case $a in [a-zA-Z]:/*) a=/${a%%:*}${a#*:} ;; esac
case $b in [a-zA-Z]:/*) b=/${b%%:*}${b#*:} ;; esac
module_list |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
if test -e "$sm_path"/.git
then
say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
module_list "$@" |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
name=$(module_name "$sm_path") || exit
# Copy url setting when it is not set yet
err=
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
if test "$stage" = U
then
echo >&2 "Skipping unmerged submodule $sm_path"
die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
fi
- if test "$subsha1" != "$sha1"
+ if test "$subsha1" != "$sha1" -o -n "$force"
then
subforce=$force
# If we don't already have a -f flag and the submodule has never been checked out
if [ -n "$files" ]
then
test -n "$cached" &&
- die "$(gettext -- "--cached cannot be used with --files")"
+ die "$(gettext "The --cached option cannot be used with the --files option")"
diff_cmd=diff-files
head=
fi
module_list "$@" |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
name=$(module_name "$sm_path") || exit
url=$(git config submodule."$name".url)
displaypath="$prefix$sm_path"
module_list "$@" |
while read mode sha1 stage sm_path
do
+ die_if_unmatched "$mode"
name=$(module_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="$(echo "$sm_path" | sed "s/[^/][^/]*/../g")" &&
+ # guarantee a trailing /
+ up_path=${up_path%/}/ &&
+ # path from submodule work tree to submodule origin repo
+ sub_origin_url=$(resolve_relative_url "$url" "$up_path") &&
+ # path from superproject work tree to submodule origin repo
+ super_config_url=$(resolve_relative_url "$url") || exit
+ ;;
+ *)
+ sub_origin_url="$url"
+ super_config_url="$url"
;;
esac
if git config "submodule.$name.url" >/dev/null 2>/dev/null
then
say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
- git config submodule."$name".url "$url"
+ git config submodule."$name".url "$super_config_url"
if test -e "$sm_path"/.git
then
clear_local_git_env
cd "$sm_path"
remote=$(get_default_remote)
- git config remote."$remote".url "$url"
+ git config remote."$remote".url "$sub_origin_url"
)
fi
fi
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