| #!/bin/sh |
| # |
| # git-submodules.sh: add, init, update or list git submodules |
| # |
| # Copyright (c) 2007 Lars Hjemli |
| |
| USAGE='[--quiet] [--cached] [add <repo> [-b branch]|status|init|update] [--] [<path>...]' |
| OPTIONS_SPEC= |
| . git-sh-setup |
| require_work_tree |
| |
| add= |
| branch= |
| init= |
| update= |
| status= |
| quiet= |
| cached= |
| |
| # |
| # print stuff on stdout unless -q was specified |
| # |
| say() |
| { |
| if test -z "$quiet" |
| then |
| echo "$@" |
| 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" |
| url="$1" |
| while test -n "$url" |
| do |
| case "$url" in |
| ../*) |
| url="${url#../}" |
| remoteurl="${remoteurl%/*}" |
| ;; |
| ./*) |
| url="${url#./}" |
| ;; |
| *) |
| break;; |
| esac |
| done |
| echo "$remoteurl/$url" |
| } |
| |
| # |
| # Map submodule path to submodule name |
| # |
| # $1 = path |
| # |
| 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$' | |
| 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, modules_update checks that a possibly existing |
| # path is not a git repository. |
| # Likewise, module_add checks that path does not exist at all, |
| # since it is the location of a new submodule. |
| # |
| module_clone() |
| { |
| path=$1 |
| url=$2 |
| |
| # 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' 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" || |
| 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 |
| # |
| module_add() |
| { |
| repo=$1 |
| path=$2 |
| |
| if test -z "$repo"; then |
| usage |
| fi |
| |
| 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=$repo |
| ;; |
| 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" |
| |
| 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'" |
| 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 add .gitmodules || |
| die "Failed to register submodule '$path'" |
| } |
| |
| # |
| # Register submodules in .git/config |
| # |
| # $@ = requested paths (default to all) |
| # |
| modules_init() |
| { |
| git ls-files --stage -- "$@" | grep -e '^160000 ' | |
| 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=.gitmodules git config 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")" |
| ;; |
| esac |
| |
| git config submodule."$name".url "$url" || |
| die "Failed to register url 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) |
| # |
| modules_update() |
| { |
| git ls-files --stage -- "$@" | grep -e '^160000 ' | |
| while read mode sha1 stage path |
| do |
| name=$(module_name "$path") || exit |
| url=$(git config submodule."$name".url) |
| if test -z "$url" |
| then |
| # Only mention uninitialized submodules when its |
| # path have been specified |
| test "$#" != "0" && |
| say "Submodule path '$path' not initialized" |
| continue |
| fi |
| |
| if ! test -d "$path"/.git |
| then |
| module_clone "$path" "$url" || exit |
| subsha1= |
| else |
| subsha1=$(unset GIT_DIR; cd "$path" && |
| git rev-parse --verify HEAD) || |
| die "Unable to find current revision in submodule path '$path'" |
| fi |
| |
| if test "$subsha1" != "$sha1" |
| then |
| (unset GIT_DIR; cd "$path" && git-fetch && |
| git-checkout -q "$sha1") || |
| die "Unable to checkout '$sha1' in submodule path '$path'" |
| |
| say "Submodule path '$path': checked out '$sha1'" |
| fi |
| done |
| } |
| |
| set_name_rev () { |
| revname=$( ( |
| unset GIT_DIR |
| cd "$1" && { |
| git describe "$2" 2>/dev/null || |
| git describe --tags "$2" 2>/dev/null || |
| git describe --contains --tags "$2" |
| } |
| ) ) |
| test -z "$revname" || revname=" ($revname)" |
| } |
| |
| # |
| # 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) |
| # |
| modules_list() |
| { |
| git ls-files --stage -- "$@" | grep -e '^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 |
| then |
| say "-$sha1 $path" |
| continue; |
| fi |
| set_name_rev "$path" "$sha1" |
| if git diff-files --quiet -- "$path" |
| then |
| say " $sha1 $path$revname" |
| else |
| if test -z "$cached" |
| then |
| sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD) |
| set_name_rev "$path" "$sha1" |
| fi |
| say "+$sha1 $path$revname" |
| fi |
| done |
| } |
| |
| while test $# != 0 |
| do |
| case "$1" in |
| add) |
| add=1 |
| ;; |
| init) |
| init=1 |
| ;; |
| update) |
| update=1 |
| ;; |
| status) |
| status=1 |
| ;; |
| -q|--quiet) |
| quiet=1 |
| ;; |
| -b|--branch) |
| case "$2" in |
| '') |
| usage |
| ;; |
| esac |
| branch="$2"; shift |
| ;; |
| --cached) |
| cached=1 |
| ;; |
| --) |
| break |
| ;; |
| -*) |
| usage |
| ;; |
| *) |
| break |
| ;; |
| esac |
| shift |
| done |
| |
| case "$add,$branch" in |
| 1,*) |
| ;; |
| ,) |
| ;; |
| ,*) |
| usage |
| ;; |
| esac |
| |
| case "$add,$init,$update,$status,$cached" in |
| 1,,,,) |
| module_add "$@" |
| ;; |
| ,1,,,) |
| modules_init "$@" |
| ;; |
| ,,1,,) |
| modules_update "$@" |
| ;; |
| ,,,*,*) |
| modules_list "$@" |
| ;; |
| *) |
| usage |
| ;; |
| esac |