3 # git-submodules.sh: add, init, update or list git submodules
4 # or recurse any git command over the submodules recursively.
6 # Copyright (c) 2007 Lars Hjemli
8 # Synopsis of this commands are as follows
9 # git-submodule [-q|--quiet] add [-b|--branch branch] <repository> [<path>]
10 # git-submodule [-q|--quiet] [status] [-c|--cached] [--] [<path>...]
11 # git-submodule [-q|--quiet] init [--] [<path>...]
12 # git-submodule [-q|--quiet] update [--] [<path>...]
13 # git-submodule [-q|--quiet] recurse [-i|--initialize] [-e|--exit-after-error] [-d|--depth <recursion depth>] [-df|--depth-first] [-ca|--customized-argument] [-p|--pre-command] <command> [<arguments> ...]
14 USAGE
='[-q|--quiet] [[[add [-b|--branch branch] <repo>]|[[[status [-c|--cached]]|init|update] [--]]] [<path>...]]|[recurse [-i|--initialize] [-e|--exit-after-error] [-d|--depth <recursion depth>] [-df|--depth-first] [-ca|--customized-argument] [-p|--pre-command] <command> [<arguments> ...]]'
19 MODULES_LIST
='modules_list'
36 # print stuff on stdout unless -q was specified
46 # NEEDSWORK: identical function exists in get_repo_base in clone.sh
50 cd "$1" ||
cd "$1.git" &&
58 # Resolve relative url by appending to parent's url
59 resolve_relative_url
()
61 branch
="$(git symbolic-ref HEAD 2>/dev/null)"
62 remote
="$(git config branch.${branch#refs/heads/}.remote)"
63 remote
="${remote:-origin}"
64 remoteurl
="$(git config remote.$remote.url)" ||
65 die
"remote ($remote) does not have a url in .git/config"
72 remoteurl
="${remoteurl%/*}"
81 echo "$remoteurl/$url"
85 # Map submodule path to submodule name
91 # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
92 re
=$
(printf '%s' "$1" |
sed -e 's/[].[^$\\*]/\\&/g')
93 name
=$
( GIT_CONFIG
=.gitmodules \
94 git config
--get-regexp '^submodule\..*\.path$' |
95 sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
97 die
"No submodule mapping found in .gitmodules for path '$path'"
104 # Prior to calling, modules_update checks that a possibly existing
105 # path is not a git repository.
106 # Likewise, module_add checks that path does not exist at all,
107 # since it is the location of a new submodule.
114 # If there already is a directory at the submodule path,
115 # expect it to be empty (since that is the default checkout
116 # action) and try to remove it.
117 # Note: if $path is a symlink to a directory the test will
118 # succeed but the rmdir will fail. We might want to fix this.
121 rmdir "$path" 2>/dev
/null ||
122 die
"Directory '$path' exist, but is neither empty nor a git repository"
126 die
"A file already exist at path '$path'"
128 git-clone
-n "$url" "$path" ||
129 die
"Clone of '$url' into submodule path '$path' failed"
132 # Parses the branch name and exits if not present
138 echo Branch name must me specified
144 # Add a new submodule to the working tree, .gitmodules and the index
148 # optional branch is stored in global branch variable
155 parse_branch_name
"$@" &&
165 if test -z "$repo"; then
171 # dereference source url relative to parent's url
172 realrepo
="$(resolve_relative_url $repo)" ;;
174 # Turn the source into an absolute path if
176 if base
=$
(get_repo_base
"$repo"); then
183 # Guess path from repo if not specified or strip trailing slashes
184 if test -z "$path"; then
185 path
=$
(echo "$repo" |
sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
187 path
=$
(echo "$path" |
sed -e 's|/*$||')
191 die
"'$path' already exists"
193 git ls-files
--error-unmatch "$path" > /dev
/null
2>&1 &&
194 die
"'$path' already exists in the index"
196 module_clone
"$path" "$realrepo" ||
exit
197 (unset GIT_DIR
; cd "$path" && git checkout
-q ${branch:+-b "$branch" "origin/$branch"}) ||
198 die
"Unable to checkout submodule '$path'"
200 die
"Failed to add submodule '$path'"
202 GIT_CONFIG
=.gitmodules git config submodule.
"$path".path
"$path" &&
203 GIT_CONFIG
=.gitmodules git config submodule.
"$path".url
"$repo" &&
204 git add .gitmodules ||
205 die
"Failed to register submodule '$path'"
209 # Register submodules in .git/config
211 # $@ = requested paths (default to all)
215 # Added here to ensure that no argument is passed to be treated as
216 # parameter to the sub command. This will be used to parse any
223 git ls-files
--stage -- "$@" |
grep -e '^160000 ' |
224 while read mode sha1 stage path
226 # Skip already registered paths
227 name
=$
(module_name
"$path") ||
exit
228 url
=$
(git config submodule.
"$name".url
)
229 test -z "$url" ||
continue
231 url
=$
(GIT_CONFIG
=.gitmodules git config submodule.
"$name".url
)
233 die
"No url found for submodule path '$path' in .gitmodules"
235 # Possibly a url relative to parent
238 url
="$(resolve_relative_url "$url")"
242 git config submodule.
"$name".url
"$url" ||
243 die
"Failed to register url for submodule path '$path'"
245 say
"Submodule '$name' ($url) registered for path '$path'"
250 # Update each submodule path to correct revision, using clone and checkout as needed
252 # $@ = requested paths (default to all)
256 # Added here to ensure that no argument is passed to be treated as
257 # parameter to the sub command. This will be used to parse any
264 git ls-files
--stage -- "$@" |
grep -e '^160000 ' |
265 while read mode sha1 stage path
267 name
=$
(module_name
"$path") ||
exit
268 url
=$
(git config submodule.
"$name".url
)
271 # Only mention uninitialized submodules when its
272 # path have been specified
274 say
"Submodule path '$path' not initialized"
278 if ! test -d "$path"/.git
280 module_clone
"$path" "$url" ||
exit
283 subsha1
=$
(unset GIT_DIR
; cd "$path" &&
284 git rev-parse
--verify HEAD
) ||
285 die
"Unable to find current revision in submodule path '$path'"
288 if test "$subsha1" != "$sha1"
290 (unset GIT_DIR
; cd "$path" && git-fetch
&&
291 git-checkout
-q "$sha1") ||
292 die
"Unable to checkout '$sha1' in submodule path '$path'"
294 say
"Submodule path '$path': checked out '$sha1'"
303 git describe
"$2" 2>/dev
/null ||
304 git describe
--tags "$2" 2>/dev
/null ||
305 git describe
--contains --tags "$2"
308 test -z "$revname" || revname
=" ($revname)"
312 # List all submodules, prefixed with:
313 # - submodule not initialized
314 # + different revision checked out
316 # If --cached was specified the revision in the index will be printed
317 # instead of the currently checked out revision.
319 # $@ = requested paths (default to all)
323 git ls-files
--stage -- "$@" |
grep -e '^160000 ' |
324 while read mode sha1 stage path
326 name
=$
(module_name
"$path") ||
exit
327 url
=$
(git config submodule.
"$name".url
)
328 if test -z "url" ||
! test -d "$path"/.git
333 set_name_rev
"$path" "$sha1"
334 if git diff-files
--quiet -- "$path"
336 say
" $sha1 $path$revname"
340 sha1
=$
(unset GIT_DIR
; cd "$path" && git rev-parse
--verify HEAD
)
341 set_name_rev
"$path" "$sha1"
343 say
"+$sha1 $path$revname"
348 # Delgates to modules_list after parsing its arguments
363 # If there is '--' as the first argument simply ignores it and thus shifts
364 check_for_terminator
()
366 if test -n "$1" && test "$1" = "--"
372 # Initializes the submodule if already not initialized
373 # and auto initialize is enabled
374 initialize_sub_module
()
376 if test ! -d "$1"/.git
&&
377 test -n "$auto_initialize"
379 say
"Initializing and updating $1"
380 git-submodule init
"$1" &&
381 git-submodule update
"$1" &&
383 # Returns true if module is already initialized
384 elif test -d "$1"/.git
388 say
"Module $1 is not initialized and skipped"
392 # Take command from user and execute it until user wants to discontinue
395 say
"Starting pre-comamnd execution!"
399 read -p "Please provide a git command: " pre_command
400 test -z "$pre_command" || git
"$pre_command"
402 read -p "Press y to continue with another git command... " keypress
403 if test "$keypress" != "y" &&
404 test "$keypress" != "Y"
411 # Take arguments from user to pass as custom arguments
414 read -p "Please provide arguments for this module: " custom_args
419 # If current depth is the range specified than it will continue
420 # else return with success
421 if test "$depth" -gt 0 &&
422 test "$current_depth" -ge "$depth"
426 # If submodules exists than it will traverse over them
427 if test -f .gitmodules
429 # Incrementing the depth for the next level of submodules
430 current_depth
=$
(($current_depth + 1))
431 for mod_path
in `sed -n -e 's/path = //p' .gitmodules`; do
432 traverse_module
"$mod_path" "$@"
434 # Decremented the depth to bring it back to the depth of
436 current_depth
=$
(($current_depth - 1))
440 # This actually traverses the module; checks
441 # whether the module is initialized or not.
442 # if not initialized, then done so and then the
443 # intended command is evaluated. Then it
444 # recursively goes into it modules.
447 # Will work in the module if and only if the module is initialized
448 initialize_sub_module
"$1" &&
453 # If depth-first is specified in that case submodules are
454 # are traversed before executing the command on this module
455 test -n "$depth_first" && traverse_submodule
"$@"
456 # pwd is mentioned in order to enable the ser to distinguish
457 # between same name modules, e.g. a/lib and b/lib.
458 say
"Working in mod $submod_path" @
`pwd` "with $@ ($#)"
459 test -n "$pre_cmd" && do_pre_command
460 test -n "$use_custom_args" && get_custom_args
462 git
"$@" "$custom_args" || cmd_status
=1
463 # if exit on error is specifed than script will exit if any
464 # command fails. As there is no transaction there will be
466 if test -n "$cmd_status" && test -n "$on_error"
468 die
"git $@ failed in module $submod_path @ $(pwd)"
470 # If depth-first is not specified in that case submodules are
471 # are traversed after executing the command on this module
472 test -z "$depth_first" && traverse_submodule
"$@"
476 # Propagates or recurses over all the submodules at any
477 # depth with any git command, e.g. git-clone, git-status,
478 # git-commit etc., with the arguments supplied exactly as
479 # it would have been supplied to the command otherwise.
480 # This actually starts the recursive propagation
489 echo "No <recursion depth> specified"
491 # Arithmatic operation will give an error if depth is not number
492 # thus chose to check intergerness with regular expression
493 elif test "$(expr $1 : '[1-9][0-9]*')" -eq "$(expr $1 : '.*')"
497 echo "<recursion depth> not an integer"
504 -e|
--exit-after-error)
513 -ca|
--customized-argument)
525 test "$#" -le 0 && die
"No git command specified"
526 project_home
="$(pwd)"
527 say
"Project Home: $project_home"
528 if test "$depth" -gt 0
530 say Command will recurse upto
"$depth" depth
532 if test -d "$project_home"/.git
/
534 say
"Command to recurse: git $@"
535 traverse_module .
"$@"
537 die
"$project_home not a git repo thus exiting"
541 # Command synopsis clearly shows that all arguments after
542 # subcommand are arguments to the command itself. Thus
543 # there lies no command that has configuration argument
544 # after the mention of the subcommand. Thus once the
545 # subcommand is found and the separator ('--') is ignored
546 # rest can be safely sent the subcommand action
547 # It is to be noted that pre-subcommand arguments are parsed
548 # just to have backward compatibility.
561 check_for_terminator
"$1"
569 parse_branch_name
"$@"
580 # It is shifted so that it is not passed
581 # as an argument to the default subcommand
595 # Throws usage error if branch is not used with add command
596 if test -n "$branch" &&
599 echo Branch can not be specified without add subcommand
603 # If no command is specified then default command
604 # is - git submodule status
605 test -z "$command" && command="modules_status"
607 # Throws usage if --cached is used by other than status, init or update
608 # that is used with add command
609 if test -n "$cached" &&
610 test "$command" != "modules_status"
612 echo Cached can only be used with the status subcommand