Created
October 28, 2010 16:16
-
-
Save suzak/651716 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# | |
# git-submodules.sh: add, init, update or list git submodules | |
# | |
# Copyright (c) 2007 Lars Hjemli | |
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 | |
#!/bin/sh | |
# | |
# This is included in commands that either have to be run from the toplevel | |
# of the repository, or with GIT_DIR environment variable properly. | |
# If the GIT_DIR does not look like the right correct git-repository, | |
# it dies. | |
# Having this variable in your environment would break scripts because | |
# you would cause "cd" to be taken to unexpected places. If you | |
# like CDPATH, define it for your interactive shell sessions without | |
# exporting it. | |
unset CDPATH | |
git_broken_path_fix () { | |
case ":$PATH:" in | |
*:$1:*) : ok ;; | |
*) | |
PATH=$( | |
SANE_TOOL_PATH="$1" | |
IFS=: path= sep= | |
set x $PATH | |
shift | |
for elem | |
do | |
case "$SANE_TOOL_PATH:$elem" in | |
(?*:/bin | ?*:/usr/bin) | |
path="$path$sep$SANE_TOOL_PATH" | |
sep=: | |
SANE_TOOL_PATH= | |
esac | |
path="$path$sep$elem" | |
sep=: | |
done | |
echo "$path" | |
) | |
;; | |
esac | |
} | |
die() { | |
echo >&2 "$@" | |
exit 1 | |
} | |
GIT_QUIET= | |
say () { | |
if test -z "$GIT_QUIET" | |
then | |
printf '%s\n' "$*" | |
fi | |
} | |
if test -n "$OPTIONS_SPEC"; then | |
usage() { | |
"$0" -h | |
exit 1 | |
} | |
parseopt_extra= | |
[ -n "$OPTIONS_KEEPDASHDASH" ] && | |
parseopt_extra="--keep-dashdash" | |
eval "$( | |
echo "$OPTIONS_SPEC" | | |
git rev-parse --parseopt $parseopt_extra -- "$@" || | |
echo exit $? | |
)" | |
else | |
dashless=$(basename "$0" | sed -e 's/-/ /') | |
usage() { | |
die "Usage: $dashless $USAGE" | |
} | |
if [ -z "$LONG_USAGE" ] | |
then | |
LONG_USAGE="Usage: $dashless $USAGE" | |
else | |
LONG_USAGE="Usage: $dashless $USAGE | |
$LONG_USAGE" | |
fi | |
case "$1" in | |
-h|--h|--he|--hel|--help) | |
echo "$LONG_USAGE" | |
exit | |
esac | |
fi | |
set_reflog_action() { | |
if [ -z "${GIT_REFLOG_ACTION:+set}" ] | |
then | |
GIT_REFLOG_ACTION="$*" | |
export GIT_REFLOG_ACTION | |
fi | |
} | |
git_editor() { | |
if test -z "${GIT_EDITOR:+set}" | |
then | |
GIT_EDITOR="$(git var GIT_EDITOR)" || return $? | |
fi | |
eval "$GIT_EDITOR" '"$@"' | |
} | |
git_pager() { | |
if test -t 1 | |
then | |
GIT_PAGER=$(git var GIT_PAGER) | |
else | |
GIT_PAGER=cat | |
fi | |
: ${LESS=-FRSX} | |
export LESS | |
eval "$GIT_PAGER" '"$@"' | |
} | |
sane_grep () { | |
GREP_OPTIONS= LC_ALL=C grep "$@" | |
} | |
sane_egrep () { | |
GREP_OPTIONS= LC_ALL=C egrep "$@" | |
} | |
is_bare_repository () { | |
git rev-parse --is-bare-repository | |
} | |
cd_to_toplevel () { | |
cdup=$(git rev-parse --show-toplevel) && | |
cd "$cdup" || { | |
echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" | |
exit 1 | |
} | |
} | |
require_work_tree () { | |
test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true || | |
die "fatal: $0 cannot be used without a working tree." | |
} | |
get_author_ident_from_commit () { | |
pick_author_script=' | |
/^author /{ | |
s/'\''/'\''\\'\'\''/g | |
h | |
s/^author \([^<]*\) <[^>]*> .*$/\1/ | |
s/'\''/'\''\'\'\''/g | |
s/.*/GIT_AUTHOR_NAME='\''&'\''/p | |
g | |
s/^author [^<]* <\([^>]*\)> .*$/\1/ | |
s/'\''/'\''\'\'\''/g | |
s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p | |
g | |
s/^author [^<]* <[^>]*> \(.*\)$/\1/ | |
s/'\''/'\''\'\'\''/g | |
s/.*/GIT_AUTHOR_DATE='\''&'\''/p | |
q | |
} | |
' | |
encoding=$(git config i18n.commitencoding || echo UTF-8) | |
git show -s --pretty=raw --encoding="$encoding" "$1" -- | | |
LANG=C LC_ALL=C sed -ne "$pick_author_script" | |
} | |
# Clear repo-local GIT_* environment variables. Useful when switching to | |
# another repository (e.g. when entering a submodule). See also the env | |
# list in git_connect() | |
clear_local_git_env() { | |
unset $(git rev-parse --local-env-vars) | |
} | |
# Make sure we are in a valid repository of a vintage we understand, | |
# if we require to be in a git repository. | |
if test -z "$NONGIT_OK" | |
then | |
GIT_DIR=$(git rev-parse --git-dir) || exit | |
if [ -z "$SUBDIRECTORY_OK" ] | |
then | |
test -z "$(git rev-parse --show-cdup)" || { | |
exit=$? | |
echo >&2 "You need to run this command from the toplevel of the working tree." | |
exit $exit | |
} | |
fi | |
test -n "$GIT_DIR" && GIT_DIR=$(cd "$GIT_DIR" && pwd) || { | |
echo >&2 "Unable to determine absolute path of git directory" | |
exit 1 | |
} | |
: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} | |
fi | |
# Fix some commands on Windows | |
case $(uname -s) in | |
*MINGW*) | |
# Windows has its own (incompatible) sort and find | |
sort () { | |
/usr/bin/sort "$@" | |
} | |
find () { | |
/usr/bin/find "$@" | |
} | |
;; | |
esac | |
#### end of git-sh-setup | |
### . git-parse-remote | |
#!/bin/sh | |
# git-ls-remote could be called from outside a git managed repository; | |
# this would fail in that case and would issue an error message. | |
GIT_DIR=$(git rev-parse -q --git-dir) || :; | |
get_data_source () { | |
case "$1" in | |
*/*) | |
echo '' | |
;; | |
.) | |
echo self | |
;; | |
*) | |
if test "$(git config --get "remote.$1.url")" | |
then | |
echo config | |
elif test -f "$GIT_DIR/remotes/$1" | |
then | |
echo remotes | |
elif test -f "$GIT_DIR/branches/$1" | |
then | |
echo branches | |
else | |
echo '' | |
fi ;; | |
esac | |
} | |
get_remote_url () { | |
data_source=$(get_data_source "$1") | |
case "$data_source" in | |
'') | |
echo "$1" | |
;; | |
self) | |
echo "$1" | |
;; | |
config) | |
git config --get "remote.$1.url" | |
;; | |
remotes) | |
sed -ne '/^URL: */{ | |
s///p | |
q | |
}' "$GIT_DIR/remotes/$1" | |
;; | |
branches) | |
sed -e 's/#.*//' "$GIT_DIR/branches/$1" | |
;; | |
*) | |
die "internal error: get-remote-url $1" ;; | |
esac | |
} | |
get_default_remote () { | |
curr_branch=$(git symbolic-ref -q HEAD | sed -e 's|^refs/heads/||') | |
origin=$(git config --get "branch.$curr_branch.remote") | |
echo ${origin:-origin} | |
} | |
get_remote_merge_branch () { | |
case "$#" in | |
0|1) | |
origin="$1" | |
default=$(get_default_remote) | |
test -z "$origin" && origin=$default | |
curr_branch=$(git symbolic-ref -q HEAD) | |
[ "$origin" = "$default" ] && | |
echo $(git for-each-ref --format='%(upstream)' $curr_branch) | |
;; | |
*) | |
repo=$1 | |
shift | |
ref=$1 | |
# FIXME: It should return the tracking branch | |
# Currently only works with the default mapping | |
case "$ref" in | |
+*) | |
ref=$(expr "z$ref" : 'z+\(.*\)') | |
;; | |
esac | |
expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:" | |
remote=$(expr "z$ref" : 'z\([^:]*\):') | |
case "$remote" in | |
'' | HEAD ) remote=HEAD ;; | |
heads/*) remote=${remote#heads/} ;; | |
refs/heads/*) remote=${remote#refs/heads/} ;; | |
refs/* | tags/* | remotes/* ) remote= | |
esac | |
[ -n "$remote" ] && echo "refs/remotes/$repo/$remote" | |
esac | |
} | |
#### end of git-parse-remote | |
require_work_tree | |
command= | |
branch= | |
force= | |
reference= | |
cached= | |
recursive= | |
init= | |
files= | |
nofetch= | |
update= | |
prefix= | |
# Resolve relative url by appending to parent's url | |
resolve_relative_url () | |
{ | |
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%/} | |
while test -n "$url" | |
do | |
case "$url" in | |
../*) | |
url="${url#../}" | |
remoteurl="${remoteurl%/*}" | |
;; | |
./*) | |
url="${url#./}" | |
;; | |
*) | |
break;; | |
esac | |
done | |
echo "$remoteurl/${url%/}" | |
} | |
# | |
# Get submodule info for registered submodules | |
# $@ = path to limit submodule list | |
# | |
module_list() | |
{ | |
git ls-files --error-unmatch --stage -- "$@" | sane_grep '^160000 ' | |
} | |
# | |
# Map submodule path to submodule name | |
# | |
# $1 = path | |
# | |
module_name() | |
{ | |
# Do we have "submodule.<something>.path = $1" defined in .gitmodules file? | |
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'" | |
echo "$name" | |
} | |
# | |
# Clone a submodule | |
# | |
# Prior to calling, cmd_update checks that a possibly existing | |
# path is not a git repository. | |
# Likewise, cmd_add checks that path does not exist at all, | |
# since it is the location of a new submodule. | |
# | |
module_clone() | |
{ | |
path=$1 | |
url=$2 | |
reference="$3" | |
submodulecachedir=$(git config --get submodule.cachedir) | |
# 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" | |
then | |
rmdir "$path" 2>/dev/null || | |
die "Directory '$path' exists, but is neither empty nor a git repository" | |
fi | |
test -e "$path" && | |
die "A file already exist at path '$path'" | |
if test -n "$reference" | |
then | |
git-clone "$reference" -n "$url" "$path" | |
else | |
if test -n "$submodulecachedir" | |
then | |
cpath="$submodulecachedir/$(echo $url | sed -e 's/:/\//g')" | |
if test ! -e "$cpath" | |
then | |
git-clone -n "$url" "$cpath" | |
fi | |
git-new-workdir "$cpath" "$path" | |
else | |
git-clone -n "$url" "$path" | |
fi | |
fi || | |
die "Clone of '$url' into submodule path '$path' failed" | |
} | |
# | |
# Add a new submodule to the working tree, .gitmodules and the index | |
# | |
# $@ = repo path | |
# | |
# optional branch is stored in global branch variable | |
# | |
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 | |
;; | |
-f | --force) | |
force=$1 | |
;; | |
-q|--quiet) | |
GIT_QUIET=1 | |
;; | |
--reference) | |
case "$2" in '') usage ;; esac | |
reference="--reference=$2" | |
shift | |
;; | |
--reference=*) | |
reference="$1" | |
shift | |
;; | |
--) | |
shift | |
break | |
;; | |
-*) | |
usage | |
;; | |
*) | |
break | |
;; | |
esac | |
shift | |
done | |
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 | |
# assure repo is absolute or relative to parent | |
case "$repo" in | |
./*|../*) | |
# dereference source url relative to parent's url | |
realrepo=$(resolve_relative_url "$repo") || exit | |
;; | |
*:*|/*) | |
# absolute url | |
realrepo=$repo | |
;; | |
*) | |
die "repo URL: '$repo' must be absolute or begin with ./|../" | |
;; | |
esac | |
# 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 | |
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" "$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 $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 --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) | |
# | |
cmd_init() | |
{ | |
# parse $args after "submodule ... init". | |
while test $# -ne 0 | |
do | |
case "$1" in | |
-q|--quiet) | |
GIT_QUIET=1 | |
;; | |
--) | |
shift | |
break | |
;; | |
-*) | |
usage | |
;; | |
*) | |
break | |
;; | |
esac | |
shift | |
done | |
module_list "$@" | | |
while read mode sha1 stage path | |
do | |
# Skip already registered paths | |
name=$(module_name "$path") || exit | |
url=$(git config submodule."$name".url) | |
test -z "$url" || continue | |
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") || exit | |
;; | |
esac | |
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 | |
} | |
# | |
# Update each submodule path to correct revision, using clone and checkout as needed | |
# | |
# $@ = requested paths (default to all) | |
# | |
cmd_update() | |
{ | |
# parse $args after "submodule ... update". | |
orig_args="$@" | |
while test $# -ne 0 | |
do | |
case "$1" in | |
-q|--quiet) | |
shift | |
GIT_QUIET=1 | |
;; | |
-i|--init) | |
init=1 | |
shift | |
;; | |
-N|--no-fetch) | |
shift | |
nofetch=1 | |
;; | |
-r|--rebase) | |
shift | |
update="rebase" | |
;; | |
--reference) | |
case "$2" in '') usage ;; esac | |
reference="--reference=$2" | |
shift 2 | |
;; | |
--reference=*) | |
reference="$1" | |
shift | |
;; | |
-m|--merge) | |
shift | |
update="merge" | |
;; | |
--recursive) | |
shift | |
recursive=1 | |
;; | |
--) | |
shift | |
break | |
;; | |
-*) | |
usage | |
;; | |
*) | |
break | |
;; | |
esac | |
done | |
if test -n "$init" | |
then | |
cmd_init "--" "$@" || return | |
fi | |
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 | |
# 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 -o -f "$path"/.git | |
then | |
module_clone "$path" "$url" "$reference"|| exit | |
subsha1= | |
else | |
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" | |
then | |
force="-f" | |
fi | |
if test -z "$nofetch" | |
then | |
(clear_local_git_env; cd "$path" && | |
git-fetch) || | |
die "Unable to fetch in submodule path '$path'" | |
fi | |
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" && cmd_update $orig_args) || | |
die "Failed to recurse into submodule path '$path'" | |
fi | |
done | |
} | |
set_name_rev () { | |
revname=$( ( | |
clear_local_git_env | |
cd "$1" && { | |
git describe "$2" 2>/dev/null || | |
git describe --tags "$2" 2>/dev/null || | |
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= | |
diff_cmd=diff-index | |
# parse $args after "submodule ... summary". | |
while test $# -ne 0 | |
do | |
case "$1" in | |
--cached) | |
cached="$1" | |
;; | |
--files) | |
files="$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 -q --verify --default HEAD ${1+"$1"}) | |
then | |
head=$rev | |
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" | |
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_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) | |
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_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 | |
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 -q --verify $sha1_src^0 >/dev/null && | |
missing_src=t | |
test $mod_dst = 160000 && | |
! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null && | |
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 rev-list --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 | |
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 | |
cat | |
fi | |
} | |
# | |
# List all submodules, prefixed with: | |
# - submodule not initialized | |
# + different revision checked out | |
# | |
# If --cached was specified the revision in the index will be printed | |
# instead of the currently checked out revision. | |
# | |
# $@ = requested paths (default to all) | |
# | |
cmd_status() | |
{ | |
# parse $args after "submodule ... status". | |
orig_args="$@" | |
while test $# -ne 0 | |
do | |
case "$1" in | |
-q|--quiet) | |
GIT_QUIET=1 | |
;; | |
--cached) | |
cached=1 | |
;; | |
--recursive) | |
recursive=1 | |
;; | |
--) | |
shift | |
break | |
;; | |
-*) | |
usage | |
;; | |
*) | |
break | |
;; | |
esac | |
shift | |
done | |
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 $displaypath" | |
continue; | |
fi | |
set_name_rev "$path" "$sha1" | |
if git diff-files --ignore-submodules=dirty --quiet -- "$path" | |
then | |
say " $sha1 $displaypath$revname" | |
else | |
if test -z "$cached" | |
then | |
sha1=$(clear_local_git_env; cd "$path" && git rev-parse --verify HEAD) | |
set_name_rev "$path" "$sha1" | |
fi | |
say "+$sha1 $displaypath$revname" | |
fi | |
if test -n "$recursive" | |
then | |
( | |
prefix="$displaypath/" | |
clear_local_git_env | |
cd "$path" && | |
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 | |
if test -e "$path"/.git | |
then | |
( | |
say "Synchronizing submodule url for '$name'" | |
git config submodule."$name".url "$url" | |
clear_local_git_env | |
cd "$path" | |
remote=$(get_default_remote) | |
git config remote."$remote".url "$url" | |
) | |
fi | |
done | |
} | |
# 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 | foreach | init | update | status | summary | sync) | |
command=$1 | |
;; | |
-q|--quiet) | |
GIT_QUIET=1 | |
;; | |
-b|--branch) | |
case "$2" in | |
'') | |
usage | |
;; | |
esac | |
branch="$2"; shift | |
;; | |
--cached) | |
cached="$1" | |
;; | |
--) | |
break | |
;; | |
-*) | |
usage | |
;; | |
*) | |
break | |
;; | |
esac | |
shift | |
done | |
# 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 | |
fi | |
# "--cached" is accepted only by "status" and "summary" | |
if test -n "$cached" && test "$command" != status -a "$command" != summary | |
then | |
usage | |
fi | |
"cmd_$command" "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment