From: William Hatfield <whatfield.git@gmail.com>
To: git@vger.kernel.org
Cc: glencbz@gmail.com, avarab@gmail.com, gitster@pobox.com,
ps@pks.im, William Hatfield <whatfield.git@gmail.com>
Subject: [PATCH 2/5] submodule: teach and plumb reverse-traversal behavior
Date: Sat, 31 Jan 2026 16:43:06 -0500 [thread overview]
Message-ID: <20260131214309.1899376-3-whatfield.git@gmail.com> (raw)
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
next prev parent reply other threads:[~2026-01-31 21:43 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-31 21:43 [PATCH 0/5] submodule: add 'reversive' traversal options to foreach William Hatfield
2026-01-31 21:43 ` [PATCH 1/5] t7425: add tests for reversive submodule traversal William Hatfield
2026-01-31 21:43 ` William Hatfield [this message]
2026-01-31 21:43 ` [PATCH 3/5] submodule: teach and plumb append-superproject behavior William Hatfield
2026-01-31 21:43 ` [PATCH 4/5] submodule: introduce reversive shorthand mode William Hatfield
2026-01-31 21:43 ` [PATCH 5/5] doc: document reversive traversal and related modes William Hatfield
2026-02-01 9:03 ` Jean-Noël AVILA
2026-02-02 21:10 ` William Hatfield
2026-02-02 18:52 ` [PATCH 0/5] submodule: add 'reversive' traversal options to foreach Junio C Hamano
2026-02-02 21:02 ` William Hatfield
2026-02-02 21:25 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260131214309.1899376-3-whatfield.git@gmail.com \
--to=whatfield.git@gmail.com \
--cc=avarab@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=glencbz@gmail.com \
--cc=ps@pks.im \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox