git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] - Added 'recurse' subcommand to git submodule

From
imyousuf@gmail.com <imyousuf@gmail.com>
Date
Mar 6, 2008, 07:33 UTC
Message-ID
<1204788817-22720-2-git-send-email-imyousuf@gmail.com>
In-Reply-To
<1204788817-22720-1-git-send-email-imyousuf@gmail.com>
From: Imran M Yousuf <imyousuf@smartitengineering.com>
The purpose of the recurse command in the git submodule is to recurse
a command in its submodule. For example if one wants to do a diff on its
project with submodules at once, one can simply do
	git-submodule recurse diff HEAD
and would see the diff for all the modules it contains.

The recurse commands behavior can be customized with several arguments that it accepts. The synopsis for the recurse command is:

	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> ...]

When traversing modules, a module could be uninitialized that is git submodule init and update has not been called for it; if [-i|--initialize] option is specified, it will initialize any module that is not initialized; else if the module is not initialized it will simply skip it.

There are commands that can fail for a certain submodule but succeed for others; if one wants to stop execution once the top level module's execution fails, one can specify [-e|--exit-after-error]. It will ensure that once execution of git <command> fails in the top level module it will not recurse into its submodules.

If the project has submodule hierarchy upto n depth and we want to restrict recursion to (n-p) depth; we can use the [-d|--depth <recursion depth>] option. Value has to be greater than 0 and command will at least recurse into the first depth. If depth is specified to p than all depths <= p will be recursed over.

While discussion on the recurse command one thing which was put forward
in several occassions is that there might be scenario where a command should be
executed over the child module before the parent module. For such scenario
[-df|--depth-first] option can be used; one use case in particualar presented
as an example is git commit; where almost everybody mentioned that they prefer
to commit the child module before the parent and -df will enable just that.
E.g. p -> a, b, c, e; a ->d is a module structure. If the following command is
used,
	git submodule recurse -df commit -a
it will execute git commit -a in the following sequence - d, a, b, c, e, p.
Signed-off-by: Imran M Yousuf <imyousuf@smartitengineering.com>
---
 git-submodule.sh |  162 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 160 insertions(+), 2 deletions(-)
diff --git a/git-submodule.sh b/git-submodule.sh
index 257be4c..ee3c928 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -8,7 +8,8 @@
 # git-submodule [-q|--quiet] add [-b|--branch branch] <repository> [<path>]
 # git-submodule [-q|--quiet] [status] [-c|--cached] [--] [<path>...]
 # git-submodule [-q|--quiet] init|update [--] [<path>...]
-USAGE='[-q|--quiet] [--cached] [add <repo> [-b branch]|status|init|update] [--] [<path>...]'
+# git-submodule [-q|--quiet] recurse [-i|--initialize] [-e|--exit-after-error] [-d|--depth <recursion depth>] [-df|--depth-first] <command> [<arguments> ...]
+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] <command> [<arguments> ...]]'
 OPTIONS_SPEC=
 . git-sh-setup
 require_work_tree
@@ -17,6 +18,11 @@ command=
 branch=
 quiet=
 cached=
+depth=0
+current_depth=0
+auto_initialize=
+depth_first=
+on_error=
 
 #
 # print stuff on stdout unless -q was specified
@@ -386,6 +392,157 @@ cmd_status()
 	done
 }
 
+# Initializes the submodule if already not initialized
+# and auto initialize is enabled
+initialize_sub_module()
+{
+	if test ! -d "$1"/.git &&
+	   test -n "$auto_initialize"
+	then
+		say "Initializing and updating $1"
+		git-submodule init "$1" &&
+		git-submodule update "$1" &&
+		return 0
+	# Returns true if module is already initialized
+	elif test -d "$1"/.git
+	then
+		return 0
+	fi
+	say "Module $1 is not initialized and skipped"
+	return 1
+}
+
+# This module simply checks whether the depth is traverseable
+# in terms of depth and if so then it sequentially traverses
+# its submodules
+traverse_submodules()
+{
+	# If current depth is the range specified than it will continue
+	# else return with success
+	if test "$depth" -gt 0 &&
+		test "$current_depth" -ge "$depth"
+	then
+		return 0;
+	fi
+	# If submodules exists than it will traverse over them
+	if test -f .gitmodules
+	then
+		# Incrementing the depth for the next level of submodules
+		current_depth=$(($current_depth + 1))
+                for mod_path in `sed -n -e 's/path = //p' .gitmodules`; do
+                        traverse_module "$mod_path" "$@"
+                done
+		# Decremented the depth to bring it back to the depth of
+		# the current module
+		current_depth=$(($current_depth - 1))
+	fi
+}
+
+# This actually traverses a module; checks
+# whether the module is initialized or not.
+# if not initialized, then tries to do so 
+# based on the user preference and then the
+# intended command is evaluated in the 
+# traversal manner requested - breadth first 
+# or depth first. Then it# recursively goes 
+# into it modules.
+traverse_module()
+{
+	# Will work in the module if and only if the module is initialized
+	initialize_sub_module "$1" &&
+	(
+		submod_path="$1"
+		shift
+		cd "$submod_path"
+		# If depth-first is specified in that case submodules are
+		# are traversed before executing the command on this module
+		test -n "$depth_first" && traverse_submodules "$@"
+		# pwd is mentioned in order to enable the ser to distinguish
+		# between same name modules, e.g. a/lib and b/lib.
+		say "Working in mod $submod_path" @ `pwd` "with $@ ($#)"
+		cmd_status=
+		git "$@" || cmd_status=1
+		# if exit on error is specifed than script will exit if any
+		# command fails. As there is no transaction there will be
+		# no rollback either
+		# TODO - If possible facilitate transaction
+		if  test -n "$cmd_status" && test -n "$on_error"
+		then
+			die "git $@ failed in module $submod_path @ $(pwd)"
+		fi
+		# If depth-first is not specified in that case submodules are
+		# are traversed after executing the command on this module
+		test -z "$depth_first" && traverse_submodules "$@"
+	)
+}
+
+# Propagates or recurses over all the submodules at any
+# depth with any git command, e.g. git-clone, git-status,
+# git-commit etc., with the arguments supplied exactly as
+# it would have been supplied to the command otherwise.
+# This actually starts the recursive propagation
+cmd_recurse() {
+	while :
+	do
+		case "$1" in
+		-q|--quiet)
+                	quiet=1
+                	;;
+		-d|--depth)
+			shift
+			if test -z "$1"
+			then
+				echo "No <recursion depth> specified"
+				usage
+			# Arithmatic operation will give an error if depth is not number
+			# thus chose to check intergerness with regular expression
+			elif test "$(expr $1 : '[1-9][0-9]*')" -eq "$(expr $1 : '.*')"
+			then
+				depth="$1"
+			else
+				echo "<recursion depth> not an integer"
+				usage
+			fi
+			;;
+		-df|--depth-first)
+			depth_first=1
+			;;
+		-e|--exit-after-error)
+			on_error=1
+			;;
+		-i|--initialize)
+			auto_initialize=1
+			;;
+		-p|--pre-command)
+			pre_cmd=1
+			;;
+		-ca|--customized-argument)
+			use_custom_args=1
+			;;
+		-*)
+			usage
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done
+	test "$#" -le 0 && die "No git command specified"
+	project_home="$(pwd)"
+	if test "$depth" -gt 0
+	then
+		say Command will recurse upto "$depth" depth
+	fi
+	if test -d "$project_home"/.git/
+	then
+		say "Command to recurse: git $@"
+		traverse_module . "$@"
+	else
+		die "$project_home not a git repo thus exiting"
+	fi
+}
+
 # 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.
@@ -395,7 +552,7 @@ cmd_status()
 while test $# != 0 && test -z "$command"
 do
 	case "$1" in
-	add | init | update | status)
+	add | init | update | status | recurse)
 		command=$1
 		;;
 	-q|--quiet)
@@ -441,3 +598,4 @@ then
 fi
 
 "cmd_$command" "$@"
+
-- 
1.5.4.2
Previous: imyousuf@gmail.comNext: imyousuf@gmail.com
Message 2 of 8 in “- Added command synopsis in code and edited them in manual”
  1. - Added command synopsis in code and edited them in manualimyousuf@gmail.com, Mar 6, 2008
  2. - Added 'recurse' subcommand to git submoduleimyousuf@gmail.com, Mar 6, 2008
  3. - Added pre command and custom argument support to git submodule recurse commandimyousuf@gmail.com, Mar 6, 2008
  4. - Added pre command and custom argument support to git submodule recurse commandimyousuf@gmail.com, Mar 6, 2008
  5. - Added pre command and custom argument support to git submodule recurse commandimyousuf@gmail.com, Mar 6, 2008
  6. Junio C HamanoMar 6, 2008
  7. Junio C HamanoMar 6, 2008
  8. Junio C HamanoMar 6, 2008

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.