summaryrefslogtreecommitdiff
path: root/git-submodule.sh
diff options
context:
space:
mode:
Diffstat (limited to 'git-submodule.sh')
-rwxr-xr-xgit-submodule.sh381
1 files changed, 305 insertions, 76 deletions
diff --git a/git-submodule.sh b/git-submodule.sh
index 2f47e065fe..d189a24c71 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -4,9 +4,14 @@
#
# 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>...]|[foreach <command>]|[sync [--] [<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] [-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 [--] [<path>...]"
OPTIONS_SPEC=
. git-sh-setup
. git-parse-remote
@@ -14,19 +19,15 @@ 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 ()
@@ -36,12 +37,24 @@ resolve_relative_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,7 @@ resolve_relative_url ()
break;;
esac
done
- echo "$remoteurl/${url%/}"
+ echo "$remoteurl$sep${url%/}"
}
#
@@ -59,7 +72,24 @@ resolve_relative_url ()
#
module_list()
{
- git ls-files --stage -- "$@" | grep '^160000 '
+ git ls-files --error-unmatch --stage -- "$@" |
+ perl -e '
+ my %unmerged = ();
+ my ($null_sha1) = ("0" x 40);
+ while (<STDIN>) {
+ 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";
+ }
+ next;
+ }
+ print "$_\n";
+ }
+ '
}
#
@@ -90,22 +120,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"
}
@@ -127,8 +149,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
@@ -147,6 +181,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
@@ -166,12 +205,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
@@ -193,17 +249,24 @@ 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'"
}
@@ -215,13 +278,45 @@ cmd_add()
#
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 '$path'"
- (cd "$path" && eval "$@") ||
+ 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
@@ -239,7 +334,7 @@ cmd_init()
do
case "$1" in
-q|--quiet)
- quiet=1
+ GIT_QUIET=1
;;
--)
shift
@@ -277,6 +372,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
}
@@ -289,16 +389,39 @@ 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
+ ;;
+ -f|--force)
+ force=$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
@@ -311,13 +434,27 @@ cmd_update()
break
;;
esac
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
+ shift
done
+ if test -n "$init"
+ then
+ cmd_init "--" "$@" || return
+ fi
+
+ cloned_modules=
module_list "$@" |
while read mode sha1 stage path
do
+ if test "$stage" = U
+ then
+ echo >&2 "Skipping unmerged submodule $path"
+ continue
+ fi
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
@@ -330,33 +467,80 @@ 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=
- 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" -a -z "$force"
then
- force="-f"
+ subforce="-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
+ # Run fetch only if $sha1 isn't present or it
+ # is not reachable from a ref.
+ (clear_local_git_env; cd "$path" &&
+ ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
+ test -z "$rev") || 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 $subforce -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 ||
@@ -377,6 +561,7 @@ set_name_rev () {
cmd_summary() {
summary_limit=-1
for_status=
+ diff_cmd=diff-index
# parse $args after "submodule ... summary".
while test $# -ne 0
@@ -385,6 +570,9 @@ cmd_summary() {
--cached)
cached="$1"
;;
+ --files)
+ files="$1"
+ ;;
--for-status)
for_status="$1"
;;
@@ -413,18 +601,31 @@ cmd_summary() {
test $summary_limit = 0 && return
- if rev=$(git rev-parse -q --verify "$1^0")
+ 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 -- "$@" |
- egrep '^:([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)
@@ -437,8 +638,8 @@ cmd_summary() {
test -z "$modules" && return
- git diff-index $cached --raw $head -- $modules |
- egrep '^:([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
@@ -495,7 +696,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)))"
;;
@@ -540,7 +741,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
@@ -560,15 +765,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
@@ -580,6 +789,7 @@ cmd_status()
break
;;
esac
+ orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
shift
done
@@ -588,22 +798,39 @@ cmd_status()
do
name=$(module_name "$path") || exit
url=$(git config submodule."$name".url)
+ displaypath="$prefix$path"
+ if test "$stage" = U
+ then
+ say "U$sha1 $displaypath"
+ continue
+ fi
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
}
@@ -618,7 +845,7 @@ cmd_sync()
do
case "$1" in
-q|--quiet)
- quiet=1
+ GIT_QUIET=1
shift
;;
--)
@@ -647,13 +874,15 @@ cmd_sync()
;;
esac
+ say "Synchronizing submodule url for '$name'"
+ git config submodule."$name".url "$url"
+
if test -e "$path"/.git
then
(
- unset GIT_DIR
+ clear_local_git_env
cd "$path"
remote=$(get_default_remote)
- say "Synchronizing submodule url for '$name'"
git config remote."$remote".url "$url"
)
fi
@@ -673,7 +902,7 @@ do
command=$1
;;
-q|--quiet)
- quiet=1
+ GIT_QUIET=1
;;
-b|--branch)
case "$2" in