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

[PATCH 2/5] submodule: teach and plumb reverse-traversal behavior

From
WHWilliam Hatfield <whatfield.git@gmail.com>
Date
Jan 31, 2026, 21:43 UTC
Message-ID
<20260131214309.1899376-3-whatfield.git@gmail.com>
In-Reply-To
<20260131214309.1899376-1-whatfield.git@gmail.com>

Add --reverse-traversal flag to 'git submodule foreach' that reverses the order in which submodules are traversed. When combined with --recursive, this provides post-order traversal where nested submodules are visited before their parents.

This is useful for operations that need to process dependencies before dependents, such as building from leaves to root or cleaning up in reverse dependency order.

Implementation:
 - Add reverse_traversal field to struct foreach_cb
 - Extend for_each_listed_submodule() with reverse parameter
 - Add post-order recursion block in runcommand_in_submodule_cb()
 - Parse --reverse-traversal in module_foreach() and git-submodule.sh
Signed-off-by: William Hatfield <whatfield.git@gmail.com>
---
 builtin/submodule--helper.c    | 60 +++++++++++++++++++++++++++-------
 git-submodule.sh               |  4 +++
 t/t7425-submodule-reversion.sh |  4 +--
 3 files changed, 55 insertions(+), 13 deletions(-)
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index d537ab087a..b1e202399e 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -264,12 +264,18 @@ static char *get_up_path(const char *path)
 }
 
 static void for_each_listed_submodule(const struct module_list *list,
-				      each_submodule_fn fn, void *cb_data)
+				      each_submodule_fn fn, void *cb_data,
+				      int reverse)
 {
 	int i;
 
-	for (i = 0; i < list->nr; i++)
-		fn(list->entries[i], cb_data);
+	if (reverse) {
+		for (i = list->nr - 1; i >= 0; i--)
+			fn(list->entries[i], cb_data);
+	} else {
+		for (i = 0; i < list->nr; i++)
+			fn(list->entries[i], cb_data);
+	}
 }
 
 struct foreach_cb {
@@ -279,6 +285,7 @@ struct foreach_cb {
 	const char *super_prefix;
 	int quiet;
 	int recursive;
+	int reverse_traversal;
 };
 #define FOREACH_CB_INIT { 0 }
 
@@ -352,6 +359,33 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
 		strvec_pushv(&cp.args, info->argv);
 	}
 
+	/*
+	 * For --reverse-traversal, recurse into nested submodules first
+	 * (post-order traversal: children before parents).
+	 */
+	if (info->recursive && info->reverse_traversal) {
+		struct child_process cpr = CHILD_PROCESS_INIT;
+
+		cpr.git_cmd = 1;
+		cpr.dir = path;
+		prepare_submodule_repo_env(&cpr.env);
+
+		strvec_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive",
+			     "--reverse-traversal", NULL);
+		strvec_pushf(&cpr.args, "--super-prefix=%s/", displaypath);
+
+		if (info->quiet)
+			strvec_push(&cpr.args, "--quiet");
+
+		strvec_push(&cpr.args, "--");
+		strvec_pushv(&cpr.args, info->argv);
+
+		if (run_command(&cpr))
+			die(_("run_command returned non-zero status while "
+				"recursing in the nested submodules of %s\n."),
+				displaypath);
+	}
+
 	if (!info->quiet)
 		printf(_("Entering '%s'\n"), displaypath);
 
@@ -363,7 +397,8 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
 		child_process_clear(&cp);
 	}
 
-	if (info->recursive) {
+	/* For normal (pre-order) recursion, recurse after command execution. */
+	if (info->recursive && !info->reverse_traversal) {
 		struct child_process cpr = CHILD_PROCESS_INIT;
 
 		cpr.git_cmd = 1;
@@ -401,10 +436,12 @@ static int module_foreach(int argc, const char **argv, const char *prefix,
 		OPT__QUIET(&info.quiet, N_("suppress output of entering each submodule command")),
 		OPT_BOOL(0, "recursive", &info.recursive,
 			 N_("recurse into nested submodules")),
+		OPT_BOOL(0, "reverse-traversal", &info.reverse_traversal,
+			 N_("traverse submodules in reverse order (post-order)")),
 		OPT_END()
 	};
 	const char *const git_submodule_helper_usage[] = {
-		N_("git submodule foreach [--quiet] [--recursive] [--] <command>"),
+		N_("git submodule foreach [--quiet] [--recursive] [--reverse-traversal] [--] <command>"),
 		NULL
 	};
 	int ret = 1;
@@ -419,7 +456,8 @@ static int module_foreach(int argc, const char **argv, const char *prefix,
 	info.argv = argv;
 	info.prefix = prefix;
 
-	for_each_listed_submodule(&list, runcommand_in_submodule_cb, &info);
+	for_each_listed_submodule(&list, runcommand_in_submodule_cb, &info,
+				  info.reverse_traversal);
 
 	ret = 0;
 cleanup:
@@ -558,7 +596,7 @@ static int module_init(int argc, const char **argv, const char *prefix,
 	if (quiet)
 		info.flags |= OPT_QUIET;
 
-	for_each_listed_submodule(&list, init_submodule_cb, &info);
+	for_each_listed_submodule(&list, init_submodule_cb, &info, 0);
 
 	ret = 0;
 cleanup:
@@ -742,7 +780,7 @@ static int module_status(int argc, const char **argv, const char *prefix,
 	if (quiet)
 		info.flags |= OPT_QUIET;
 
-	for_each_listed_submodule(&list, status_submodule_cb, &info);
+	for_each_listed_submodule(&list, status_submodule_cb, &info, 0);
 
 	ret = 0;
 cleanup:
@@ -1345,7 +1383,7 @@ static int module_sync(int argc, const char **argv, const char *prefix,
 	if (recursive)
 		info.flags |= OPT_RECURSIVE;
 
-	for_each_listed_submodule(&list, sync_submodule_cb, &info);
+	for_each_listed_submodule(&list, sync_submodule_cb, &info, 0);
 
 	ret = 0;
 cleanup:
@@ -1501,7 +1539,7 @@ static int module_deinit(int argc, const char **argv, const char *prefix,
 	if (force)
 		info.flags |= OPT_FORCE;
 
-	for_each_listed_submodule(&list, deinit_submodule_cb, &info);
+	for_each_listed_submodule(&list, deinit_submodule_cb, &info, 0);
 
 	ret = 0;
 cleanup:
@@ -2885,7 +2923,7 @@ static int module_update(int argc, const char **argv, const char *prefix,
 		if (opt.quiet)
 			info.flags |= OPT_QUIET;
 
-		for_each_listed_submodule(&list, init_submodule_cb, &info);
+		for_each_listed_submodule(&list, init_submodule_cb, &info, 0);
 		module_list_release(&list);
 	}
 
diff --git a/git-submodule.sh b/git-submodule.sh
index 2999b31fad..49e9541a3d 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -173,6 +173,9 @@ cmd_foreach()
 		--recursive)
 			recursive=$1
 			;;
+		--reverse-traversal)
+			reverse_traversal=$1
+			;;
 		-*)
 			usage
 			;;
@@ -186,6 +189,7 @@ cmd_foreach()
 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper foreach \
 		$quiet \
 		$recursive \
+		$reverse_traversal \
 		-- \
 		"$@"
 }
diff --git a/t/t7425-submodule-reversion.sh b/t/t7425-submodule-reversion.sh
index 06c3ab6294..c6733234aa 100755
--- a/t/t7425-submodule-reversion.sh
+++ b/t/t7425-submodule-reversion.sh
@@ -138,14 +138,14 @@ EOF
 	test_cmp expect actual
 '
 
-test_expect_failure '--recursive and --reverse-traversal parses' '
+test_expect_success '--recursive and --reverse-traversal parses' '
 	(
 		cd reversive/top &&
 		git submodule foreach --recursive --reverse-traversal "true"
 	)
 '
 
-test_expect_failure '--recursive and --reverse-traversal runs' '
+test_expect_success '--recursive and --reverse-traversal runs' '
 	(
 		cd reversive/top &&
 		git submodule --quiet foreach --recursive \
-- 
2.53.0-rc0
Previous: William HatfieldNext: William Hatfield
Message 3 of 11 in “submodule: add 'reversive' traversal options to foreach”
  1. 0/5 submodule: add 'reversive' traversal options to foreachWilliam Hatfield, Jan 31, 2026
  2. 1/5 t7425: add tests for reversive submodule traversalWilliam Hatfield, Jan 31, 2026
  3. 2/5 submodule: teach and plumb reverse-traversal behaviorWilliam Hatfield, Jan 31, 2026
  4. 3/5 submodule: teach and plumb append-superproject behaviorWilliam Hatfield, Jan 31, 2026
  5. 4/5 submodule: introduce reversive shorthand modeWilliam Hatfield, Jan 31, 2026
  6. 5/5 doc: document reversive traversal and related modesWilliam Hatfield, Jan 31, 2026
  7. Jean-Noël AVILAFeb 1, 2026
  8. William HatfieldFeb 2, 2026
  9. Junio C HamanoFeb 2, 2026
  10. William HatfieldFeb 2, 2026
  11. Junio C HamanoFeb 2, 2026

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.