threads / patch / 56453

patch, 13 partssubmodule: convert the rest of 'update' to C

Subject: [PATCH 00/13] submodule: convert the rest of 'update' to C

## tl;dr

142 messages between Sep 7, 2021 and Feb 17, 2022. Diffs are folded; open one to read it.

replies: 141people: 6as markdown or json

Atharva Raykar· Sep 7, 2021, 11:59 UTC · lore
NOTE: This series uses ar/submodule-run-update-procedure [1]

This series builds upon the previous conversion work on 'submodule update' and moves out all of that shell logic in 'git-submodule.sh' into 'builtin/submodule--helper.c'. Even though this patch series looks long, a lot of it is preparatory patches and cleanup of unused functions that result from this conversion. The real action happens at [6/8].

As with the other series, the goal is to be a faithful conversion, with no change in behaviour.

This would be the last command whose logic would be moved into C, other than 'submodule add', whose patches have been sent already.

After this works out, we can invert the shell-C relationship and make 'submodule' a proper C builtin.

Fetch-it-Via: git fetch https://github.com/tfidfwastaken/git submodule-update-list-1

[1] https://lore.kernel.org/git/20210824140609.1496-1-raykar.ath@gmail.com/
Atharva Raykar (13):
  submodule--helper: split up ensure_core_worktree()
  submodule--helper: get remote names from any repository
  submodule--helper: introduce get_default_remote_submodule()
  submodule--helper: rename helpers for update-clone
  submodule--helper: refactor get_submodule_displaypath()
  submodule: move core cmd_update() logic to C
  submodule: remove fetch_in_submodule shell function
  submodule--helper: remove update-clone subcommand
  submodule--helper: remove update-module-mode subcommand
  submodule--helper: remove shell interface to ensure_core_worktree()
  submodule--helper: remove print-default-remote subcommand
  submodule--helper: remove relative-path subcommand
  submodule--helper: remove run-update-procedure subcommand
 builtin/submodule--helper.c | 764 +++++++++++++++++++++---------------
 git-submodule.sh            | 145 +------
 2 files changed, 455 insertions(+), 454 deletions(-)
-- 
2.32.0
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 01/13] submodule--helper: split up ensure_core_worktree()

Let's split up `ensure_core_worktree()` so that we can call it from C code without needing to deal with command line arguments.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)
Show changes to builtin/submodule--helper.c +12 −7
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 80619361fc..97512ccf0b 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2794,18 +2794,12 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+static void do_ensure_core_worktree(const char *path)
 {
 	const struct submodule *sub;
-	const char *path;
 	const char *cw;
 	struct repository subrepo;
 
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-
 	sub = submodule_from_path(the_repository, null_oid(), path);
 	if (!sub)
 		BUG("We could get the submodule handle before?");
@@ -2829,6 +2823,17 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
 		free(abs_path);
 		strbuf_release(&sb);
 	}
+}
+
+static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+{
+	const char *path;
+
+	if (argc != 2)
+		BUG("submodule--helper ensure-core-worktree <path>");
+
+	path = argv[1];
+	do_ensure_core_worktree(path);
 
 	return 0;
 }
-- 
2.32.0
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 02/13] submodule--helper: get remote names from any repository

`get_default_remote()` retrieves the name of a remote by resolving the refs from of the current repository's ref store.

Thus in order to use it for retrieving the remote name of a submodule, we have to start a new subprocess which runs from the submodule directory.

Let's instead introduce a function called `repo_get_default_remote()` which takes any repository object and retrieves the remote accordingly.

`get_default_remote()` is then defined as a call to `repo_get_default_remote()` with 'the_repository' passed to it.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
Show changes to builtin/submodule--helper.c +8 −3
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 97512ccf0b..1a65de4fa4 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -29,11 +29,10 @@
 typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
 				  void *cb_data);
 
-static char *get_default_remote(void)
+static char *repo_get_default_remote(struct repository *repo, const char *refname)
 {
 	char *dest = NULL, *ret;
 	struct strbuf sb = STRBUF_INIT;
-	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
 
 	if (!refname)
 		die(_("No such ref: %s"), "HEAD");
@@ -46,7 +45,7 @@ static char *get_default_remote(void)
 		die(_("Expecting a full ref name, got %s"), refname);
 
 	strbuf_addf(&sb, "branch.%s.remote", refname);
-	if (git_config_get_string(sb.buf, &dest))
+	if (repo_config_get_string(repo, sb.buf, &dest))
 		ret = xstrdup("origin");
 	else
 		ret = dest;
@@ -55,6 +54,12 @@ static char *get_default_remote(void)
 	return ret;
 }
 
+static char *get_default_remote(void)
+{
+	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+	return repo_get_default_remote(the_repository, refname);
+}
+
 static int print_default_remote(int argc, const char **argv, const char *prefix)
 {
 	char *remote;
-- 
2.32.0
Ævar Arnfjörð Bjarmason· Sep 7, 2021, 12:37 UTC · re: Atharva Raykar · lore

Re: [PATCH 02/13] submodule--helper: get remote names from any repository

On Tue, Sep 07 2021, Atharva Raykar wrote:
Show 12 quoted lines
> `get_default_remote()` retrieves the name of a remote by resolving the
> refs from of the current repository's ref store.
>
> Thus in order to use it for retrieving the remote name of a submodule,
> we have to start a new subprocess which runs from the submodule
> directory.
>
> Let's instead introduce a function called `repo_get_default_remote()`
> which takes any repository object and retrieves the remote accordingly.
>
> `get_default_remote()` is then defined as a call to
> `repo_get_default_remote()` with 'the_repository' passed to it.

I'd find this easier to follow if this were just squashed into the next commit. Both are rather small, but following the context of first adding a function, then using it, instead of just adding it, changing the old users etc. is harder than just having it in one commit.

Atharva Raykar· Sep 7, 2021, 13:33 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH 02/13] submodule--helper: get remote names from any repository

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 19 quoted lines
> On Tue, Sep 07 2021, Atharva Raykar wrote:
>
>> `get_default_remote()` retrieves the name of a remote by resolving the
>> refs from of the current repository's ref store.
>>
>> Thus in order to use it for retrieving the remote name of a submodule,
>> we have to start a new subprocess which runs from the submodule
>> directory.
>>
>> Let's instead introduce a function called `repo_get_default_remote()`
>> which takes any repository object and retrieves the remote accordingly.
>>
>> `get_default_remote()` is then defined as a call to
>> `repo_get_default_remote()` with 'the_repository' passed to it.
>
> I'd find this easier to follow if this were just squashed into the next
> commit. Both are rather small, but following the context of first adding
> a function, then using it, instead of just adding it, changing the old
> users etc. is harder than just having it in one commit.

I am in two minds about this. I initially had both these changes in one commit, but Christian suggested I split the changes into a part that refactors existing code (this commit), and one that introduces a new helper (the next commit).

I guess I will squash it for now and see how it is received. Maybe bringing down the "/13" might help get more reviews ;-)

Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 03/13] submodule--helper: introduce get_default_remote_submodule()

Before 8ef1d2b549 (submodule--helper: get remote names from any repository, 2021-07-20), it was not possible to directly retrieve a submodule's remote name within the same process, because `get_default_remote()` used only knew about the current repository.

Now that we have `repo_get_default_remote()`, we no longer have to start a subprocess that called `submodule--helper get-default-remote` from within the submodule directory.

Let's make a function called `get_default_remote_submodule()` which takes a submodule path, and returns the default remote for that submodule, all within the same process.

We can now use this function to save an unnecessary subprocess spawn in `sync_submodule()`, and also in the next patch, which will require this functionality.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)
Show changes to builtin/submodule--helper.c +15 −8
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 1a65de4fa4..f6c4fc349b 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -54,6 +54,19 @@ static char *repo_get_default_remote(struct repository *repo, const char *refnam
 	return ret;
 }
 
+static char *get_default_remote_submodule(const char *module_path)
+{
+	const char *refname;
+	const struct submodule *sub;
+	struct repository subrepo;
+
+	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
+					  "HEAD", 0, NULL, NULL);
+	sub = submodule_from_path(the_repository, null_oid(), module_path);
+	repo_submodule_init(&subrepo, the_repository, sub);
+	return repo_get_default_remote(&subrepo, refname);
+}
+
 static char *get_default_remote(void)
 {
 	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
@@ -1374,7 +1387,6 @@ static void sync_submodule(const char *path, const char *prefix,
 	char *remote_key = NULL;
 	char *sub_origin_url, *super_config_url, *displaypath;
 	struct strbuf sb = STRBUF_INIT;
-	struct child_process cp = CHILD_PROCESS_INIT;
 	char *sub_config_path = NULL;
 
 	if (!is_submodule_active(the_repository, path))
@@ -1423,14 +1435,9 @@ static void sync_submodule(const char *path, const char *prefix,
 	if (!is_submodule_populated_gently(path, NULL))
 		goto cleanup;
 
-	prepare_submodule_repo_env(&cp.env_array);
-	cp.git_cmd = 1;
-	cp.dir = path;
-	strvec_pushl(&cp.args, "submodule--helper",
-		     "print-default-remote", NULL);
-
 	strbuf_reset(&sb);
-	if (capture_command(&cp, &sb, 0))
+	strbuf_addstr(&sb, get_default_remote_submodule(path));
+	if (!sb.buf)
 		die(_("failed to get the default remote for submodule '%s'"),
 		      path);
 
-- 
2.32.0
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 04/13] submodule--helper: rename helpers for update-clone

The `update-clone` subcommand helpers that perform the parallel clone and printing to stdout for shell script consumption, are renamed.

This lets us use the names `update_submodules()` and `update_submodule()` for the helpers in the next patch, when we create an `update` subcommand that does a full conversion.

We will get rid of these helpers in a cleanup patch at the end of this series, when the `update-clone` command is no longer useful to us.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +4 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f6c4fc349b..b0336b0377 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2499,7 +2499,7 @@ static int do_run_update_procedure(struct update_data *ud)
 	return run_update_command(ud, subforce);
 }
 
-static void update_submodule(struct update_clone_data *ucd)
+static void update_clone_submodule(struct update_clone_data *ucd)
 {
 	fprintf(stdout, "dummy %s %d\t%s\n",
 		oid_to_hex(&ucd->oid),
@@ -2507,7 +2507,7 @@ static void update_submodule(struct update_clone_data *ucd)
 		ucd->sub->path);
 }
 
-static int update_submodules(struct submodule_update_clone *suc)
+static int update_clone_submodules(struct submodule_update_clone *suc)
 {
 	int i;
 
@@ -2528,7 +2528,7 @@ static int update_submodules(struct submodule_update_clone *suc)
 		return 1;
 
 	for (i = 0; i < suc->update_clone_nr; i++)
-		update_submodule(&suc->update_clone[i]);
+		update_clone_submodule(&suc->update_clone[i]);
 
 	return 0;
 }
@@ -2593,7 +2593,7 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 	if (pathspec.nr)
 		suc.warn_if_uninitialized = 1;
 
-	return update_submodules(&suc);
+	return update_clone_submodules(&suc);
 }
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
-- 
2.32.0
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 05/13] submodule--helper: refactor get_submodule_displaypath()

We create a function called `do_get_submodule_displaypath()` that generates the display path required by several submodule functions, and takes a custom superprefix parameter, instead of reading it from the environment.

We then redefine the existing `get_submodule_displaypath()` function as a call to this new function, where the superprefix is obtained from the environment.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +8 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index b0336b0377..10de01a1f7 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -269,11 +269,8 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr
 	return 0;
 }
 
-/* the result should be freed by the caller. */
-static char *get_submodule_displaypath(const char *path, const char *prefix)
+static char *do_get_submodule_displaypath(const char *path, const char *prefix, const char *super_prefix)
 {
-	const char *super_prefix = get_super_prefix();
-
 	if (prefix && super_prefix) {
 		BUG("cannot have prefix '%s' and superprefix '%s'",
 		    prefix, super_prefix);
@@ -289,6 +286,13 @@ static char *get_submodule_displaypath(const char *path, const char *prefix)
 	}
 }
 
+/* the result should be freed by the caller. */
+static char *get_submodule_displaypath(const char *path, const char *prefix)
+{
+	const char *super_prefix = get_super_prefix();
+	return do_get_submodule_displaypath(path, prefix, super_prefix);
+}
+
 static char *compute_rev_name(const char *sub_path, const char* object_id)
 {
 	struct strbuf sb = STRBUF_INIT;
-- 
2.32.0
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 06/13] submodule: move core cmd_update() logic to C

This patch completes the conversion past the flag parsing of `submodule update` by introducing a helper subcommand called `submodule--helper update`. The behaviour of `submodule update` should remain the same after this patch.

We add more fields to the `struct update_data` that are required by `struct submodule_update_clone` to be able to perform a clone, when that is needed to be done.

Recursing on a submodule is done by calling a subprocess that launches `submodule--helper update`, with a modified `--recursive-prefix` and `--prefix` parameter.

We also introduce `update_submodules()` and `update_submodule()` which are quite similar to `update_clone_submodules()` and `update_clone_submodule()`, and will supersede them.

When the `--init` flag is passed to the subcommand, we do not spawn a new subprocess and call `submodule--helper init` on the submodule paths, because the Git machinery is not able to pick up the configuration changes introduced by that init call[1]. So we instead run the `init_submodule_cb()` callback over each submodule directly.

This introduces another problem, because there is no mechanism to pass the superproject path prefix (ie, `--super-prefix`) without starting a new git process. This field is required for obtaining the display path for that is used by the command's output messages. So let's add a field into the `init_cb` struct that lets us pass this information to `init_submodule()`, which will now also take an explicit 'superprefix' argument.

[1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 502 ++++++++++++++++++++++++++++++------
 git-submodule.sh            | 131 +---------
 2 files changed, 430 insertions(+), 203 deletions(-)
Show changes to 2 files +430 −203

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 10de01a1f7..2cccb9a9ab 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -634,18 +634,22 @@ static char *compute_submodule_clone_url(const char *rel_url)
 
 struct init_cb {
 	const char *prefix;
+	const char *superprefix;
 	unsigned int flags;
 };
-#define INIT_CB_INIT { NULL, 0 }
+#define INIT_CB_INIT { NULL, NULL, 0 }
 
 static void init_submodule(const char *path, const char *prefix,
-			   unsigned int flags)
+			   const char *superprefix, unsigned int flags)
 {
 	const struct submodule *sub;
 	struct strbuf sb = STRBUF_INIT;
 	char *upd = NULL, *url = NULL, *displaypath;
 
-	displaypath = get_submodule_displaypath(path, prefix);
+	/* try superprefix from the environment, if it is not passed explicitly */
+	if (!superprefix)
+		superprefix = get_super_prefix();
+	displaypath = do_get_submodule_displaypath(path, prefix, superprefix);
 
 	sub = submodule_from_path(the_repository, null_oid(), path);
 
@@ -719,7 +723,7 @@ static void init_submodule(const char *path, const char *prefix,
 static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
 {
 	struct init_cb *info = cb_data;
-	init_submodule(list_item->name, info->prefix, info->flags);
+	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
 }
 
 static int module_init(int argc, const char **argv, const char *prefix)
@@ -2039,7 +2043,6 @@ struct submodule_update_clone {
 	const char *prefix;
 	int single_branch;
 
-	/* to be consumed by git-submodule.sh */
 	struct update_clone_data *update_clone;
 	int update_clone_nr; int update_clone_alloc;
 
@@ -2062,19 +2065,63 @@ struct submodule_update_clone {
 }
 
 struct update_data {
+	const char *prefix;
 	const char *recursive_prefix;
 	const char *sm_path;
 	const char *displaypath;
 	struct object_id oid;
 	struct object_id suboid;
-	struct submodule_update_strategy update_strategy;
+	int max_jobs;
 	int depth;
+	int recommend_shallow;
+	int single_branch;
+	unsigned int init: 1;
 	unsigned int force: 1;
 	unsigned int quiet: 1;
 	unsigned int nofetch: 1;
-	unsigned int just_cloned: 1;
+	unsigned int remote: 1;
+	unsigned int recursive: 1;
+	unsigned int progress: 1;
+	unsigned int dissociate: 1;
+	unsigned int require_init: 1;
+	unsigned warn_if_uninitialized : 1;
+	unsigned int just_cloned : 1;
+	struct submodule_update_strategy update_strategy;
+	struct string_list references;
+	struct module_list list;
 };
-#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
+#define UPDATE_DATA_INIT { \
+	.list = MODULE_LIST_INIT, \
+	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+	.recommend_shallow = -1, \
+	.references = STRING_LIST_INIT_DUP, \
+	.single_branch = -1, \
+	.max_jobs = 1, \
+}
+
+static void update_clone_from_update_data(struct submodule_update_clone *suc,
+					  struct update_data *update_data)
+{
+	suc->prefix = update_data->prefix;
+	suc->recursive_prefix = update_data->recursive_prefix;
+	suc->max_jobs = update_data->max_jobs;
+	suc->progress = update_data->progress;
+	suc->quiet = update_data->quiet;
+	suc->dissociate = update_data->dissociate;
+	suc->require_init = update_data->require_init;
+	suc->single_branch = update_data->single_branch;
+	suc->warn_if_uninitialized = update_data->warn_if_uninitialized;
+	suc->list = update_data->list;
+	suc->update = update_data->update_strategy;
+	suc->recommend_shallow = update_data->recommend_shallow;
+	if (update_data->depth)
+		suc->depth = xstrfmt("--depth=%d", update_data->depth);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			string_list_append(&suc->references, item->string);
+	}
+}
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
 		struct strbuf *out, const char *displaypath)
@@ -2369,111 +2416,113 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 	return run_command(&cp);
 }
 
-static int run_update_command(struct update_data *ud, int subforce)
+static int run_update_command(struct update_data *ud, int subforce, struct string_list *err)
 {
-	struct strvec args = STRVEC_INIT;
-	struct strvec child_env = STRVEC_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
+	struct strbuf out = STRBUF_INIT;
 	int must_die_on_failure = 0;
-	int git_cmd;
+	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
 
-	switch (ud->update_strategy.type) {
+	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
+		determine_submodule_update_strategy(the_repository, ud->just_cloned,
+						    ud->sm_path, NULL, &strategy);
+	else
+		strategy = ud->update_strategy;
+
+	cp.dir = xstrdup(ud->sm_path);
+	switch (strategy.type) {
 	case SM_UPDATE_CHECKOUT:
-		git_cmd = 1;
-		strvec_pushl(&args, "checkout", "-q", NULL);
+		cp.git_cmd = 1;
+		strvec_pushl(&cp.args, "checkout", "-q", NULL);
 		if (subforce)
-			strvec_push(&args, "-f");
+			strvec_push(&cp.args, "-f");
 		break;
 	case SM_UPDATE_REBASE:
-		git_cmd = 1;
-		strvec_push(&args, "rebase");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "rebase");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_MERGE:
-		git_cmd = 1;
-		strvec_push(&args, "merge");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "merge");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_COMMAND:
-		git_cmd = 0;
-		strvec_push(&args, ud->update_strategy.command);
+		cp.git_cmd = 0;
+		cp.use_shell = 1;
+		strvec_push(&cp.args, strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+		    submodule_strategy_to_string(&strategy));
 	}
-	strvec_push(&args, oid);
+	strvec_push(&cp.args, oid);
 
-	prepare_submodule_repo_env(&child_env);
-	if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
-				     ud->sm_path, child_env.v)) {
-		switch (ud->update_strategy.type) {
-		case SM_UPDATE_CHECKOUT:
-			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
-			break;
-		case SM_UPDATE_REBASE:
-			printf(_("Unable to rebase '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
-			break;
-		case SM_UPDATE_MERGE:
-			printf(_("Unable to merge '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
-			break;
-		case SM_UPDATE_COMMAND:
-			printf(_("Execution of '%s %s' failed in submodule path '%s'"),
-			       ud->update_strategy.command, oid, ud->displaypath);
-			break;
-		default:
-			BUG("unexpected update strategy type: %s",
-			    submodule_strategy_to_string(&ud->update_strategy));
+	prepare_submodule_repo_env(&cp.env_array);
+	if (capture_command(&cp, &out, 0)) {
+		if (must_die_on_failure) {
+			switch (strategy.type) {
+			case SM_UPDATE_CHECKOUT:
+				die(_("Unable to checkout '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_REBASE:
+				die(_("Unable to rebase '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_MERGE:
+				die(_("Unable to merge '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_COMMAND:
+				die(_("Execution of '%s %s' failed in submodule path '%s'"),
+				    strategy.command, oid, ud->displaypath);
+				break;
+			default:
+				BUG("unexpected update strategy type: %s",
+				    submodule_strategy_to_string(&strategy));
+			}
 		}
-		/*
-		 * NEEDSWORK: We are currently printing to stdout with error
-		 * return so that the shell caller handles the error output
-		 * properly. Once we start handling the error messages within
-		 * C, we should use die() instead.
-		 */
-		if (must_die_on_failure)
-			return 2;
-		/*
-		 * This signifies to the caller in shell that the command
-		 * failed without dying
-		 */
+
+		/* the command failed, but update must continue */
+		string_list_append(err, out.buf);
 		return 1;
 	}
 
-	switch (ud->update_strategy.type) {
-	case SM_UPDATE_CHECKOUT:
-		printf(_("Submodule path '%s': checked out '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_REBASE:
-		printf(_("Submodule path '%s': rebased into '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_MERGE:
-		printf(_("Submodule path '%s': merged in '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_COMMAND:
-		printf(_("Submodule path '%s': '%s %s'\n"),
-		       ud->displaypath, ud->update_strategy.command, oid);
-		break;
-	default:
-		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+	if (!ud->quiet) {
+		switch (strategy.type) {
+		case SM_UPDATE_CHECKOUT:
+			printf(_("Submodule path '%s': checked out '%s'\n"),
+			       ud->displaypath, oid);
+			break;
+		case SM_UPDATE_REBASE:
+			printf(_("Submodule path '%s': rebased into '%s'\n"),
+			       ud->displaypath, oid);
+			break;
+		case SM_UPDATE_MERGE:
+			printf(_("Submodule path '%s': merged in '%s'\n"),
+			       ud->displaypath, oid);
+			break;
+		case SM_UPDATE_COMMAND:
+			printf(_("Submodule path '%s': '%s %s'\n"),
+			       ud->displaypath, strategy.command, oid);
+			break;
+		default:
+			BUG("unexpected update strategy type: %s",
+			    submodule_strategy_to_string(&strategy));
+		}
 	}
 
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud)
+static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2500,7 +2549,7 @@ static int do_run_update_procedure(struct update_data *ud)
 			    ud->displaypath, oid_to_hex(&ud->oid));
 	}
 
-	return run_update_command(ud, subforce);
+	return run_update_command(ud, subforce, err);
 }
 
 static void update_clone_submodule(struct update_clone_data *ucd)
@@ -2605,6 +2654,7 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
 	char *prefixed_path, *update = NULL;
 	struct update_data update_data = UPDATE_DATA_INIT;
+	struct string_list err = STRING_LIST_INIT_DUP;
 
 	struct option options[] = {
 		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
@@ -2662,7 +2712,7 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	free(prefixed_path);
 
 	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data);
+		return do_run_update_procedure(&update_data, &err);
 
 	return 3;
 }
@@ -3038,6 +3088,288 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 	return !!ret;
 }
 
+static void update_data_to_args(struct update_data *update_data, struct strvec *args)
+{
+	const char *update = submodule_strategy_to_string(&update_data->update_strategy);
+
+	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
+	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
+	if (update_data->prefix)
+		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
+	if (update_data->recursive_prefix)
+		strvec_pushl(args, "--recursive-prefix",
+			     update_data->recursive_prefix, NULL);
+	if (update_data->quiet)
+		strvec_push(args, "--quiet");
+	if (update_data->force)
+		strvec_push(args, "--force");
+	if (update_data->init)
+		strvec_push(args, "--init");
+	if (update_data->remote)
+		strvec_push(args, "--remote");
+	if (update_data->nofetch)
+		strvec_push(args, "--no-fetch");
+	if (update_data->dissociate)
+		strvec_push(args, "--dissociate");
+	if (update_data->progress)
+		strvec_push(args, "--progress");
+	if (update_data->require_init)
+		strvec_push(args, "--require-init");
+	if (update_data->depth)
+		strvec_pushf(args, "--depth=%d", update_data->depth);
+	if (update)
+		strvec_pushl(args, "--update", update, NULL);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			strvec_pushl(args, "--reference", item->string, NULL);
+	}
+	if (update_data->recommend_shallow == 0)
+		strvec_push(args, "--no-recommend-shallow");
+	else if (update_data->recommend_shallow == 1)
+		strvec_push(args, "--recommend-shallow");
+	if (update_data->single_branch >= 0)
+		strvec_push(args, "--single-branch");
+}
+
+static int update_submodule(struct update_data *update_data)
+{
+	char *prefixed_path;
+	struct string_list err = STRING_LIST_INIT_DUP;
+
+	do_ensure_core_worktree(update_data->sm_path);
+
+	if (update_data->recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
+					update_data->sm_path);
+	else
+		prefixed_path = xstrdup(update_data->sm_path);
+
+	update_data->displaypath = get_submodule_displaypath(prefixed_path,
+							     update_data->prefix);
+	free(prefixed_path);
+
+	if (update_data->just_cloned) {
+		oidcpy(&update_data->suboid, null_oid());
+	} else {
+		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+			die(_("Unable to find current revision in submodule path '%s'"),
+			    update_data->displaypath);
+	}
+
+	if (update_data->remote) {
+		char *remote_name = get_default_remote_submodule(update_data->sm_path);
+		const char *branch = remote_submodule_branch(update_data->sm_path);
+		char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch);
+
+		if (!update_data->nofetch) {
+			if(fetch_in_submodule(update_data->sm_path, update_data->depth,
+					      0, NULL))
+				die(_("Unable to fetch in submodule path '%s'"),
+				    update_data->sm_path);
+		}
+
+		if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
+			die(_("Unable to find %s revision in submodule path '%s'"),
+			    remote_ref, update_data->sm_path);
+
+		free(remote_ref);
+	}
+
+	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
+		if (do_run_update_procedure(update_data, &err))
+			return 1;
+
+	if (update_data->recursive) {
+		int res;
+		struct child_process cp = CHILD_PROCESS_INIT;
+		struct update_data next = *update_data;
+		char *die_msg = xstrfmt(_("Failed to recurse into submodule path '%s'"),
+					update_data->displaypath);
+
+		if (update_data->recursive_prefix)
+			prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix,
+						update_data->sm_path);
+		else
+			prefixed_path = xstrfmt("%s/", update_data->sm_path);
+
+		next.recursive_prefix = get_submodule_displaypath(prefixed_path,
+								  update_data->prefix);
+		next.prefix = NULL;
+		oidcpy(&next.oid, null_oid());
+		oidcpy(&next.suboid, null_oid());
+
+		cp.dir = update_data->sm_path;
+		cp.git_cmd = 1;
+		prepare_submodule_repo_env(&cp.env_array);
+		update_data_to_args(&next, &cp.args);
+
+		/* die() if child process die()'d */
+		if ((res = run_command(&cp)) == 128)
+			die("%s", die_msg);
+		if (res)
+			string_list_append(&err, die_msg);
+
+		free(die_msg);
+	}
+
+	if (err.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &err)
+			fputs(item->string, stderr);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int update_submodules(struct update_data *update_data)
+{
+	int i, res = 0;
+	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+
+	update_clone_from_update_data(&suc, update_data);
+	run_processes_parallel_tr2(suc.max_jobs, update_clone_get_next_task,
+				   update_clone_start_failure,
+				   update_clone_task_finished, &suc, "submodule",
+				   "parallel/update");
+
+	/*
+	 * We saved the output and put it out all at once now.
+	 * That means:
+	 * - the listener does not have to interleave their (checkout)
+	 *   work with our fetching.  The writes involved in a
+	 *   checkout involve more straightforward sequential I/O.
+	 * - the listener can avoid doing any work if fetching failed.
+	 */
+	if (suc.quickstop)
+		return 1;
+
+	for (i = 0; i < suc.update_clone_nr; i++) {
+		struct update_clone_data ucd = suc.update_clone[i];
+
+		oidcpy(&update_data->oid, &ucd.oid);
+		update_data->just_cloned = ucd.just_cloned;
+		update_data->sm_path = ucd.sub->path;
+
+		if (update_submodule(update_data))
+			res = 1;
+	}
+
+	return res;
+}
+
+static int module_update(int argc, const char **argv, const char *prefix)
+{
+	int init = 0, force = 0, quiet = 0, nofetch = 0;
+	int remote = 0, recursive = 0, dissociate = 0;
+	int progress = 0, require_init = 0;
+	const char *update = NULL;
+	struct pathspec pathspec;
+	struct update_data update_data = UPDATE_DATA_INIT;
+
+	struct option module_update_clone_options[] = {
+		OPT__FORCE(&force, N_("force checkout updates"), 0),
+		OPT_BOOL(0, "init", &init,
+			 N_("initialize uninitialized submodules before update")),
+		OPT_BOOL(0, "remote", &remote,
+			 N_("use SHA-1 of submodule's remote tracking branch")),
+		OPT_BOOL(0, "recursive", &recursive,
+			 N_("traverse submodules recursively")),
+		OPT_BOOL('N', "no-fetch", &nofetch,
+			 N_("don't fetch new objects from the remote site")),
+		OPT_STRING(0, "prefix", &prefix,
+			   N_("path"),
+			   N_("path into the working tree")),
+		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix,
+			   N_("path"),
+			   N_("path into the working tree, across nested "
+			      "submodule boundaries")),
+		OPT_STRING(0, "update", &update,
+			   N_("string"),
+			   N_("rebase, merge, checkout or none")),
+		OPT_STRING_LIST(0, "reference", &update_data.references, N_("repo"),
+				N_("reference repository")),
+		OPT_BOOL(0, "dissociate", &dissociate,
+			 N_("use --reference only while cloning")),
+		OPT_INTEGER(0, "depth", &update_data.depth,
+			    N_("create a shallow clone truncated to the "
+			       "specified number of revisions")),
+		OPT_INTEGER('j', "jobs", &update_data.max_jobs,
+			    N_("parallel jobs")),
+		OPT_BOOL(0, "recommend-shallow", &update_data.recommend_shallow,
+			 N_("whether the initial clone should follow the shallow recommendation")),
+		OPT__QUIET(&quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &progress,
+			 N_("force cloning progress")),
+		OPT_BOOL(0, "require-init", &require_init,
+			 N_("disallow cloning into non-empty directory")),
+		OPT_BOOL(0, "single-branch", &update_data.single_branch,
+			 N_("clone only one branch, HEAD or --branch")),
+		OPT_END()
+	};
+
+	const char *const git_submodule_helper_usage[] = {
+		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
+		NULL
+	};
+
+	update_clone_config_from_gitmodules(&update_data.max_jobs);
+	git_config(git_update_clone_config, &update_data.max_jobs);
+
+	argc = parse_options(argc, argv, prefix, module_update_clone_options,
+			     git_submodule_helper_usage, 0);
+	update_data.prefix = prefix;
+
+	update_data.force = !!force;
+	update_data.quiet = !!quiet;
+	update_data.nofetch = !!nofetch;
+	update_data.init = !!init;
+	update_data.require_init = !!require_init;
+	update_data.remote = !!remote;
+	update_data.recursive = !!recursive;
+	update_data.progress = !!progress;
+	update_data.dissociate = !!dissociate;
+	oidcpy(&update_data.oid, null_oid());
+	oidcpy(&update_data.suboid, null_oid());
+
+	if (update)
+		if (parse_submodule_update_strategy(update,
+						    &update_data.update_strategy) < 0)
+			die(_("bad value for update parameter"));
+
+	if (module_list_compute(argc, argv, prefix, &pathspec, &update_data.list) < 0)
+		return 1;
+
+	if (pathspec.nr)
+		update_data.warn_if_uninitialized = 1;
+
+	if (update_data.init) {
+		struct module_list list = MODULE_LIST_INIT;
+		struct init_cb info = INIT_CB_INIT;
+
+		if (module_list_compute(argc, argv, update_data.prefix,
+					&pathspec, &list) < 0)
+			return 1;
+
+		/*
+		 * If there are no path args and submodule.active is set then,
+		 * by default, only initialize 'active' modules.
+		 */
+		if (!argc && git_config_get_value_multi("submodule.active"))
+			module_list_active(&list);
+
+		info.prefix = update_data.prefix;
+		info.superprefix = update_data.recursive_prefix;
+		if (update_data.quiet)
+			info.flags |= OPT_QUIET;
+
+		for_each_listed_submodule(&list, init_submodule_cb, &info);
+	}
+
+	return update_submodules(&update_data);
+}
+
 struct add_data {
 	const char *prefix;
 	const char *branch;
@@ -3213,6 +3545,7 @@ static int add_clone(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
+
 #define SUPPORT_SUPER_PREFIX (1<<0)
 
 struct cmd_struct {
@@ -3226,6 +3559,7 @@ static struct cmd_struct commands[] = {
 	{"name", module_name, 0},
 	{"clone", module_clone, 0},
 	{"add-clone", add_clone, 0},
+	{"update", module_update, 0},
 	{"update-module-mode", module_update_module_mode, 0},
 	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
diff --git a/git-submodule.sh b/git-submodule.sh
index f703cddce8..4e21d9715c 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -484,133 +484,26 @@ cmd_update()
 		shift
 	done
 
-	if test -n "$init"
-	then
-		cmd_init "--" "$@" || return
-	fi
-
-	{
-	git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
-		${progress:+"--progress"} \
+	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
+		${GIT_QUIET:+--quiet} \
+		${force:+--force} \
+		${progress:+--progress} \
+		${dissociate:+--dissociate} \
+		${remote:+--remote} \
+		${recursive:+--recursive} \
+		${init:+--init} \
+		${require_init:+--require-init} \
+		${nofetch:+--no-fetch} \
 		${wt_prefix:+--prefix "$wt_prefix"} \
 		${prefix:+--recursive-prefix "$prefix"} \
 		${update:+--update "$update"} \
 		${reference:+"$reference"} \
-		${dissociate:+"--dissociate"} \
-		${depth:+--depth "$depth"} \
-		${require_init:+--require-init} \
+		${depth:+"$depth"} \
 		$single_branch \
 		$recommend_shallow \
 		$jobs \
 		-- \
-		"$@" || echo "#unmatched" $?
-	} | {
-	err=
-	while read -r quickabort sha1 just_cloned sm_path
-	do
-		die_if_unmatched "$quickabort" "$sha1"
-
-		git submodule--helper ensure-core-worktree "$sm_path" || exit 1
-
-		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
-
-		if test $just_cloned -eq 1
-		then
-			subsha1=
-		else
-			just_cloned=
-			subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify HEAD) ||
-			die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
-		fi
-
-		if test -n "$remote"
-		then
-			branch=$(git submodule--helper remote-branch "$sm_path")
-			if test -z "$nofetch"
-			then
-				# Fetch remote before determining tracking $sha1
-				fetch_in_submodule "$sm_path" $depth ||
-				die "fatal: $(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
-			fi
-			remote_name=$(sanitize_submodule_env; cd "$sm_path" && git submodule--helper print-default-remote)
-			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify "${remote_name}/${branch}") ||
-			die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
-		fi
-
-		out=$(git submodule--helper run-update-procedure \
-			  ${wt_prefix:+--prefix "$wt_prefix"} \
-			  ${GIT_QUIET:+--quiet} \
-			  ${force:+--force} \
-			  ${just_cloned:+--just-cloned} \
-			  ${nofetch:+--no-fetch} \
-			  ${depth:+"$depth"} \
-			  ${update:+--update "$update"} \
-			  ${prefix:+--recursive-prefix "$prefix"} \
-			  ${sha1:+--oid "$sha1"} \
-			  ${subsha1:+--suboid "$subsha1"} \
-			  "--" \
-			  "$sm_path")
-
-		# exit codes for run-update-procedure:
-		# 0: update was successful, say command output
-		# 1: update procedure failed, but should not die
-		# 2 or 128: subcommand died during execution
-		# 3: no update procedure was run
-		res="$?"
-		case $res in
-		0)
-			say "$out"
-			;;
-		1)
-			err="${err};fatal: $out"
-			continue
-			;;
-		2|128)
-			die_with_status $res "fatal: $out"
-			;;
-		esac
-
-		if test -n "$recursive"
-		then
-			(
-				prefix=$(git submodule--helper relative-path "$prefix$sm_path/" "$wt_prefix")
-				wt_prefix=
-				sanitize_submodule_env
-				cd "$sm_path" &&
-				eval cmd_update
-			)
-			res=$?
-			if test $res -gt 0
-			then
-				die_msg="fatal: $(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
-				if test $res -ne 2
-				then
-					err="${err};$die_msg"
-					continue
-				else
-					die_with_status $res "$die_msg"
-				fi
-			fi
-		fi
-	done
-
-	if test -n "$err"
-	then
-		OIFS=$IFS
-		IFS=';'
-		for e in $err
-		do
-			if test -n "$e"
-			then
-				echo >&2 "$e"
-			fi
-		done
-		IFS=$OIFS
-		exit 1
-	fi
-	}
+		"$@"
 }
 
 #
-- 
2.32.0
Ævar Arnfjörð Bjarmason· Sep 7, 2021, 12:40 UTC · re: Atharva Raykar · lore

Re: [PATCH 06/13] submodule: move core cmd_update() logic to C

On Tue, Sep 07 2021, Atharva Raykar wrote:
Show 7 quoted lines
>  struct init_cb {
>  	const char *prefix;
> +	const char *superprefix;
>  	unsigned int flags;
>  };
> -#define INIT_CB_INIT { NULL, 0 }
> +#define INIT_CB_INIT { NULL, NULL, 0 }

Since you're doing some cleanup while you're at it, just changing this in some earlier step to:

    define INIT_CB_INIT { 0 }

Is better, i.e. the NULL-ing out is implicit here. I have an unsubmitted series that does that across the codebase.

> +	.references = STRING_LIST_INIT_DUP, \
We do inits here, and append, but it seems nothing clears this string_list.
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 07/13] submodule: remove fetch_in_submodule shell function

This function has no more use in 'git-submodule.sh' after bd82d7d467 (submodule: move core cmd_update() logic to C, 2021-07-20), where we moved all of its uses to C, which has its own version for the same.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 git-submodule.sh | 14 --------------
 1 file changed, 14 deletions(-)
Show changes to git-submodule.sh +0 −14
diff --git a/git-submodule.sh b/git-submodule.sh
index 4e21d9715c..5197de4551 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -369,20 +369,6 @@ cmd_deinit()
 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
 }
 
-# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
-# Because arguments are positional, use an empty string to omit <depth>
-# but include <sha1>.
-fetch_in_submodule () (
-	sanitize_submodule_env &&
-	cd "$1" &&
-	if test $# -eq 3
-	then
-		echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
-	else
-		git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
-	fi
-)
-
 #
 # Update each submodule path to correct revision, using clone and checkout as needed
 #
-- 
2.32.0
Ævar Arnfjörð Bjarmason· Sep 7, 2021, 12:44 UTC · re: Atharva Raykar · lore

Re: [PATCH 07/13] submodule: remove fetch_in_submodule shell function

On Tue, Sep 07 2021, Atharva Raykar wrote:
> This function has no more use in 'git-submodule.sh' after
> bd82d7d467 (submodule: move core cmd_update() logic to C, 2021-07-20),
> where we moved all of its uses to C, which has its own version for the
> same.

This commit ID appears to be a reference to your own 06/13, so the OID won't work once this is merged to git.git.

Perhaps just squash this into 06/13 instead?
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 08/13] submodule--helper: remove update-clone subcommand

We no longer need this subcommand as the shell version calls the 'update' subcommand instead, which does all the cloning within C itself.

We also no longer need the 'update_clone_submodules()' and 'update_clone_submodule()' functions, so we remove those as well.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 98 -------------------------------------
 1 file changed, 98 deletions(-)
Show changes to builtin/submodule--helper.c +0 −98
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 2cccb9a9ab..a628660d6b 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2552,103 +2552,6 @@ static int do_run_update_procedure(struct update_data *ud, struct string_list *e
 	return run_update_command(ud, subforce, err);
 }
 
-static void update_clone_submodule(struct update_clone_data *ucd)
-{
-	fprintf(stdout, "dummy %s %d\t%s\n",
-		oid_to_hex(&ucd->oid),
-		ucd->just_cloned,
-		ucd->sub->path);
-}
-
-static int update_clone_submodules(struct submodule_update_clone *suc)
-{
-	int i;
-
-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
-				   update_clone_start_failure,
-				   update_clone_task_finished, suc, "submodule",
-				   "parallel/update");
-
-	/*
-	 * We saved the output and put it out all at once now.
-	 * That means:
-	 * - the listener does not have to interleave their (checkout)
-	 *   work with our fetching.  The writes involved in a
-	 *   checkout involve more straightforward sequential I/O.
-	 * - the listener can avoid doing any work if fetching failed.
-	 */
-	if (suc->quickstop)
-		return 1;
-
-	for (i = 0; i < suc->update_clone_nr; i++)
-		update_clone_submodule(&suc->update_clone[i]);
-
-	return 0;
-}
-
-static int update_clone(int argc, const char **argv, const char *prefix)
-{
-	const char *update = NULL;
-	struct pathspec pathspec;
-	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
-
-	struct option module_update_clone_options[] = {
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
-			   N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
-			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &suc.dissociate,
-			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &suc.depth, "<depth>",
-			   N_("create a shallow clone truncated to the "
-			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &suc.max_jobs,
-			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
-			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &suc.progress,
-			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &suc.require_init,
-			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &suc.single_branch,
-			 N_("clone only one branch, HEAD or --branch")),
-		OPT_END()
-	};
-
-	const char *const git_submodule_helper_usage[] = {
-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
-		NULL
-	};
-	suc.prefix = prefix;
-
-	update_clone_config_from_gitmodules(&suc.max_jobs);
-	git_config(git_update_clone_config, &suc.max_jobs);
-
-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
-			     git_submodule_helper_usage, 0);
-
-	if (update)
-		if (parse_submodule_update_strategy(update, &suc.update) < 0)
-			die(_("bad value for update parameter"));
-
-	if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
-		return 1;
-
-	if (pathspec.nr)
-		suc.warn_if_uninitialized = 1;
-
-	return update_clone_submodules(&suc);
-}
-
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
@@ -3561,7 +3464,6 @@ static struct cmd_struct commands[] = {
 	{"add-clone", add_clone, 0},
 	{"update", module_update, 0},
 	{"update-module-mode", module_update_module_mode, 0},
-	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
 	{"ensure-core-worktree", ensure_core_worktree, 0},
 	{"relative-path", resolve_relative_path, 0},
-- 
2.32.0
Ævar Arnfjörð Bjarmason· Sep 7, 2021, 12:46 UTC · re: Atharva Raykar · lore

Re: [PATCH 08/13] submodule--helper: remove update-clone subcommand

On Tue, Sep 07 2021, Atharva Raykar wrote:
Show 5 quoted lines
> We no longer need this subcommand as the shell version calls the
> 'update' subcommand instead, which does all the cloning within C itself.
>
> We also no longer need the 'update_clone_submodules()' and
> 'update_clone_submodule()' functions, so we remove those as well.

So in 04/13 update_clone_submodules() was renamed, but now we're getting rid of it. Maybe there's not an easy way to avoid this churn, but if there is...

Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 09/13] submodule--helper: remove update-module-mode subcommand

This subcommand was once useful for 'submodule update', but now that we have converted the shell code to C, it is no longer used.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 24 ------------------------
 1 file changed, 24 deletions(-)
Show changes to builtin/submodule--helper.c +0 −24
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index a628660d6b..e3e85600c3 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1993,29 +1993,6 @@ static void determine_submodule_update_strategy(struct repository *r,
 	free(key);
 }
 
-static int module_update_module_mode(int argc, const char **argv, const char *prefix)
-{
-	const char *path, *update = NULL;
-	int just_cloned;
-	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
-
-	if (argc < 3 || argc > 4)
-		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
-
-	just_cloned = git_config_int("just_cloned", argv[1]);
-	path = argv[2];
-
-	if (argc == 4)
-		update = argv[3];
-
-	determine_submodule_update_strategy(the_repository,
-					    just_cloned, path, update,
-					    &update_strategy);
-	fputs(submodule_strategy_to_string(&update_strategy), stdout);
-
-	return 0;
-}
-
 struct update_clone_data {
 	const struct submodule *sub;
 	struct object_id oid;
@@ -3463,7 +3440,6 @@ static struct cmd_struct commands[] = {
 	{"clone", module_clone, 0},
 	{"add-clone", add_clone, 0},
 	{"update", module_update, 0},
-	{"update-module-mode", module_update_module_mode, 0},
 	{"run-update-procedure", run_update_procedure, 0},
 	{"ensure-core-worktree", ensure_core_worktree, 0},
 	{"relative-path", resolve_relative_path, 0},
-- 
2.32.0
Ævar Arnfjörð Bjarmason· Sep 7, 2021, 12:49 UTC · re: Atharva Raykar · lore

Re: [PATCH 09/13] submodule--helper: remove update-module-mode subcommand

On Tue, Sep 07 2021, Atharva Raykar wrote:
Show 52 quoted lines
> This subcommand was once useful for 'submodule update', but now that we
> have converted the shell code to C, it is no longer used.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> ---
>  builtin/submodule--helper.c | 24 ------------------------
>  1 file changed, 24 deletions(-)
>
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index a628660d6b..e3e85600c3 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -1993,29 +1993,6 @@ static void determine_submodule_update_strategy(struct repository *r,
>  	free(key);
>  }
>  
> -static int module_update_module_mode(int argc, const char **argv, const char *prefix)
> -{
> -	const char *path, *update = NULL;
> -	int just_cloned;
> -	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
> -
> -	if (argc < 3 || argc > 4)
> -		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
> -
> -	just_cloned = git_config_int("just_cloned", argv[1]);
> -	path = argv[2];
> -
> -	if (argc == 4)
> -		update = argv[3];
> -
> -	determine_submodule_update_strategy(the_repository,
> -					    just_cloned, path, update,
> -					    &update_strategy);
> -	fputs(submodule_strategy_to_string(&update_strategy), stdout);
> -
> -	return 0;
> -}
> -
>  struct update_clone_data {
>  	const struct submodule *sub;
>  	struct object_id oid;
> @@ -3463,7 +3440,6 @@ static struct cmd_struct commands[] = {
>  	{"clone", module_clone, 0},
>  	{"add-clone", add_clone, 0},
>  	{"update", module_update, 0},
> -	{"update-module-mode", module_update_module_mode, 0},
>  	{"run-update-procedure", run_update_procedure, 0},
>  	{"ensure-core-worktree", ensure_core_worktree, 0},
>  	{"relative-path", resolve_relative_path, 0},

So in https://lore.kernel.org/git/87sfyglfl9.fsf@evledraar.gmail.com I suggested squashing the shell removal, but I see now that here later in 09-13/13.

So yeah, having 08/13 stand-alone is easier to read then, but I think then squashing all of 09-13 together is better. I.e. there's no reason to remove these one at a time, let's just remove them all at once.

That also makes it clear that it's a remove-only change aside from your "refactor while at it" of renaming this function:

    -static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
    +static int run_update_procedure(struct update_data *ud, struct string_list *err)

We could either skip that, or split that later refactoring into another commit.

Thanks for working on this, I'm exciting to see more of git-submodule.sh go away. Hopefully these comments I left are useful / will aid future reviewer readability of this series.

Atharva Raykar· Sep 7, 2021, 13:50 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH 09/13] submodule--helper: remove update-module-mode subcommand

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 62 quoted lines
> On Tue, Sep 07 2021, Atharva Raykar wrote:
>
>> This subcommand was once useful for 'submodule update', but now that we
>> have converted the shell code to C, it is no longer used.
>>
>> Mentored-by: Christian Couder <christian.couder@gmail.com>
>> Mentored-by: Shourya Shukla <periperidip@gmail.com>
>> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
>> ---
>>  builtin/submodule--helper.c | 24 ------------------------
>>  1 file changed, 24 deletions(-)
>>
>> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
>> index a628660d6b..e3e85600c3 100644
>> --- a/builtin/submodule--helper.c
>> +++ b/builtin/submodule--helper.c
>> @@ -1993,29 +1993,6 @@ static void determine_submodule_update_strategy(struct repository *r,
>>  	free(key);
>>  }
>>
>> -static int module_update_module_mode(int argc, const char **argv, const char *prefix)
>> -{
>> -	const char *path, *update = NULL;
>> -	int just_cloned;
>> -	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
>> -
>> -	if (argc < 3 || argc > 4)
>> -		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
>> -
>> -	just_cloned = git_config_int("just_cloned", argv[1]);
>> -	path = argv[2];
>> -
>> -	if (argc == 4)
>> -		update = argv[3];
>> -
>> -	determine_submodule_update_strategy(the_repository,
>> -					    just_cloned, path, update,
>> -					    &update_strategy);
>> -	fputs(submodule_strategy_to_string(&update_strategy), stdout);
>> -
>> -	return 0;
>> -}
>> -
>>  struct update_clone_data {
>>  	const struct submodule *sub;
>>  	struct object_id oid;
>> @@ -3463,7 +3440,6 @@ static struct cmd_struct commands[] = {
>>  	{"clone", module_clone, 0},
>>  	{"add-clone", add_clone, 0},
>>  	{"update", module_update, 0},
>> -	{"update-module-mode", module_update_module_mode, 0},
>>  	{"run-update-procedure", run_update_procedure, 0},
>>  	{"ensure-core-worktree", ensure_core_worktree, 0},
>>  	{"relative-path", resolve_relative_path, 0},
>
> So in https://lore.kernel.org/git/87sfyglfl9.fsf@evledraar.gmail.com I
> suggested squashing the shell removal, but I see now that here later in
> 09-13/13.
>
> So yeah, having 08/13 stand-alone is easier to read then, but I think
> then squashing all of 09-13 together is better. I.e. there's no reason
> to remove these one at a time, let's just remove them all at once.
Okay, I shall do this.
Show 12 quoted lines
> That also makes it clear that it's a remove-only change aside from your
> "refactor while at it" of renaming this function:
>
>     -static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
>     +static int run_update_procedure(struct update_data *ud, struct string_list *err)
>
> We could either skip that, or split that later refactoring into another
> commit.
>
> Thanks for working on this, I'm exciting to see more of git-submodule.sh
> go away. Hopefully these comments I left are useful / will aid future
> reviewer readability of this series.

Thanks for your suggestions. I've gone through all of them, and I'll reroll soon.

Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 10/13] submodule--helper: remove shell interface to ensure_core_worktree()

The 'ensure-core-worktree' subcommand is no longer needed since the conversion of the update code from shell to C.

Let's remove the subcommand, and while we are at it, let's rename 'do_ensure_core_worktree()' to 'ensure_core_worktree()' to signal that it is no longer a utility function meant to be called by another function.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 18 ++----------------
 1 file changed, 2 insertions(+), 16 deletions(-)
Show changes to builtin/submodule--helper.c +2 −16
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index e3e85600c3..597e303889 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2740,7 +2740,7 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static void do_ensure_core_worktree(const char *path)
+static void ensure_core_worktree(const char *path)
 {
 	const struct submodule *sub;
 	const char *cw;
@@ -2771,19 +2771,6 @@ static void do_ensure_core_worktree(const char *path)
 	}
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
-{
-	const char *path;
-
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-	do_ensure_core_worktree(path);
-
-	return 0;
-}
-
 static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
 {
 	int i;
@@ -3017,7 +3004,7 @@ static int update_submodule(struct update_data *update_data)
 	char *prefixed_path;
 	struct string_list err = STRING_LIST_INIT_DUP;
 
-	do_ensure_core_worktree(update_data->sm_path);
+	ensure_core_worktree(update_data->sm_path);
 
 	if (update_data->recursive_prefix)
 		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
@@ -3441,7 +3428,6 @@ static struct cmd_struct commands[] = {
 	{"add-clone", add_clone, 0},
 	{"update", module_update, 0},
 	{"run-update-procedure", run_update_procedure, 0},
-	{"ensure-core-worktree", ensure_core_worktree, 0},
 	{"relative-path", resolve_relative_path, 0},
 	{"resolve-relative-url", resolve_relative_url, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
-- 
2.32.0
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 11/13] submodule--helper: remove print-default-remote subcommand

This subcommand was once useful for submodule functionality, but after the various conversions of shell code to C, it is no longer used.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 16 ----------------
 1 file changed, 16 deletions(-)
Show changes to builtin/submodule--helper.c +0 −16
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 597e303889..9470ff3de0 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -73,21 +73,6 @@ static char *get_default_remote(void)
 	return repo_get_default_remote(the_repository, refname);
 }
 
-static int print_default_remote(int argc, const char **argv, const char *prefix)
-{
-	char *remote;
-
-	if (argc != 1)
-		die(_("submodule--helper print-default-remote takes no arguments"));
-
-	remote = get_default_remote();
-	if (remote)
-		printf("%s\n", remote);
-
-	free(remote);
-	return 0;
-}
-
 static int starts_with_dot_slash(const char *str)
 {
 	return str[0] == '.' && is_dir_sep(str[1]);
@@ -3434,7 +3419,6 @@ static struct cmd_struct commands[] = {
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
 	{"init", module_init, SUPPORT_SUPER_PREFIX},
 	{"status", module_status, SUPPORT_SUPER_PREFIX},
-	{"print-default-remote", print_default_remote, 0},
 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
 	{"deinit", module_deinit, 0},
 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
-- 
2.32.0
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 12/13] submodule--helper: remove relative-path subcommand

This subcommand was once used extensively for submodule functionality when it was written in shell, but now that we have converted the shell code to C, it is no longer used.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 12 ------------
 1 file changed, 12 deletions(-)
Show changes to builtin/submodule--helper.c +0 −12
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 9470ff3de0..7622d6bd24 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2582,17 +2582,6 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	return 3;
 }
 
-static int resolve_relative_path(int argc, const char **argv, const char *prefix)
-{
-	struct strbuf sb = STRBUF_INIT;
-	if (argc != 3)
-		die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
-
-	printf("%s", relative_path(argv[1], argv[2], &sb));
-	strbuf_release(&sb);
-	return 0;
-}
-
 static const char *remote_submodule_branch(const char *path)
 {
 	const struct submodule *sub;
@@ -3413,7 +3402,6 @@ static struct cmd_struct commands[] = {
 	{"add-clone", add_clone, 0},
 	{"update", module_update, 0},
 	{"run-update-procedure", run_update_procedure, 0},
-	{"relative-path", resolve_relative_path, 0},
 	{"resolve-relative-url", resolve_relative_url, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
-- 
2.32.0
Atharva Raykar· Sep 7, 2021, 11:59 UTC · re: Atharva Raykar · lore

[PATCH 13/13] submodule--helper: remove run-update-procedure subcommand

The subcommand 'submodule--helper run-update-procedure' is no longer needed after the conversion of the bulk of 'update' to C.

While we are at it, let's rename 'do_run_update_procedure()' to 'run_update_procedure()' to reflect the fact that it is no longer a utility function meant to be wrapped in another function.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 73 +------------------------------------
 1 file changed, 2 insertions(+), 71 deletions(-)
Show changes to builtin/submodule--helper.c +2 −71
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 7622d6bd24..6565e2cd78 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2484,7 +2484,7 @@ static int run_update_command(struct update_data *ud, int subforce, struct strin
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
+static int run_update_procedure(struct update_data *ud, struct string_list *err)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2514,74 +2514,6 @@ static int do_run_update_procedure(struct update_data *ud, struct string_list *e
 	return run_update_command(ud, subforce, err);
 }
 
-static int run_update_procedure(int argc, const char **argv, const char *prefix)
-{
-	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
-	char *prefixed_path, *update = NULL;
-	struct update_data update_data = UPDATE_DATA_INIT;
-	struct string_list err = STRING_LIST_INIT_DUP;
-
-	struct option options[] = {
-		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&force, N_("force checkout updates"), 0),
-		OPT_BOOL('N', "no-fetch", &nofetch,
-			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &just_cloned,
-			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
-			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
-			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_END()
-	};
-
-	const char *const usage[] = {
-		N_("git submodule--helper run-update-procedure [<options>] <path>"),
-		NULL
-	};
-
-	argc = parse_options(argc, argv, prefix, options, usage, 0);
-
-	if (argc != 1)
-		usage_with_options(usage, options);
-
-	update_data.force = !!force;
-	update_data.quiet = !!quiet;
-	update_data.nofetch = !!nofetch;
-	update_data.just_cloned = !!just_cloned;
-	update_data.sm_path = argv[0];
-
-	if (update_data.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
-	else
-		prefixed_path = xstrdup(update_data.sm_path);
-
-	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
-
-	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
-					    update_data.sm_path, update,
-					    &update_data.update_strategy);
-
-	free(prefixed_path);
-
-	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data, &err);
-
-	return 3;
-}
-
 static const char *remote_submodule_branch(const char *path)
 {
 	const struct submodule *sub;
@@ -3018,7 +2950,7 @@ static int update_submodule(struct update_data *update_data)
 	}
 
 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
-		if (do_run_update_procedure(update_data, &err))
+		if (run_update_procedure(update_data, &err))
 			return 1;
 
 	if (update_data->recursive) {
@@ -3401,7 +3333,6 @@ static struct cmd_struct commands[] = {
 	{"clone", module_clone, 0},
 	{"add-clone", add_clone, 0},
 	{"update", module_update, 0},
-	{"run-update-procedure", run_update_procedure, 0},
 	{"resolve-relative-url", resolve_relative_url, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
-- 
2.32.0
Ævar Arnfjörð Bjarmason· Sep 7, 2021, 12:34 UTC · re: Atharva Raykar · lore

Re: [PATCH 00/13] submodule: convert the rest of 'update' to C

On Tue, Sep 07 2021, Atharva Raykar wrote:
Show 7 quoted lines
> NOTE: This series uses ar/submodule-run-update-procedure [1]
>
> This series builds upon the previous conversion work on 'submodule update' and
> moves out all of that shell logic in 'git-submodule.sh' into
> 'builtin/submodule--helper.c'. Even though this patch series looks long, a lot
> of it is preparatory patches and cleanup of unused functions that result from
> this conversion. The real action happens at [6/8].

It looks like the 6/x part of that still applies, but not the "/8", i.e. this is now a 13-part series. Is this summary otherwise still current with what's being submitted here?

Atharva Raykar· Sep 7, 2021, 12:53 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH 00/13] submodule: convert the rest of 'update' to C

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 13 quoted lines
> On Tue, Sep 07 2021, Atharva Raykar wrote:
>
>> NOTE: This series uses ar/submodule-run-update-procedure [1]
>>
>> This series builds upon the previous conversion work on 'submodule update' and
>> moves out all of that shell logic in 'git-submodule.sh' into
>> 'builtin/submodule--helper.c'. Even though this patch series looks long, a lot
>> of it is preparatory patches and cleanup of unused functions that result from
>> this conversion. The real action happens at [6/8].
>
> It looks like the 6/x part of that still applies, but not the "/8",
> i.e. this is now a 13-part series. Is this summary otherwise still
> current with what's being submitted here?

Sorry, I meant [6/13], this is meant to be 13 parts only. Other than that typo, the summary is up-to-date with what the series contains (I wrote all of it today).

Atharva Raykar· Sep 16, 2021, 10:32 UTC · re: Atharva Raykar · lore

[PATCH v2 0/8] submodule: convert the rest of 'update' to C

NOTE: This series uses ar/submodule-run-update-procedure, which is now part of
'next'. [1]
Since v1:
  I have incorporated Ævar's suggestions, and attempted to make this easier to
  review.

This series builds upon the previous conversion work on 'submodule update' and moves out all of that shell logic in 'git-submodule.sh' into 'builtin/submodule--helper.c'. Even though this patch series looks a bit long, a lot of it is preparatory patches and cleanup of unused functions that result from this conversion. The real action happens at [5/8].

As with the other series, the goal is to be a faithful conversion, with no change in behaviour.

This would be the last command whose logic would be moved into C, other than 'submodule add', whose patches have been sent already.

After this works out, we can invert the shell-C relationship and make 'submodule' a proper C builtin.

Fetch-it-Via: git fetch https://github.com/tfidfwastaken/git submodule-update-list-2

[1] https://lore.kernel.org/git/20210824140609.1496-1-raykar.ath@gmail.com/
Atharva Raykar (8):
  submodule--helper: split up ensure_core_worktree()
  submodule--helper: get remote names from any repository
  submodule--helper: rename helpers for update-clone
  submodule--helper: refactor get_submodule_displaypath()
  submodule: move core cmd_update() logic to C
  submodule--helper: remove update-clone subcommand
  submodule--helper: remove unused helpers
  submodule--helper: rename helper functions
 builtin/submodule--helper.c | 767 +++++++++++++++++++++---------------
 git-submodule.sh            | 145 +------
 2 files changed, 458 insertions(+), 454 deletions(-)
Range-diff against v1:
 1:  2cfdc0e10a <  -:  ---------- submodule--helper: get remote names from any repository
 -:  ---------- >  1:  f83a5b7f34 submodule--helper: split up ensure_core_worktree()
 2:  be83ba7fdb !  2:  7f4e24ce25 submodule--helper: introduce get_default_remote_submodule()
    @@ Metadata
     Author: Atharva Raykar <raykar.ath@gmail.com>
     
      ## Commit message ##
    -    submodule--helper: introduce get_default_remote_submodule()
    +    submodule--helper: get remote names from any repository
     
    -    Before 8ef1d2b549 (submodule--helper: get remote names from any
    -    repository, 2021-07-20), it was not possible to directly retrieve a
    -    submodule's remote name within the same process, because
    -    `get_default_remote()` used only knew about the current repository.
    +    `get_default_remote()` retrieves the name of a remote by resolving the
    +    refs from of the current repository's ref store.
    +
    +    Thus in order to use it for retrieving the remote name of a submodule,
    +    we have to start a new subprocess which runs from the submodule
    +    directory.
    +
    +    Let's instead introduce a function called `repo_get_default_remote()`
    +    which takes any repository object and retrieves the remote accordingly.
    +
    +    `get_default_remote()` is then defined as a call to
    +    `repo_get_default_remote()` with 'the_repository' passed to it.
     
         Now that we have `repo_get_default_remote()`, we no longer have to start
         a subprocess that called `submodule--helper get-default-remote` from
         within the submodule directory.
     
    -    Let's make a function called `get_default_remote_submodule()` which
    +    So let's make a function called `get_default_remote_submodule()` which
         takes a submodule path, and returns the default remote for that
         submodule, all within the same process.
     
    @@ Commit message
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
     
      ## builtin/submodule--helper.c ##
    -@@ builtin/submodule--helper.c: static char *repo_get_default_remote(struct repository *repo, const char *refnam
    +@@
    + typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
    + 				  void *cb_data);
    + 
    +-static char *get_default_remote(void)
    ++static char *repo_get_default_remote(struct repository *repo, const char *refname)
    + {
    + 	char *dest = NULL, *ret;
    + 	struct strbuf sb = STRBUF_INIT;
    +-	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
    + 
    + 	if (!refname)
    + 		die(_("No such ref: %s"), "HEAD");
    +@@ builtin/submodule--helper.c: static char *get_default_remote(void)
    + 		die(_("Expecting a full ref name, got %s"), refname);
    + 
    + 	strbuf_addf(&sb, "branch.%s.remote", refname);
    +-	if (git_config_get_string(sb.buf, &dest))
    ++	if (repo_config_get_string(repo, sb.buf, &dest))
    + 		ret = xstrdup("origin");
    + 	else
    + 		ret = dest;
    +@@ builtin/submodule--helper.c: static char *get_default_remote(void)
      	return ret;
      }
      
    @@ builtin/submodule--helper.c: static char *repo_get_default_remote(struct reposit
     +	return repo_get_default_remote(&subrepo, refname);
     +}
     +
    - static char *get_default_remote(void)
    ++static char *get_default_remote(void)
    ++{
    ++	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
    ++	return repo_get_default_remote(the_repository, refname);
    ++}
    ++
    + static int print_default_remote(int argc, const char **argv, const char *prefix)
      {
    - 	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
    + 	char *remote;
     @@ builtin/submodule--helper.c: static void sync_submodule(const char *path, const char *prefix,
      	char *remote_key = NULL;
      	char *sub_origin_url, *super_config_url, *displaypath;
 3:  8981b2c4c5 =  3:  390596ee7c submodule--helper: rename helpers for update-clone
 4:  420d792f99 =  4:  2a2ddcac91 submodule--helper: refactor get_submodule_displaypath()
 5:  6c4696b70a !  5:  832020a290 submodule: move core cmd_update() logic to C
    @@ Commit message
         `init_submodule()`, which will now also take an explicit 'superprefix'
         argument.
     
    +    While we are at it, we also remove the fetch_in_submodule() shell
    +    function since it is no longer used anywhere.
    +
         [1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
     
         Mentored-by: Christian Couder <christian.couder@gmail.com>
    @@ builtin/submodule--helper.c: static char *compute_submodule_clone_url(const char
      	unsigned int flags;
      };
     -#define INIT_CB_INIT { NULL, 0 }
    -+#define INIT_CB_INIT { NULL, NULL, 0 }
    ++#define INIT_CB_INIT { 0 }
      
      static void init_submodule(const char *path, const char *prefix,
     -			   unsigned int flags)
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +	 *   checkout involve more straightforward sequential I/O.
     +	 * - the listener can avoid doing any work if fetching failed.
     +	 */
    -+	if (suc.quickstop)
    ++	if (suc.quickstop) {
    ++		string_list_clear(&update_data->references, 0);
     +		return 1;
    ++	}
     +
     +	for (i = 0; i < suc.update_clone_nr; i++) {
     +		struct update_clone_data ucd = suc.update_clone[i];
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +			res = 1;
     +	}
     +
    ++	string_list_clear(&update_data->references, 0);
     +	return res;
     +}
     +
    @@ builtin/submodule--helper.c: static struct cmd_struct commands[] = {
      	{"run-update-procedure", run_update_procedure, 0},
     
      ## git-submodule.sh ##
    +@@ git-submodule.sh: cmd_deinit()
    + 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
    + }
    + 
    +-# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
    +-# Because arguments are positional, use an empty string to omit <depth>
    +-# but include <sha1>.
    +-fetch_in_submodule () (
    +-	sanitize_submodule_env &&
    +-	cd "$1" &&
    +-	if test $# -eq 3
    +-	then
    +-		echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
    +-	else
    +-		git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
    +-	fi
    +-)
    +-
    + #
    + # Update each submodule path to correct revision, using clone and checkout as needed
    + #
     @@ git-submodule.sh: cmd_update()
      		shift
      	done
 6:  524ae77c3f <  -:  ---------- submodule: remove fetch_in_submodule shell function
 7:  ea56f7319a =  6:  fb3fa8174a submodule--helper: remove update-clone subcommand
 8:  10a62172a2 <  -:  ---------- submodule--helper: remove update-module-mode subcommand
 9:  dbbe5d3f53 <  -:  ---------- submodule--helper: remove shell interface to ensure_core_worktree()
10:  a015af3a16 <  -:  ---------- submodule--helper: remove print-default-remote subcommand
11:  f5a7ba1405 <  -:  ---------- submodule--helper: remove relative-path subcommand
12:  9f54eb5972 !  7:  364f72f870 submodule--helper: remove run-update-procedure subcommand
    @@ Metadata
     Author: Atharva Raykar <raykar.ath@gmail.com>
     
      ## Commit message ##
    -    submodule--helper: remove run-update-procedure subcommand
    +    submodule--helper: remove unused helpers
     
    -    The subcommand 'submodule--helper run-update-procedure' is no longer
    -    needed after the conversion of the bulk of 'update' to C.
    -
    -    While we are at it, let's rename 'do_run_update_procedure()' to
    -    'run_update_procedure()' to reflect the fact that it is no longer a
    -    utility function meant to be wrapped in another function.
    +    These helpers were useful back when 'submodule update' had most of its
    +    logic in shell. Now that they will never be invoked, let us remove them.
     
         Mentored-by: Christian Couder <christian.couder@gmail.com>
         Mentored-by: Shourya Shukla <periperidip@gmail.com>
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
     
      ## builtin/submodule--helper.c ##
    -@@ builtin/submodule--helper.c: static int run_update_command(struct update_data *ud, int subforce, struct strin
    - 	return 0;
    +@@ builtin/submodule--helper.c: static char *get_default_remote(void)
    + 	return repo_get_default_remote(the_repository, refname);
      }
      
    --static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
    -+static int run_update_procedure(struct update_data *ud, struct string_list *err)
    +-static int print_default_remote(int argc, const char **argv, const char *prefix)
    +-{
    +-	char *remote;
    +-
    +-	if (argc != 1)
    +-		die(_("submodule--helper print-default-remote takes no arguments"));
    +-
    +-	remote = get_default_remote();
    +-	if (remote)
    +-		printf("%s\n", remote);
    +-
    +-	free(remote);
    +-	return 0;
    +-}
    +-
    + static int starts_with_dot_slash(const char *str)
      {
    - 	int subforce = is_null_oid(&ud->suboid) || ud->force;
    + 	return str[0] == '.' && is_dir_sep(str[1]);
    +@@ builtin/submodule--helper.c: static void determine_submodule_update_strategy(struct repository *r,
    + 	free(key);
    + }
      
    +-static int module_update_module_mode(int argc, const char **argv, const char *prefix)
    +-{
    +-	const char *path, *update = NULL;
    +-	int just_cloned;
    +-	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
    +-
    +-	if (argc < 3 || argc > 4)
    +-		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
    +-
    +-	just_cloned = git_config_int("just_cloned", argv[1]);
    +-	path = argv[2];
    +-
    +-	if (argc == 4)
    +-		update = argv[3];
    +-
    +-	determine_submodule_update_strategy(the_repository,
    +-					    just_cloned, path, update,
    +-					    &update_strategy);
    +-	fputs(submodule_strategy_to_string(&update_strategy), stdout);
    +-
    +-	return 0;
    +-}
    +-
    + struct update_clone_data {
    + 	const struct submodule *sub;
    + 	struct object_id oid;
     @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_data *ud, struct string_list *e
      	return run_update_command(ud, subforce, err);
      }
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -
     -	return 3;
     -}
    +-
    +-static int resolve_relative_path(int argc, const char **argv, const char *prefix)
    +-{
    +-	struct strbuf sb = STRBUF_INIT;
    +-	if (argc != 3)
    +-		die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
    +-
    +-	printf("%s", relative_path(argv[1], argv[2], &sb));
    +-	strbuf_release(&sb);
    +-	return 0;
    +-}
     -
      static const char *remote_submodule_branch(const char *path)
      {
      	const struct submodule *sub;
    -@@ builtin/submodule--helper.c: static int update_submodule(struct update_data *update_data)
    +@@ builtin/submodule--helper.c: static void do_ensure_core_worktree(const char *path)
      	}
    + }
      
    - 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
    --		if (do_run_update_procedure(update_data, &err))
    -+		if (run_update_procedure(update_data, &err))
    - 			return 1;
    - 
    - 	if (update_data->recursive) {
    +-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
    +-{
    +-	const char *path;
    +-
    +-	if (argc != 2)
    +-		BUG("submodule--helper ensure-core-worktree <path>");
    +-
    +-	path = argv[1];
    +-	do_ensure_core_worktree(path);
    +-
    +-	return 0;
    +-}
    +-
    + static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
    + {
    + 	int i;
     @@ builtin/submodule--helper.c: static struct cmd_struct commands[] = {
      	{"clone", module_clone, 0},
      	{"add-clone", add_clone, 0},
      	{"update", module_update, 0},
    +-	{"update-module-mode", module_update_module_mode, 0},
     -	{"run-update-procedure", run_update_procedure, 0},
    +-	{"ensure-core-worktree", ensure_core_worktree, 0},
    +-	{"relative-path", resolve_relative_path, 0},
      	{"resolve-relative-url", resolve_relative_url, 0},
      	{"resolve-relative-url-test", resolve_relative_url_test, 0},
      	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
    + 	{"init", module_init, SUPPORT_SUPER_PREFIX},
    + 	{"status", module_status, SUPPORT_SUPER_PREFIX},
    +-	{"print-default-remote", print_default_remote, 0},
    + 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
    + 	{"deinit", module_deinit, 0},
    + 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
 -:  ---------- >  8:  ca48dd452c submodule--helper: rename helper functions
-- 
2.32.0
Atharva Raykar· Sep 16, 2021, 10:32 UTC · re: Atharva Raykar · lore

[PATCH v2 1/8] submodule--helper: split up ensure_core_worktree()

Let's split up `ensure_core_worktree()` so that we can call it from C code without needing to deal with command line arguments.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)
Show changes to builtin/submodule--helper.c +12 −7
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 80619361fc..97512ccf0b 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2794,18 +2794,12 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+static void do_ensure_core_worktree(const char *path)
 {
 	const struct submodule *sub;
-	const char *path;
 	const char *cw;
 	struct repository subrepo;
 
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-
 	sub = submodule_from_path(the_repository, null_oid(), path);
 	if (!sub)
 		BUG("We could get the submodule handle before?");
@@ -2829,6 +2823,17 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
 		free(abs_path);
 		strbuf_release(&sb);
 	}
+}
+
+static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+{
+	const char *path;
+
+	if (argc != 2)
+		BUG("submodule--helper ensure-core-worktree <path>");
+
+	path = argv[1];
+	do_ensure_core_worktree(path);
 
 	return 0;
 }
-- 
2.32.0
Atharva Raykar· Sep 16, 2021, 10:32 UTC · re: Atharva Raykar · lore

[PATCH v2 2/8] submodule--helper: get remote names from any repository

`get_default_remote()` retrieves the name of a remote by resolving the refs from of the current repository's ref store.

Thus in order to use it for retrieving the remote name of a submodule, we have to start a new subprocess which runs from the submodule directory.

Let's instead introduce a function called `repo_get_default_remote()` which takes any repository object and retrieves the remote accordingly.

`get_default_remote()` is then defined as a call to `repo_get_default_remote()` with 'the_repository' passed to it.

Now that we have `repo_get_default_remote()`, we no longer have to start a subprocess that called `submodule--helper get-default-remote` from within the submodule directory.

So let's make a function called `get_default_remote_submodule()` which takes a submodule path, and returns the default remote for that submodule, all within the same process.

We can now use this function to save an unnecessary subprocess spawn in `sync_submodule()`, and also in the next patch, which will require this functionality.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 34 +++++++++++++++++++++++-----------
 1 file changed, 23 insertions(+), 11 deletions(-)
Show changes to builtin/submodule--helper.c +23 −11
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 97512ccf0b..f6c4fc349b 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -29,11 +29,10 @@
 typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
 				  void *cb_data);
 
-static char *get_default_remote(void)
+static char *repo_get_default_remote(struct repository *repo, const char *refname)
 {
 	char *dest = NULL, *ret;
 	struct strbuf sb = STRBUF_INIT;
-	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
 
 	if (!refname)
 		die(_("No such ref: %s"), "HEAD");
@@ -46,7 +45,7 @@ static char *get_default_remote(void)
 		die(_("Expecting a full ref name, got %s"), refname);
 
 	strbuf_addf(&sb, "branch.%s.remote", refname);
-	if (git_config_get_string(sb.buf, &dest))
+	if (repo_config_get_string(repo, sb.buf, &dest))
 		ret = xstrdup("origin");
 	else
 		ret = dest;
@@ -55,6 +54,25 @@ static char *get_default_remote(void)
 	return ret;
 }
 
+static char *get_default_remote_submodule(const char *module_path)
+{
+	const char *refname;
+	const struct submodule *sub;
+	struct repository subrepo;
+
+	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
+					  "HEAD", 0, NULL, NULL);
+	sub = submodule_from_path(the_repository, null_oid(), module_path);
+	repo_submodule_init(&subrepo, the_repository, sub);
+	return repo_get_default_remote(&subrepo, refname);
+}
+
+static char *get_default_remote(void)
+{
+	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+	return repo_get_default_remote(the_repository, refname);
+}
+
 static int print_default_remote(int argc, const char **argv, const char *prefix)
 {
 	char *remote;
@@ -1369,7 +1387,6 @@ static void sync_submodule(const char *path, const char *prefix,
 	char *remote_key = NULL;
 	char *sub_origin_url, *super_config_url, *displaypath;
 	struct strbuf sb = STRBUF_INIT;
-	struct child_process cp = CHILD_PROCESS_INIT;
 	char *sub_config_path = NULL;
 
 	if (!is_submodule_active(the_repository, path))
@@ -1418,14 +1435,9 @@ static void sync_submodule(const char *path, const char *prefix,
 	if (!is_submodule_populated_gently(path, NULL))
 		goto cleanup;
 
-	prepare_submodule_repo_env(&cp.env_array);
-	cp.git_cmd = 1;
-	cp.dir = path;
-	strvec_pushl(&cp.args, "submodule--helper",
-		     "print-default-remote", NULL);
-
 	strbuf_reset(&sb);
-	if (capture_command(&cp, &sb, 0))
+	strbuf_addstr(&sb, get_default_remote_submodule(path));
+	if (!sb.buf)
 		die(_("failed to get the default remote for submodule '%s'"),
 		      path);
 
-- 
2.32.0
Junio C Hamano· Sep 20, 2021, 16:52 UTC · re: Atharva Raykar · lore

Re: [PATCH v2 2/8] submodule--helper: get remote names from any repository

Atharva Raykar <raykar.ath@gmail.com> writes:
Show 31 quoted lines
> `get_default_remote()` retrieves the name of a remote by resolving the
> refs from of the current repository's ref store.
>
> Thus in order to use it for retrieving the remote name of a submodule,
> we have to start a new subprocess which runs from the submodule
> directory.
>
> Let's instead introduce a function called `repo_get_default_remote()`
> which takes any repository object and retrieves the remote accordingly.
>
> `get_default_remote()` is then defined as a call to
> `repo_get_default_remote()` with 'the_repository' passed to it.
>
> Now that we have `repo_get_default_remote()`, we no longer have to start
> a subprocess that called `submodule--helper get-default-remote` from
> within the submodule directory.
>
> So let's make a function called `get_default_remote_submodule()` which
> takes a submodule path, and returns the default remote for that
> submodule, all within the same process.
>
> We can now use this function to save an unnecessary subprocess spawn in
> `sync_submodule()`, and also in the next patch, which will require this
> functionality.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> ---
>  builtin/submodule--helper.c | 34 +++++++++++++++++++++++-----------
>  1 file changed, 23 insertions(+), 11 deletions(-)

The above covers a lot of stuff, because this change does a lot of things ;-)

The commit could be split into 3 logically distinct phases (two independent, the third depends on the other two):

 - extract the part that uses the run-command() API to run
   "submodule--helper print-default-remote" from sync_submodule()
   and create get_default_remote_submodule() function out of it.
 - move bulk of get_default_remote() into repo_get_default_remote()
   and reimplement the former as a thin wrapper of the latter.
 - using the repo_get_default_remote() created in the second step,
   update the get_default_remote_submodule() created in the first
   step to work in-process without a subprocess.
but a bit larger granularity used here is probably OK.
Show 76 quoted lines
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index 97512ccf0b..f6c4fc349b 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -29,11 +29,10 @@
>  typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
>  				  void *cb_data);
>  
> -static char *get_default_remote(void)
> +static char *repo_get_default_remote(struct repository *repo, const char *refname)
>  {
>  	char *dest = NULL, *ret;
>  	struct strbuf sb = STRBUF_INIT;
> -	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
>  
>  	if (!refname)
>  		die(_("No such ref: %s"), "HEAD");
> @@ -46,7 +45,7 @@ static char *get_default_remote(void)
>  		die(_("Expecting a full ref name, got %s"), refname);
>  
>  	strbuf_addf(&sb, "branch.%s.remote", refname);
> -	if (git_config_get_string(sb.buf, &dest))
> +	if (repo_config_get_string(repo, sb.buf, &dest))
>  		ret = xstrdup("origin");
>  	else
>  		ret = dest;
> @@ -55,6 +54,25 @@ static char *get_default_remote(void)
>  	return ret;
>  }
>  
> +static char *get_default_remote_submodule(const char *module_path)
> +{
> +	const char *refname;
> +	const struct submodule *sub;
> +	struct repository subrepo;
> +
> +	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
> +					  "HEAD", 0, NULL, NULL);
> +	sub = submodule_from_path(the_repository, null_oid(), module_path);
> +	repo_submodule_init(&subrepo, the_repository, sub);
> +	return repo_get_default_remote(&subrepo, refname);
> +}
> +
> +static char *get_default_remote(void)
> +{
> +	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
> +	return repo_get_default_remote(the_repository, refname);
> +}
> +
>  static int print_default_remote(int argc, const char **argv, const char *prefix)
>  {
>  	char *remote;
> @@ -1369,7 +1387,6 @@ static void sync_submodule(const char *path, const char *prefix,
>  	char *remote_key = NULL;
>  	char *sub_origin_url, *super_config_url, *displaypath;
>  	struct strbuf sb = STRBUF_INIT;
> -	struct child_process cp = CHILD_PROCESS_INIT;
>  	char *sub_config_path = NULL;
>  
>  	if (!is_submodule_active(the_repository, path))
> @@ -1418,14 +1435,9 @@ static void sync_submodule(const char *path, const char *prefix,
>  	if (!is_submodule_populated_gently(path, NULL))
>  		goto cleanup;
>  
> -	prepare_submodule_repo_env(&cp.env_array);
> -	cp.git_cmd = 1;
> -	cp.dir = path;
> -	strvec_pushl(&cp.args, "submodule--helper",
> -		     "print-default-remote", NULL);
> -
>  	strbuf_reset(&sb);
> -	if (capture_command(&cp, &sb, 0))
> +	strbuf_addstr(&sb, get_default_remote_submodule(path));
> +	if (!sb.buf)
>  		die(_("failed to get the default remote for submodule '%s'"),
>  		      path);
There is this line after the post context presented here:
        strbuf_strip_suffix(&sb, "\n");
        remote_key = xstrfmt("remote.%s.url", sb.buf);

The LF was expected to come from the capture_command(), but it no longer is needed after get_default_remote_submodule() switches to use the in-process method. In fact, the use of sb for the purpose of forming remote_key is not needed.

    tmp = get_default_remote_submodule(path);
    if (!tmp)
	die(_("failed to get..."));
    remote_key = xstrfmt("remote.%s.url", tmp);
    free(tmp);

Yes, I think the new code leaks, and makes it impossible not to leak, the returned value from get_default_remote_submodule() by passing the call as an argument to strbuf_addstr().

Both of the two bugs pointed out here would have been easier to spot if the commits were smaller, I would think, but as I said, a bit larger granularity used here was still manageable.

Thanks.
Atharva Raykar· Oct 3, 2021, 13:22 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 2/8] submodule--helper: get remote names from any repository

Junio C Hamano <gitster@pobox.com> writes:
Show 53 quoted lines
> Atharva Raykar <raykar.ath@gmail.com> writes:
>
>> `get_default_remote()` retrieves the name of a remote by resolving the
>> refs from of the current repository's ref store.
>>
>> Thus in order to use it for retrieving the remote name of a submodule,
>> we have to start a new subprocess which runs from the submodule
>> directory.
>>
>> Let's instead introduce a function called `repo_get_default_remote()`
>> which takes any repository object and retrieves the remote accordingly.
>>
>> `get_default_remote()` is then defined as a call to
>> `repo_get_default_remote()` with 'the_repository' passed to it.
>>
>> Now that we have `repo_get_default_remote()`, we no longer have to start
>> a subprocess that called `submodule--helper get-default-remote` from
>> within the submodule directory.
>>
>> So let's make a function called `get_default_remote_submodule()` which
>> takes a submodule path, and returns the default remote for that
>> submodule, all within the same process.
>>
>> We can now use this function to save an unnecessary subprocess spawn in
>> `sync_submodule()`, and also in the next patch, which will require this
>> functionality.
>>
>> Mentored-by: Christian Couder <christian.couder@gmail.com>
>> Mentored-by: Shourya Shukla <periperidip@gmail.com>
>> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
>> ---
>>  builtin/submodule--helper.c | 34 +++++++++++++++++++++++-----------
>>  1 file changed, 23 insertions(+), 11 deletions(-)
>
> The above covers a lot of stuff, because this change does a lot of
> things ;-)
>
> The commit could be split into 3 logically distinct phases (two
> independent, the third depends on the other two):
>
>  - extract the part that uses the run-command() API to run
>    "submodule--helper print-default-remote" from sync_submodule()
>    and create get_default_remote_submodule() function out of it.
>
>  - move bulk of get_default_remote() into repo_get_default_remote()
>    and reimplement the former as a thin wrapper of the latter.
>
>  - using the repo_get_default_remote() created in the second step,
>    update the get_default_remote_submodule() created in the first
>    step to work in-process without a subprocess.
>
> but a bit larger granularity used here is probably OK.
>
[...]
Show 26 quoted lines
>>  	strbuf_reset(&sb);
>> -	if (capture_command(&cp, &sb, 0))
>> +	strbuf_addstr(&sb, get_default_remote_submodule(path));
>> +	if (!sb.buf)
>>  		die(_("failed to get the default remote for submodule '%s'"),
>>  		      path);
>
> There is this line after the post context presented here:
>
>         strbuf_strip_suffix(&sb, "\n");
>         remote_key = xstrfmt("remote.%s.url", sb.buf);
>
> The LF was expected to come from the capture_command(), but it no
> longer is needed after get_default_remote_submodule() switches to
> use the in-process method.  In fact, the use of sb for the purpose
> of forming remote_key is not needed.
>
>     tmp = get_default_remote_submodule(path);
>     if (!tmp)
> 	die(_("failed to get..."));
>     remote_key = xstrfmt("remote.%s.url", tmp);
>     free(tmp);
>
> Yes, I think the new code leaks, and makes it impossible not to
> leak, the returned value from get_default_remote_submodule() by
> passing the call as an argument to strbuf_addstr().
Thanks, this was a good catch.
> Both of the two bugs pointed out here would have been easier to spot
> if the commits were smaller, I would think, but as I said, a bit
> larger granularity used here was still manageable.

My initial series had this patch split up similar to what you suggested, but I ultimately chose to favour the larger granularity to make the whole series a bit shorter. This patch definitely towed the line a bit.

> Thanks.
Junio C Hamano· Sep 20, 2021, 21:28 UTC · re: Atharva Raykar · lore

Re: [PATCH v2 2/8] submodule--helper: get remote names from any repository

Atharva Raykar <raykar.ath@gmail.com> writes:
Show 12 quoted lines
> +static char *get_default_remote_submodule(const char *module_path)
> +{
> +	const char *refname;
> +	const struct submodule *sub;
> +	struct repository subrepo;
> +
> +	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
> +					  "HEAD", 0, NULL, NULL);
> +	sub = submodule_from_path(the_repository, null_oid(), module_path);
> +	repo_submodule_init(&subrepo, the_repository, sub);
> +	return repo_get_default_remote(&subrepo, refname);
> +}

This call (and I think there is another call in this file) to repo_submodule_init() is affected by what Jonathan's 8eb8dcf9 (repository: support unabsorbed in repo_submodule_init, 2021-09-09) wants to do, namely to lose "struct submodule sub" as a parameter and instead take the path to the module and the treeish name as parameters to repo_submodule_init().

I _think_ I resolved the conflict correctly, but please double check the result when it is pushed out later today, both of you.

Thanks.
Jonathan Tan· Sep 21, 2021, 16:33 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 2/8] submodule--helper: get remote names from any repository

Show 26 quoted lines
> Atharva Raykar <raykar.ath@gmail.com> writes:
> 
> > +static char *get_default_remote_submodule(const char *module_path)
> > +{
> > +	const char *refname;
> > +	const struct submodule *sub;
> > +	struct repository subrepo;
> > +
> > +	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
> > +					  "HEAD", 0, NULL, NULL);
> > +	sub = submodule_from_path(the_repository, null_oid(), module_path);
> > +	repo_submodule_init(&subrepo, the_repository, sub);
> > +	return repo_get_default_remote(&subrepo, refname);
> > +}
> 
> This call (and I think there is another call in this file) to
> repo_submodule_init() is affected by what Jonathan's 8eb8dcf9
> (repository: support unabsorbed in repo_submodule_init, 2021-09-09)
> wants to do, namely to lose "struct submodule sub" as a parameter
> and instead take the path to the module and the treeish name as
> parameters to repo_submodule_init().
> 
> I _think_ I resolved the conflict correctly, but please double check
> the result when it is pushed out later today, both of you.
> 
> Thanks.

Thanks for calling this out. There are indeed 2 such calls in 33cfc43433 ("Merge branch 'ar/submodule-update' into seen", 2021-09-20) and both look correct.

Atharva Raykar· Sep 16, 2021, 10:32 UTC · re: Atharva Raykar · lore

[PATCH v2 3/8] submodule--helper: rename helpers for update-clone

The `update-clone` subcommand helpers that perform the parallel clone and printing to stdout for shell script consumption, are renamed.

This lets us use the names `update_submodules()` and `update_submodule()` for the helpers in the next patch, when we create an `update` subcommand that does a full conversion.

We will get rid of these helpers in a cleanup patch at the end of this series, when the `update-clone` command is no longer useful to us.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +4 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f6c4fc349b..b0336b0377 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2499,7 +2499,7 @@ static int do_run_update_procedure(struct update_data *ud)
 	return run_update_command(ud, subforce);
 }
 
-static void update_submodule(struct update_clone_data *ucd)
+static void update_clone_submodule(struct update_clone_data *ucd)
 {
 	fprintf(stdout, "dummy %s %d\t%s\n",
 		oid_to_hex(&ucd->oid),
@@ -2507,7 +2507,7 @@ static void update_submodule(struct update_clone_data *ucd)
 		ucd->sub->path);
 }
 
-static int update_submodules(struct submodule_update_clone *suc)
+static int update_clone_submodules(struct submodule_update_clone *suc)
 {
 	int i;
 
@@ -2528,7 +2528,7 @@ static int update_submodules(struct submodule_update_clone *suc)
 		return 1;
 
 	for (i = 0; i < suc->update_clone_nr; i++)
-		update_submodule(&suc->update_clone[i]);
+		update_clone_submodule(&suc->update_clone[i]);
 
 	return 0;
 }
@@ -2593,7 +2593,7 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 	if (pathspec.nr)
 		suc.warn_if_uninitialized = 1;
 
-	return update_submodules(&suc);
+	return update_clone_submodules(&suc);
 }
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
-- 
2.32.0
Atharva Raykar· Sep 16, 2021, 10:32 UTC · re: Atharva Raykar · lore

[PATCH v2 4/8] submodule--helper: refactor get_submodule_displaypath()

We create a function called `do_get_submodule_displaypath()` that generates the display path required by several submodule functions, and takes a custom superprefix parameter, instead of reading it from the environment.

We then redefine the existing `get_submodule_displaypath()` function as a call to this new function, where the superprefix is obtained from the environment.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +8 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index b0336b0377..10de01a1f7 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -269,11 +269,8 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr
 	return 0;
 }
 
-/* the result should be freed by the caller. */
-static char *get_submodule_displaypath(const char *path, const char *prefix)
+static char *do_get_submodule_displaypath(const char *path, const char *prefix, const char *super_prefix)
 {
-	const char *super_prefix = get_super_prefix();
-
 	if (prefix && super_prefix) {
 		BUG("cannot have prefix '%s' and superprefix '%s'",
 		    prefix, super_prefix);
@@ -289,6 +286,13 @@ static char *get_submodule_displaypath(const char *path, const char *prefix)
 	}
 }
 
+/* the result should be freed by the caller. */
+static char *get_submodule_displaypath(const char *path, const char *prefix)
+{
+	const char *super_prefix = get_super_prefix();
+	return do_get_submodule_displaypath(path, prefix, super_prefix);
+}
+
 static char *compute_rev_name(const char *sub_path, const char* object_id)
 {
 	struct strbuf sb = STRBUF_INIT;
-- 
2.32.0
Atharva Raykar· Sep 16, 2021, 10:32 UTC · re: Atharva Raykar · lore

[PATCH v2 5/8] submodule: move core cmd_update() logic to C

This patch completes the conversion past the flag parsing of `submodule update` by introducing a helper subcommand called `submodule--helper update`. The behaviour of `submodule update` should remain the same after this patch.

We add more fields to the `struct update_data` that are required by `struct submodule_update_clone` to be able to perform a clone, when that is needed to be done.

Recursing on a submodule is done by calling a subprocess that launches `submodule--helper update`, with a modified `--recursive-prefix` and `--prefix` parameter.

We also introduce `update_submodules()` and `update_submodule()` which are quite similar to `update_clone_submodules()` and `update_clone_submodule()`, and will supersede them.

When the `--init` flag is passed to the subcommand, we do not spawn a new subprocess and call `submodule--helper init` on the submodule paths, because the Git machinery is not able to pick up the configuration changes introduced by that init call[1]. So we instead run the `init_submodule_cb()` callback over each submodule directly.

This introduces another problem, because there is no mechanism to pass the superproject path prefix (ie, `--super-prefix`) without starting a new git process. This field is required for obtaining the display path for that is used by the command's output messages. So let's add a field into the `init_cb` struct that lets us pass this information to `init_submodule()`, which will now also take an explicit 'superprefix' argument.

While we are at it, we also remove the fetch_in_submodule() shell function since it is no longer used anywhere.

[1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 505 ++++++++++++++++++++++++++++++------
 git-submodule.sh            | 145 +----------
 2 files changed, 433 insertions(+), 217 deletions(-)
Show changes to 2 files +433 −217

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 10de01a1f7..1eea626864 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -634,18 +634,22 @@ static char *compute_submodule_clone_url(const char *rel_url)
 
 struct init_cb {
 	const char *prefix;
+	const char *superprefix;
 	unsigned int flags;
 };
-#define INIT_CB_INIT { NULL, 0 }
+#define INIT_CB_INIT { 0 }
 
 static void init_submodule(const char *path, const char *prefix,
-			   unsigned int flags)
+			   const char *superprefix, unsigned int flags)
 {
 	const struct submodule *sub;
 	struct strbuf sb = STRBUF_INIT;
 	char *upd = NULL, *url = NULL, *displaypath;
 
-	displaypath = get_submodule_displaypath(path, prefix);
+	/* try superprefix from the environment, if it is not passed explicitly */
+	if (!superprefix)
+		superprefix = get_super_prefix();
+	displaypath = do_get_submodule_displaypath(path, prefix, superprefix);
 
 	sub = submodule_from_path(the_repository, null_oid(), path);
 
@@ -719,7 +723,7 @@ static void init_submodule(const char *path, const char *prefix,
 static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
 {
 	struct init_cb *info = cb_data;
-	init_submodule(list_item->name, info->prefix, info->flags);
+	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
 }
 
 static int module_init(int argc, const char **argv, const char *prefix)
@@ -2039,7 +2043,6 @@ struct submodule_update_clone {
 	const char *prefix;
 	int single_branch;
 
-	/* to be consumed by git-submodule.sh */
 	struct update_clone_data *update_clone;
 	int update_clone_nr; int update_clone_alloc;
 
@@ -2062,19 +2065,63 @@ struct submodule_update_clone {
 }
 
 struct update_data {
+	const char *prefix;
 	const char *recursive_prefix;
 	const char *sm_path;
 	const char *displaypath;
 	struct object_id oid;
 	struct object_id suboid;
-	struct submodule_update_strategy update_strategy;
+	int max_jobs;
 	int depth;
+	int recommend_shallow;
+	int single_branch;
+	unsigned int init: 1;
 	unsigned int force: 1;
 	unsigned int quiet: 1;
 	unsigned int nofetch: 1;
-	unsigned int just_cloned: 1;
+	unsigned int remote: 1;
+	unsigned int recursive: 1;
+	unsigned int progress: 1;
+	unsigned int dissociate: 1;
+	unsigned int require_init: 1;
+	unsigned warn_if_uninitialized : 1;
+	unsigned int just_cloned : 1;
+	struct submodule_update_strategy update_strategy;
+	struct string_list references;
+	struct module_list list;
 };
-#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
+#define UPDATE_DATA_INIT { \
+	.list = MODULE_LIST_INIT, \
+	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+	.recommend_shallow = -1, \
+	.references = STRING_LIST_INIT_DUP, \
+	.single_branch = -1, \
+	.max_jobs = 1, \
+}
+
+static void update_clone_from_update_data(struct submodule_update_clone *suc,
+					  struct update_data *update_data)
+{
+	suc->prefix = update_data->prefix;
+	suc->recursive_prefix = update_data->recursive_prefix;
+	suc->max_jobs = update_data->max_jobs;
+	suc->progress = update_data->progress;
+	suc->quiet = update_data->quiet;
+	suc->dissociate = update_data->dissociate;
+	suc->require_init = update_data->require_init;
+	suc->single_branch = update_data->single_branch;
+	suc->warn_if_uninitialized = update_data->warn_if_uninitialized;
+	suc->list = update_data->list;
+	suc->update = update_data->update_strategy;
+	suc->recommend_shallow = update_data->recommend_shallow;
+	if (update_data->depth)
+		suc->depth = xstrfmt("--depth=%d", update_data->depth);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			string_list_append(&suc->references, item->string);
+	}
+}
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
 		struct strbuf *out, const char *displaypath)
@@ -2369,111 +2416,113 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 	return run_command(&cp);
 }
 
-static int run_update_command(struct update_data *ud, int subforce)
+static int run_update_command(struct update_data *ud, int subforce, struct string_list *err)
 {
-	struct strvec args = STRVEC_INIT;
-	struct strvec child_env = STRVEC_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
+	struct strbuf out = STRBUF_INIT;
 	int must_die_on_failure = 0;
-	int git_cmd;
+	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
 
-	switch (ud->update_strategy.type) {
+	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
+		determine_submodule_update_strategy(the_repository, ud->just_cloned,
+						    ud->sm_path, NULL, &strategy);
+	else
+		strategy = ud->update_strategy;
+
+	cp.dir = xstrdup(ud->sm_path);
+	switch (strategy.type) {
 	case SM_UPDATE_CHECKOUT:
-		git_cmd = 1;
-		strvec_pushl(&args, "checkout", "-q", NULL);
+		cp.git_cmd = 1;
+		strvec_pushl(&cp.args, "checkout", "-q", NULL);
 		if (subforce)
-			strvec_push(&args, "-f");
+			strvec_push(&cp.args, "-f");
 		break;
 	case SM_UPDATE_REBASE:
-		git_cmd = 1;
-		strvec_push(&args, "rebase");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "rebase");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_MERGE:
-		git_cmd = 1;
-		strvec_push(&args, "merge");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "merge");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_COMMAND:
-		git_cmd = 0;
-		strvec_push(&args, ud->update_strategy.command);
+		cp.git_cmd = 0;
+		cp.use_shell = 1;
+		strvec_push(&cp.args, strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+		    submodule_strategy_to_string(&strategy));
 	}
-	strvec_push(&args, oid);
+	strvec_push(&cp.args, oid);
 
-	prepare_submodule_repo_env(&child_env);
-	if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
-				     ud->sm_path, child_env.v)) {
-		switch (ud->update_strategy.type) {
-		case SM_UPDATE_CHECKOUT:
-			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
-			break;
-		case SM_UPDATE_REBASE:
-			printf(_("Unable to rebase '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
-			break;
-		case SM_UPDATE_MERGE:
-			printf(_("Unable to merge '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
-			break;
-		case SM_UPDATE_COMMAND:
-			printf(_("Execution of '%s %s' failed in submodule path '%s'"),
-			       ud->update_strategy.command, oid, ud->displaypath);
-			break;
-		default:
-			BUG("unexpected update strategy type: %s",
-			    submodule_strategy_to_string(&ud->update_strategy));
+	prepare_submodule_repo_env(&cp.env_array);
+	if (capture_command(&cp, &out, 0)) {
+		if (must_die_on_failure) {
+			switch (strategy.type) {
+			case SM_UPDATE_CHECKOUT:
+				die(_("Unable to checkout '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_REBASE:
+				die(_("Unable to rebase '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_MERGE:
+				die(_("Unable to merge '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_COMMAND:
+				die(_("Execution of '%s %s' failed in submodule path '%s'"),
+				    strategy.command, oid, ud->displaypath);
+				break;
+			default:
+				BUG("unexpected update strategy type: %s",
+				    submodule_strategy_to_string(&strategy));
+			}
 		}
-		/*
-		 * NEEDSWORK: We are currently printing to stdout with error
-		 * return so that the shell caller handles the error output
-		 * properly. Once we start handling the error messages within
-		 * C, we should use die() instead.
-		 */
-		if (must_die_on_failure)
-			return 2;
-		/*
-		 * This signifies to the caller in shell that the command
-		 * failed without dying
-		 */
+
+		/* the command failed, but update must continue */
+		string_list_append(err, out.buf);
 		return 1;
 	}
 
-	switch (ud->update_strategy.type) {
-	case SM_UPDATE_CHECKOUT:
-		printf(_("Submodule path '%s': checked out '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_REBASE:
-		printf(_("Submodule path '%s': rebased into '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_MERGE:
-		printf(_("Submodule path '%s': merged in '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_COMMAND:
-		printf(_("Submodule path '%s': '%s %s'\n"),
-		       ud->displaypath, ud->update_strategy.command, oid);
-		break;
-	default:
-		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+	if (!ud->quiet) {
+		switch (strategy.type) {
+		case SM_UPDATE_CHECKOUT:
+			printf(_("Submodule path '%s': checked out '%s'\n"),
+			       ud->displaypath, oid);
+			break;
+		case SM_UPDATE_REBASE:
+			printf(_("Submodule path '%s': rebased into '%s'\n"),
+			       ud->displaypath, oid);
+			break;
+		case SM_UPDATE_MERGE:
+			printf(_("Submodule path '%s': merged in '%s'\n"),
+			       ud->displaypath, oid);
+			break;
+		case SM_UPDATE_COMMAND:
+			printf(_("Submodule path '%s': '%s %s'\n"),
+			       ud->displaypath, strategy.command, oid);
+			break;
+		default:
+			BUG("unexpected update strategy type: %s",
+			    submodule_strategy_to_string(&strategy));
+		}
 	}
 
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud)
+static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2500,7 +2549,7 @@ static int do_run_update_procedure(struct update_data *ud)
 			    ud->displaypath, oid_to_hex(&ud->oid));
 	}
 
-	return run_update_command(ud, subforce);
+	return run_update_command(ud, subforce, err);
 }
 
 static void update_clone_submodule(struct update_clone_data *ucd)
@@ -2605,6 +2654,7 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
 	char *prefixed_path, *update = NULL;
 	struct update_data update_data = UPDATE_DATA_INIT;
+	struct string_list err = STRING_LIST_INIT_DUP;
 
 	struct option options[] = {
 		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
@@ -2662,7 +2712,7 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	free(prefixed_path);
 
 	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data);
+		return do_run_update_procedure(&update_data, &err);
 
 	return 3;
 }
@@ -3038,6 +3088,291 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 	return !!ret;
 }
 
+static void update_data_to_args(struct update_data *update_data, struct strvec *args)
+{
+	const char *update = submodule_strategy_to_string(&update_data->update_strategy);
+
+	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
+	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
+	if (update_data->prefix)
+		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
+	if (update_data->recursive_prefix)
+		strvec_pushl(args, "--recursive-prefix",
+			     update_data->recursive_prefix, NULL);
+	if (update_data->quiet)
+		strvec_push(args, "--quiet");
+	if (update_data->force)
+		strvec_push(args, "--force");
+	if (update_data->init)
+		strvec_push(args, "--init");
+	if (update_data->remote)
+		strvec_push(args, "--remote");
+	if (update_data->nofetch)
+		strvec_push(args, "--no-fetch");
+	if (update_data->dissociate)
+		strvec_push(args, "--dissociate");
+	if (update_data->progress)
+		strvec_push(args, "--progress");
+	if (update_data->require_init)
+		strvec_push(args, "--require-init");
+	if (update_data->depth)
+		strvec_pushf(args, "--depth=%d", update_data->depth);
+	if (update)
+		strvec_pushl(args, "--update", update, NULL);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			strvec_pushl(args, "--reference", item->string, NULL);
+	}
+	if (update_data->recommend_shallow == 0)
+		strvec_push(args, "--no-recommend-shallow");
+	else if (update_data->recommend_shallow == 1)
+		strvec_push(args, "--recommend-shallow");
+	if (update_data->single_branch >= 0)
+		strvec_push(args, "--single-branch");
+}
+
+static int update_submodule(struct update_data *update_data)
+{
+	char *prefixed_path;
+	struct string_list err = STRING_LIST_INIT_DUP;
+
+	do_ensure_core_worktree(update_data->sm_path);
+
+	if (update_data->recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
+					update_data->sm_path);
+	else
+		prefixed_path = xstrdup(update_data->sm_path);
+
+	update_data->displaypath = get_submodule_displaypath(prefixed_path,
+							     update_data->prefix);
+	free(prefixed_path);
+
+	if (update_data->just_cloned) {
+		oidcpy(&update_data->suboid, null_oid());
+	} else {
+		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+			die(_("Unable to find current revision in submodule path '%s'"),
+			    update_data->displaypath);
+	}
+
+	if (update_data->remote) {
+		char *remote_name = get_default_remote_submodule(update_data->sm_path);
+		const char *branch = remote_submodule_branch(update_data->sm_path);
+		char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch);
+
+		if (!update_data->nofetch) {
+			if(fetch_in_submodule(update_data->sm_path, update_data->depth,
+					      0, NULL))
+				die(_("Unable to fetch in submodule path '%s'"),
+				    update_data->sm_path);
+		}
+
+		if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
+			die(_("Unable to find %s revision in submodule path '%s'"),
+			    remote_ref, update_data->sm_path);
+
+		free(remote_ref);
+	}
+
+	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
+		if (do_run_update_procedure(update_data, &err))
+			return 1;
+
+	if (update_data->recursive) {
+		int res;
+		struct child_process cp = CHILD_PROCESS_INIT;
+		struct update_data next = *update_data;
+		char *die_msg = xstrfmt(_("Failed to recurse into submodule path '%s'"),
+					update_data->displaypath);
+
+		if (update_data->recursive_prefix)
+			prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix,
+						update_data->sm_path);
+		else
+			prefixed_path = xstrfmt("%s/", update_data->sm_path);
+
+		next.recursive_prefix = get_submodule_displaypath(prefixed_path,
+								  update_data->prefix);
+		next.prefix = NULL;
+		oidcpy(&next.oid, null_oid());
+		oidcpy(&next.suboid, null_oid());
+
+		cp.dir = update_data->sm_path;
+		cp.git_cmd = 1;
+		prepare_submodule_repo_env(&cp.env_array);
+		update_data_to_args(&next, &cp.args);
+
+		/* die() if child process die()'d */
+		if ((res = run_command(&cp)) == 128)
+			die("%s", die_msg);
+		if (res)
+			string_list_append(&err, die_msg);
+
+		free(die_msg);
+	}
+
+	if (err.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &err)
+			fputs(item->string, stderr);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int update_submodules(struct update_data *update_data)
+{
+	int i, res = 0;
+	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+
+	update_clone_from_update_data(&suc, update_data);
+	run_processes_parallel_tr2(suc.max_jobs, update_clone_get_next_task,
+				   update_clone_start_failure,
+				   update_clone_task_finished, &suc, "submodule",
+				   "parallel/update");
+
+	/*
+	 * We saved the output and put it out all at once now.
+	 * That means:
+	 * - the listener does not have to interleave their (checkout)
+	 *   work with our fetching.  The writes involved in a
+	 *   checkout involve more straightforward sequential I/O.
+	 * - the listener can avoid doing any work if fetching failed.
+	 */
+	if (suc.quickstop) {
+		string_list_clear(&update_data->references, 0);
+		return 1;
+	}
+
+	for (i = 0; i < suc.update_clone_nr; i++) {
+		struct update_clone_data ucd = suc.update_clone[i];
+
+		oidcpy(&update_data->oid, &ucd.oid);
+		update_data->just_cloned = ucd.just_cloned;
+		update_data->sm_path = ucd.sub->path;
+
+		if (update_submodule(update_data))
+			res = 1;
+	}
+
+	string_list_clear(&update_data->references, 0);
+	return res;
+}
+
+static int module_update(int argc, const char **argv, const char *prefix)
+{
+	int init = 0, force = 0, quiet = 0, nofetch = 0;
+	int remote = 0, recursive = 0, dissociate = 0;
+	int progress = 0, require_init = 0;
+	const char *update = NULL;
+	struct pathspec pathspec;
+	struct update_data update_data = UPDATE_DATA_INIT;
+
+	struct option module_update_clone_options[] = {
+		OPT__FORCE(&force, N_("force checkout updates"), 0),
+		OPT_BOOL(0, "init", &init,
+			 N_("initialize uninitialized submodules before update")),
+		OPT_BOOL(0, "remote", &remote,
+			 N_("use SHA-1 of submodule's remote tracking branch")),
+		OPT_BOOL(0, "recursive", &recursive,
+			 N_("traverse submodules recursively")),
+		OPT_BOOL('N', "no-fetch", &nofetch,
+			 N_("don't fetch new objects from the remote site")),
+		OPT_STRING(0, "prefix", &prefix,
+			   N_("path"),
+			   N_("path into the working tree")),
+		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix,
+			   N_("path"),
+			   N_("path into the working tree, across nested "
+			      "submodule boundaries")),
+		OPT_STRING(0, "update", &update,
+			   N_("string"),
+			   N_("rebase, merge, checkout or none")),
+		OPT_STRING_LIST(0, "reference", &update_data.references, N_("repo"),
+				N_("reference repository")),
+		OPT_BOOL(0, "dissociate", &dissociate,
+			 N_("use --reference only while cloning")),
+		OPT_INTEGER(0, "depth", &update_data.depth,
+			    N_("create a shallow clone truncated to the "
+			       "specified number of revisions")),
+		OPT_INTEGER('j', "jobs", &update_data.max_jobs,
+			    N_("parallel jobs")),
+		OPT_BOOL(0, "recommend-shallow", &update_data.recommend_shallow,
+			 N_("whether the initial clone should follow the shallow recommendation")),
+		OPT__QUIET(&quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &progress,
+			 N_("force cloning progress")),
+		OPT_BOOL(0, "require-init", &require_init,
+			 N_("disallow cloning into non-empty directory")),
+		OPT_BOOL(0, "single-branch", &update_data.single_branch,
+			 N_("clone only one branch, HEAD or --branch")),
+		OPT_END()
+	};
+
+	const char *const git_submodule_helper_usage[] = {
+		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
+		NULL
+	};
+
+	update_clone_config_from_gitmodules(&update_data.max_jobs);
+	git_config(git_update_clone_config, &update_data.max_jobs);
+
+	argc = parse_options(argc, argv, prefix, module_update_clone_options,
+			     git_submodule_helper_usage, 0);
+	update_data.prefix = prefix;
+
+	update_data.force = !!force;
+	update_data.quiet = !!quiet;
+	update_data.nofetch = !!nofetch;
+	update_data.init = !!init;
+	update_data.require_init = !!require_init;
+	update_data.remote = !!remote;
+	update_data.recursive = !!recursive;
+	update_data.progress = !!progress;
+	update_data.dissociate = !!dissociate;
+	oidcpy(&update_data.oid, null_oid());
+	oidcpy(&update_data.suboid, null_oid());
+
+	if (update)
+		if (parse_submodule_update_strategy(update,
+						    &update_data.update_strategy) < 0)
+			die(_("bad value for update parameter"));
+
+	if (module_list_compute(argc, argv, prefix, &pathspec, &update_data.list) < 0)
+		return 1;
+
+	if (pathspec.nr)
+		update_data.warn_if_uninitialized = 1;
+
+	if (update_data.init) {
+		struct module_list list = MODULE_LIST_INIT;
+		struct init_cb info = INIT_CB_INIT;
+
+		if (module_list_compute(argc, argv, update_data.prefix,
+					&pathspec, &list) < 0)
+			return 1;
+
+		/*
+		 * If there are no path args and submodule.active is set then,
+		 * by default, only initialize 'active' modules.
+		 */
+		if (!argc && git_config_get_value_multi("submodule.active"))
+			module_list_active(&list);
+
+		info.prefix = update_data.prefix;
+		info.superprefix = update_data.recursive_prefix;
+		if (update_data.quiet)
+			info.flags |= OPT_QUIET;
+
+		for_each_listed_submodule(&list, init_submodule_cb, &info);
+	}
+
+	return update_submodules(&update_data);
+}
+
 struct add_data {
 	const char *prefix;
 	const char *branch;
@@ -3213,6 +3548,7 @@ static int add_clone(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
+
 #define SUPPORT_SUPER_PREFIX (1<<0)
 
 struct cmd_struct {
@@ -3226,6 +3562,7 @@ static struct cmd_struct commands[] = {
 	{"name", module_name, 0},
 	{"clone", module_clone, 0},
 	{"add-clone", add_clone, 0},
+	{"update", module_update, 0},
 	{"update-module-mode", module_update_module_mode, 0},
 	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
diff --git a/git-submodule.sh b/git-submodule.sh
index f703cddce8..5197de4551 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -369,20 +369,6 @@ cmd_deinit()
 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
 }
 
-# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
-# Because arguments are positional, use an empty string to omit <depth>
-# but include <sha1>.
-fetch_in_submodule () (
-	sanitize_submodule_env &&
-	cd "$1" &&
-	if test $# -eq 3
-	then
-		echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
-	else
-		git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
-	fi
-)
-
 #
 # Update each submodule path to correct revision, using clone and checkout as needed
 #
@@ -484,133 +470,26 @@ cmd_update()
 		shift
 	done
 
-	if test -n "$init"
-	then
-		cmd_init "--" "$@" || return
-	fi
-
-	{
-	git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
-		${progress:+"--progress"} \
+	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
+		${GIT_QUIET:+--quiet} \
+		${force:+--force} \
+		${progress:+--progress} \
+		${dissociate:+--dissociate} \
+		${remote:+--remote} \
+		${recursive:+--recursive} \
+		${init:+--init} \
+		${require_init:+--require-init} \
+		${nofetch:+--no-fetch} \
 		${wt_prefix:+--prefix "$wt_prefix"} \
 		${prefix:+--recursive-prefix "$prefix"} \
 		${update:+--update "$update"} \
 		${reference:+"$reference"} \
-		${dissociate:+"--dissociate"} \
-		${depth:+--depth "$depth"} \
-		${require_init:+--require-init} \
+		${depth:+"$depth"} \
 		$single_branch \
 		$recommend_shallow \
 		$jobs \
 		-- \
-		"$@" || echo "#unmatched" $?
-	} | {
-	err=
-	while read -r quickabort sha1 just_cloned sm_path
-	do
-		die_if_unmatched "$quickabort" "$sha1"
-
-		git submodule--helper ensure-core-worktree "$sm_path" || exit 1
-
-		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
-
-		if test $just_cloned -eq 1
-		then
-			subsha1=
-		else
-			just_cloned=
-			subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify HEAD) ||
-			die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
-		fi
-
-		if test -n "$remote"
-		then
-			branch=$(git submodule--helper remote-branch "$sm_path")
-			if test -z "$nofetch"
-			then
-				# Fetch remote before determining tracking $sha1
-				fetch_in_submodule "$sm_path" $depth ||
-				die "fatal: $(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
-			fi
-			remote_name=$(sanitize_submodule_env; cd "$sm_path" && git submodule--helper print-default-remote)
-			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify "${remote_name}/${branch}") ||
-			die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
-		fi
-
-		out=$(git submodule--helper run-update-procedure \
-			  ${wt_prefix:+--prefix "$wt_prefix"} \
-			  ${GIT_QUIET:+--quiet} \
-			  ${force:+--force} \
-			  ${just_cloned:+--just-cloned} \
-			  ${nofetch:+--no-fetch} \
-			  ${depth:+"$depth"} \
-			  ${update:+--update "$update"} \
-			  ${prefix:+--recursive-prefix "$prefix"} \
-			  ${sha1:+--oid "$sha1"} \
-			  ${subsha1:+--suboid "$subsha1"} \
-			  "--" \
-			  "$sm_path")
-
-		# exit codes for run-update-procedure:
-		# 0: update was successful, say command output
-		# 1: update procedure failed, but should not die
-		# 2 or 128: subcommand died during execution
-		# 3: no update procedure was run
-		res="$?"
-		case $res in
-		0)
-			say "$out"
-			;;
-		1)
-			err="${err};fatal: $out"
-			continue
-			;;
-		2|128)
-			die_with_status $res "fatal: $out"
-			;;
-		esac
-
-		if test -n "$recursive"
-		then
-			(
-				prefix=$(git submodule--helper relative-path "$prefix$sm_path/" "$wt_prefix")
-				wt_prefix=
-				sanitize_submodule_env
-				cd "$sm_path" &&
-				eval cmd_update
-			)
-			res=$?
-			if test $res -gt 0
-			then
-				die_msg="fatal: $(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
-				if test $res -ne 2
-				then
-					err="${err};$die_msg"
-					continue
-				else
-					die_with_status $res "$die_msg"
-				fi
-			fi
-		fi
-	done
-
-	if test -n "$err"
-	then
-		OIFS=$IFS
-		IFS=';'
-		for e in $err
-		do
-			if test -n "$e"
-			then
-				echo >&2 "$e"
-			fi
-		done
-		IFS=$OIFS
-		exit 1
-	fi
-	}
+		"$@"
 }
 
 #
-- 
2.32.0
Junio C Hamano· Sep 20, 2021, 17:13 UTC · re: Atharva Raykar · lore

Re: [PATCH v2 5/8] submodule: move core cmd_update() logic to C

Atharva Raykar <raykar.ath@gmail.com> writes:
Show 43 quoted lines
> This patch completes the conversion past the flag parsing of
> `submodule update` by introducing a helper subcommand called
> `submodule--helper update`. The behaviour of `submodule update` should
> remain the same after this patch.
>
> We add more fields to the `struct update_data` that are required by
> `struct submodule_update_clone` to be able to perform a clone, when that
> is needed to be done.
>
> Recursing on a submodule is done by calling a subprocess that launches
> `submodule--helper update`, with a modified `--recursive-prefix` and
> `--prefix` parameter.
>
> We also introduce `update_submodules()` and `update_submodule()` which
> are quite similar to `update_clone_submodules()` and
> `update_clone_submodule()`, and will supersede them.
>
> When the `--init` flag is passed to the subcommand, we do not spawn a
> new subprocess and call `submodule--helper init` on the submodule paths,
> because the Git machinery is not able to pick up the configuration
> changes introduced by that init call[1]. So we instead run the
> `init_submodule_cb()` callback over each submodule directly.
>
> This introduces another problem, because there is no mechanism to pass
> the superproject path prefix (ie, `--super-prefix`) without starting a
> new git process. This field is required for obtaining the display path
> for that is used by the command's output messages. So let's add a field
> into the `init_cb` struct that lets us pass this information to
> `init_submodule()`, which will now also take an explicit 'superprefix'
> argument.
>
> While we are at it, we also remove the fetch_in_submodule() shell
> function since it is no longer used anywhere.
>
> [1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> ---
>  builtin/submodule--helper.c | 505 ++++++++++++++++++++++++++++++------
>  git-submodule.sh            | 145 +----------
>  2 files changed, 433 insertions(+), 217 deletions(-)

It seems that this step does too many things in one step and makes it harder than necessary for readers to understand what is going on.

For example, as far as I can see, the change to optionally allow passing superprefix to init_submodule() can be made without anything else in this step (i.e. "we allow superprefix from the parameter to override what get_super_prefix() would give us, but at this step, everybody passes NULL and the behaviour is unchanged" would be one such step), later to be followed by a change that involves passing a non-NULL value there, and it would become a lot easier to see who passes a non-NULL super prefix, what value is passed and for what purpose, with such an organization.

A 650+ lines single patch like this buries the details and forces the readers to disect them out. The change that teaches run_update_command() to instead use capture_command() and gives the error message to its callers may be another example of what may be better done as an independent step before tying the whole thing together.

It is larger than what I comfortably can spend my time on while tending patches from other topics, so I'll skip reviewing this step for now.

Thanks.
Atharva Raykar· Oct 3, 2021, 10:38 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 5/8] submodule: move core cmd_update() logic to C

Junio C Hamano <gitster@pobox.com> writes:
Show 26 quoted lines
>
> It seems that this step does too many things in one step and makes
> it harder than necessary for readers to understand what is going on.
>
> For example, as far as I can see, the change to optionally allow
> passing superprefix to init_submodule() can be made without anything
> else in this step (i.e. "we allow superprefix from the parameter to
> override what get_super_prefix() would give us, but at this step,
> everybody passes NULL and the behaviour is unchanged" would be one
> such step), later to be followed by a change that involves passing a
> non-NULL value there, and it would become a lot easier to see who
> passes a non-NULL super prefix, what value is passed and for what
> purpose, with such an organization.
>
> A 650+ lines single patch like this buries the details and forces
> the readers to disect them out.  The change that teaches
> run_update_command() to instead use capture_command() and gives the
> error message to its callers may be another example of what may be
> better done as an independent step before tying the whole thing
> together.
>
> It is larger than what I comfortably can spend my time on while
> tending patches from other topics, so I'll skip reviewing this step
> for now.
>
> Thanks.

Understood. Apologies for the lack of updates on this topic, I will work on organising this patch (and this series) better. I've taken a note of the conflicts with other topics as well.

I don't mind the other topics being prioritized over this one. At the moment I am not too confident about being able to quickly iterate over this series. I'll try my best anyway.

Junio C Hamano· Sep 20, 2021, 19:58 UTC · re: Atharva Raykar · lore

Re: [PATCH v2 5/8] submodule: move core cmd_update() logic to C

Atharva Raykar <raykar.ath@gmail.com> writes:
Show 9 quoted lines
> This patch completes the conversion past the flag parsing of
> `submodule update` by introducing a helper subcommand called
> `submodule--helper update`. The behaviour of `submodule update` should
> remain the same after this patch.
> ...
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> ---

This seems to overlap with Emily's 242655f2 (submodule: record superproject gitdir during 'update', 2021-08-19) that came in <20210819200953.2105230-5-emilyshaffer@google.com>.

Should I expect there probably needs a few more iterations for this topic? Depending, the "a submodule knows which superproject it is part of" topic may have to be done in C on top of this topic.

I'll eject the 'es/superproject-aware-submodules' topic for now, just to get today's integration going.

Thanks.
Junio C Hamano· Sep 20, 2021, 21:28 UTC · re: Atharva Raykar · lore

Re: [PATCH v2 5/8] submodule: move core cmd_update() logic to C

Atharva Raykar <raykar.ath@gmail.com> writes:
Show 11 quoted lines
> +static int update_submodules(struct update_data *update_data)
> +{
> +	int i, res = 0;
> +	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
> +
> +	update_clone_from_update_data(&suc, update_data);
> +	run_processes_parallel_tr2(suc.max_jobs, update_clone_get_next_task,
> +				   update_clone_start_failure,
> +				   update_clone_task_finished, &suc, "submodule",
> +				   "parallel/update");
> + ...

As ab/config-based-hooks-base topic from Ævar changes the way this helper function gets called at 73367f2f (run-command: add stdin callback for parallelization, 2021-09-02) and then again in 2aba2f5f (run-command: allow capturing of collated output, 2021-09-02), this part needs to be adjusted when the topics collide in 'seen'.

I _think_ I've resolved conflict correctly, but please double-check the result when today's integration result is pushed out later, both of you.

Thanks.
Atharva Raykar· Sep 16, 2021, 10:32 UTC · re: Atharva Raykar · lore

[PATCH v2 6/8] submodule--helper: remove update-clone subcommand

We no longer need this subcommand as the shell version calls the 'update' subcommand instead, which does all the cloning within C itself.

We also no longer need the 'update_clone_submodules()' and 'update_clone_submodule()' functions, so we remove those as well.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 98 -------------------------------------
 1 file changed, 98 deletions(-)
Show changes to builtin/submodule--helper.c +0 −98
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 1eea626864..c1ce3981ad 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2552,103 +2552,6 @@ static int do_run_update_procedure(struct update_data *ud, struct string_list *e
 	return run_update_command(ud, subforce, err);
 }
 
-static void update_clone_submodule(struct update_clone_data *ucd)
-{
-	fprintf(stdout, "dummy %s %d\t%s\n",
-		oid_to_hex(&ucd->oid),
-		ucd->just_cloned,
-		ucd->sub->path);
-}
-
-static int update_clone_submodules(struct submodule_update_clone *suc)
-{
-	int i;
-
-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
-				   update_clone_start_failure,
-				   update_clone_task_finished, suc, "submodule",
-				   "parallel/update");
-
-	/*
-	 * We saved the output and put it out all at once now.
-	 * That means:
-	 * - the listener does not have to interleave their (checkout)
-	 *   work with our fetching.  The writes involved in a
-	 *   checkout involve more straightforward sequential I/O.
-	 * - the listener can avoid doing any work if fetching failed.
-	 */
-	if (suc->quickstop)
-		return 1;
-
-	for (i = 0; i < suc->update_clone_nr; i++)
-		update_clone_submodule(&suc->update_clone[i]);
-
-	return 0;
-}
-
-static int update_clone(int argc, const char **argv, const char *prefix)
-{
-	const char *update = NULL;
-	struct pathspec pathspec;
-	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
-
-	struct option module_update_clone_options[] = {
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
-			   N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
-			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &suc.dissociate,
-			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &suc.depth, "<depth>",
-			   N_("create a shallow clone truncated to the "
-			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &suc.max_jobs,
-			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
-			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &suc.progress,
-			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &suc.require_init,
-			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &suc.single_branch,
-			 N_("clone only one branch, HEAD or --branch")),
-		OPT_END()
-	};
-
-	const char *const git_submodule_helper_usage[] = {
-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
-		NULL
-	};
-	suc.prefix = prefix;
-
-	update_clone_config_from_gitmodules(&suc.max_jobs);
-	git_config(git_update_clone_config, &suc.max_jobs);
-
-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
-			     git_submodule_helper_usage, 0);
-
-	if (update)
-		if (parse_submodule_update_strategy(update, &suc.update) < 0)
-			die(_("bad value for update parameter"));
-
-	if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
-		return 1;
-
-	if (pathspec.nr)
-		suc.warn_if_uninitialized = 1;
-
-	return update_clone_submodules(&suc);
-}
-
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
@@ -3564,7 +3467,6 @@ static struct cmd_struct commands[] = {
 	{"add-clone", add_clone, 0},
 	{"update", module_update, 0},
 	{"update-module-mode", module_update_module_mode, 0},
-	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
 	{"ensure-core-worktree", ensure_core_worktree, 0},
 	{"relative-path", resolve_relative_path, 0},
-- 
2.32.0
Atharva Raykar· Sep 16, 2021, 10:32 UTC · re: Atharva Raykar · lore

[PATCH v2 7/8] submodule--helper: remove unused helpers

These helpers were useful back when 'submodule update' had most of its logic in shell. Now that they will never be invoked, let us remove them.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 135 ------------------------------------
 1 file changed, 135 deletions(-)
Show changes to builtin/submodule--helper.c +0 −135
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c1ce3981ad..0910af9c71 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -73,21 +73,6 @@ static char *get_default_remote(void)
 	return repo_get_default_remote(the_repository, refname);
 }
 
-static int print_default_remote(int argc, const char **argv, const char *prefix)
-{
-	char *remote;
-
-	if (argc != 1)
-		die(_("submodule--helper print-default-remote takes no arguments"));
-
-	remote = get_default_remote();
-	if (remote)
-		printf("%s\n", remote);
-
-	free(remote);
-	return 0;
-}
-
 static int starts_with_dot_slash(const char *str)
 {
 	return str[0] == '.' && is_dir_sep(str[1]);
@@ -1993,29 +1978,6 @@ static void determine_submodule_update_strategy(struct repository *r,
 	free(key);
 }
 
-static int module_update_module_mode(int argc, const char **argv, const char *prefix)
-{
-	const char *path, *update = NULL;
-	int just_cloned;
-	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
-
-	if (argc < 3 || argc > 4)
-		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
-
-	just_cloned = git_config_int("just_cloned", argv[1]);
-	path = argv[2];
-
-	if (argc == 4)
-		update = argv[3];
-
-	determine_submodule_update_strategy(the_repository,
-					    just_cloned, path, update,
-					    &update_strategy);
-	fputs(submodule_strategy_to_string(&update_strategy), stdout);
-
-	return 0;
-}
-
 struct update_clone_data {
 	const struct submodule *sub;
 	struct object_id oid;
@@ -2552,85 +2514,6 @@ static int do_run_update_procedure(struct update_data *ud, struct string_list *e
 	return run_update_command(ud, subforce, err);
 }
 
-static int run_update_procedure(int argc, const char **argv, const char *prefix)
-{
-	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
-	char *prefixed_path, *update = NULL;
-	struct update_data update_data = UPDATE_DATA_INIT;
-	struct string_list err = STRING_LIST_INIT_DUP;
-
-	struct option options[] = {
-		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&force, N_("force checkout updates"), 0),
-		OPT_BOOL('N', "no-fetch", &nofetch,
-			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &just_cloned,
-			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
-			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
-			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_END()
-	};
-
-	const char *const usage[] = {
-		N_("git submodule--helper run-update-procedure [<options>] <path>"),
-		NULL
-	};
-
-	argc = parse_options(argc, argv, prefix, options, usage, 0);
-
-	if (argc != 1)
-		usage_with_options(usage, options);
-
-	update_data.force = !!force;
-	update_data.quiet = !!quiet;
-	update_data.nofetch = !!nofetch;
-	update_data.just_cloned = !!just_cloned;
-	update_data.sm_path = argv[0];
-
-	if (update_data.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
-	else
-		prefixed_path = xstrdup(update_data.sm_path);
-
-	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
-
-	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
-					    update_data.sm_path, update,
-					    &update_data.update_strategy);
-
-	free(prefixed_path);
-
-	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data, &err);
-
-	return 3;
-}
-
-static int resolve_relative_path(int argc, const char **argv, const char *prefix)
-{
-	struct strbuf sb = STRBUF_INIT;
-	if (argc != 3)
-		die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
-
-	printf("%s", relative_path(argv[1], argv[2], &sb));
-	strbuf_release(&sb);
-	return 0;
-}
-
 static const char *remote_submodule_branch(const char *path)
 {
 	const struct submodule *sub;
@@ -2794,19 +2677,6 @@ static void do_ensure_core_worktree(const char *path)
 	}
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
-{
-	const char *path;
-
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-	do_ensure_core_worktree(path);
-
-	return 0;
-}
-
 static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
 {
 	int i;
@@ -3466,16 +3336,11 @@ static struct cmd_struct commands[] = {
 	{"clone", module_clone, 0},
 	{"add-clone", add_clone, 0},
 	{"update", module_update, 0},
-	{"update-module-mode", module_update_module_mode, 0},
-	{"run-update-procedure", run_update_procedure, 0},
-	{"ensure-core-worktree", ensure_core_worktree, 0},
-	{"relative-path", resolve_relative_path, 0},
 	{"resolve-relative-url", resolve_relative_url, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
 	{"init", module_init, SUPPORT_SUPER_PREFIX},
 	{"status", module_status, SUPPORT_SUPER_PREFIX},
-	{"print-default-remote", print_default_remote, 0},
 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
 	{"deinit", module_deinit, 0},
 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
-- 
2.32.0
Junio C Hamano· Sep 20, 2021, 17:19 UTC · re: Atharva Raykar · lore

Re: [PATCH v2 7/8] submodule--helper: remove unused helpers

Atharva Raykar <raykar.ath@gmail.com> writes:
Show 9 quoted lines
> These helpers were useful back when 'submodule update' had most of its
> logic in shell. Now that they will never be invoked, let us remove them.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> ---
>  builtin/submodule--helper.c | 135 ------------------------------------
>  1 file changed, 135 deletions(-)

Makes sense. I briefly hoped that print-default-remote could go after 2/8 but there is another user that is in the cmd_update() that is not removed until 5/8 and removing them in a separate step after we are all done like this patch does makes sense. I am not sure where and how the line to separate the two "remove" patches is drawn, though. Is there a reason why they need to be two patches?

Atharva Raykar· Sep 16, 2021, 10:32 UTC · re: Atharva Raykar · lore

[PATCH v2 8/8] submodule--helper: rename helper functions

These two functions were prefixed with 'do' before the shell-to-C conversion because they were utility functions meant to be called by their non-prefixed counterpart.

Since those callers don't exist anymore, and these functions can now be used directly, let's rename them to signal this fact.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +4 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 0910af9c71..e5e33be6ea 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2484,7 +2484,7 @@ static int run_update_command(struct update_data *ud, int subforce, struct strin
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
+static int run_update_procedure(struct update_data *ud, struct string_list *err)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2646,7 +2646,7 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static void do_ensure_core_worktree(const char *path)
+static void ensure_core_worktree(const char *path)
 {
 	const struct submodule *sub;
 	const char *cw;
@@ -2910,7 +2910,7 @@ static int update_submodule(struct update_data *update_data)
 	char *prefixed_path;
 	struct string_list err = STRING_LIST_INIT_DUP;
 
-	do_ensure_core_worktree(update_data->sm_path);
+	ensure_core_worktree(update_data->sm_path);
 
 	if (update_data->recursive_prefix)
 		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
@@ -2950,7 +2950,7 @@ static int update_submodule(struct update_data *update_data)
 	}
 
 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
-		if (do_run_update_procedure(update_data, &err))
+		if (run_update_procedure(update_data, &err))
 			return 1;
 
 	if (update_data->recursive) {
-- 
2.32.0
Junio C Hamano· Sep 20, 2021, 17:19 UTC · re: Atharva Raykar · lore

Re: [PATCH v2 8/8] submodule--helper: rename helper functions

Atharva Raykar <raykar.ath@gmail.com> writes:
Show 13 quoted lines
> These two functions were prefixed with 'do' before the shell-to-C
> conversion because they were utility functions meant to be called by
> their non-prefixed counterpart.
>
> Since those callers don't exist anymore, and these functions can now be
> used directly, let's rename them to signal this fact.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> ---
>  builtin/submodule--helper.c | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
Nice.
Atharva Raykar· Oct 13, 2021, 05:17 UTC · re: Atharva Raykar · lore

[PATCH v3 0/9] submodule: convert the rest of 'update' to C

Since v2:
 - Rebased this series on top of a more up-to-date master branch.
 - Reorganized this series to (hopefully) make it easier to review, as per
   Junio's suggestions. I split up the patch that converts the rest of the
   update shell script to C into multiple steps. I also squashed the patches
   that removed unused helpers, as there was not clear criteria for it being two
   patches.
 - Plugged a memory leak while refactoring sync_submodule() to use my new
   helper.

Note on conflicting series: --------------------------

This series currently conflicts with the work being done by Emily in 'es/superproject-aware-submodules' [1], mainly in the part where they cache the superproject gitdir in the shell script that gets removed during conversion in this series.

I have attempted to make a version of this series that is based on that topic [2], and added the superproject gitdir caching code in C [3]. It passes the tests, but I am not too confident about its correctness. I hope that branch can be helpful in some way.

[1] https://lore.kernel.org/git/20210819200953.2105230-1-emilyshaffer@google.com/
[2] https://github.com/tfidfwastaken/git/commits/submodule-update-on-es-superproject-aware
    (fetch-it-via: git fetch https://github.com/tfidfwastaken/git submodule-update-on-es-superproject-aware)
[3] https://github.com/tfidfwastaken/git/blob/a74aaf2540c536970f2541d3042c825f82a69770/builtin/submodule--helper.c#L2922-L2930

Fetch-it-Via: git fetch https://github.com/tfidfwastaken/git submodule-update-list-3

Atharva Raykar (9):
  submodule--helper: split up ensure_core_worktree()
  submodule--helper: get remote names from any repository
  submodule--helper: rename helpers for update-clone
  submodule--helper: refactor get_submodule_displaypath()
  submodule--helper: allow setting superprefix for init_submodule()
  submodule--helper: run update using child process struct
  submodule: move core cmd_update() logic to C
  submodule--helper: remove unused helpers
  submodule--helper: rename helper functions
 builtin/submodule--helper.c | 771 +++++++++++++++++++++---------------
 git-submodule.sh            | 145 +------
 2 files changed, 458 insertions(+), 458 deletions(-)
Range diff against the previous version:
 1:  f83a5b7f34 !  1:  d057ba04bd submodule--helper: split up ensure_core_worktree()
    @@ builtin/submodule--helper.c: static int push_check(int argc, const char **argv,
     -static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
     +static void do_ensure_core_worktree(const char *path)
      {
    - 	const struct submodule *sub;
     -	const char *path;
      	const char *cw;
      	struct repository subrepo;
    @@ builtin/submodule--helper.c: static int push_check(int argc, const char **argv,
     -
     -	path = argv[1];
     -
    - 	sub = submodule_from_path(the_repository, null_oid(), path);
    - 	if (!sub)
    - 		BUG("We could get the submodule handle before?");
    + 	if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
    + 		die(_("could not get a repository handle for submodule '%s'"), path);
    +
     @@ builtin/submodule--helper.c: static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
      		free(abs_path);
      		strbuf_release(&sb);
 2:  7f4e24ce25 !  2:  895cc10b97 submodule--helper: get remote names from any repository
    @@ builtin/submodule--helper.c: static char *get_default_remote(void)
     +static char *get_default_remote_submodule(const char *module_path)
     +{
     +	const char *refname;
    -+	const struct submodule *sub;
     +	struct repository subrepo;
     +
     +	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
     +					  "HEAD", 0, NULL, NULL);
    -+	sub = submodule_from_path(the_repository, null_oid(), module_path);
    -+	repo_submodule_init(&subrepo, the_repository, sub);
    ++	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
     +	return repo_get_default_remote(&subrepo, refname);
     +}
     +
    @@ builtin/submodule--helper.c: static char *get_default_remote(void)
      {
      	char *remote;
     @@ builtin/submodule--helper.c: static void sync_submodule(const char *path, const char *prefix,
    + {
    + 	const struct submodule *sub;
      	char *remote_key = NULL;
    - 	char *sub_origin_url, *super_config_url, *displaypath;
    +-	char *sub_origin_url, *super_config_url, *displaypath;
    ++	char *sub_origin_url, *super_config_url, *displaypath, *default_remote;
      	struct strbuf sb = STRBUF_INIT;
     -	struct child_process cp = CHILD_PROCESS_INIT;
      	char *sub_config_path = NULL;
    @@ builtin/submodule--helper.c: static void sync_submodule(const char *path, const
     -
      	strbuf_reset(&sb);
     -	if (capture_command(&cp, &sb, 0))
    -+	strbuf_addstr(&sb, get_default_remote_submodule(path));
    -+	if (!sb.buf)
    ++	default_remote = get_default_remote_submodule(path);
    ++	if (!default_remote)
      		die(_("failed to get the default remote for submodule '%s'"),
      		      path);
    +-	strbuf_strip_suffix(&sb, "\n");
    +-	remote_key = xstrfmt("remote.%s.url", sb.buf);
    ++	remote_key = xstrfmt("remote.%s.url", default_remote);
    ++	free(default_remote);
    +
    +-	strbuf_reset(&sb);
    + 	submodule_to_gitdir(&sb, path);
    + 	strbuf_addstr(&sb, "/config");
    +
 3:  390596ee7c =  3:  1defe3e0ca submodule--helper: rename helpers for update-clone
 4:  2a2ddcac91 =  4:  693b23772a submodule--helper: refactor get_submodule_displaypath()
 -:  ---------- >  5:  d1fd902a2a submodule--helper: allow setting superprefix for init_submodule()
 -:  ---------- >  6:  19c6c4e81a submodule--helper: run update using child process struct
 5:  832020a290 !  7:  7cc32ad6f9 submodule: move core cmd_update() logic to C
    @@ Commit message
         new subprocess and call `submodule--helper init` on the submodule paths,
         because the Git machinery is not able to pick up the configuration
         changes introduced by that init call[1]. So we instead run the
    -    `init_submodule_cb()` callback over each submodule directly.
    -
    -    This introduces another problem, because there is no mechanism to pass
    -    the superproject path prefix (ie, `--super-prefix`) without starting a
    -    new git process. This field is required for obtaining the display path
    -    for that is used by the command's output messages. So let's add a field
    -    into the `init_cb` struct that lets us pass this information to
    -    `init_submodule()`, which will now also take an explicit 'superprefix'
    -    argument.
    +    `init_submodule_cb()` callback over each submodule in the same process.
         While we are at it, we also remove the fetch_in_submodule() shell
         function since it is no longer used anywhere.
    @@ Commit message
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
      ## builtin/submodule--helper.c ##
    -@@ builtin/submodule--helper.c: static char *compute_submodule_clone_url(const char *rel_url)
    -
    - struct init_cb {
    - 	const char *prefix;
    -+	const char *superprefix;
    - 	unsigned int flags;
    - };
    --#define INIT_CB_INIT { NULL, 0 }
    -+#define INIT_CB_INIT { 0 }
    -
    - static void init_submodule(const char *path, const char *prefix,
    --			   unsigned int flags)
    -+			   const char *superprefix, unsigned int flags)
    - {
    - 	const struct submodule *sub;
    - 	struct strbuf sb = STRBUF_INIT;
    - 	char *upd = NULL, *url = NULL, *displaypath;
    -
    --	displaypath = get_submodule_displaypath(path, prefix);
    -+	/* try superprefix from the environment, if it is not passed explicitly */
    -+	if (!superprefix)
    -+		superprefix = get_super_prefix();
    -+	displaypath = do_get_submodule_displaypath(path, prefix, superprefix);
    -
    - 	sub = submodule_from_path(the_repository, null_oid(), path);
    -
    -@@ builtin/submodule--helper.c: static void init_submodule(const char *path, const char *prefix,
    - static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
    - {
    - 	struct init_cb *info = cb_data;
    --	init_submodule(list_item->name, info->prefix, info->flags);
    -+	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
    - }
    -
    - static int module_init(int argc, const char **argv, const char *prefix)
     @@ builtin/submodule--helper.c: struct submodule_update_clone {
      	const char *prefix;
      	int single_branch;
    @@ builtin/submodule--helper.c: static int fetch_in_submodule(const char *module_pa
     -static int run_update_command(struct update_data *ud, int subforce)
     +static int run_update_command(struct update_data *ud, int subforce, struct string_list *err)
      {
    --	struct strvec args = STRVEC_INIT;
    --	struct strvec child_env = STRVEC_INIT;
    -+	struct child_process cp = CHILD_PROCESS_INIT;
    + 	struct child_process cp = CHILD_PROCESS_INIT;
      	char *oid = oid_to_hex(&ud->oid);
     +	struct strbuf out = STRBUF_INIT;
      	int must_die_on_failure = 0;
    --	int git_cmd;
     +	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
    -+
    +
    +-	switch (ud->update_strategy.type) {
     +	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
     +		determine_submodule_update_strategy(the_repository, ud->just_cloned,
     +						    ud->sm_path, NULL, &strategy);
     +	else
     +		strategy = ud->update_strategy;
    -
    --	switch (ud->update_strategy.type) {
    -+	cp.dir = xstrdup(ud->sm_path);
    ++
     +	switch (strategy.type) {
      	case SM_UPDATE_CHECKOUT:
    --		git_cmd = 1;
    --		strvec_pushl(&args, "checkout", "-q", NULL);
    -+		cp.git_cmd = 1;
    -+		strvec_pushl(&cp.args, "checkout", "-q", NULL);
    - 		if (subforce)
    --			strvec_push(&args, "-f");
    -+			strvec_push(&cp.args, "-f");
    - 		break;
    - 	case SM_UPDATE_REBASE:
    --		git_cmd = 1;
    --		strvec_push(&args, "rebase");
    -+		cp.git_cmd = 1;
    -+		strvec_push(&cp.args, "rebase");
    - 		if (ud->quiet)
    --			strvec_push(&args, "--quiet");
    -+			strvec_push(&cp.args, "--quiet");
    - 		must_die_on_failure = 1;
    - 		break;
    - 	case SM_UPDATE_MERGE:
    --		git_cmd = 1;
    --		strvec_push(&args, "merge");
    -+		cp.git_cmd = 1;
    -+		strvec_push(&cp.args, "merge");
    - 		if (ud->quiet)
    --			strvec_push(&args, "--quiet");
    -+			strvec_push(&cp.args, "--quiet");
    - 		must_die_on_failure = 1;
    + 		cp.git_cmd = 1;
    + 		strvec_pushl(&cp.args, "checkout", "-q", NULL);
    +@@ builtin/submodule--helper.c: static int run_update_command(struct update_data *ud, int subforce)
      		break;
      	case SM_UPDATE_COMMAND:
    --		git_cmd = 0;
    --		strvec_push(&args, ud->update_strategy.command);
    -+		cp.git_cmd = 0;
    -+		cp.use_shell = 1;
    + 		cp.use_shell = 1;
    +-		strvec_push(&cp.args, ud->update_strategy.command);
     +		strvec_push(&cp.args, strategy.command);
      		must_die_on_failure = 1;
      		break;
    @@ builtin/submodule--helper.c: static int fetch_in_submodule(const char *module_pa
     -		    submodule_strategy_to_string(&ud->update_strategy));
     +		    submodule_strategy_to_string(&strategy));
      	}
    --	strvec_push(&args, oid);
    -+	strvec_push(&cp.args, oid);
    + 	strvec_push(&cp.args, oid);
    --	prepare_submodule_repo_env(&child_env);
    --	if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
    --				     ud->sm_path, child_env.v)) {
    + 	cp.dir = xstrdup(ud->sm_path);
    + 	prepare_submodule_repo_env(&cp.env_array);
    +-	if (run_command(&cp)) {
     -		switch (ud->update_strategy.type) {
    -+	prepare_submodule_repo_env(&cp.env_array);
     +	if (capture_command(&cp, &out, 0)) {
     +		if (must_die_on_failure) {
     +			switch (strategy.type) {
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
      struct add_data {
      	const char *prefix;
      	const char *branch;
    -@@ builtin/submodule--helper.c: static int add_clone(int argc, const char **argv, const char *prefix)
    +@@ builtin/submodule--helper.c: static int module_add(int argc, const char **argv, const char *prefix)
      	return 0;
      }
    @@ builtin/submodule--helper.c: static int add_clone(int argc, const char **argv, c
     @@ builtin/submodule--helper.c: static struct cmd_struct commands[] = {
      	{"name", module_name, 0},
      	{"clone", module_clone, 0},
    - 	{"add-clone", add_clone, 0},
    + 	{"add", module_add, SUPPORT_SUPER_PREFIX},
     +	{"update", module_update, 0},
      	{"update-module-mode", module_update_module_mode, 0},
      	{"update-clone", update_clone, 0},
 6:  fb3fa8174a <  -:  ---------- submodule--helper: remove update-clone subcommand
 7:  364f72f870 !  8:  486f9d0a0e submodule--helper: remove unused helpers
    @@ Commit message
         These helpers were useful back when 'submodule update' had most of its
         logic in shell. Now that they will never be invoked, let us remove them.
    +    We also no longer need the 'update_clone_submodules()' and
    +    'update_clone_submodule()' functions, so we remove those as well.
    +
         Mentored-by: Christian Couder <christian.couder@gmail.com>
         Mentored-by: Shourya Shukla <periperidip@gmail.com>
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
      	return run_update_command(ud, subforce, err);
      }
    +-static void update_clone_submodule(struct update_clone_data *ucd)
    +-{
    +-	fprintf(stdout, "dummy %s %d\t%s\n",
    +-		oid_to_hex(&ucd->oid),
    +-		ucd->just_cloned,
    +-		ucd->sub->path);
    +-}
    +-
    +-static int update_clone_submodules(struct submodule_update_clone *suc)
    +-{
    +-	int i;
    +-
    +-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
    +-				   update_clone_start_failure,
    +-				   update_clone_task_finished, suc, "submodule",
    +-				   "parallel/update");
    +-
    +-	/*
    +-	 * We saved the output and put it out all at once now.
    +-	 * That means:
    +-	 * - the listener does not have to interleave their (checkout)
    +-	 *   work with our fetching.  The writes involved in a
    +-	 *   checkout involve more straightforward sequential I/O.
    +-	 * - the listener can avoid doing any work if fetching failed.
    +-	 */
    +-	if (suc->quickstop)
    +-		return 1;
    +-
    +-	for (i = 0; i < suc->update_clone_nr; i++)
    +-		update_clone_submodule(&suc->update_clone[i]);
    +-
    +-	return 0;
    +-}
    +-
    +-static int update_clone(int argc, const char **argv, const char *prefix)
    +-{
    +-	const char *update = NULL;
    +-	struct pathspec pathspec;
    +-	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
    +-
    +-	struct option module_update_clone_options[] = {
    +-		OPT_STRING(0, "prefix", &prefix,
    +-			   N_("path"),
    +-			   N_("path into the working tree")),
    +-		OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
    +-			   N_("path"),
    +-			   N_("path into the working tree, across nested "
    +-			      "submodule boundaries")),
    +-		OPT_STRING(0, "update", &update,
    +-			   N_("string"),
    +-			   N_("rebase, merge, checkout or none")),
    +-		OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
    +-			   N_("reference repository")),
    +-		OPT_BOOL(0, "dissociate", &suc.dissociate,
    +-			   N_("use --reference only while cloning")),
    +-		OPT_STRING(0, "depth", &suc.depth, "<depth>",
    +-			   N_("create a shallow clone truncated to the "
    +-			      "specified number of revisions")),
    +-		OPT_INTEGER('j', "jobs", &suc.max_jobs,
    +-			    N_("parallel jobs")),
    +-		OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
    +-			    N_("whether the initial clone should follow the shallow recommendation")),
    +-		OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
    +-		OPT_BOOL(0, "progress", &suc.progress,
    +-			    N_("force cloning progress")),
    +-		OPT_BOOL(0, "require-init", &suc.require_init,
    +-			   N_("disallow cloning into non-empty directory")),
    +-		OPT_BOOL(0, "single-branch", &suc.single_branch,
    +-			 N_("clone only one branch, HEAD or --branch")),
    +-		OPT_END()
    +-	};
    +-
    +-	const char *const git_submodule_helper_usage[] = {
    +-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
    +-		NULL
    +-	};
    +-	suc.prefix = prefix;
    +-
    +-	update_clone_config_from_gitmodules(&suc.max_jobs);
    +-	git_config(git_update_clone_config, &suc.max_jobs);
    +-
    +-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
    +-			     git_submodule_helper_usage, 0);
    +-
    +-	if (update)
    +-		if (parse_submodule_update_strategy(update, &suc.update) < 0)
    +-			die(_("bad value for update parameter"));
    +-
    +-	if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
    +-		return 1;
    +-
    +-	if (pathspec.nr)
    +-		suc.warn_if_uninitialized = 1;
    +-
    +-	return update_clone_submodules(&suc);
    +-}
    +-
     -static int run_update_procedure(int argc, const char **argv, const char *prefix)
     -{
     -	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
    @@ builtin/submodule--helper.c: static void do_ensure_core_worktree(const char *pat
      	int i;
     @@ builtin/submodule--helper.c: static struct cmd_struct commands[] = {
      	{"clone", module_clone, 0},
    - 	{"add-clone", add_clone, 0},
    + 	{"add", module_add, SUPPORT_SUPER_PREFIX},
      	{"update", module_update, 0},
     -	{"update-module-mode", module_update_module_mode, 0},
    +-	{"update-clone", update_clone, 0},
     -	{"run-update-procedure", run_update_procedure, 0},
     -	{"ensure-core-worktree", ensure_core_worktree, 0},
     -	{"relative-path", resolve_relative_path, 0},
    - 	{"resolve-relative-url", resolve_relative_url, 0},
      	{"resolve-relative-url-test", resolve_relative_url_test, 0},
      	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
      	{"init", module_init, SUPPORT_SUPER_PREFIX},
 8:  ca48dd452c !  9:  7b5b2be1a0 submodule--helper: rename helper functions
    @@ builtin/submodule--helper.c: static int push_check(int argc, const char **argv,
     -static void do_ensure_core_worktree(const char *path)
     +static void ensure_core_worktree(const char *path)
      {
    - 	const struct submodule *sub;
      	const char *cw;
    + 	struct repository subrepo;
     @@ builtin/submodule--helper.c: static int update_submodule(struct update_data *update_data)
      	char *prefixed_path;
      	struct string_list err = STRING_LIST_INIT_DUP;
-- 
2.32.0
Atharva Raykar· Oct 13, 2021, 05:17 UTC · re: Atharva Raykar · lore

[PATCH v3 1/9] submodule--helper: split up ensure_core_worktree()

Let's split up `ensure_core_worktree()` so that we can call it from C code without needing to deal with command line arguments.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)
Show changes to builtin/submodule--helper.c +12 −7
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 88ce6be69c..764fcb7dba 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2764,17 +2764,11 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+static void do_ensure_core_worktree(const char *path)
 {
-	const char *path;
 	const char *cw;
 	struct repository subrepo;
 
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-
 	if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
 		die(_("could not get a repository handle for submodule '%s'"), path);
 
@@ -2794,6 +2788,17 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
 		free(abs_path);
 		strbuf_release(&sb);
 	}
+}
+
+static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+{
+	const char *path;
+
+	if (argc != 2)
+		BUG("submodule--helper ensure-core-worktree <path>");
+
+	path = argv[1];
+	do_ensure_core_worktree(path);
 
 	return 0;
 }
-- 
2.32.0
Atharva Raykar· Oct 13, 2021, 05:17 UTC · re: Atharva Raykar · lore

[PATCH v3 2/9] submodule--helper: get remote names from any repository

`get_default_remote()` retrieves the name of a remote by resolving the refs from of the current repository's ref store.

Thus in order to use it for retrieving the remote name of a submodule, we have to start a new subprocess which runs from the submodule directory.

Let's instead introduce a function called `repo_get_default_remote()` which takes any repository object and retrieves the remote accordingly.

`get_default_remote()` is then defined as a call to `repo_get_default_remote()` with 'the_repository' passed to it.

Now that we have `repo_get_default_remote()`, we no longer have to start a subprocess that called `submodule--helper get-default-remote` from within the submodule directory.

So let's make a function called `get_default_remote_submodule()` which takes a submodule path, and returns the default remote for that submodule, all within the same process.

We can now use this function to save an unnecessary subprocess spawn in `sync_submodule()`, and also in the next patch, which will require this functionality.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 39 +++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 15 deletions(-)
Show changes to builtin/submodule--helper.c +24 −15
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 764fcb7dba..bbab43230b 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -29,11 +29,10 @@
 typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
 				  void *cb_data);
 
-static char *get_default_remote(void)
+static char *repo_get_default_remote(struct repository *repo, const char *refname)
 {
 	char *dest = NULL, *ret;
 	struct strbuf sb = STRBUF_INIT;
-	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
 
 	if (!refname)
 		die(_("No such ref: %s"), "HEAD");
@@ -46,7 +45,7 @@ static char *get_default_remote(void)
 		die(_("Expecting a full ref name, got %s"), refname);
 
 	strbuf_addf(&sb, "branch.%s.remote", refname);
-	if (git_config_get_string(sb.buf, &dest))
+	if (repo_config_get_string(repo, sb.buf, &dest))
 		ret = xstrdup("origin");
 	else
 		ret = dest;
@@ -55,6 +54,23 @@ static char *get_default_remote(void)
 	return ret;
 }
 
+static char *get_default_remote_submodule(const char *module_path)
+{
+	const char *refname;
+	struct repository subrepo;
+
+	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
+					  "HEAD", 0, NULL, NULL);
+	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
+	return repo_get_default_remote(&subrepo, refname);
+}
+
+static char *get_default_remote(void)
+{
+	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+	return repo_get_default_remote(the_repository, refname);
+}
+
 static int print_default_remote(int argc, const char **argv, const char *prefix)
 {
 	char *remote;
@@ -1341,9 +1357,8 @@ static void sync_submodule(const char *path, const char *prefix,
 {
 	const struct submodule *sub;
 	char *remote_key = NULL;
-	char *sub_origin_url, *super_config_url, *displaypath;
+	char *sub_origin_url, *super_config_url, *displaypath, *default_remote;
 	struct strbuf sb = STRBUF_INIT;
-	struct child_process cp = CHILD_PROCESS_INIT;
 	char *sub_config_path = NULL;
 
 	if (!is_submodule_active(the_repository, path))
@@ -1382,21 +1397,15 @@ static void sync_submodule(const char *path, const char *prefix,
 	if (!is_submodule_populated_gently(path, NULL))
 		goto cleanup;
 
-	prepare_submodule_repo_env(&cp.env_array);
-	cp.git_cmd = 1;
-	cp.dir = path;
-	strvec_pushl(&cp.args, "submodule--helper",
-		     "print-default-remote", NULL);
-
 	strbuf_reset(&sb);
-	if (capture_command(&cp, &sb, 0))
+	default_remote = get_default_remote_submodule(path);
+	if (!default_remote)
 		die(_("failed to get the default remote for submodule '%s'"),
 		      path);
 
-	strbuf_strip_suffix(&sb, "\n");
-	remote_key = xstrfmt("remote.%s.url", sb.buf);
+	remote_key = xstrfmt("remote.%s.url", default_remote);
+	free(default_remote);
 
-	strbuf_reset(&sb);
 	submodule_to_gitdir(&sb, path);
 	strbuf_addstr(&sb, "/config");
 
-- 
2.32.0
Atharva Raykar· Oct 13, 2021, 05:17 UTC · re: Atharva Raykar · lore

[PATCH v3 3/9] submodule--helper: rename helpers for update-clone

The `update-clone` subcommand helpers that perform the parallel clone and printing to stdout for shell script consumption, are renamed.

This lets us use the names `update_submodules()` and `update_submodule()` for the helpers in the next patch, when we create an `update` subcommand that does a full conversion.

We will get rid of these helpers in a cleanup patch at the end of this series, when the `update-clone` command is no longer useful to us.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +4 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index bbab43230b..f60db966ea 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2466,7 +2466,7 @@ static int do_run_update_procedure(struct update_data *ud)
 	return run_update_command(ud, subforce);
 }
 
-static void update_submodule(struct update_clone_data *ucd)
+static void update_clone_submodule(struct update_clone_data *ucd)
 {
 	fprintf(stdout, "dummy %s %d\t%s\n",
 		oid_to_hex(&ucd->oid),
@@ -2474,7 +2474,7 @@ static void update_submodule(struct update_clone_data *ucd)
 		ucd->sub->path);
 }
 
-static int update_submodules(struct submodule_update_clone *suc)
+static int update_clone_submodules(struct submodule_update_clone *suc)
 {
 	int i;
 
@@ -2495,7 +2495,7 @@ static int update_submodules(struct submodule_update_clone *suc)
 		return 1;
 
 	for (i = 0; i < suc->update_clone_nr; i++)
-		update_submodule(&suc->update_clone[i]);
+		update_clone_submodule(&suc->update_clone[i]);
 
 	return 0;
 }
@@ -2560,7 +2560,7 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 	if (pathspec.nr)
 		suc.warn_if_uninitialized = 1;
 
-	return update_submodules(&suc);
+	return update_clone_submodules(&suc);
 }
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
-- 
2.32.0
Atharva Raykar· Oct 13, 2021, 05:18 UTC · re: Atharva Raykar · lore

[PATCH v3 4/9] submodule--helper: refactor get_submodule_displaypath()

We create a function called `do_get_submodule_displaypath()` that generates the display path required by several submodule functions, and takes a custom superprefix parameter, instead of reading it from the environment.

We then redefine the existing `get_submodule_displaypath()` function as a call to this new function, where the superprefix is obtained from the environment.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +8 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f60db966ea..5b4f1dbcbd 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -261,11 +261,8 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr
 	return 0;
 }
 
-/* the result should be freed by the caller. */
-static char *get_submodule_displaypath(const char *path, const char *prefix)
+static char *do_get_submodule_displaypath(const char *path, const char *prefix, const char *super_prefix)
 {
-	const char *super_prefix = get_super_prefix();
-
 	if (prefix && super_prefix) {
 		BUG("cannot have prefix '%s' and superprefix '%s'",
 		    prefix, super_prefix);
@@ -281,6 +278,13 @@ static char *get_submodule_displaypath(const char *path, const char *prefix)
 	}
 }
 
+/* the result should be freed by the caller. */
+static char *get_submodule_displaypath(const char *path, const char *prefix)
+{
+	const char *super_prefix = get_super_prefix();
+	return do_get_submodule_displaypath(path, prefix, super_prefix);
+}
+
 static char *compute_rev_name(const char *sub_path, const char* object_id)
 {
 	struct strbuf sb = STRBUF_INIT;
-- 
2.32.0
Atharva Raykar· Oct 13, 2021, 05:18 UTC · re: Atharva Raykar · lore

[PATCH v3 8/9] submodule--helper: remove unused helpers

These helpers were useful back when 'submodule update' had most of its logic in shell. Now that they will never be invoked, let us remove them.

We also no longer need the 'update_clone_submodules()' and 'update_clone_submodule()' functions, so we remove those as well.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 233 ------------------------------------
 1 file changed, 233 deletions(-)
Show changes to builtin/submodule--helper.c +0 −233
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 3049628f88..0d6e65bd6c 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -71,21 +71,6 @@ static char *get_default_remote(void)
 	return repo_get_default_remote(the_repository, refname);
 }
 
-static int print_default_remote(int argc, const char **argv, const char *prefix)
-{
-	char *remote;
-
-	if (argc != 1)
-		die(_("submodule--helper print-default-remote takes no arguments"));
-
-	remote = get_default_remote();
-	if (remote)
-		printf("%s\n", remote);
-
-	free(remote);
-	return 0;
-}
-
 static int starts_with_dot_slash(const char *str)
 {
 	return str[0] == '.' && is_dir_sep(str[1]);
@@ -1960,29 +1945,6 @@ static void determine_submodule_update_strategy(struct repository *r,
 	free(key);
 }
 
-static int module_update_module_mode(int argc, const char **argv, const char *prefix)
-{
-	const char *path, *update = NULL;
-	int just_cloned;
-	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
-
-	if (argc < 3 || argc > 4)
-		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
-
-	just_cloned = git_config_int("just_cloned", argv[1]);
-	path = argv[2];
-
-	if (argc == 4)
-		update = argv[3];
-
-	determine_submodule_update_strategy(the_repository,
-					    just_cloned, path, update,
-					    &update_strategy);
-	fputs(submodule_strategy_to_string(&update_strategy), stdout);
-
-	return 0;
-}
-
 struct update_clone_data {
 	const struct submodule *sub;
 	struct object_id oid;
@@ -2518,182 +2480,6 @@ static int do_run_update_procedure(struct update_data *ud, struct string_list *e
 	return run_update_command(ud, subforce, err);
 }
 
-static void update_clone_submodule(struct update_clone_data *ucd)
-{
-	fprintf(stdout, "dummy %s %d\t%s\n",
-		oid_to_hex(&ucd->oid),
-		ucd->just_cloned,
-		ucd->sub->path);
-}
-
-static int update_clone_submodules(struct submodule_update_clone *suc)
-{
-	int i;
-
-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
-				   update_clone_start_failure,
-				   update_clone_task_finished, suc, "submodule",
-				   "parallel/update");
-
-	/*
-	 * We saved the output and put it out all at once now.
-	 * That means:
-	 * - the listener does not have to interleave their (checkout)
-	 *   work with our fetching.  The writes involved in a
-	 *   checkout involve more straightforward sequential I/O.
-	 * - the listener can avoid doing any work if fetching failed.
-	 */
-	if (suc->quickstop)
-		return 1;
-
-	for (i = 0; i < suc->update_clone_nr; i++)
-		update_clone_submodule(&suc->update_clone[i]);
-
-	return 0;
-}
-
-static int update_clone(int argc, const char **argv, const char *prefix)
-{
-	const char *update = NULL;
-	struct pathspec pathspec;
-	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
-
-	struct option module_update_clone_options[] = {
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
-			   N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
-			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &suc.dissociate,
-			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &suc.depth, "<depth>",
-			   N_("create a shallow clone truncated to the "
-			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &suc.max_jobs,
-			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
-			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &suc.progress,
-			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &suc.require_init,
-			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &suc.single_branch,
-			 N_("clone only one branch, HEAD or --branch")),
-		OPT_END()
-	};
-
-	const char *const git_submodule_helper_usage[] = {
-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
-		NULL
-	};
-	suc.prefix = prefix;
-
-	update_clone_config_from_gitmodules(&suc.max_jobs);
-	git_config(git_update_clone_config, &suc.max_jobs);
-
-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
-			     git_submodule_helper_usage, 0);
-
-	if (update)
-		if (parse_submodule_update_strategy(update, &suc.update) < 0)
-			die(_("bad value for update parameter"));
-
-	if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
-		return 1;
-
-	if (pathspec.nr)
-		suc.warn_if_uninitialized = 1;
-
-	return update_clone_submodules(&suc);
-}
-
-static int run_update_procedure(int argc, const char **argv, const char *prefix)
-{
-	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
-	char *prefixed_path, *update = NULL;
-	struct update_data update_data = UPDATE_DATA_INIT;
-	struct string_list err = STRING_LIST_INIT_DUP;
-
-	struct option options[] = {
-		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&force, N_("force checkout updates"), 0),
-		OPT_BOOL('N', "no-fetch", &nofetch,
-			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &just_cloned,
-			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
-			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
-			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_END()
-	};
-
-	const char *const usage[] = {
-		N_("git submodule--helper run-update-procedure [<options>] <path>"),
-		NULL
-	};
-
-	argc = parse_options(argc, argv, prefix, options, usage, 0);
-
-	if (argc != 1)
-		usage_with_options(usage, options);
-
-	update_data.force = !!force;
-	update_data.quiet = !!quiet;
-	update_data.nofetch = !!nofetch;
-	update_data.just_cloned = !!just_cloned;
-	update_data.sm_path = argv[0];
-
-	if (update_data.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
-	else
-		prefixed_path = xstrdup(update_data.sm_path);
-
-	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
-
-	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
-					    update_data.sm_path, update,
-					    &update_data.update_strategy);
-
-	free(prefixed_path);
-
-	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data, &err);
-
-	return 3;
-}
-
-static int resolve_relative_path(int argc, const char **argv, const char *prefix)
-{
-	struct strbuf sb = STRBUF_INIT;
-	if (argc != 3)
-		die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
-
-	printf("%s", relative_path(argv[1], argv[2], &sb));
-	strbuf_release(&sb);
-	return 0;
-}
-
 static const char *remote_submodule_branch(const char *path)
 {
 	const struct submodule *sub;
@@ -2852,19 +2638,6 @@ static void do_ensure_core_worktree(const char *path)
 	}
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
-{
-	const char *path;
-
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-	do_ensure_core_worktree(path);
-
-	return 0;
-}
-
 static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
 {
 	int i;
@@ -3707,16 +3480,10 @@ static struct cmd_struct commands[] = {
 	{"clone", module_clone, 0},
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
 	{"update", module_update, 0},
-	{"update-module-mode", module_update_module_mode, 0},
-	{"update-clone", update_clone, 0},
-	{"run-update-procedure", run_update_procedure, 0},
-	{"ensure-core-worktree", ensure_core_worktree, 0},
-	{"relative-path", resolve_relative_path, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
 	{"init", module_init, SUPPORT_SUPER_PREFIX},
 	{"status", module_status, SUPPORT_SUPER_PREFIX},
-	{"print-default-remote", print_default_remote, 0},
 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
 	{"deinit", module_deinit, 0},
 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
-- 
2.32.0
Atharva Raykar· Oct 13, 2021, 05:18 UTC · re: Atharva Raykar · lore

[PATCH v3 6/9] submodule--helper: run update using child process struct

We switch to using the run-command API function that takes a 'struct child process', since we are using a lot of the options. This will also make it simple to switch over to using 'capture_command()' when we start handling the output of the command completely in C.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 34 ++++++++++++++++------------------
 1 file changed, 16 insertions(+), 18 deletions(-)
Show changes to builtin/submodule--helper.c +16 −18
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 74f084a9c9..640b3bd220 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2342,47 +2342,45 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 
 static int run_update_command(struct update_data *ud, int subforce)
 {
-	struct strvec args = STRVEC_INIT;
-	struct strvec child_env = STRVEC_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
 	int must_die_on_failure = 0;
-	int git_cmd;
 
 	switch (ud->update_strategy.type) {
 	case SM_UPDATE_CHECKOUT:
-		git_cmd = 1;
-		strvec_pushl(&args, "checkout", "-q", NULL);
+		cp.git_cmd = 1;
+		strvec_pushl(&cp.args, "checkout", "-q", NULL);
 		if (subforce)
-			strvec_push(&args, "-f");
+			strvec_push(&cp.args, "-f");
 		break;
 	case SM_UPDATE_REBASE:
-		git_cmd = 1;
-		strvec_push(&args, "rebase");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "rebase");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_MERGE:
-		git_cmd = 1;
-		strvec_push(&args, "merge");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "merge");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_COMMAND:
-		git_cmd = 0;
-		strvec_push(&args, ud->update_strategy.command);
+		cp.use_shell = 1;
+		strvec_push(&cp.args, ud->update_strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
 		    submodule_strategy_to_string(&ud->update_strategy));
 	}
-	strvec_push(&args, oid);
+	strvec_push(&cp.args, oid);
 
-	prepare_submodule_repo_env(&child_env);
-	if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
-				     ud->sm_path, child_env.v)) {
+	cp.dir = xstrdup(ud->sm_path);
+	prepare_submodule_repo_env(&cp.env_array);
+	if (run_command(&cp)) {
 		switch (ud->update_strategy.type) {
 		case SM_UPDATE_CHECKOUT:
 			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-- 
2.32.0
Atharva Raykar· Oct 13, 2021, 05:18 UTC · re: Atharva Raykar · lore

[PATCH v3 7/9] submodule: move core cmd_update() logic to C

This patch completes the conversion past the flag parsing of `submodule update` by introducing a helper subcommand called `submodule--helper update`. The behaviour of `submodule update` should remain the same after this patch.

We add more fields to the `struct update_data` that are required by `struct submodule_update_clone` to be able to perform a clone, when that is needed to be done.

Recursing on a submodule is done by calling a subprocess that launches `submodule--helper update`, with a modified `--recursive-prefix` and `--prefix` parameter.

We also introduce `update_submodules()` and `update_submodule()` which are quite similar to `update_clone_submodules()` and `update_clone_submodule()`, and will supersede them.

When the `--init` flag is passed to the subcommand, we do not spawn a new subprocess and call `submodule--helper init` on the submodule paths, because the Git machinery is not able to pick up the configuration changes introduced by that init call[1]. So we instead run the `init_submodule_cb()` callback over each submodule in the same process.

While we are at it, we also remove the fetch_in_submodule() shell function since it is no longer used anywhere.

[1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 462 +++++++++++++++++++++++++++++++-----
 git-submodule.sh            | 145 +----------
 2 files changed, 410 insertions(+), 197 deletions(-)
Show changes to 2 files +410 −197

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 640b3bd220..3049628f88 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2010,7 +2010,6 @@ struct submodule_update_clone {
 	const char *prefix;
 	int single_branch;
 
-	/* to be consumed by git-submodule.sh */
 	struct update_clone_data *update_clone;
 	int update_clone_nr; int update_clone_alloc;
 
@@ -2033,19 +2032,63 @@ struct submodule_update_clone {
 }
 
 struct update_data {
+	const char *prefix;
 	const char *recursive_prefix;
 	const char *sm_path;
 	const char *displaypath;
 	struct object_id oid;
 	struct object_id suboid;
-	struct submodule_update_strategy update_strategy;
+	int max_jobs;
 	int depth;
+	int recommend_shallow;
+	int single_branch;
+	unsigned int init: 1;
 	unsigned int force: 1;
 	unsigned int quiet: 1;
 	unsigned int nofetch: 1;
-	unsigned int just_cloned: 1;
+	unsigned int remote: 1;
+	unsigned int recursive: 1;
+	unsigned int progress: 1;
+	unsigned int dissociate: 1;
+	unsigned int require_init: 1;
+	unsigned warn_if_uninitialized : 1;
+	unsigned int just_cloned : 1;
+	struct submodule_update_strategy update_strategy;
+	struct string_list references;
+	struct module_list list;
 };
-#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
+#define UPDATE_DATA_INIT { \
+	.list = MODULE_LIST_INIT, \
+	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+	.recommend_shallow = -1, \
+	.references = STRING_LIST_INIT_DUP, \
+	.single_branch = -1, \
+	.max_jobs = 1, \
+}
+
+static void update_clone_from_update_data(struct submodule_update_clone *suc,
+					  struct update_data *update_data)
+{
+	suc->prefix = update_data->prefix;
+	suc->recursive_prefix = update_data->recursive_prefix;
+	suc->max_jobs = update_data->max_jobs;
+	suc->progress = update_data->progress;
+	suc->quiet = update_data->quiet;
+	suc->dissociate = update_data->dissociate;
+	suc->require_init = update_data->require_init;
+	suc->single_branch = update_data->single_branch;
+	suc->warn_if_uninitialized = update_data->warn_if_uninitialized;
+	suc->list = update_data->list;
+	suc->update = update_data->update_strategy;
+	suc->recommend_shallow = update_data->recommend_shallow;
+	if (update_data->depth)
+		suc->depth = xstrfmt("--depth=%d", update_data->depth);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			string_list_append(&suc->references, item->string);
+	}
+}
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
 		struct strbuf *out, const char *displaypath)
@@ -2340,13 +2383,21 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 	return run_command(&cp);
 }
 
-static int run_update_command(struct update_data *ud, int subforce)
+static int run_update_command(struct update_data *ud, int subforce, struct string_list *err)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
+	struct strbuf out = STRBUF_INIT;
 	int must_die_on_failure = 0;
+	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
 
-	switch (ud->update_strategy.type) {
+	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
+		determine_submodule_update_strategy(the_repository, ud->just_cloned,
+						    ud->sm_path, NULL, &strategy);
+	else
+		strategy = ud->update_strategy;
+
+	switch (strategy.type) {
 	case SM_UPDATE_CHECKOUT:
 		cp.git_cmd = 1;
 		strvec_pushl(&cp.args, "checkout", "-q", NULL);
@@ -2369,80 +2420,75 @@ static int run_update_command(struct update_data *ud, int subforce)
 		break;
 	case SM_UPDATE_COMMAND:
 		cp.use_shell = 1;
-		strvec_push(&cp.args, ud->update_strategy.command);
+		strvec_push(&cp.args, strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+		    submodule_strategy_to_string(&strategy));
 	}
 	strvec_push(&cp.args, oid);
 
 	cp.dir = xstrdup(ud->sm_path);
 	prepare_submodule_repo_env(&cp.env_array);
-	if (run_command(&cp)) {
-		switch (ud->update_strategy.type) {
-		case SM_UPDATE_CHECKOUT:
-			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
-			break;
-		case SM_UPDATE_REBASE:
-			printf(_("Unable to rebase '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
-			break;
-		case SM_UPDATE_MERGE:
-			printf(_("Unable to merge '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
-			break;
-		case SM_UPDATE_COMMAND:
-			printf(_("Execution of '%s %s' failed in submodule path '%s'"),
-			       ud->update_strategy.command, oid, ud->displaypath);
-			break;
-		default:
-			BUG("unexpected update strategy type: %s",
-			    submodule_strategy_to_string(&ud->update_strategy));
+	if (capture_command(&cp, &out, 0)) {
+		if (must_die_on_failure) {
+			switch (strategy.type) {
+			case SM_UPDATE_CHECKOUT:
+				die(_("Unable to checkout '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_REBASE:
+				die(_("Unable to rebase '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_MERGE:
+				die(_("Unable to merge '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_COMMAND:
+				die(_("Execution of '%s %s' failed in submodule path '%s'"),
+				    strategy.command, oid, ud->displaypath);
+				break;
+			default:
+				BUG("unexpected update strategy type: %s",
+				    submodule_strategy_to_string(&strategy));
+			}
 		}
-		/*
-		 * NEEDSWORK: We are currently printing to stdout with error
-		 * return so that the shell caller handles the error output
-		 * properly. Once we start handling the error messages within
-		 * C, we should use die() instead.
-		 */
-		if (must_die_on_failure)
-			return 2;
-		/*
-		 * This signifies to the caller in shell that the command
-		 * failed without dying
-		 */
+
+		/* the command failed, but update must continue */
+		string_list_append(err, out.buf);
 		return 1;
 	}
 
-	switch (ud->update_strategy.type) {
-	case SM_UPDATE_CHECKOUT:
-		printf(_("Submodule path '%s': checked out '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_REBASE:
-		printf(_("Submodule path '%s': rebased into '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_MERGE:
-		printf(_("Submodule path '%s': merged in '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_COMMAND:
-		printf(_("Submodule path '%s': '%s %s'\n"),
-		       ud->displaypath, ud->update_strategy.command, oid);
-		break;
-	default:
-		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+	if (!ud->quiet) {
+		switch (strategy.type) {
+		case SM_UPDATE_CHECKOUT:
+			printf(_("Submodule path '%s': checked out '%s'\n"),
+			       ud->displaypath, oid);
+			break;
+		case SM_UPDATE_REBASE:
+			printf(_("Submodule path '%s': rebased into '%s'\n"),
+			       ud->displaypath, oid);
+			break;
+		case SM_UPDATE_MERGE:
+			printf(_("Submodule path '%s': merged in '%s'\n"),
+			       ud->displaypath, oid);
+			break;
+		case SM_UPDATE_COMMAND:
+			printf(_("Submodule path '%s': '%s %s'\n"),
+			       ud->displaypath, strategy.command, oid);
+			break;
+		default:
+			BUG("unexpected update strategy type: %s",
+			    submodule_strategy_to_string(&strategy));
+		}
 	}
 
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud)
+static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2469,7 +2515,7 @@ static int do_run_update_procedure(struct update_data *ud)
 			    ud->displaypath, oid_to_hex(&ud->oid));
 	}
 
-	return run_update_command(ud, subforce);
+	return run_update_command(ud, subforce, err);
 }
 
 static void update_clone_submodule(struct update_clone_data *ucd)
@@ -2574,6 +2620,7 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
 	char *prefixed_path, *update = NULL;
 	struct update_data update_data = UPDATE_DATA_INIT;
+	struct string_list err = STRING_LIST_INIT_DUP;
 
 	struct option options[] = {
 		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
@@ -2631,7 +2678,7 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	free(prefixed_path);
 
 	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data);
+		return do_run_update_procedure(&update_data, &err);
 
 	return 3;
 }
@@ -3002,6 +3049,291 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 	return !!ret;
 }
 
+static void update_data_to_args(struct update_data *update_data, struct strvec *args)
+{
+	const char *update = submodule_strategy_to_string(&update_data->update_strategy);
+
+	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
+	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
+	if (update_data->prefix)
+		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
+	if (update_data->recursive_prefix)
+		strvec_pushl(args, "--recursive-prefix",
+			     update_data->recursive_prefix, NULL);
+	if (update_data->quiet)
+		strvec_push(args, "--quiet");
+	if (update_data->force)
+		strvec_push(args, "--force");
+	if (update_data->init)
+		strvec_push(args, "--init");
+	if (update_data->remote)
+		strvec_push(args, "--remote");
+	if (update_data->nofetch)
+		strvec_push(args, "--no-fetch");
+	if (update_data->dissociate)
+		strvec_push(args, "--dissociate");
+	if (update_data->progress)
+		strvec_push(args, "--progress");
+	if (update_data->require_init)
+		strvec_push(args, "--require-init");
+	if (update_data->depth)
+		strvec_pushf(args, "--depth=%d", update_data->depth);
+	if (update)
+		strvec_pushl(args, "--update", update, NULL);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			strvec_pushl(args, "--reference", item->string, NULL);
+	}
+	if (update_data->recommend_shallow == 0)
+		strvec_push(args, "--no-recommend-shallow");
+	else if (update_data->recommend_shallow == 1)
+		strvec_push(args, "--recommend-shallow");
+	if (update_data->single_branch >= 0)
+		strvec_push(args, "--single-branch");
+}
+
+static int update_submodule(struct update_data *update_data)
+{
+	char *prefixed_path;
+	struct string_list err = STRING_LIST_INIT_DUP;
+
+	do_ensure_core_worktree(update_data->sm_path);
+
+	if (update_data->recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
+					update_data->sm_path);
+	else
+		prefixed_path = xstrdup(update_data->sm_path);
+
+	update_data->displaypath = get_submodule_displaypath(prefixed_path,
+							     update_data->prefix);
+	free(prefixed_path);
+
+	if (update_data->just_cloned) {
+		oidcpy(&update_data->suboid, null_oid());
+	} else {
+		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+			die(_("Unable to find current revision in submodule path '%s'"),
+			    update_data->displaypath);
+	}
+
+	if (update_data->remote) {
+		char *remote_name = get_default_remote_submodule(update_data->sm_path);
+		const char *branch = remote_submodule_branch(update_data->sm_path);
+		char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch);
+
+		if (!update_data->nofetch) {
+			if(fetch_in_submodule(update_data->sm_path, update_data->depth,
+					      0, NULL))
+				die(_("Unable to fetch in submodule path '%s'"),
+				    update_data->sm_path);
+		}
+
+		if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
+			die(_("Unable to find %s revision in submodule path '%s'"),
+			    remote_ref, update_data->sm_path);
+
+		free(remote_ref);
+	}
+
+	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
+		if (do_run_update_procedure(update_data, &err))
+			return 1;
+
+	if (update_data->recursive) {
+		int res;
+		struct child_process cp = CHILD_PROCESS_INIT;
+		struct update_data next = *update_data;
+		char *die_msg = xstrfmt(_("Failed to recurse into submodule path '%s'"),
+					update_data->displaypath);
+
+		if (update_data->recursive_prefix)
+			prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix,
+						update_data->sm_path);
+		else
+			prefixed_path = xstrfmt("%s/", update_data->sm_path);
+
+		next.recursive_prefix = get_submodule_displaypath(prefixed_path,
+								  update_data->prefix);
+		next.prefix = NULL;
+		oidcpy(&next.oid, null_oid());
+		oidcpy(&next.suboid, null_oid());
+
+		cp.dir = update_data->sm_path;
+		cp.git_cmd = 1;
+		prepare_submodule_repo_env(&cp.env_array);
+		update_data_to_args(&next, &cp.args);
+
+		/* die() if child process die()'d */
+		if ((res = run_command(&cp)) == 128)
+			die("%s", die_msg);
+		if (res)
+			string_list_append(&err, die_msg);
+
+		free(die_msg);
+	}
+
+	if (err.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &err)
+			fputs(item->string, stderr);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int update_submodules(struct update_data *update_data)
+{
+	int i, res = 0;
+	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+
+	update_clone_from_update_data(&suc, update_data);
+	run_processes_parallel_tr2(suc.max_jobs, update_clone_get_next_task,
+				   update_clone_start_failure,
+				   update_clone_task_finished, &suc, "submodule",
+				   "parallel/update");
+
+	/*
+	 * We saved the output and put it out all at once now.
+	 * That means:
+	 * - the listener does not have to interleave their (checkout)
+	 *   work with our fetching.  The writes involved in a
+	 *   checkout involve more straightforward sequential I/O.
+	 * - the listener can avoid doing any work if fetching failed.
+	 */
+	if (suc.quickstop) {
+		string_list_clear(&update_data->references, 0);
+		return 1;
+	}
+
+	for (i = 0; i < suc.update_clone_nr; i++) {
+		struct update_clone_data ucd = suc.update_clone[i];
+
+		oidcpy(&update_data->oid, &ucd.oid);
+		update_data->just_cloned = ucd.just_cloned;
+		update_data->sm_path = ucd.sub->path;
+
+		if (update_submodule(update_data))
+			res = 1;
+	}
+
+	string_list_clear(&update_data->references, 0);
+	return res;
+}
+
+static int module_update(int argc, const char **argv, const char *prefix)
+{
+	int init = 0, force = 0, quiet = 0, nofetch = 0;
+	int remote = 0, recursive = 0, dissociate = 0;
+	int progress = 0, require_init = 0;
+	const char *update = NULL;
+	struct pathspec pathspec;
+	struct update_data update_data = UPDATE_DATA_INIT;
+
+	struct option module_update_clone_options[] = {
+		OPT__FORCE(&force, N_("force checkout updates"), 0),
+		OPT_BOOL(0, "init", &init,
+			 N_("initialize uninitialized submodules before update")),
+		OPT_BOOL(0, "remote", &remote,
+			 N_("use SHA-1 of submodule's remote tracking branch")),
+		OPT_BOOL(0, "recursive", &recursive,
+			 N_("traverse submodules recursively")),
+		OPT_BOOL('N', "no-fetch", &nofetch,
+			 N_("don't fetch new objects from the remote site")),
+		OPT_STRING(0, "prefix", &prefix,
+			   N_("path"),
+			   N_("path into the working tree")),
+		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix,
+			   N_("path"),
+			   N_("path into the working tree, across nested "
+			      "submodule boundaries")),
+		OPT_STRING(0, "update", &update,
+			   N_("string"),
+			   N_("rebase, merge, checkout or none")),
+		OPT_STRING_LIST(0, "reference", &update_data.references, N_("repo"),
+				N_("reference repository")),
+		OPT_BOOL(0, "dissociate", &dissociate,
+			 N_("use --reference only while cloning")),
+		OPT_INTEGER(0, "depth", &update_data.depth,
+			    N_("create a shallow clone truncated to the "
+			       "specified number of revisions")),
+		OPT_INTEGER('j', "jobs", &update_data.max_jobs,
+			    N_("parallel jobs")),
+		OPT_BOOL(0, "recommend-shallow", &update_data.recommend_shallow,
+			 N_("whether the initial clone should follow the shallow recommendation")),
+		OPT__QUIET(&quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &progress,
+			 N_("force cloning progress")),
+		OPT_BOOL(0, "require-init", &require_init,
+			 N_("disallow cloning into non-empty directory")),
+		OPT_BOOL(0, "single-branch", &update_data.single_branch,
+			 N_("clone only one branch, HEAD or --branch")),
+		OPT_END()
+	};
+
+	const char *const git_submodule_helper_usage[] = {
+		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
+		NULL
+	};
+
+	update_clone_config_from_gitmodules(&update_data.max_jobs);
+	git_config(git_update_clone_config, &update_data.max_jobs);
+
+	argc = parse_options(argc, argv, prefix, module_update_clone_options,
+			     git_submodule_helper_usage, 0);
+	update_data.prefix = prefix;
+
+	update_data.force = !!force;
+	update_data.quiet = !!quiet;
+	update_data.nofetch = !!nofetch;
+	update_data.init = !!init;
+	update_data.require_init = !!require_init;
+	update_data.remote = !!remote;
+	update_data.recursive = !!recursive;
+	update_data.progress = !!progress;
+	update_data.dissociate = !!dissociate;
+	oidcpy(&update_data.oid, null_oid());
+	oidcpy(&update_data.suboid, null_oid());
+
+	if (update)
+		if (parse_submodule_update_strategy(update,
+						    &update_data.update_strategy) < 0)
+			die(_("bad value for update parameter"));
+
+	if (module_list_compute(argc, argv, prefix, &pathspec, &update_data.list) < 0)
+		return 1;
+
+	if (pathspec.nr)
+		update_data.warn_if_uninitialized = 1;
+
+	if (update_data.init) {
+		struct module_list list = MODULE_LIST_INIT;
+		struct init_cb info = INIT_CB_INIT;
+
+		if (module_list_compute(argc, argv, update_data.prefix,
+					&pathspec, &list) < 0)
+			return 1;
+
+		/*
+		 * If there are no path args and submodule.active is set then,
+		 * by default, only initialize 'active' modules.
+		 */
+		if (!argc && git_config_get_value_multi("submodule.active"))
+			module_list_active(&list);
+
+		info.prefix = update_data.prefix;
+		info.superprefix = update_data.recursive_prefix;
+		if (update_data.quiet)
+			info.flags |= OPT_QUIET;
+
+		for_each_listed_submodule(&list, init_submodule_cb, &info);
+	}
+
+	return update_submodules(&update_data);
+}
+
 struct add_data {
 	const char *prefix;
 	const char *branch;
@@ -3360,6 +3692,7 @@ static int module_add(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
+
 #define SUPPORT_SUPER_PREFIX (1<<0)
 
 struct cmd_struct {
@@ -3373,6 +3706,7 @@ static struct cmd_struct commands[] = {
 	{"name", module_name, 0},
 	{"clone", module_clone, 0},
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
+	{"update", module_update, 0},
 	{"update-module-mode", module_update_module_mode, 0},
 	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
diff --git a/git-submodule.sh b/git-submodule.sh
index 652861aa66..bcd8b92aab 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -246,20 +246,6 @@ cmd_deinit()
 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
 }
 
-# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
-# Because arguments are positional, use an empty string to omit <depth>
-# but include <sha1>.
-fetch_in_submodule () (
-	sanitize_submodule_env &&
-	cd "$1" &&
-	if test $# -eq 3
-	then
-		echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
-	else
-		git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
-	fi
-)
-
 #
 # Update each submodule path to correct revision, using clone and checkout as needed
 #
@@ -361,133 +347,26 @@ cmd_update()
 		shift
 	done
 
-	if test -n "$init"
-	then
-		cmd_init "--" "$@" || return
-	fi
-
-	{
-	git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
-		${progress:+"--progress"} \
+	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
+		${GIT_QUIET:+--quiet} \
+		${force:+--force} \
+		${progress:+--progress} \
+		${dissociate:+--dissociate} \
+		${remote:+--remote} \
+		${recursive:+--recursive} \
+		${init:+--init} \
+		${require_init:+--require-init} \
+		${nofetch:+--no-fetch} \
 		${wt_prefix:+--prefix "$wt_prefix"} \
 		${prefix:+--recursive-prefix "$prefix"} \
 		${update:+--update "$update"} \
 		${reference:+"$reference"} \
-		${dissociate:+"--dissociate"} \
-		${depth:+--depth "$depth"} \
-		${require_init:+--require-init} \
+		${depth:+"$depth"} \
 		$single_branch \
 		$recommend_shallow \
 		$jobs \
 		-- \
-		"$@" || echo "#unmatched" $?
-	} | {
-	err=
-	while read -r quickabort sha1 just_cloned sm_path
-	do
-		die_if_unmatched "$quickabort" "$sha1"
-
-		git submodule--helper ensure-core-worktree "$sm_path" || exit 1
-
-		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
-
-		if test $just_cloned -eq 1
-		then
-			subsha1=
-		else
-			just_cloned=
-			subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify HEAD) ||
-			die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
-		fi
-
-		if test -n "$remote"
-		then
-			branch=$(git submodule--helper remote-branch "$sm_path")
-			if test -z "$nofetch"
-			then
-				# Fetch remote before determining tracking $sha1
-				fetch_in_submodule "$sm_path" $depth ||
-				die "fatal: $(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
-			fi
-			remote_name=$(sanitize_submodule_env; cd "$sm_path" && git submodule--helper print-default-remote)
-			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify "${remote_name}/${branch}") ||
-			die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
-		fi
-
-		out=$(git submodule--helper run-update-procedure \
-			  ${wt_prefix:+--prefix "$wt_prefix"} \
-			  ${GIT_QUIET:+--quiet} \
-			  ${force:+--force} \
-			  ${just_cloned:+--just-cloned} \
-			  ${nofetch:+--no-fetch} \
-			  ${depth:+"$depth"} \
-			  ${update:+--update "$update"} \
-			  ${prefix:+--recursive-prefix "$prefix"} \
-			  ${sha1:+--oid "$sha1"} \
-			  ${subsha1:+--suboid "$subsha1"} \
-			  "--" \
-			  "$sm_path")
-
-		# exit codes for run-update-procedure:
-		# 0: update was successful, say command output
-		# 1: update procedure failed, but should not die
-		# 2 or 128: subcommand died during execution
-		# 3: no update procedure was run
-		res="$?"
-		case $res in
-		0)
-			say "$out"
-			;;
-		1)
-			err="${err};fatal: $out"
-			continue
-			;;
-		2|128)
-			die_with_status $res "fatal: $out"
-			;;
-		esac
-
-		if test -n "$recursive"
-		then
-			(
-				prefix=$(git submodule--helper relative-path "$prefix$sm_path/" "$wt_prefix")
-				wt_prefix=
-				sanitize_submodule_env
-				cd "$sm_path" &&
-				eval cmd_update
-			)
-			res=$?
-			if test $res -gt 0
-			then
-				die_msg="fatal: $(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
-				if test $res -ne 2
-				then
-					err="${err};$die_msg"
-					continue
-				else
-					die_with_status $res "$die_msg"
-				fi
-			fi
-		fi
-	done
-
-	if test -n "$err"
-	then
-		OIFS=$IFS
-		IFS=';'
-		for e in $err
-		do
-			if test -n "$e"
-			then
-				echo >&2 "$e"
-			fi
-		done
-		IFS=$OIFS
-		exit 1
-	fi
-	}
+		"$@"
 }
 
 #
-- 
2.32.0
Atharva Raykar· Oct 13, 2021, 05:18 UTC · re: Atharva Raykar · lore

[PATCH v3 5/9] submodule--helper: allow setting superprefix for init_submodule()

We allow callers of the `init_submodule()` function to optionally override the superprefix from the environment.

We need to enable this option because in our conversion of the update command that will follow, the '--init' option will be handled through this API. We will need to change the superprefix at that time to ensure the display paths show correctly in the output messages.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +8 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 5b4f1dbcbd..74f084a9c9 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -606,18 +606,22 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
 
 struct init_cb {
 	const char *prefix;
+	const char *superprefix;
 	unsigned int flags;
 };
-#define INIT_CB_INIT { NULL, 0 }
+#define INIT_CB_INIT { 0 }
 
 static void init_submodule(const char *path, const char *prefix,
-			   unsigned int flags)
+			   const char *superprefix, unsigned int flags)
 {
 	const struct submodule *sub;
 	struct strbuf sb = STRBUF_INIT;
 	char *upd = NULL, *url = NULL, *displaypath;
 
-	displaypath = get_submodule_displaypath(path, prefix);
+	/* try superprefix from the environment, if it is not passed explicitly */
+	if (!superprefix)
+		superprefix = get_super_prefix();
+	displaypath = do_get_submodule_displaypath(path, prefix, superprefix);
 
 	sub = submodule_from_path(the_repository, null_oid(), path);
 
@@ -691,7 +695,7 @@ static void init_submodule(const char *path, const char *prefix,
 static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
 {
 	struct init_cb *info = cb_data;
-	init_submodule(list_item->name, info->prefix, info->flags);
+	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
 }
 
 static int module_init(int argc, const char **argv, const char *prefix)
-- 
2.32.0
Atharva Raykar· Oct 13, 2021, 05:18 UTC · re: Atharva Raykar · lore

[PATCH v3 9/9] submodule--helper: rename helper functions

These two functions were prefixed with 'do' before the shell-to-C conversion because they were utility functions meant to be called by their non-prefixed counterpart.

Since those callers don't exist anymore, and these functions can now be used directly, let's rename them to signal this fact.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
---
 builtin/submodule--helper.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +4 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 0d6e65bd6c..82d68650e8 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2450,7 +2450,7 @@ static int run_update_command(struct update_data *ud, int subforce, struct strin
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
+static int run_update_procedure(struct update_data *ud, struct string_list *err)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2612,7 +2612,7 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static void do_ensure_core_worktree(const char *path)
+static void ensure_core_worktree(const char *path)
 {
 	const char *cw;
 	struct repository subrepo;
@@ -2871,7 +2871,7 @@ static int update_submodule(struct update_data *update_data)
 	char *prefixed_path;
 	struct string_list err = STRING_LIST_INIT_DUP;
 
-	do_ensure_core_worktree(update_data->sm_path);
+	ensure_core_worktree(update_data->sm_path);
 
 	if (update_data->recursive_prefix)
 		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
@@ -2911,7 +2911,7 @@ static int update_submodule(struct update_data *update_data)
 	}
 
 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
-		if (do_run_update_procedure(update_data, &err))
+		if (run_update_procedure(update_data, &err))
 			return 1;
 
 	if (update_data->recursive) {
-- 
2.32.0
Junio C Hamano· Oct 14, 2021, 00:05 UTC · re: Atharva Raykar · lore

Re: [PATCH v3 0/9] submodule: convert the rest of 'update' to C

Atharva Raykar <raykar.ath@gmail.com> writes:
Show 9 quoted lines
> I have attempted to make a version of this series that is based on that topic [2],
> and added the superproject gitdir caching code in C [3]. It passes the tests,
> but I am not too confident about its correctness. I hope that branch can be
> helpful in some way.
>
> [1] https://lore.kernel.org/git/20210819200953.2105230-1-emilyshaffer@google.com/
> [2] https://github.com/tfidfwastaken/git/commits/submodule-update-on-es-superproject-aware
>     (fetch-it-via: git fetch https://github.com/tfidfwastaken/git submodule-update-on-es-superproject-aware)
> [3] https://github.com/tfidfwastaken/git/blob/a74aaf2540c536970f2541d3042c825f82a69770/builtin/submodule--helper.c#L2922-L2930

The "C rewrite" of the code [3] that unconditionally sets of the submodule.superprojectgitdir varible seems straightforward enough.

Emily, how solid do you think your "superproject aware submodule" topic already is? Would it be stable enough to build other things on top, or is it a bit too premature?

Emily Shaffer· Oct 14, 2021, 20:46 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 0/9] submodule: convert the rest of 'update' to C

On Wed, Oct 13, 2021 at 05:05:58PM -0700, Junio C Hamano wrote:
Show 19 quoted lines
> 
> Atharva Raykar <raykar.ath@gmail.com> writes:
> 
> > I have attempted to make a version of this series that is based on that topic [2],
> > and added the superproject gitdir caching code in C [3]. It passes the tests,
> > but I am not too confident about its correctness. I hope that branch can be
> > helpful in some way.
> >
> > [1] https://lore.kernel.org/git/20210819200953.2105230-1-emilyshaffer@google.com/
> > [2] https://github.com/tfidfwastaken/git/commits/submodule-update-on-es-superproject-aware
> >     (fetch-it-via: git fetch https://github.com/tfidfwastaken/git submodule-update-on-es-superproject-aware)
> > [3] https://github.com/tfidfwastaken/git/blob/a74aaf2540c536970f2541d3042c825f82a69770/builtin/submodule--helper.c#L2922-L2930
> 
> The "C rewrite" of the code [3] that unconditionally sets of the
> submodule.superprojectgitdir varible seems straightforward enough.
> 
> Emily, how solid do you think your "superproject aware submodule"
> topic already is?  Would it be stable enough to build other things
> on top, or is it a bit too premature?

As of the version I sent today (https://lore.kernel.org/git/20211014203416.2802639-1-emilyshaffer%40google.com) I think it is stable enough to build on top of. There was general consensus on the semantics of submodule.superprojectgitdir as it's sent in v4.

 - Emily
Junio C Hamano· Dec 3, 2021, 19:00 UTC · re: Emily Shaffer · lore

Re: [PATCH v3 0/9] submodule: convert the rest of 'update' to C

Emily Shaffer <emilyshaffer@google.com> writes:
Show 21 quoted lines
> On Wed, Oct 13, 2021 at 05:05:58PM -0700, Junio C Hamano wrote:
>> 
>> Atharva Raykar <raykar.ath@gmail.com> writes:
>> 
>> > I have attempted to make a version of this series that is based on that topic [2],
>> > and added the superproject gitdir caching code in C [3]. It passes the tests,
>> > but I am not too confident about its correctness. I hope that branch can be
>> > helpful in some way.
>> ..
>> The "C rewrite" of the code [3] that unconditionally sets of the
>> submodule.superprojectgitdir varible seems straightforward enough.
>> 
>> Emily, how solid do you think your "superproject aware submodule"
>> topic already is?  Would it be stable enough to build other things
>> on top, or is it a bit too premature?
>
> As of the version I sent today
> (https://lore.kernel.org/git/20211014203416.2802639-1-emilyshaffer%40google.com)
> I think it is stable enough to build on top of. There was general
> consensus on the semantics of submodule.superprojectgitdir as it's sent
> in v4.
Does the above statement still hold true today?

And more importantly, Atharva, are you on board with the plan Emily suggested to have this one built on top of her series?

Thanks.
Ævar Arnfjörð Bjarmason· Dec 3, 2021, 20:15 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 0/9] submodule: convert the rest of 'update' to C

On Fri, Dec 03 2021, Junio C Hamano wrote:
Show 28 quoted lines
> Emily Shaffer <emilyshaffer@google.com> writes:
>
>> On Wed, Oct 13, 2021 at 05:05:58PM -0700, Junio C Hamano wrote:
>>> 
>>> Atharva Raykar <raykar.ath@gmail.com> writes:
>>> 
>>> > I have attempted to make a version of this series that is based on that topic [2],
>>> > and added the superproject gitdir caching code in C [3]. It passes the tests,
>>> > but I am not too confident about its correctness. I hope that branch can be
>>> > helpful in some way.
>>> ..
>>> The "C rewrite" of the code [3] that unconditionally sets of the
>>> submodule.superprojectgitdir varible seems straightforward enough.
>>> 
>>> Emily, how solid do you think your "superproject aware submodule"
>>> topic already is?  Would it be stable enough to build other things
>>> on top, or is it a bit too premature?
>>
>> As of the version I sent today
>> (https://lore.kernel.org/git/20211014203416.2802639-1-emilyshaffer%40google.com)
>> I think it is stable enough to build on top of. There was general
>> consensus on the semantics of submodule.superprojectgitdir as it's sent
>> in v4.
>
> Does the above statement still hold true today?
>
> And more importantly, Atharva, are you on board with the plan Emily
> suggested to have this one built on top of her series?

I think the current state of it is that Emily & I were having a discussion about the semantics of that series. See [1] and [2].

That's still outstanding. I.e. I haven't been able to reproduce cases where we actually need this caching for performance reasons (which is the reason it exists), but even if we end up keeping it I'd think we'd want something picked from the RFC patches I sent[1] to test the "caching" v.s. "non-caching" behavior, and assert that they're the same.

1. https://lore.kernel.org/git/RFC-cover-0.2-00000000000-20211117T113134Z-avarab@gmail.com/
2. https://lore.kernel.org/git/211124.86a6hue2wk.gmgdl@evledraar.gmail.com/
Atharva Raykar· Dec 4, 2021, 10:38 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 0/9] submodule: convert the rest of 'update' to C

Junio C Hamano <gitster@pobox.com> writes:
Show 28 quoted lines
> Emily Shaffer <emilyshaffer@google.com> writes:
>
>> On Wed, Oct 13, 2021 at 05:05:58PM -0700, Junio C Hamano wrote:
>>>
>>> Atharva Raykar <raykar.ath@gmail.com> writes:
>>>
>>> > I have attempted to make a version of this series that is based on that topic [2],
>>> > and added the superproject gitdir caching code in C [3]. It passes the tests,
>>> > but I am not too confident about its correctness. I hope that branch can be
>>> > helpful in some way.
>>> ..
>>> The "C rewrite" of the code [3] that unconditionally sets of the
>>> submodule.superprojectgitdir varible seems straightforward enough.
>>>
>>> Emily, how solid do you think your "superproject aware submodule"
>>> topic already is?  Would it be stable enough to build other things
>>> on top, or is it a bit too premature?
>>
>> As of the version I sent today
>> (https://lore.kernel.org/git/20211014203416.2802639-1-emilyshaffer%40google.com)
>> I think it is stable enough to build on top of. There was general
>> consensus on the semantics of submodule.superprojectgitdir as it's sent
>> in v4.
>
> Does the above statement still hold true today?
>
> And more importantly, Atharva, are you on board with the plan Emily
> suggested to have this one built on top of her series?

Yes, I am on board with that. I have not been closely following their series, but the places where it clashes with this series are minimal, and look quite easy to port later to C. So I'm totally fine with it.

> Thanks.
Ævar Arnfjörð Bjarmason· Jan 27, 2022, 16:22 UTC · re: Atharva Raykar · lore

[PATCH v4 0/7] submodule: convert the rest of 'update' to C

This series rewrites "git submodule update" in C. It's a rebased and re-rolled version of v3[1] submitted back in October. Per [2] the author doesn't have time to push it forward now.

There was discussion in the thread at [2] about the interaction between this and es/superproject-aware-submodules.

Per [3] I think it would be better to get this in first, as the big outstanding question about the superproject aware submodules[3] was whether it was an optimization or not. It's currently an optimization, but whether it's needed is muddied by "git-submodule.sh" being a shellscript and invoking "git rev-parse" in a loop, which a version rebased on this could do in C.

Per the v3 CL of this series[1] there is a C version of the required code for es/superproject-aware-submodules, but when I tried to rebase it it failed because it's based on an older version of es/superproject-aware-submodules which linked to the .git dir rather than the working tree.

I.e. the C code[4] produces "../.git" in the tests, but we're expecting "../..". I couldn't find the right API to get that path from path.c et al, but it's presumably easy for someone more familiar with it.

As for changes in this v4:
 * Rebased on master, it didn't compile due to my changes to the
   refs_resolve_ref_unsafe() API.
 * There was some back & forth about large patches v.s. split-up
   commits in previous iterations. This version squashes several
   patches together, because I thought a rename/add/removal of code
   that's all related (and really can only be done as one logical step
   without a major rewrite) was easier to look at than having to jump
   back and forth in the series.
   The resulting diff is large, but I think it's better than looking
   at it piecemeal. You can see what new code replaces what old code.
 * There's new formatting-only changes in [56]/7 that I added to make
   the 7/7 diff a tad smaller.
   The gitster/ar/submodule-update~3..gitster/ar/submodule-update diff
   (the real meat of the series) is "402 insertions(+), 422
   deletions(-)", and 7/7 here is "389 insertions(+), 418
   deletions(-)".
   So it's not much, but thaht churn before 7/7 makes the big change a
   bit logical change at the end easier to reason about.
 * I fixed a strbuf and string_list memory leak in the new code, and
   simplified one codepath that freed correctly, but could use a "goto
   cleanup" pattern to do away with duplication.
   There's still a *lot* of memory leaks in
   builtin/submodule--helper.c, but they're all (well, to the extent
   that I can extract signal from the noise) in existing code, or in
   generic APIs that uses which leak.
1. https://lore.kernel.org/git/20211013051805.45662-1-raykar.ath@gmail.com/
2. https://lore.kernel.org/git/CADi-XoThCqfvPnBd0p6yAhrtotK_3z2pQQMugWPsYpHLbXge7w@mail.gmail.com/
3. https://lore.kernel.org/git/211203.86o85xqw7o.gmgdl@evledraar.gmail.com/
4. https://github.com/tfidfwastaken/git/blob/a74aaf2540c536970f2541d3042c825f82a69770/builtin/submodule--helper.c#L2922-L2930
Atharva Raykar (6):
  submodule--helper: get remote names from any repository
  submodule--helper: refactor get_submodule_displaypath()
  submodule--helper: allow setting superprefix for init_submodule()
  submodule--helper: run update using child process struct
  builtin/submodule--helper.c: reformat designated initializers
  submodule: move core cmd_update() logic to C
Ævar Arnfjörð Bjarmason (1):
  builtin/submodule--helper.c: rename "suc" variable to "opt"
 builtin/submodule--helper.c | 758 +++++++++++++++++++++---------------
 git-submodule.sh            | 145 +------
 2 files changed, 449 insertions(+), 454 deletions(-)
Range-diff against v3:
 1:  775f7c01ddb <  -:  ----------- submodule--helper: split up ensure_core_worktree()
 2:  fe3d30c1160 !  1:  1a0b1323cd7 submodule--helper: get remote names from any repository
    @@ Commit message
         Mentored-by: Shourya Shukla <periperidip@gmail.com>
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## builtin/submodule--helper.c ##
     @@
    @@ builtin/submodule--helper.c: static char *get_default_remote(void)
     +{
     +	const char *refname;
     +	struct repository subrepo;
    ++	int ignore_errno;
     +
     +	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
    -+					  "HEAD", 0, NULL, NULL);
    ++					  "HEAD", 0, NULL, NULL,
    ++					  &ignore_errno);
     +	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
     +	return repo_get_default_remote(&subrepo, refname);
     +}
 3:  8085571a7cb <  -:  ----------- submodule--helper: rename helpers for update-clone
 4:  18c551543ed !  2:  7e2df3ff220 submodule--helper: refactor get_submodule_displaypath()
    @@ Commit message
         Mentored-by: Shourya Shukla <periperidip@gmail.com>
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## builtin/submodule--helper.c ##
     @@ builtin/submodule--helper.c: static int resolve_relative_url_test(int argc, const char **argv, const char *pr
 5:  2be0453750a !  3:  f31fd72fba2 submodule--helper: allow setting superprefix for init_submodule()
    @@ Commit message
         Mentored-by: Shourya Shukla <periperidip@gmail.com>
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## builtin/submodule--helper.c ##
     @@ builtin/submodule--helper.c: static int module_foreach(int argc, const char **argv, const char *prefix)
    @@ builtin/submodule--helper.c: static int module_foreach(int argc, const char **ar
     +	const char *superprefix;
      	unsigned int flags;
      };
    --#define INIT_CB_INIT { NULL, 0 }
    -+#define INIT_CB_INIT { 0 }
    + #define INIT_CB_INIT { 0 }
      
      static void init_submodule(const char *path, const char *prefix,
     -			   unsigned int flags)
 6:  a882b53177d !  4:  76c5a826a80 submodule--helper: run update using child process struct
    @@ Commit message
         Mentored-by: Shourya Shukla <periperidip@gmail.com>
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## builtin/submodule--helper.c ##
     @@ builtin/submodule--helper.c: static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 -:  ----------- >  5:  29aa2fc0851 builtin/submodule--helper.c: reformat designated initializers
 -:  ----------- >  6:  d3ad6e7a351 builtin/submodule--helper.c: rename "suc" variable to "opt"
 7:  3092469f380 !  7:  02954603763 submodule: move core cmd_update() logic to C
    @@ Commit message
         `submodule--helper update`, with a modified `--recursive-prefix` and
         `--prefix` parameter.
     
    -    We also introduce `update_submodules()` and `update_submodule()` which
    -    are quite similar to `update_clone_submodules()` and
    -    `update_clone_submodule()`, and will supersede them.
    +    We also introduce `update_submodules2()` and `update_submodule2()`
    +    which will supersede `update_submodules()` and `update_submodule()`.
     
         When the `--init` flag is passed to the subcommand, we do not spawn a
         new subprocess and call `submodule--helper init` on the submodule paths,
    @@ Commit message
         Mentored-by: Shourya Shukla <periperidip@gmail.com>
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## builtin/submodule--helper.c ##
    +@@ builtin/submodule--helper.c: static char *get_default_remote(void)
    + 	return repo_get_default_remote(the_repository, refname);
    + }
    + 
    +-static int print_default_remote(int argc, const char **argv, const char *prefix)
    +-{
    +-	char *remote;
    +-
    +-	if (argc != 1)
    +-		die(_("submodule--helper print-default-remote takes no arguments"));
    +-
    +-	remote = get_default_remote();
    +-	if (remote)
    +-		printf("%s\n", remote);
    +-
    +-	free(remote);
    +-	return 0;
    +-}
    +-
    + static int starts_with_dot_slash(const char *str)
    + {
    + 	return str[0] == '.' && is_dir_sep(str[1]);
    +@@ builtin/submodule--helper.c: static void determine_submodule_update_strategy(struct repository *r,
    + 	free(key);
    + }
    + 
    +-static int module_update_module_mode(int argc, const char **argv, const char *prefix)
    +-{
    +-	const char *path, *update = NULL;
    +-	int just_cloned;
    +-	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
    +-
    +-	if (argc < 3 || argc > 4)
    +-		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
    +-
    +-	just_cloned = git_config_int("just_cloned", argv[1]);
    +-	path = argv[2];
    +-
    +-	if (argc == 4)
    +-		update = argv[3];
    +-
    +-	determine_submodule_update_strategy(the_repository,
    +-					    just_cloned, path, update,
    +-					    &update_strategy);
    +-	fputs(submodule_strategy_to_string(&update_strategy), stdout);
    +-
    +-	return 0;
    +-}
    +-
    + struct update_clone_data {
    + 	const struct submodule *sub;
    + 	struct object_id oid;
     @@ builtin/submodule--helper.c: struct submodule_update_clone {
      	const char *prefix;
      	int single_branch;
    @@ builtin/submodule--helper.c: struct submodule_update_clone {
      	const char *displaypath;
      	struct object_id oid;
      	struct object_id suboid;
    --	struct submodule_update_strategy update_strategy;
    + 	struct submodule_update_strategy update_strategy;
     +	int max_jobs;
      	int depth;
    +-	unsigned int force: 1;
    +-	unsigned int quiet: 1;
    +-	unsigned int nofetch: 1;
    +-	unsigned int just_cloned: 1;
     +	int recommend_shallow;
     +	int single_branch;
    -+	unsigned int init: 1;
    - 	unsigned int force: 1;
    - 	unsigned int quiet: 1;
    - 	unsigned int nofetch: 1;
    --	unsigned int just_cloned: 1;
    -+	unsigned int remote: 1;
    -+	unsigned int recursive: 1;
    -+	unsigned int progress: 1;
    -+	unsigned int dissociate: 1;
    -+	unsigned int require_init: 1;
    -+	unsigned warn_if_uninitialized : 1;
    -+	unsigned int just_cloned : 1;
    -+	struct submodule_update_strategy update_strategy;
    ++	unsigned int init;
    ++	unsigned int force;
    ++	unsigned int quiet;
    ++	unsigned int nofetch;
    ++	unsigned int remote;
    ++	unsigned int recursive;
    ++	unsigned int progress;
    ++	unsigned int dissociate;
    ++	unsigned int require_init;
    ++	unsigned warn_if_uninitialized ;
    ++	unsigned int just_cloned ;
     +	struct string_list references;
     +	struct module_list list;
      };
    --#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
    -+#define UPDATE_DATA_INIT { \
    + #define UPDATE_DATA_INIT { \
     +	.list = MODULE_LIST_INIT, \
    -+	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
    + 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
     +	.recommend_shallow = -1, \
     +	.references = STRING_LIST_INIT_DUP, \
     +	.single_branch = -1, \
    @@ builtin/submodule--helper.c: struct submodule_update_clone {
     +		for_each_string_list_item(item, &update_data->references)
     +			string_list_append(&suc->references, item->string);
     +	}
    -+}
    + }
      
      static void next_submodule_warn_missing(struct submodule_update_clone *suc,
    - 		struct strbuf *out, const char *displaypath)
     @@ builtin/submodule--helper.c: static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
      	return run_command(&cp);
      }
    @@ builtin/submodule--helper.c: static int run_update_command(struct update_data *u
     +		}
     +
     +		/* the command failed, but update must continue */
    -+		string_list_append(err, out.buf);
    ++		string_list_append_nodup(err, strbuf_detach(&out, NULL));
     +		return 1;
     +	}
     +
    @@ builtin/submodule--helper.c: static int run_update_command(struct update_data *u
     -		    submodule_strategy_to_string(&ud->update_strategy));
      	}
      
    ++	strbuf_release(&out);
      	return 0;
      }
      
     -static int do_run_update_procedure(struct update_data *ud)
    -+static int do_run_update_procedure(struct update_data *ud, struct string_list *err)
    ++static int run_update_procedure(struct update_data *ud, struct string_list *err)
      {
      	int subforce = is_null_oid(&ud->suboid) || ud->force;
      
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
      	}
      
     -	return run_update_command(ud, subforce);
    +-}
    +-
    +-static void update_submodule(struct update_clone_data *ucd)
    +-{
    +-	fprintf(stdout, "dummy %s %d\t%s\n",
    +-		oid_to_hex(&ucd->oid),
    +-		ucd->just_cloned,
    +-		ucd->sub->path);
    +-}
    +-
    +-static int update_submodules(struct submodule_update_clone *suc)
    +-{
    +-	int i;
    +-
    +-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
    +-				   update_clone_start_failure,
    +-				   update_clone_task_finished, suc, "submodule",
    +-				   "parallel/update");
    +-
    +-	/*
    +-	 * We saved the output and put it out all at once now.
    +-	 * That means:
    +-	 * - the listener does not have to interleave their (checkout)
    +-	 *   work with our fetching.  The writes involved in a
    +-	 *   checkout involve more straightforward sequential I/O.
    +-	 * - the listener can avoid doing any work if fetching failed.
    +-	 */
    +-	if (suc->quickstop)
    +-		return 1;
    +-
    +-	for (i = 0; i < suc->update_clone_nr; i++)
    +-		update_submodule(&suc->update_clone[i]);
    +-
    +-	return 0;
    +-}
    +-
    +-static int update_clone(int argc, const char **argv, const char *prefix)
    +-{
    +-	const char *update = NULL;
    +-	struct pathspec pathspec;
    +-	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
    +-
    +-	struct option module_update_clone_options[] = {
    +-		OPT_STRING(0, "prefix", &prefix,
    +-			   N_("path"),
    +-			   N_("path into the working tree")),
    +-		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
    +-			   N_("path"),
    +-			   N_("path into the working tree, across nested "
    +-			      "submodule boundaries")),
    +-		OPT_STRING(0, "update", &update,
    +-			   N_("string"),
    +-			   N_("rebase, merge, checkout or none")),
    +-		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
    +-			   N_("reference repository")),
    +-		OPT_BOOL(0, "dissociate", &opt.dissociate,
    +-			   N_("use --reference only while cloning")),
    +-		OPT_STRING(0, "depth", &opt.depth, "<depth>",
    +-			   N_("create a shallow clone truncated to the "
    +-			      "specified number of revisions")),
    +-		OPT_INTEGER('j', "jobs", &opt.max_jobs,
    +-			    N_("parallel jobs")),
    +-		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
    +-			    N_("whether the initial clone should follow the shallow recommendation")),
    +-		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
    +-		OPT_BOOL(0, "progress", &opt.progress,
    +-			    N_("force cloning progress")),
    +-		OPT_BOOL(0, "require-init", &opt.require_init,
    +-			   N_("disallow cloning into non-empty directory")),
    +-		OPT_BOOL(0, "single-branch", &opt.single_branch,
    +-			 N_("clone only one branch, HEAD or --branch")),
    +-		OPT_END()
    +-	};
    +-
    +-	const char *const git_submodule_helper_usage[] = {
    +-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
    +-		NULL
    +-	};
    +-	opt.prefix = prefix;
    +-
    +-	update_clone_config_from_gitmodules(&opt.max_jobs);
    +-	git_config(git_update_clone_config, &opt.max_jobs);
    +-
    +-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
    +-			     git_submodule_helper_usage, 0);
    +-
    +-	if (update)
    +-		if (parse_submodule_update_strategy(update, &opt.update) < 0)
    +-			die(_("bad value for update parameter"));
    +-
    +-	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
    +-		return 1;
    +-
    +-	if (pathspec.nr)
    +-		opt.warn_if_uninitialized = 1;
    +-
    +-	return update_submodules(&opt);
    +-}
    +-
    +-static int run_update_procedure(int argc, const char **argv, const char *prefix)
    +-{
    +-	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
    +-	char *prefixed_path, *update = NULL;
    +-	struct update_data update_data = UPDATE_DATA_INIT;
    +-
    +-	struct option options[] = {
    +-		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
    +-		OPT__FORCE(&force, N_("force checkout updates"), 0),
    +-		OPT_BOOL('N', "no-fetch", &nofetch,
    +-			 N_("don't fetch new objects from the remote site")),
    +-		OPT_BOOL(0, "just-cloned", &just_cloned,
    +-			 N_("overrides update mode in case the repository is a fresh clone")),
    +-		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
    +-		OPT_STRING(0, "prefix", &prefix,
    +-			   N_("path"),
    +-			   N_("path into the working tree")),
    +-		OPT_STRING(0, "update", &update,
    +-			   N_("string"),
    +-			   N_("rebase, merge, checkout or none")),
    +-		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
    +-			   N_("path into the working tree, across nested "
    +-			      "submodule boundaries")),
    +-		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
    +-			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
    +-			       parse_opt_object_id),
    +-		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
    +-			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
    +-			       parse_opt_object_id),
    +-		OPT_END()
    +-	};
    +-
    +-	const char *const usage[] = {
    +-		N_("git submodule--helper run-update-procedure [<options>] <path>"),
    +-		NULL
    +-	};
    +-
    +-	argc = parse_options(argc, argv, prefix, options, usage, 0);
    +-
    +-	if (argc != 1)
    +-		usage_with_options(usage, options);
    +-
    +-	update_data.force = !!force;
    +-	update_data.quiet = !!quiet;
    +-	update_data.nofetch = !!nofetch;
    +-	update_data.just_cloned = !!just_cloned;
    +-	update_data.sm_path = argv[0];
    +-
    +-	if (update_data.recursive_prefix)
    +-		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
    +-	else
    +-		prefixed_path = xstrdup(update_data.sm_path);
    +-
    +-	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
    +-
    +-	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
    +-					    update_data.sm_path, update,
    +-					    &update_data.update_strategy);
    +-
    +-	free(prefixed_path);
    +-
    +-	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
    +-		return do_run_update_procedure(&update_data);
    +-
    +-	return 3;
    +-}
    +-
    +-static int resolve_relative_path(int argc, const char **argv, const char *prefix)
    +-{
    +-	struct strbuf sb = STRBUF_INIT;
    +-	if (argc != 3)
    +-		die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
    +-
    +-	printf("%s", relative_path(argv[1], argv[2], &sb));
    +-	strbuf_release(&sb);
    +-	return 0;
     +	return run_update_command(ud, subforce, err);
      }
      
    - static void update_clone_submodule(struct update_clone_data *ucd)
    -@@ builtin/submodule--helper.c: static int run_update_procedure(int argc, const char **argv, const char *prefix)
    - 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
    - 	char *prefixed_path, *update = NULL;
    - 	struct update_data update_data = UPDATE_DATA_INIT;
    -+	struct string_list err = STRING_LIST_INIT_DUP;
    + static const char *remote_submodule_branch(const char *path)
    +@@ builtin/submodule--helper.c: static int push_check(int argc, const char **argv, const char *prefix)
    + 	return 0;
    + }
      
    - 	struct option options[] = {
    - 		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
    -@@ builtin/submodule--helper.c: static int run_update_procedure(int argc, const char **argv, const char *prefix)
    - 	free(prefixed_path);
    +-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
    ++static void ensure_core_worktree(const char *path)
    + {
    +-	const char *path;
    + 	const char *cw;
    + 	struct repository subrepo;
      
    - 	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
    --		return do_run_update_procedure(&update_data);
    -+		return do_run_update_procedure(&update_data, &err);
    +-	if (argc != 2)
    +-		BUG("submodule--helper ensure-core-worktree <path>");
    +-
    +-	path = argv[1];
    +-
    + 	if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
    + 		die(_("could not get a repository handle for submodule '%s'"), path);
      
    - 	return 3;
    +@@ builtin/submodule--helper.c: static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
    + 		free(abs_path);
    + 		strbuf_release(&sb);
    + 	}
    +-
    +-	return 0;
      }
    + 
    + static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
     @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char **argv, const char *prefix)
      	return !!ret;
      }
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +	char *prefixed_path;
     +	struct string_list err = STRING_LIST_INIT_DUP;
     +
    -+	do_ensure_core_worktree(update_data->sm_path);
    ++	ensure_core_worktree(update_data->sm_path);
     +
     +	if (update_data->recursive_prefix)
     +		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +		char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch);
     +
     +		if (!update_data->nofetch) {
    -+			if(fetch_in_submodule(update_data->sm_path, update_data->depth,
    ++			if (fetch_in_submodule(update_data->sm_path, update_data->depth,
     +					      0, NULL))
     +				die(_("Unable to fetch in submodule path '%s'"),
     +				    update_data->sm_path);
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +	}
     +
     +	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
    -+		if (do_run_update_procedure(update_data, &err))
    ++		if (run_update_procedure(update_data, &err))
     +			return 1;
     +
     +	if (update_data->recursive) {
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +		struct string_list_item *item;
     +		for_each_string_list_item(item, &err)
     +			fputs(item->string, stderr);
    ++		string_list_clear(&err, 0);
     +		return 1;
     +	}
     +
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +	 * - the listener can avoid doing any work if fetching failed.
     +	 */
     +	if (suc.quickstop) {
    -+		string_list_clear(&update_data->references, 0);
    -+		return 1;
    ++		res = 1;
    ++		goto cleanup;
     +	}
     +
     +	for (i = 0; i < suc.update_clone_nr; i++) {
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +			res = 1;
     +	}
     +
    ++cleanup:
     +	string_list_clear(&update_data->references, 0);
     +	return res;
     +}
     +
     +static int module_update(int argc, const char **argv, const char *prefix)
     +{
    -+	int init = 0, force = 0, quiet = 0, nofetch = 0;
    -+	int remote = 0, recursive = 0, dissociate = 0;
    -+	int progress = 0, require_init = 0;
     +	const char *update = NULL;
     +	struct pathspec pathspec;
    -+	struct update_data update_data = UPDATE_DATA_INIT;
    ++	struct update_data opt = UPDATE_DATA_INIT;
     +
     +	struct option module_update_clone_options[] = {
    -+		OPT__FORCE(&force, N_("force checkout updates"), 0),
    -+		OPT_BOOL(0, "init", &init,
    ++		OPT__FORCE(&opt.force, N_("force checkout updates"), 0),
    ++		OPT_BOOL(0, "init", &opt.init,
     +			 N_("initialize uninitialized submodules before update")),
    -+		OPT_BOOL(0, "remote", &remote,
    ++		OPT_BOOL(0, "remote", &opt.remote,
     +			 N_("use SHA-1 of submodule's remote tracking branch")),
    -+		OPT_BOOL(0, "recursive", &recursive,
    ++		OPT_BOOL(0, "recursive", &opt.recursive,
     +			 N_("traverse submodules recursively")),
    -+		OPT_BOOL('N', "no-fetch", &nofetch,
    ++		OPT_BOOL('N', "no-fetch", &opt.nofetch,
     +			 N_("don't fetch new objects from the remote site")),
    -+		OPT_STRING(0, "prefix", &prefix,
    ++		OPT_STRING(0, "prefix", &opt.prefix,
     +			   N_("path"),
     +			   N_("path into the working tree")),
    -+		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix,
    ++		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
     +			   N_("path"),
     +			   N_("path into the working tree, across nested "
     +			      "submodule boundaries")),
     +		OPT_STRING(0, "update", &update,
     +			   N_("string"),
     +			   N_("rebase, merge, checkout or none")),
    -+		OPT_STRING_LIST(0, "reference", &update_data.references, N_("repo"),
    ++		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
     +				N_("reference repository")),
    -+		OPT_BOOL(0, "dissociate", &dissociate,
    ++		OPT_BOOL(0, "dissociate", &opt.dissociate,
     +			 N_("use --reference only while cloning")),
    -+		OPT_INTEGER(0, "depth", &update_data.depth,
    ++		OPT_INTEGER(0, "depth", &opt.depth,
     +			    N_("create a shallow clone truncated to the "
     +			       "specified number of revisions")),
    -+		OPT_INTEGER('j', "jobs", &update_data.max_jobs,
    ++		OPT_INTEGER('j', "jobs", &opt.max_jobs,
     +			    N_("parallel jobs")),
    -+		OPT_BOOL(0, "recommend-shallow", &update_data.recommend_shallow,
    ++		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
     +			 N_("whether the initial clone should follow the shallow recommendation")),
    -+		OPT__QUIET(&quiet, N_("don't print cloning progress")),
    -+		OPT_BOOL(0, "progress", &progress,
    ++		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
    ++		OPT_BOOL(0, "progress", &opt.progress,
     +			 N_("force cloning progress")),
    -+		OPT_BOOL(0, "require-init", &require_init,
    ++		OPT_BOOL(0, "require-init", &opt.require_init,
     +			 N_("disallow cloning into non-empty directory")),
    -+		OPT_BOOL(0, "single-branch", &update_data.single_branch,
    ++		OPT_BOOL(0, "single-branch", &opt.single_branch,
     +			 N_("clone only one branch, HEAD or --branch")),
     +		OPT_END()
     +	};
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +		NULL
     +	};
     +
    -+	update_clone_config_from_gitmodules(&update_data.max_jobs);
    -+	git_config(git_update_clone_config, &update_data.max_jobs);
    ++	update_clone_config_from_gitmodules(&opt.max_jobs);
    ++	git_config(git_update_clone_config, &opt.max_jobs);
     +
     +	argc = parse_options(argc, argv, prefix, module_update_clone_options,
     +			     git_submodule_helper_usage, 0);
    -+	update_data.prefix = prefix;
    -+
    -+	update_data.force = !!force;
    -+	update_data.quiet = !!quiet;
    -+	update_data.nofetch = !!nofetch;
    -+	update_data.init = !!init;
    -+	update_data.require_init = !!require_init;
    -+	update_data.remote = !!remote;
    -+	update_data.recursive = !!recursive;
    -+	update_data.progress = !!progress;
    -+	update_data.dissociate = !!dissociate;
    -+	oidcpy(&update_data.oid, null_oid());
    -+	oidcpy(&update_data.suboid, null_oid());
    ++	oidcpy(&opt.oid, null_oid());
    ++	oidcpy(&opt.suboid, null_oid());
     +
     +	if (update)
     +		if (parse_submodule_update_strategy(update,
    -+						    &update_data.update_strategy) < 0)
    ++						    &opt.update_strategy) < 0)
     +			die(_("bad value for update parameter"));
     +
    -+	if (module_list_compute(argc, argv, prefix, &pathspec, &update_data.list) < 0)
    ++	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
     +		return 1;
     +
     +	if (pathspec.nr)
    -+		update_data.warn_if_uninitialized = 1;
    ++		opt.warn_if_uninitialized = 1;
     +
    -+	if (update_data.init) {
    ++	if (opt.init) {
     +		struct module_list list = MODULE_LIST_INIT;
     +		struct init_cb info = INIT_CB_INIT;
     +
    -+		if (module_list_compute(argc, argv, update_data.prefix,
    ++		if (module_list_compute(argc, argv, opt.prefix,
     +					&pathspec, &list) < 0)
     +			return 1;
     +
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +		if (!argc && git_config_get_value_multi("submodule.active"))
     +			module_list_active(&list);
     +
    -+		info.prefix = update_data.prefix;
    -+		info.superprefix = update_data.recursive_prefix;
    -+		if (update_data.quiet)
    ++		info.prefix = opt.prefix;
    ++		info.superprefix = opt.recursive_prefix;
    ++		if (opt.quiet)
     +			info.flags |= OPT_QUIET;
     +
     +		for_each_listed_submodule(&list, init_submodule_cb, &info);
     +	}
     +
    -+	return update_submodules(&update_data);
    ++	return update_submodules(&opt);
     +}
     +
      struct add_data {
      	const char *prefix;
      	const char *branch;
    -@@ builtin/submodule--helper.c: static int module_add(int argc, const char **argv, const char *prefix)
    - 	return 0;
    - }
    - 
    -+
    - #define SUPPORT_SUPER_PREFIX (1<<0)
    - 
    - struct cmd_struct {
     @@ builtin/submodule--helper.c: static struct cmd_struct commands[] = {
      	{"name", module_name, 0},
      	{"clone", module_clone, 0},
      	{"add", module_add, SUPPORT_SUPER_PREFIX},
    +-	{"update-module-mode", module_update_module_mode, 0},
    +-	{"update-clone", update_clone, 0},
    +-	{"run-update-procedure", run_update_procedure, 0},
    +-	{"ensure-core-worktree", ensure_core_worktree, 0},
    +-	{"relative-path", resolve_relative_path, 0},
     +	{"update", module_update, 0},
    - 	{"update-module-mode", module_update_module_mode, 0},
    - 	{"update-clone", update_clone, 0},
    - 	{"run-update-procedure", run_update_procedure, 0},
    + 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
    + 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
    + 	{"init", module_init, SUPPORT_SUPER_PREFIX},
    + 	{"status", module_status, SUPPORT_SUPER_PREFIX},
    +-	{"print-default-remote", print_default_remote, 0},
    + 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
    + 	{"deinit", module_deinit, 0},
    + 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
     
      ## git-submodule.sh ##
     @@ git-submodule.sh: cmd_deinit()
 8:  cee4beb7abe <  -:  ----------- submodule--helper: remove unused helpers
 9:  2194c1729f1 <  -:  ----------- submodule--helper: rename helper functions
-- 
2.35.0.894.g563b84683b9
Ævar Arnfjörð Bjarmason· Jan 27, 2022, 16:22 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v4 1/7] submodule--helper: get remote names from any repository

From: Atharva Raykar <raykar.ath@gmail.com>

`get_default_remote()` retrieves the name of a remote by resolving the refs from of the current repository's ref store.

Thus in order to use it for retrieving the remote name of a submodule, we have to start a new subprocess which runs from the submodule directory.

Let's instead introduce a function called `repo_get_default_remote()` which takes any repository object and retrieves the remote accordingly.

`get_default_remote()` is then defined as a call to `repo_get_default_remote()` with 'the_repository' passed to it.

Now that we have `repo_get_default_remote()`, we no longer have to start a subprocess that called `submodule--helper get-default-remote` from within the submodule directory.

So let's make a function called `get_default_remote_submodule()` which takes a submodule path, and returns the default remote for that submodule, all within the same process.

We can now use this function to save an unnecessary subprocess spawn in `sync_submodule()`, and also in the next patch, which will require this functionality.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 41 +++++++++++++++++++++++--------------
 1 file changed, 26 insertions(+), 15 deletions(-)
Show changes to builtin/submodule--helper.c +26 −15
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c5d3fc3817f..965260edb22 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -29,11 +29,10 @@
 typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
 				  void *cb_data);
 
-static char *get_default_remote(void)
+static char *repo_get_default_remote(struct repository *repo, const char *refname)
 {
 	char *dest = NULL, *ret;
 	struct strbuf sb = STRBUF_INIT;
-	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
 
 	if (!refname)
 		die(_("No such ref: %s"), "HEAD");
@@ -46,7 +45,7 @@ static char *get_default_remote(void)
 		die(_("Expecting a full ref name, got %s"), refname);
 
 	strbuf_addf(&sb, "branch.%s.remote", refname);
-	if (git_config_get_string(sb.buf, &dest))
+	if (repo_config_get_string(repo, sb.buf, &dest))
 		ret = xstrdup("origin");
 	else
 		ret = dest;
@@ -55,6 +54,25 @@ static char *get_default_remote(void)
 	return ret;
 }
 
+static char *get_default_remote_submodule(const char *module_path)
+{
+	const char *refname;
+	struct repository subrepo;
+	int ignore_errno;
+
+	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
+					  "HEAD", 0, NULL, NULL,
+					  &ignore_errno);
+	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
+	return repo_get_default_remote(&subrepo, refname);
+}
+
+static char *get_default_remote(void)
+{
+	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+	return repo_get_default_remote(the_repository, refname);
+}
+
 static int print_default_remote(int argc, const char **argv, const char *prefix)
 {
 	char *remote;
@@ -1341,9 +1359,8 @@ static void sync_submodule(const char *path, const char *prefix,
 {
 	const struct submodule *sub;
 	char *remote_key = NULL;
-	char *sub_origin_url, *super_config_url, *displaypath;
+	char *sub_origin_url, *super_config_url, *displaypath, *default_remote;
 	struct strbuf sb = STRBUF_INIT;
-	struct child_process cp = CHILD_PROCESS_INIT;
 	char *sub_config_path = NULL;
 
 	if (!is_submodule_active(the_repository, path))
@@ -1382,21 +1399,15 @@ static void sync_submodule(const char *path, const char *prefix,
 	if (!is_submodule_populated_gently(path, NULL))
 		goto cleanup;
 
-	prepare_submodule_repo_env(&cp.env_array);
-	cp.git_cmd = 1;
-	cp.dir = path;
-	strvec_pushl(&cp.args, "submodule--helper",
-		     "print-default-remote", NULL);
-
 	strbuf_reset(&sb);
-	if (capture_command(&cp, &sb, 0))
+	default_remote = get_default_remote_submodule(path);
+	if (!default_remote)
 		die(_("failed to get the default remote for submodule '%s'"),
 		      path);
 
-	strbuf_strip_suffix(&sb, "\n");
-	remote_key = xstrfmt("remote.%s.url", sb.buf);
+	remote_key = xstrfmt("remote.%s.url", default_remote);
+	free(default_remote);
 
-	strbuf_reset(&sb);
 	submodule_to_gitdir(&sb, path);
 	strbuf_addstr(&sb, "/config");
 
-- 
2.35.0.894.g563b84683b9
Glen Choo· Jan 27, 2022, 18:45 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v4 1/7] submodule--helper: get remote names from any repository

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

This wasn't introduced by you (it was introduced in v1 [1]), but I think it's worth pointing out.

Show 5 quoted lines
> Let's instead introduce a function called `repo_get_default_remote()`
> which takes any repository object and retrieves the remote accordingly.
>
> `get_default_remote()` is then defined as a call to
> `repo_get_default_remote()` with 'the_repository' passed to it.

We say this, suggesting that repo_get_default_remote()'s signature is just get_default_remote()'s plus a "struct repository *" (like most repo_*). But..

Show 49 quoted lines
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index c5d3fc3817f..965260edb22 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -29,11 +29,10 @@
>  typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
>  				  void *cb_data);
>  
> -static char *get_default_remote(void)
> +static char *repo_get_default_remote(struct repository *repo, const char *refname)
>  {
>  	char *dest = NULL, *ret;
>  	struct strbuf sb = STRBUF_INIT;
> -	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
>  
>  	if (!refname)
>  		die(_("No such ref: %s"), "HEAD");
> @@ -46,7 +45,7 @@ static char *get_default_remote(void)
>  		die(_("Expecting a full ref name, got %s"), refname);
>  
>  	strbuf_addf(&sb, "branch.%s.remote", refname);
> -	if (git_config_get_string(sb.buf, &dest))
> +	if (repo_config_get_string(repo, sb.buf, &dest))
>  		ret = xstrdup("origin");
>  	else
>  		ret = dest;
> @@ -55,6 +54,25 @@ static char *get_default_remote(void)
>  	return ret;
>  }
>  
> +static char *get_default_remote_submodule(const char *module_path)
> +{
> +	const char *refname;
> +	struct repository subrepo;
> +	int ignore_errno;
> +
> +	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
> +					  "HEAD", 0, NULL, NULL,
> +					  &ignore_errno);
> +	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
> +	return repo_get_default_remote(&subrepo, refname);
> +}
> +
> +static char *get_default_remote(void)
> +{
> +	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
> +	return repo_get_default_remote(the_repository, refname);
> +}
> +
repo_get_default_remote() actually take yet another argument - refname.

It looks to me that repo_get_default_remote() shouldn't take the refname argument at all and that we should be using refs_resolve_ref_unsafe() instead, like:

  +static char *repo_get_default_remote(struct repository *repo)
    {
    char *dest = NULL, *ret;
    struct strbuf sb = STRBUF_INIT;
  -	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
  +	const char *refname = refs_resolve_ref_unsafe(
  +   get_main_ref_store(repo), "HEAD", 0, NULL, NULL /*, errno? */);
this makes the rest of the code a lot cleaner..
  +static char *get_default_remote_submodule(const char *module_path)
  +{
  +	struct repository subrepo;
  +
  +	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
  +	return repo_get_default_remote(&subrepo);
  +}
  +
  +static char *get_default_remote(void)
  +{
  +  return repo_get_default_remote(the_repository);
  +}

And because it's quite idiomatic to initialize the subrepo struct in order to can call repo_* functions, we could even drop get_default_remote_submodule() altogether.

As for why this wasn't the original approach, the only reason I can think of is that we didn't realize get_main_ref_store(subrepo) was an option.

[1] https://lore.kernel.org/git/20210907115932.36068-3-raykar.ath@gmail.com/
Ævar Arnfjörð Bjarmason· Jan 27, 2022, 16:22 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v4 2/7] submodule--helper: refactor get_submodule_displaypath()

From: Atharva Raykar <raykar.ath@gmail.com>

We create a function called `do_get_submodule_displaypath()` that generates the display path required by several submodule functions, and takes a custom superprefix parameter, instead of reading it from the environment.

We then redefine the existing `get_submodule_displaypath()` function as a call to this new function, where the superprefix is obtained from the environment.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +8 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 965260edb22..d38a64c7b7d 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -263,11 +263,8 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr
 	return 0;
 }
 
-/* the result should be freed by the caller. */
-static char *get_submodule_displaypath(const char *path, const char *prefix)
+static char *do_get_submodule_displaypath(const char *path, const char *prefix, const char *super_prefix)
 {
-	const char *super_prefix = get_super_prefix();
-
 	if (prefix && super_prefix) {
 		BUG("cannot have prefix '%s' and superprefix '%s'",
 		    prefix, super_prefix);
@@ -283,6 +280,13 @@ static char *get_submodule_displaypath(const char *path, const char *prefix)
 	}
 }
 
+/* the result should be freed by the caller. */
+static char *get_submodule_displaypath(const char *path, const char *prefix)
+{
+	const char *super_prefix = get_super_prefix();
+	return do_get_submodule_displaypath(path, prefix, super_prefix);
+}
+
 static char *compute_rev_name(const char *sub_path, const char* object_id)
 {
 	struct strbuf sb = STRBUF_INIT;
-- 
2.35.0.894.g563b84683b9
Ævar Arnfjörð Bjarmason· Jan 27, 2022, 16:22 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v4 3/7] submodule--helper: allow setting superprefix for init_submodule()

From: Atharva Raykar <raykar.ath@gmail.com>

We allow callers of the `init_submodule()` function to optionally override the superprefix from the environment.

We need to enable this option because in our conversion of the update command that will follow, the '--init' option will be handled through this API. We will need to change the superprefix at that time to ensure the display paths show correctly in the output messages.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)
Show changes to builtin/submodule--helper.c +7 −3
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index d38a64c7b7d..f8e18820ace 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -608,18 +608,22 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
 
 struct init_cb {
 	const char *prefix;
+	const char *superprefix;
 	unsigned int flags;
 };
 #define INIT_CB_INIT { 0 }
 
 static void init_submodule(const char *path, const char *prefix,
-			   unsigned int flags)
+			   const char *superprefix, unsigned int flags)
 {
 	const struct submodule *sub;
 	struct strbuf sb = STRBUF_INIT;
 	char *upd = NULL, *url = NULL, *displaypath;
 
-	displaypath = get_submodule_displaypath(path, prefix);
+	/* try superprefix from the environment, if it is not passed explicitly */
+	if (!superprefix)
+		superprefix = get_super_prefix();
+	displaypath = do_get_submodule_displaypath(path, prefix, superprefix);
 
 	sub = submodule_from_path(the_repository, null_oid(), path);
 
@@ -693,7 +697,7 @@ static void init_submodule(const char *path, const char *prefix,
 static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
 {
 	struct init_cb *info = cb_data;
-	init_submodule(list_item->name, info->prefix, info->flags);
+	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
 }
 
 static int module_init(int argc, const char **argv, const char *prefix)
-- 
2.35.0.894.g563b84683b9
Ævar Arnfjörð Bjarmason· Jan 27, 2022, 16:22 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v4 4/7] submodule--helper: run update using child process struct

From: Atharva Raykar <raykar.ath@gmail.com>

We switch to using the run-command API function that takes a 'struct child process', since we are using a lot of the options. This will also make it simple to switch over to using 'capture_command()' when we start handling the output of the command completely in C.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 34 ++++++++++++++++------------------
 1 file changed, 16 insertions(+), 18 deletions(-)
Show changes to builtin/submodule--helper.c +16 −18
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f8e18820ace..328c6cc87cd 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2346,47 +2346,45 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 
 static int run_update_command(struct update_data *ud, int subforce)
 {
-	struct strvec args = STRVEC_INIT;
-	struct strvec child_env = STRVEC_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
 	int must_die_on_failure = 0;
-	int git_cmd;
 
 	switch (ud->update_strategy.type) {
 	case SM_UPDATE_CHECKOUT:
-		git_cmd = 1;
-		strvec_pushl(&args, "checkout", "-q", NULL);
+		cp.git_cmd = 1;
+		strvec_pushl(&cp.args, "checkout", "-q", NULL);
 		if (subforce)
-			strvec_push(&args, "-f");
+			strvec_push(&cp.args, "-f");
 		break;
 	case SM_UPDATE_REBASE:
-		git_cmd = 1;
-		strvec_push(&args, "rebase");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "rebase");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_MERGE:
-		git_cmd = 1;
-		strvec_push(&args, "merge");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "merge");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_COMMAND:
-		git_cmd = 0;
-		strvec_push(&args, ud->update_strategy.command);
+		cp.use_shell = 1;
+		strvec_push(&cp.args, ud->update_strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
 		    submodule_strategy_to_string(&ud->update_strategy));
 	}
-	strvec_push(&args, oid);
+	strvec_push(&cp.args, oid);
 
-	prepare_submodule_repo_env(&child_env);
-	if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
-				     ud->sm_path, child_env.v)) {
+	cp.dir = xstrdup(ud->sm_path);
+	prepare_submodule_repo_env(&cp.env_array);
+	if (run_command(&cp)) {
 		switch (ud->update_strategy.type) {
 		case SM_UPDATE_CHECKOUT:
 			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-- 
2.35.0.894.g563b84683b9
Ævar Arnfjörð Bjarmason· Jan 27, 2022, 16:22 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v4 5/7] builtin/submodule--helper.c: reformat designated initializers

From: Atharva Raykar <raykar.ath@gmail.com>

The second hunk here will make a subsequent commit's diff smaller, and let's do the first and third hunks while we're at it so that we consistently format all of these.

Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)
Show changes to builtin/submodule--helper.c +10 −3
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 328c6cc87cd..1553b318cc7 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1656,7 +1656,10 @@ struct module_clone_data {
 	unsigned int require_init: 1;
 	int single_branch;
 };
-#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 }
+#define MODULE_CLONE_DATA_INIT { \
+	.reference = STRING_LIST_INIT_NODUP, \
+	.single_branch = -1, \
+}
 
 struct submodule_alternate_setup {
 	const char *submodule_name;
@@ -2049,7 +2052,9 @@ struct update_data {
 	unsigned int nofetch: 1;
 	unsigned int just_cloned: 1;
 };
-#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
+#define UPDATE_DATA_INIT { \
+	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+}
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
 		struct strbuf *out, const char *displaypath)
@@ -3015,7 +3020,9 @@ struct add_data {
 	unsigned int progress: 1;
 	unsigned int dissociate: 1;
 };
-#define ADD_DATA_INIT { .depth = -1 }
+#define ADD_DATA_INIT { \
+	.depth = -1, \
+}
 
 static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path)
 {
-- 
2.35.0.894.g563b84683b9
Ævar Arnfjörð Bjarmason· Jan 27, 2022, 16:22 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v4 6/7] builtin/submodule--helper.c: rename "suc" variable to "opt"

Rename the "suc" variable in "builtin/submodule--helper.c" to "opt". The only reason for this change is to make the subsequent commit's diff smaller, by doing this rename we can "anchor" the diff better, as it "borrow" most of the options declared here as-is as far as the diff rename detection is concerned.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)
Show changes to builtin/submodule--helper.c +18 −18
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 1553b318cc7..a96976b1772 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2519,36 +2519,36 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 {
 	const char *update = NULL;
 	struct pathspec pathspec;
-	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
 
 	struct option module_update_clone_options[] = {
 		OPT_STRING(0, "prefix", &prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
 			   N_("path"),
 			   N_("path into the working tree, across nested "
 			      "submodule boundaries")),
 		OPT_STRING(0, "update", &update,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
+		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
 			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &suc.dissociate,
+		OPT_BOOL(0, "dissociate", &opt.dissociate,
 			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &suc.depth, "<depth>",
+		OPT_STRING(0, "depth", &opt.depth, "<depth>",
 			   N_("create a shallow clone truncated to the "
 			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &suc.max_jobs,
+		OPT_INTEGER('j', "jobs", &opt.max_jobs,
 			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
+		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
 			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &suc.progress,
+		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &opt.progress,
 			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &suc.require_init,
+		OPT_BOOL(0, "require-init", &opt.require_init,
 			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &suc.single_branch,
+		OPT_BOOL(0, "single-branch", &opt.single_branch,
 			 N_("clone only one branch, HEAD or --branch")),
 		OPT_END()
 	};
@@ -2557,25 +2557,25 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
 		NULL
 	};
-	suc.prefix = prefix;
+	opt.prefix = prefix;
 
-	update_clone_config_from_gitmodules(&suc.max_jobs);
-	git_config(git_update_clone_config, &suc.max_jobs);
+	update_clone_config_from_gitmodules(&opt.max_jobs);
+	git_config(git_update_clone_config, &opt.max_jobs);
 
 	argc = parse_options(argc, argv, prefix, module_update_clone_options,
 			     git_submodule_helper_usage, 0);
 
 	if (update)
-		if (parse_submodule_update_strategy(update, &suc.update) < 0)
+		if (parse_submodule_update_strategy(update, &opt.update) < 0)
 			die(_("bad value for update parameter"));
 
-	if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
+	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
 		return 1;
 
 	if (pathspec.nr)
-		suc.warn_if_uninitialized = 1;
+		opt.warn_if_uninitialized = 1;
 
-	return update_submodules(&suc);
+	return update_submodules(&opt);
 }
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
-- 
2.35.0.894.g563b84683b9
Ævar Arnfjörð Bjarmason· Jan 27, 2022, 16:22 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v4 7/7] submodule: move core cmd_update() logic to C

From: Atharva Raykar <raykar.ath@gmail.com>

This patch completes the conversion past the flag parsing of `submodule update` by introducing a helper subcommand called `submodule--helper update`. The behaviour of `submodule update` should remain the same after this patch.

We add more fields to the `struct update_data` that are required by `struct submodule_update_clone` to be able to perform a clone, when that is needed to be done.

Recursing on a submodule is done by calling a subprocess that launches `submodule--helper update`, with a modified `--recursive-prefix` and `--prefix` parameter.

We also introduce `update_submodules2()` and `update_submodule2()` which will supersede `update_submodules()` and `update_submodule()`.

When the `--init` flag is passed to the subcommand, we do not spawn a new subprocess and call `submodule--helper init` on the submodule paths, because the Git machinery is not able to pick up the configuration changes introduced by that init call[1]. So we instead run the `init_submodule_cb()` callback over each submodule in the same process.

While we are at it, we also remove the fetch_in_submodule() shell function since it is no longer used anywhere.

[1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 662 ++++++++++++++++++++----------------
 git-submodule.sh            | 145 +-------
 2 files changed, 389 insertions(+), 418 deletions(-)
Show changes to 2 files +389 −418

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index a96976b1772..916df6d9947 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -73,21 +73,6 @@ static char *get_default_remote(void)
 	return repo_get_default_remote(the_repository, refname);
 }
 
-static int print_default_remote(int argc, const char **argv, const char *prefix)
-{
-	char *remote;
-
-	if (argc != 1)
-		die(_("submodule--helper print-default-remote takes no arguments"));
-
-	remote = get_default_remote();
-	if (remote)
-		printf("%s\n", remote);
-
-	free(remote);
-	return 0;
-}
-
 static int starts_with_dot_slash(const char *str)
 {
 	return str[0] == '.' && is_dir_sep(str[1]);
@@ -1967,29 +1952,6 @@ static void determine_submodule_update_strategy(struct repository *r,
 	free(key);
 }
 
-static int module_update_module_mode(int argc, const char **argv, const char *prefix)
-{
-	const char *path, *update = NULL;
-	int just_cloned;
-	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
-
-	if (argc < 3 || argc > 4)
-		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
-
-	just_cloned = git_config_int("just_cloned", argv[1]);
-	path = argv[2];
-
-	if (argc == 4)
-		update = argv[3];
-
-	determine_submodule_update_strategy(the_repository,
-					    just_cloned, path, update,
-					    &update_strategy);
-	fputs(submodule_strategy_to_string(&update_strategy), stdout);
-
-	return 0;
-}
-
 struct update_clone_data {
 	const struct submodule *sub;
 	struct object_id oid;
@@ -2017,7 +1979,6 @@ struct submodule_update_clone {
 	const char *prefix;
 	int single_branch;
 
-	/* to be consumed by git-submodule.sh */
 	struct update_clone_data *update_clone;
 	int update_clone_nr; int update_clone_alloc;
 
@@ -2040,20 +2001,62 @@ struct submodule_update_clone {
 }
 
 struct update_data {
+	const char *prefix;
 	const char *recursive_prefix;
 	const char *sm_path;
 	const char *displaypath;
 	struct object_id oid;
 	struct object_id suboid;
 	struct submodule_update_strategy update_strategy;
+	int max_jobs;
 	int depth;
-	unsigned int force: 1;
-	unsigned int quiet: 1;
-	unsigned int nofetch: 1;
-	unsigned int just_cloned: 1;
+	int recommend_shallow;
+	int single_branch;
+	unsigned int init;
+	unsigned int force;
+	unsigned int quiet;
+	unsigned int nofetch;
+	unsigned int remote;
+	unsigned int recursive;
+	unsigned int progress;
+	unsigned int dissociate;
+	unsigned int require_init;
+	unsigned warn_if_uninitialized ;
+	unsigned int just_cloned ;
+	struct string_list references;
+	struct module_list list;
 };
 #define UPDATE_DATA_INIT { \
+	.list = MODULE_LIST_INIT, \
 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+	.recommend_shallow = -1, \
+	.references = STRING_LIST_INIT_DUP, \
+	.single_branch = -1, \
+	.max_jobs = 1, \
+}
+
+static void update_clone_from_update_data(struct submodule_update_clone *suc,
+					  struct update_data *update_data)
+{
+	suc->prefix = update_data->prefix;
+	suc->recursive_prefix = update_data->recursive_prefix;
+	suc->max_jobs = update_data->max_jobs;
+	suc->progress = update_data->progress;
+	suc->quiet = update_data->quiet;
+	suc->dissociate = update_data->dissociate;
+	suc->require_init = update_data->require_init;
+	suc->single_branch = update_data->single_branch;
+	suc->warn_if_uninitialized = update_data->warn_if_uninitialized;
+	suc->list = update_data->list;
+	suc->update = update_data->update_strategy;
+	suc->recommend_shallow = update_data->recommend_shallow;
+	if (update_data->depth)
+		suc->depth = xstrfmt("--depth=%d", update_data->depth);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			string_list_append(&suc->references, item->string);
+	}
 }
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
@@ -2349,13 +2352,21 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 	return run_command(&cp);
 }
 
-static int run_update_command(struct update_data *ud, int subforce)
+static int run_update_command(struct update_data *ud, int subforce, struct string_list *err)
 {
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
+	struct strbuf out = STRBUF_INIT;
 	int must_die_on_failure = 0;
+	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
 
-	switch (ud->update_strategy.type) {
+	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
+		determine_submodule_update_strategy(the_repository, ud->just_cloned,
+						    ud->sm_path, NULL, &strategy);
+	else
+		strategy = ud->update_strategy;
+
+	switch (strategy.type) {
 	case SM_UPDATE_CHECKOUT:
 		cp.git_cmd = 1;
 		strvec_pushl(&cp.args, "checkout", "-q", NULL);
@@ -2378,80 +2389,76 @@ static int run_update_command(struct update_data *ud, int subforce)
 		break;
 	case SM_UPDATE_COMMAND:
 		cp.use_shell = 1;
-		strvec_push(&cp.args, ud->update_strategy.command);
+		strvec_push(&cp.args, strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+		    submodule_strategy_to_string(&strategy));
 	}
 	strvec_push(&cp.args, oid);
 
 	cp.dir = xstrdup(ud->sm_path);
 	prepare_submodule_repo_env(&cp.env_array);
-	if (run_command(&cp)) {
-		switch (ud->update_strategy.type) {
+	if (capture_command(&cp, &out, 0)) {
+		if (must_die_on_failure) {
+			switch (strategy.type) {
+			case SM_UPDATE_CHECKOUT:
+				die(_("Unable to checkout '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_REBASE:
+				die(_("Unable to rebase '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_MERGE:
+				die(_("Unable to merge '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
+				break;
+			case SM_UPDATE_COMMAND:
+				die(_("Execution of '%s %s' failed in submodule path '%s'"),
+				    strategy.command, oid, ud->displaypath);
+				break;
+			default:
+				BUG("unexpected update strategy type: %s",
+				    submodule_strategy_to_string(&strategy));
+			}
+		}
+
+		/* the command failed, but update must continue */
+		string_list_append_nodup(err, strbuf_detach(&out, NULL));
+		return 1;
+	}
+
+	if (!ud->quiet) {
+		switch (strategy.type) {
 		case SM_UPDATE_CHECKOUT:
-			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			printf(_("Submodule path '%s': checked out '%s'\n"),
+			       ud->displaypath, oid);
 			break;
 		case SM_UPDATE_REBASE:
-			printf(_("Unable to rebase '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			printf(_("Submodule path '%s': rebased into '%s'\n"),
+			       ud->displaypath, oid);
 			break;
 		case SM_UPDATE_MERGE:
-			printf(_("Unable to merge '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			printf(_("Submodule path '%s': merged in '%s'\n"),
+			       ud->displaypath, oid);
 			break;
 		case SM_UPDATE_COMMAND:
-			printf(_("Execution of '%s %s' failed in submodule path '%s'"),
-			       ud->update_strategy.command, oid, ud->displaypath);
+			printf(_("Submodule path '%s': '%s %s'\n"),
+			       ud->displaypath, strategy.command, oid);
 			break;
 		default:
 			BUG("unexpected update strategy type: %s",
-			    submodule_strategy_to_string(&ud->update_strategy));
+			    submodule_strategy_to_string(&strategy));
 		}
-		/*
-		 * NEEDSWORK: We are currently printing to stdout with error
-		 * return so that the shell caller handles the error output
-		 * properly. Once we start handling the error messages within
-		 * C, we should use die() instead.
-		 */
-		if (must_die_on_failure)
-			return 2;
-		/*
-		 * This signifies to the caller in shell that the command
-		 * failed without dying
-		 */
-		return 1;
-	}
-
-	switch (ud->update_strategy.type) {
-	case SM_UPDATE_CHECKOUT:
-		printf(_("Submodule path '%s': checked out '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_REBASE:
-		printf(_("Submodule path '%s': rebased into '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_MERGE:
-		printf(_("Submodule path '%s': merged in '%s'\n"),
-		       ud->displaypath, oid);
-		break;
-	case SM_UPDATE_COMMAND:
-		printf(_("Submodule path '%s': '%s %s'\n"),
-		       ud->displaypath, ud->update_strategy.command, oid);
-		break;
-	default:
-		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
 	}
 
+	strbuf_release(&out);
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud)
+static int run_update_procedure(struct update_data *ud, struct string_list *err)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2478,182 +2485,7 @@ static int do_run_update_procedure(struct update_data *ud)
 			    ud->displaypath, oid_to_hex(&ud->oid));
 	}
 
-	return run_update_command(ud, subforce);
-}
-
-static void update_submodule(struct update_clone_data *ucd)
-{
-	fprintf(stdout, "dummy %s %d\t%s\n",
-		oid_to_hex(&ucd->oid),
-		ucd->just_cloned,
-		ucd->sub->path);
-}
-
-static int update_submodules(struct submodule_update_clone *suc)
-{
-	int i;
-
-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
-				   update_clone_start_failure,
-				   update_clone_task_finished, suc, "submodule",
-				   "parallel/update");
-
-	/*
-	 * We saved the output and put it out all at once now.
-	 * That means:
-	 * - the listener does not have to interleave their (checkout)
-	 *   work with our fetching.  The writes involved in a
-	 *   checkout involve more straightforward sequential I/O.
-	 * - the listener can avoid doing any work if fetching failed.
-	 */
-	if (suc->quickstop)
-		return 1;
-
-	for (i = 0; i < suc->update_clone_nr; i++)
-		update_submodule(&suc->update_clone[i]);
-
-	return 0;
-}
-
-static int update_clone(int argc, const char **argv, const char *prefix)
-{
-	const char *update = NULL;
-	struct pathspec pathspec;
-	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
-
-	struct option module_update_clone_options[] = {
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
-			   N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
-			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &opt.dissociate,
-			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &opt.depth, "<depth>",
-			   N_("create a shallow clone truncated to the "
-			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &opt.max_jobs,
-			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
-			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &opt.progress,
-			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &opt.require_init,
-			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &opt.single_branch,
-			 N_("clone only one branch, HEAD or --branch")),
-		OPT_END()
-	};
-
-	const char *const git_submodule_helper_usage[] = {
-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
-		NULL
-	};
-	opt.prefix = prefix;
-
-	update_clone_config_from_gitmodules(&opt.max_jobs);
-	git_config(git_update_clone_config, &opt.max_jobs);
-
-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
-			     git_submodule_helper_usage, 0);
-
-	if (update)
-		if (parse_submodule_update_strategy(update, &opt.update) < 0)
-			die(_("bad value for update parameter"));
-
-	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
-		return 1;
-
-	if (pathspec.nr)
-		opt.warn_if_uninitialized = 1;
-
-	return update_submodules(&opt);
-}
-
-static int run_update_procedure(int argc, const char **argv, const char *prefix)
-{
-	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
-	char *prefixed_path, *update = NULL;
-	struct update_data update_data = UPDATE_DATA_INIT;
-
-	struct option options[] = {
-		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&force, N_("force checkout updates"), 0),
-		OPT_BOOL('N', "no-fetch", &nofetch,
-			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &just_cloned,
-			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
-			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
-			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_END()
-	};
-
-	const char *const usage[] = {
-		N_("git submodule--helper run-update-procedure [<options>] <path>"),
-		NULL
-	};
-
-	argc = parse_options(argc, argv, prefix, options, usage, 0);
-
-	if (argc != 1)
-		usage_with_options(usage, options);
-
-	update_data.force = !!force;
-	update_data.quiet = !!quiet;
-	update_data.nofetch = !!nofetch;
-	update_data.just_cloned = !!just_cloned;
-	update_data.sm_path = argv[0];
-
-	if (update_data.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
-	else
-		prefixed_path = xstrdup(update_data.sm_path);
-
-	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
-
-	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
-					    update_data.sm_path, update,
-					    &update_data.update_strategy);
-
-	free(prefixed_path);
-
-	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data);
-
-	return 3;
-}
-
-static int resolve_relative_path(int argc, const char **argv, const char *prefix)
-{
-	struct strbuf sb = STRBUF_INIT;
-	if (argc != 3)
-		die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
-
-	printf("%s", relative_path(argv[1], argv[2], &sb));
-	strbuf_release(&sb);
-	return 0;
+	return run_update_command(ud, subforce, err);
 }
 
 static const char *remote_submodule_branch(const char *path)
@@ -2788,17 +2620,11 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+static void ensure_core_worktree(const char *path)
 {
-	const char *path;
 	const char *cw;
 	struct repository subrepo;
 
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-
 	if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
 		die(_("could not get a repository handle for submodule '%s'"), path);
 
@@ -2818,8 +2644,6 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
 		free(abs_path);
 		strbuf_release(&sb);
 	}
-
-	return 0;
 }
 
 static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
@@ -3006,6 +2830,279 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 	return !!ret;
 }
 
+static void update_data_to_args(struct update_data *update_data, struct strvec *args)
+{
+	const char *update = submodule_strategy_to_string(&update_data->update_strategy);
+
+	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
+	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
+	if (update_data->prefix)
+		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
+	if (update_data->recursive_prefix)
+		strvec_pushl(args, "--recursive-prefix",
+			     update_data->recursive_prefix, NULL);
+	if (update_data->quiet)
+		strvec_push(args, "--quiet");
+	if (update_data->force)
+		strvec_push(args, "--force");
+	if (update_data->init)
+		strvec_push(args, "--init");
+	if (update_data->remote)
+		strvec_push(args, "--remote");
+	if (update_data->nofetch)
+		strvec_push(args, "--no-fetch");
+	if (update_data->dissociate)
+		strvec_push(args, "--dissociate");
+	if (update_data->progress)
+		strvec_push(args, "--progress");
+	if (update_data->require_init)
+		strvec_push(args, "--require-init");
+	if (update_data->depth)
+		strvec_pushf(args, "--depth=%d", update_data->depth);
+	if (update)
+		strvec_pushl(args, "--update", update, NULL);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			strvec_pushl(args, "--reference", item->string, NULL);
+	}
+	if (update_data->recommend_shallow == 0)
+		strvec_push(args, "--no-recommend-shallow");
+	else if (update_data->recommend_shallow == 1)
+		strvec_push(args, "--recommend-shallow");
+	if (update_data->single_branch >= 0)
+		strvec_push(args, "--single-branch");
+}
+
+static int update_submodule(struct update_data *update_data)
+{
+	char *prefixed_path;
+	struct string_list err = STRING_LIST_INIT_DUP;
+
+	ensure_core_worktree(update_data->sm_path);
+
+	if (update_data->recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
+					update_data->sm_path);
+	else
+		prefixed_path = xstrdup(update_data->sm_path);
+
+	update_data->displaypath = get_submodule_displaypath(prefixed_path,
+							     update_data->prefix);
+	free(prefixed_path);
+
+	if (update_data->just_cloned) {
+		oidcpy(&update_data->suboid, null_oid());
+	} else {
+		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+			die(_("Unable to find current revision in submodule path '%s'"),
+			    update_data->displaypath);
+	}
+
+	if (update_data->remote) {
+		char *remote_name = get_default_remote_submodule(update_data->sm_path);
+		const char *branch = remote_submodule_branch(update_data->sm_path);
+		char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch);
+
+		if (!update_data->nofetch) {
+			if (fetch_in_submodule(update_data->sm_path, update_data->depth,
+					      0, NULL))
+				die(_("Unable to fetch in submodule path '%s'"),
+				    update_data->sm_path);
+		}
+
+		if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
+			die(_("Unable to find %s revision in submodule path '%s'"),
+			    remote_ref, update_data->sm_path);
+
+		free(remote_ref);
+	}
+
+	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
+		if (run_update_procedure(update_data, &err))
+			return 1;
+
+	if (update_data->recursive) {
+		int res;
+		struct child_process cp = CHILD_PROCESS_INIT;
+		struct update_data next = *update_data;
+		char *die_msg = xstrfmt(_("Failed to recurse into submodule path '%s'"),
+					update_data->displaypath);
+
+		if (update_data->recursive_prefix)
+			prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix,
+						update_data->sm_path);
+		else
+			prefixed_path = xstrfmt("%s/", update_data->sm_path);
+
+		next.recursive_prefix = get_submodule_displaypath(prefixed_path,
+								  update_data->prefix);
+		next.prefix = NULL;
+		oidcpy(&next.oid, null_oid());
+		oidcpy(&next.suboid, null_oid());
+
+		cp.dir = update_data->sm_path;
+		cp.git_cmd = 1;
+		prepare_submodule_repo_env(&cp.env_array);
+		update_data_to_args(&next, &cp.args);
+
+		/* die() if child process die()'d */
+		if ((res = run_command(&cp)) == 128)
+			die("%s", die_msg);
+		if (res)
+			string_list_append(&err, die_msg);
+
+		free(die_msg);
+	}
+
+	if (err.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &err)
+			fputs(item->string, stderr);
+		string_list_clear(&err, 0);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int update_submodules(struct update_data *update_data)
+{
+	int i, res = 0;
+	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+
+	update_clone_from_update_data(&suc, update_data);
+	run_processes_parallel_tr2(suc.max_jobs, update_clone_get_next_task,
+				   update_clone_start_failure,
+				   update_clone_task_finished, &suc, "submodule",
+				   "parallel/update");
+
+	/*
+	 * We saved the output and put it out all at once now.
+	 * That means:
+	 * - the listener does not have to interleave their (checkout)
+	 *   work with our fetching.  The writes involved in a
+	 *   checkout involve more straightforward sequential I/O.
+	 * - the listener can avoid doing any work if fetching failed.
+	 */
+	if (suc.quickstop) {
+		res = 1;
+		goto cleanup;
+	}
+
+	for (i = 0; i < suc.update_clone_nr; i++) {
+		struct update_clone_data ucd = suc.update_clone[i];
+
+		oidcpy(&update_data->oid, &ucd.oid);
+		update_data->just_cloned = ucd.just_cloned;
+		update_data->sm_path = ucd.sub->path;
+
+		if (update_submodule(update_data))
+			res = 1;
+	}
+
+cleanup:
+	string_list_clear(&update_data->references, 0);
+	return res;
+}
+
+static int module_update(int argc, const char **argv, const char *prefix)
+{
+	const char *update = NULL;
+	struct pathspec pathspec;
+	struct update_data opt = UPDATE_DATA_INIT;
+
+	struct option module_update_clone_options[] = {
+		OPT__FORCE(&opt.force, N_("force checkout updates"), 0),
+		OPT_BOOL(0, "init", &opt.init,
+			 N_("initialize uninitialized submodules before update")),
+		OPT_BOOL(0, "remote", &opt.remote,
+			 N_("use SHA-1 of submodule's remote tracking branch")),
+		OPT_BOOL(0, "recursive", &opt.recursive,
+			 N_("traverse submodules recursively")),
+		OPT_BOOL('N', "no-fetch", &opt.nofetch,
+			 N_("don't fetch new objects from the remote site")),
+		OPT_STRING(0, "prefix", &opt.prefix,
+			   N_("path"),
+			   N_("path into the working tree")),
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
+			   N_("path"),
+			   N_("path into the working tree, across nested "
+			      "submodule boundaries")),
+		OPT_STRING(0, "update", &update,
+			   N_("string"),
+			   N_("rebase, merge, checkout or none")),
+		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
+				N_("reference repository")),
+		OPT_BOOL(0, "dissociate", &opt.dissociate,
+			 N_("use --reference only while cloning")),
+		OPT_INTEGER(0, "depth", &opt.depth,
+			    N_("create a shallow clone truncated to the "
+			       "specified number of revisions")),
+		OPT_INTEGER('j', "jobs", &opt.max_jobs,
+			    N_("parallel jobs")),
+		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
+			 N_("whether the initial clone should follow the shallow recommendation")),
+		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &opt.progress,
+			 N_("force cloning progress")),
+		OPT_BOOL(0, "require-init", &opt.require_init,
+			 N_("disallow cloning into non-empty directory")),
+		OPT_BOOL(0, "single-branch", &opt.single_branch,
+			 N_("clone only one branch, HEAD or --branch")),
+		OPT_END()
+	};
+
+	const char *const git_submodule_helper_usage[] = {
+		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
+		NULL
+	};
+
+	update_clone_config_from_gitmodules(&opt.max_jobs);
+	git_config(git_update_clone_config, &opt.max_jobs);
+
+	argc = parse_options(argc, argv, prefix, module_update_clone_options,
+			     git_submodule_helper_usage, 0);
+	oidcpy(&opt.oid, null_oid());
+	oidcpy(&opt.suboid, null_oid());
+
+	if (update)
+		if (parse_submodule_update_strategy(update,
+						    &opt.update_strategy) < 0)
+			die(_("bad value for update parameter"));
+
+	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
+		return 1;
+
+	if (pathspec.nr)
+		opt.warn_if_uninitialized = 1;
+
+	if (opt.init) {
+		struct module_list list = MODULE_LIST_INIT;
+		struct init_cb info = INIT_CB_INIT;
+
+		if (module_list_compute(argc, argv, opt.prefix,
+					&pathspec, &list) < 0)
+			return 1;
+
+		/*
+		 * If there are no path args and submodule.active is set then,
+		 * by default, only initialize 'active' modules.
+		 */
+		if (!argc && git_config_get_value_multi("submodule.active"))
+			module_list_active(&list);
+
+		info.prefix = opt.prefix;
+		info.superprefix = opt.recursive_prefix;
+		if (opt.quiet)
+			info.flags |= OPT_QUIET;
+
+		for_each_listed_submodule(&list, init_submodule_cb, &info);
+	}
+
+	return update_submodules(&opt);
+}
+
 struct add_data {
 	const char *prefix;
 	const char *branch;
@@ -3393,16 +3490,11 @@ static struct cmd_struct commands[] = {
 	{"name", module_name, 0},
 	{"clone", module_clone, 0},
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
-	{"update-module-mode", module_update_module_mode, 0},
-	{"update-clone", update_clone, 0},
-	{"run-update-procedure", run_update_procedure, 0},
-	{"ensure-core-worktree", ensure_core_worktree, 0},
-	{"relative-path", resolve_relative_path, 0},
+	{"update", module_update, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
 	{"init", module_init, SUPPORT_SUPER_PREFIX},
 	{"status", module_status, SUPPORT_SUPER_PREFIX},
-	{"print-default-remote", print_default_remote, 0},
 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
 	{"deinit", module_deinit, 0},
 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index 652861aa66a..bcd8b92aabd 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -246,20 +246,6 @@ cmd_deinit()
 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
 }
 
-# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
-# Because arguments are positional, use an empty string to omit <depth>
-# but include <sha1>.
-fetch_in_submodule () (
-	sanitize_submodule_env &&
-	cd "$1" &&
-	if test $# -eq 3
-	then
-		echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
-	else
-		git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
-	fi
-)
-
 #
 # Update each submodule path to correct revision, using clone and checkout as needed
 #
@@ -361,133 +347,26 @@ cmd_update()
 		shift
 	done
 
-	if test -n "$init"
-	then
-		cmd_init "--" "$@" || return
-	fi
-
-	{
-	git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
-		${progress:+"--progress"} \
+	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
+		${GIT_QUIET:+--quiet} \
+		${force:+--force} \
+		${progress:+--progress} \
+		${dissociate:+--dissociate} \
+		${remote:+--remote} \
+		${recursive:+--recursive} \
+		${init:+--init} \
+		${require_init:+--require-init} \
+		${nofetch:+--no-fetch} \
 		${wt_prefix:+--prefix "$wt_prefix"} \
 		${prefix:+--recursive-prefix "$prefix"} \
 		${update:+--update "$update"} \
 		${reference:+"$reference"} \
-		${dissociate:+"--dissociate"} \
-		${depth:+--depth "$depth"} \
-		${require_init:+--require-init} \
+		${depth:+"$depth"} \
 		$single_branch \
 		$recommend_shallow \
 		$jobs \
 		-- \
-		"$@" || echo "#unmatched" $?
-	} | {
-	err=
-	while read -r quickabort sha1 just_cloned sm_path
-	do
-		die_if_unmatched "$quickabort" "$sha1"
-
-		git submodule--helper ensure-core-worktree "$sm_path" || exit 1
-
-		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
-
-		if test $just_cloned -eq 1
-		then
-			subsha1=
-		else
-			just_cloned=
-			subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify HEAD) ||
-			die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
-		fi
-
-		if test -n "$remote"
-		then
-			branch=$(git submodule--helper remote-branch "$sm_path")
-			if test -z "$nofetch"
-			then
-				# Fetch remote before determining tracking $sha1
-				fetch_in_submodule "$sm_path" $depth ||
-				die "fatal: $(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
-			fi
-			remote_name=$(sanitize_submodule_env; cd "$sm_path" && git submodule--helper print-default-remote)
-			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify "${remote_name}/${branch}") ||
-			die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
-		fi
-
-		out=$(git submodule--helper run-update-procedure \
-			  ${wt_prefix:+--prefix "$wt_prefix"} \
-			  ${GIT_QUIET:+--quiet} \
-			  ${force:+--force} \
-			  ${just_cloned:+--just-cloned} \
-			  ${nofetch:+--no-fetch} \
-			  ${depth:+"$depth"} \
-			  ${update:+--update "$update"} \
-			  ${prefix:+--recursive-prefix "$prefix"} \
-			  ${sha1:+--oid "$sha1"} \
-			  ${subsha1:+--suboid "$subsha1"} \
-			  "--" \
-			  "$sm_path")
-
-		# exit codes for run-update-procedure:
-		# 0: update was successful, say command output
-		# 1: update procedure failed, but should not die
-		# 2 or 128: subcommand died during execution
-		# 3: no update procedure was run
-		res="$?"
-		case $res in
-		0)
-			say "$out"
-			;;
-		1)
-			err="${err};fatal: $out"
-			continue
-			;;
-		2|128)
-			die_with_status $res "fatal: $out"
-			;;
-		esac
-
-		if test -n "$recursive"
-		then
-			(
-				prefix=$(git submodule--helper relative-path "$prefix$sm_path/" "$wt_prefix")
-				wt_prefix=
-				sanitize_submodule_env
-				cd "$sm_path" &&
-				eval cmd_update
-			)
-			res=$?
-			if test $res -gt 0
-			then
-				die_msg="fatal: $(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
-				if test $res -ne 2
-				then
-					err="${err};$die_msg"
-					continue
-				else
-					die_with_status $res "$die_msg"
-				fi
-			fi
-		fi
-	done
-
-	if test -n "$err"
-	then
-		OIFS=$IFS
-		IFS=';'
-		for e in $err
-		do
-			if test -n "$e"
-			then
-				echo >&2 "$e"
-			fi
-		done
-		IFS=$OIFS
-		exit 1
-	fi
-	}
+		"$@"
 }
 
 #
-- 
2.35.0.894.g563b84683b9
Glen Choo· Jan 27, 2022, 21:55 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v4 7/7] submodule: move core cmd_update() logic to C

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 35 quoted lines
> From: Atharva Raykar <raykar.ath@gmail.com>
>
> This patch completes the conversion past the flag parsing of
> `submodule update` by introducing a helper subcommand called
> `submodule--helper update`. The behaviour of `submodule update` should
> remain the same after this patch.
>
> We add more fields to the `struct update_data` that are required by
> `struct submodule_update_clone` to be able to perform a clone, when that
> is needed to be done.
>
> Recursing on a submodule is done by calling a subprocess that launches
> `submodule--helper update`, with a modified `--recursive-prefix` and
> `--prefix` parameter.
>
> We also introduce `update_submodules2()` and `update_submodule2()`
> which will supersede `update_submodules()` and `update_submodule()`.
>
> When the `--init` flag is passed to the subcommand, we do not spawn a
> new subprocess and call `submodule--helper init` on the submodule paths,
> because the Git machinery is not able to pick up the configuration
> changes introduced by that init call[1]. So we instead run the
> `init_submodule_cb()` callback over each submodule in the same process.
>
> While we are at it, we also remove the fetch_in_submodule() shell
> function since it is no longer used anywhere.
>
> [1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---

I've read through all of the patches besides this one - I hope to get through this one soon.

The diff is quite large, but I can't think of any way to shrink it down at the moment.

Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 0/9] submodule: convert the rest of 'update' to C

A rewrite of "git submodule" in C, except the trivial pass-through wrapping code (basically getopt etc.) in git-submodule.sh. For a greater summary & context see v4's CL: https://lore.kernel.org/git/cover-v4-0.7-00000000000-20220127T143552Z-avarab@gmail.com/

Changes in v5:
 * Took Glen Choo's suggestion for a code change in 1/8, thanks!
 * A bit more mid-series churn to make the large commit at the end
   have a smaller diff. The result is that in v4/v5 we have in that
   last commit (but see below).
   - 2 files changed, 389 insertions(+), 418 deletions(-)
   + 2 files changed, 357 insertions(+), 388 deletions(-)
 * There were some logic errors in v4 around how we handled error
   reporting. A new 8/9 adds tests for the existing behavior, and
   we're now bug-for-bug compatible.
   It turned out that we were passing around string lists of errors
   etc. in a way that wasn't needed, i.e. the initial code can just
   report the error.
   Much of this was also simplified with my recently added
   die_message() function. I.e. sometimes we want to have a "fatal: "
   message, but exit non-128.
Atharva Raykar (6):
  submodule--helper: get remote names from any repository
  submodule--helper: refactor get_submodule_displaypath()
  submodule--helper: allow setting superprefix for init_submodule()
  submodule--helper: run update using child process struct
  builtin/submodule--helper.c: reformat designated initializers
  submodule: move core cmd_update() logic to C
Ævar Arnfjörð Bjarmason (3):
  builtin/submodule--helper.c: rename option variables to "opt"
  submodule--helper: don't use bitfield indirection for parse_options()
  submodule tests: test for init and update failure output
 builtin/submodule--helper.c    | 706 +++++++++++++++++++--------------
 git-submodule.sh               | 145 +------
 t/t7406-submodule-update.sh    |  14 +-
 t/t7408-submodule-reference.sh |  14 +-
 4 files changed, 444 insertions(+), 435 deletions(-)
Range-diff against v4:
 1:  1a0b1323cd7 !  1:  088c7236a16 submodule--helper: get remote names from any repository
    @@ Commit message
     
         Mentored-by: Christian Couder <christian.couder@gmail.com>
         Mentored-by: Shourya Shukla <periperidip@gmail.com>
    +    Helped-by: Glen Choo <chooglen@google.com>
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
    @@ builtin/submodule--helper.c
      				  void *cb_data);
      
     -static char *get_default_remote(void)
    -+static char *repo_get_default_remote(struct repository *repo, const char *refname)
    ++static char *repo_get_default_remote(struct repository *repo)
      {
      	char *dest = NULL, *ret;
      	struct strbuf sb = STRBUF_INIT;
     -	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
    ++	struct ref_store *store = get_main_ref_store(repo);
    ++	int ignore_errno;
    ++	const char *refname = refs_resolve_ref_unsafe(store, "HEAD", 0, NULL,
    ++						      NULL, &ignore_errno);
      
      	if (!refname)
      		die(_("No such ref: %s"), "HEAD");
    @@ builtin/submodule--helper.c: static char *get_default_remote(void)
      
     +static char *get_default_remote_submodule(const char *module_path)
     +{
    -+	const char *refname;
     +	struct repository subrepo;
    -+	int ignore_errno;
     +
    -+	refname = refs_resolve_ref_unsafe(get_submodule_ref_store(module_path),
    -+					  "HEAD", 0, NULL, NULL,
    -+					  &ignore_errno);
     +	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
    -+	return repo_get_default_remote(&subrepo, refname);
    ++	return repo_get_default_remote(&subrepo);
     +}
     +
     +static char *get_default_remote(void)
     +{
    -+	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
    -+	return repo_get_default_remote(the_repository, refname);
    ++	return repo_get_default_remote(the_repository);
     +}
     +
      static int print_default_remote(int argc, const char **argv, const char *prefix)
 2:  7e2df3ff220 =  2:  bc694987893 submodule--helper: refactor get_submodule_displaypath()
 3:  f31fd72fba2 =  3:  e2cc7f0e23b submodule--helper: allow setting superprefix for init_submodule()
 4:  76c5a826a80 =  4:  e1df2dd4457 submodule--helper: run update using child process struct
 5:  29aa2fc0851 =  5:  fa815e37f9f builtin/submodule--helper.c: reformat designated initializers
 6:  d3ad6e7a351 !  6:  ef097f7ea51 builtin/submodule--helper.c: rename "suc" variable to "opt"
    @@ Metadata
     Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## Commit message ##
    -    builtin/submodule--helper.c: rename "suc" variable to "opt"
    +    builtin/submodule--helper.c: rename option variables to "opt"
     
    -    Rename the "suc" variable in "builtin/submodule--helper.c" to
    -    "opt". The only reason for this change is to make the subsequent
    -    commit's diff smaller, by doing this rename we can "anchor" the diff
    -    better, as it "borrow" most of the options declared here as-is as far
    -    as the diff rename detection is concerned.
    +    Rename the "suc" variable in update_clone() to "opt", and do the same
    +    for the "update_data" variable in run_update_procedure().
    +
    +    The only reason for this change is to make the subsequent commit's
    +    diff smaller, by doing this rename we can "anchor" the diff better, as
    +    it "borrow" most of the options declared here as-is as far as the diff
    +    rename detection is concerned.
     
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
    @@ builtin/submodule--helper.c: static int update_clone(int argc, const char **argv
      }
      
      static int run_update_procedure(int argc, const char **argv, const char *prefix)
    + {
    + 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
    + 	char *prefixed_path, *update = NULL;
    +-	struct update_data update_data = UPDATE_DATA_INIT;
    ++	struct update_data opt = UPDATE_DATA_INIT;
    + 
    + 	struct option options[] = {
    + 		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
    +@@ builtin/submodule--helper.c: static int run_update_procedure(int argc, const char **argv, const char *prefix)
    + 			 N_("don't fetch new objects from the remote site")),
    + 		OPT_BOOL(0, "just-cloned", &just_cloned,
    + 			 N_("overrides update mode in case the repository is a fresh clone")),
    +-		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
    ++		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
    + 		OPT_STRING(0, "prefix", &prefix,
    + 			   N_("path"),
    + 			   N_("path into the working tree")),
    + 		OPT_STRING(0, "update", &update,
    + 			   N_("string"),
    + 			   N_("rebase, merge, checkout or none")),
    +-		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
    ++		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
    + 			   N_("path into the working tree, across nested "
    + 			      "submodule boundaries")),
    +-		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
    ++		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
    + 			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
    + 			       parse_opt_object_id),
    +-		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
    ++		OPT_CALLBACK_F(0, "suboid", &opt.suboid, N_("subsha1"),
    + 			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
    + 			       parse_opt_object_id),
    + 		OPT_END()
    +@@ builtin/submodule--helper.c: static int run_update_procedure(int argc, const char **argv, const char *prefix)
    + 	if (argc != 1)
    + 		usage_with_options(usage, options);
    + 
    +-	update_data.force = !!force;
    +-	update_data.quiet = !!quiet;
    +-	update_data.nofetch = !!nofetch;
    +-	update_data.just_cloned = !!just_cloned;
    +-	update_data.sm_path = argv[0];
    ++	opt.force = !!force;
    ++	opt.quiet = !!quiet;
    ++	opt.nofetch = !!nofetch;
    ++	opt.just_cloned = !!just_cloned;
    ++	opt.sm_path = argv[0];
    + 
    +-	if (update_data.recursive_prefix)
    +-		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
    ++	if (opt.recursive_prefix)
    ++		prefixed_path = xstrfmt("%s%s", opt.recursive_prefix, opt.sm_path);
    + 	else
    +-		prefixed_path = xstrdup(update_data.sm_path);
    ++		prefixed_path = xstrdup(opt.sm_path);
    + 
    +-	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
    ++	opt.displaypath = get_submodule_displaypath(prefixed_path, prefix);
    + 
    +-	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
    +-					    update_data.sm_path, update,
    +-					    &update_data.update_strategy);
    ++	determine_submodule_update_strategy(the_repository, opt.just_cloned,
    ++					    opt.sm_path, update,
    ++					    &opt.update_strategy);
    + 
    + 	free(prefixed_path);
    + 
    +-	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
    +-		return do_run_update_procedure(&update_data);
    ++	if (!oideq(&opt.oid, &opt.suboid) || opt.force)
    ++		return do_run_update_procedure(&opt);
    + 
    + 	return 3;
    + }
 -:  ----------- >  7:  6b038f558c1 submodule--helper: don't use bitfield indirection for parse_options()
 -:  ----------- >  8:  b4f84132201 submodule tests: test for init and update failure output
 7:  02954603763 !  9:  e8e57606ee9 submodule: move core cmd_update() logic to C
    @@ Commit message
     
      ## builtin/submodule--helper.c ##
     @@ builtin/submodule--helper.c: static char *get_default_remote(void)
    - 	return repo_get_default_remote(the_repository, refname);
    + 	return repo_get_default_remote(the_repository);
      }
      
     -static int print_default_remote(int argc, const char **argv, const char *prefix)
    @@ builtin/submodule--helper.c: struct submodule_update_clone {
      	const char *recursive_prefix;
      	const char *sm_path;
      	const char *displaypath;
    - 	struct object_id oid;
    +@@ builtin/submodule--helper.c: struct update_data {
      	struct object_id suboid;
      	struct submodule_update_strategy update_strategy;
    -+	int max_jobs;
      	int depth;
    --	unsigned int force: 1;
    --	unsigned int quiet: 1;
    --	unsigned int nofetch: 1;
    --	unsigned int just_cloned: 1;
     +	int recommend_shallow;
     +	int single_branch;
    ++	int max_jobs;
     +	unsigned int init;
    -+	unsigned int force;
    -+	unsigned int quiet;
    -+	unsigned int nofetch;
    + 	unsigned int force;
    + 	unsigned int quiet;
    + 	unsigned int nofetch;
    + 	unsigned int just_cloned;
     +	unsigned int remote;
     +	unsigned int recursive;
     +	unsigned int progress;
     +	unsigned int dissociate;
     +	unsigned int require_init;
     +	unsigned warn_if_uninitialized ;
    -+	unsigned int just_cloned ;
     +	struct string_list references;
     +	struct module_list list;
      };
    @@ builtin/submodule--helper.c: struct submodule_update_clone {
      }
      
      static void next_submodule_warn_missing(struct submodule_update_clone *suc,
    -@@ builtin/submodule--helper.c: static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
    - 	return run_command(&cp);
    - }
    - 
    --static int run_update_command(struct update_data *ud, int subforce)
    -+static int run_update_command(struct update_data *ud, int subforce, struct string_list *err)
    - {
    +@@ builtin/submodule--helper.c: static int run_update_command(struct update_data *ud, int subforce)
      	struct child_process cp = CHILD_PROCESS_INIT;
      	char *oid = oid_to_hex(&ud->oid);
    -+	struct strbuf out = STRBUF_INIT;
      	int must_die_on_failure = 0;
     +	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
      
    @@ builtin/submodule--helper.c: static int run_update_command(struct update_data *u
      
      	cp.dir = xstrdup(ud->sm_path);
      	prepare_submodule_repo_env(&cp.env_array);
    --	if (run_command(&cp)) {
    + 	if (run_command(&cp)) {
     -		switch (ud->update_strategy.type) {
    -+	if (capture_command(&cp, &out, 0)) {
    -+		if (must_die_on_failure) {
    -+			switch (strategy.type) {
    -+			case SM_UPDATE_CHECKOUT:
    -+				die(_("Unable to checkout '%s' in submodule path '%s'"),
    -+				    oid, ud->displaypath);
    -+				break;
    -+			case SM_UPDATE_REBASE:
    -+				die(_("Unable to rebase '%s' in submodule path '%s'"),
    -+				    oid, ud->displaypath);
    -+				break;
    -+			case SM_UPDATE_MERGE:
    -+				die(_("Unable to merge '%s' in submodule path '%s'"),
    -+				    oid, ud->displaypath);
    -+				break;
    -+			case SM_UPDATE_COMMAND:
    -+				die(_("Execution of '%s %s' failed in submodule path '%s'"),
    -+				    strategy.command, oid, ud->displaypath);
    -+				break;
    -+			default:
    -+				BUG("unexpected update strategy type: %s",
    -+				    submodule_strategy_to_string(&strategy));
    -+			}
    -+		}
    -+
    -+		/* the command failed, but update must continue */
    -+		string_list_append_nodup(err, strbuf_detach(&out, NULL));
    -+		return 1;
    -+	}
    -+
    -+	if (!ud->quiet) {
     +		switch (strategy.type) {
      		case SM_UPDATE_CHECKOUT:
     -			printf(_("Unable to checkout '%s' in submodule path '%s'"),
     -			       oid, ud->displaypath);
    -+			printf(_("Submodule path '%s': checked out '%s'\n"),
    -+			       ud->displaypath, oid);
    ++			die_message(_("Unable to checkout '%s' in submodule path '%s'"),
    ++				    oid, ud->displaypath);
      			break;
      		case SM_UPDATE_REBASE:
     -			printf(_("Unable to rebase '%s' in submodule path '%s'"),
     -			       oid, ud->displaypath);
    -+			printf(_("Submodule path '%s': rebased into '%s'\n"),
    -+			       ud->displaypath, oid);
    ++			if (!must_die_on_failure)
    ++				break;
    ++			die(_("Unable to rebase '%s' in submodule path '%s'"),
    ++			    oid, ud->displaypath);
      			break;
      		case SM_UPDATE_MERGE:
     -			printf(_("Unable to merge '%s' in submodule path '%s'"),
     -			       oid, ud->displaypath);
    -+			printf(_("Submodule path '%s': merged in '%s'\n"),
    -+			       ud->displaypath, oid);
    ++			if (!must_die_on_failure)
    ++				break;
    ++			die(_("Unable to merge '%s' in submodule path '%s'"),
    ++			    oid, ud->displaypath);
      			break;
      		case SM_UPDATE_COMMAND:
     -			printf(_("Execution of '%s %s' failed in submodule path '%s'"),
     -			       ud->update_strategy.command, oid, ud->displaypath);
    -+			printf(_("Submodule path '%s': '%s %s'\n"),
    -+			       ud->displaypath, strategy.command, oid);
    ++			if (!must_die_on_failure)
    ++				break;
    ++			die(_("Execution of '%s %s' failed in submodule path '%s'"),
    ++			    strategy.command, oid, ud->displaypath);
      			break;
      		default:
      			BUG("unexpected update strategy type: %s",
    @@ builtin/submodule--helper.c: static int run_update_command(struct update_data *u
     -		 * This signifies to the caller in shell that the command
     -		 * failed without dying
     -		 */
    --		return 1;
    --	}
    --
    ++
    ++		/* the command failed, but update must continue */
    + 		return 1;
    + 	}
    + 
     -	switch (ud->update_strategy.type) {
    --	case SM_UPDATE_CHECKOUT:
    --		printf(_("Submodule path '%s': checked out '%s'\n"),
    --		       ud->displaypath, oid);
    --		break;
    --	case SM_UPDATE_REBASE:
    --		printf(_("Submodule path '%s': rebased into '%s'\n"),
    --		       ud->displaypath, oid);
    --		break;
    --	case SM_UPDATE_MERGE:
    --		printf(_("Submodule path '%s': merged in '%s'\n"),
    --		       ud->displaypath, oid);
    --		break;
    --	case SM_UPDATE_COMMAND:
    --		printf(_("Submodule path '%s': '%s %s'\n"),
    ++	if (ud->quiet)
    ++		return 0;
    ++
    ++	switch (strategy.type) {
    + 	case SM_UPDATE_CHECKOUT:
    + 		printf(_("Submodule path '%s': checked out '%s'\n"),
    + 		       ud->displaypath, oid);
    +@@ builtin/submodule--helper.c: static int run_update_command(struct update_data *ud, int subforce)
    + 		break;
    + 	case SM_UPDATE_COMMAND:
    + 		printf(_("Submodule path '%s': '%s %s'\n"),
     -		       ud->displaypath, ud->update_strategy.command, oid);
    --		break;
    --	default:
    --		BUG("unexpected update strategy type: %s",
    ++		       ud->displaypath, strategy.command, oid);
    + 		break;
    + 	default:
    + 		BUG("unexpected update strategy type: %s",
     -		    submodule_strategy_to_string(&ud->update_strategy));
    ++		    submodule_strategy_to_string(&strategy));
      	}
      
    -+	strbuf_release(&out);
      	return 0;
      }
      
     -static int do_run_update_procedure(struct update_data *ud)
    -+static int run_update_procedure(struct update_data *ud, struct string_list *err)
    ++static int run_update_procedure(struct update_data *ud)
      {
      	int subforce = is_null_oid(&ud->suboid) || ud->force;
      
     @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_data *ud)
    - 			    ud->displaypath, oid_to_hex(&ud->oid));
    - 	}
    + 	return run_update_command(ud, subforce);
    + }
      
    --	return run_update_command(ud, subforce);
    --}
    --
     -static void update_submodule(struct update_clone_data *ucd)
     -{
     -	fprintf(stdout, "dummy %s %d\t%s\n",
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -
     -static int run_update_procedure(int argc, const char **argv, const char *prefix)
     -{
    --	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
     -	char *prefixed_path, *update = NULL;
    --	struct update_data update_data = UPDATE_DATA_INIT;
    +-	struct update_data opt = UPDATE_DATA_INIT;
     -
     -	struct option options[] = {
    --		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
    --		OPT__FORCE(&force, N_("force checkout updates"), 0),
    --		OPT_BOOL('N', "no-fetch", &nofetch,
    +-		OPT__QUIET(&opt.quiet,
    +-			   N_("suppress output for update by rebase or merge")),
    +-		OPT__FORCE(&opt.force, N_("force checkout updates"),
    +-			   0),
    +-		OPT_BOOL('N', "no-fetch", &opt.nofetch,
     -			 N_("don't fetch new objects from the remote site")),
    --		OPT_BOOL(0, "just-cloned", &just_cloned,
    +-		OPT_BOOL(0, "just-cloned", &opt.just_cloned,
     -			 N_("overrides update mode in case the repository is a fresh clone")),
    --		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
    +-		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
     -		OPT_STRING(0, "prefix", &prefix,
     -			   N_("path"),
     -			   N_("path into the working tree")),
     -		OPT_STRING(0, "update", &update,
     -			   N_("string"),
     -			   N_("rebase, merge, checkout or none")),
    --		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
    +-		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
     -			   N_("path into the working tree, across nested "
     -			      "submodule boundaries")),
    --		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
    +-		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
     -			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
     -			       parse_opt_object_id),
    --		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
    +-		OPT_CALLBACK_F(0, "suboid", &opt.suboid, N_("subsha1"),
     -			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
     -			       parse_opt_object_id),
     -		OPT_END()
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -	if (argc != 1)
     -		usage_with_options(usage, options);
     -
    --	update_data.force = !!force;
    --	update_data.quiet = !!quiet;
    --	update_data.nofetch = !!nofetch;
    --	update_data.just_cloned = !!just_cloned;
    --	update_data.sm_path = argv[0];
    +-	opt.sm_path = argv[0];
     -
    --	if (update_data.recursive_prefix)
    --		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
    +-	if (opt.recursive_prefix)
    +-		prefixed_path = xstrfmt("%s%s", opt.recursive_prefix, opt.sm_path);
     -	else
    --		prefixed_path = xstrdup(update_data.sm_path);
    +-		prefixed_path = xstrdup(opt.sm_path);
     -
    --	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
    +-	opt.displaypath = get_submodule_displaypath(prefixed_path, prefix);
     -
    --	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
    --					    update_data.sm_path, update,
    --					    &update_data.update_strategy);
    +-	determine_submodule_update_strategy(the_repository, opt.just_cloned,
    +-					    opt.sm_path, update,
    +-					    &opt.update_strategy);
     -
     -	free(prefixed_path);
     -
    --	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
    --		return do_run_update_procedure(&update_data);
    +-	if (!oideq(&opt.oid, &opt.suboid) || opt.force)
    +-		return do_run_update_procedure(&opt);
     -
     -	return 3;
     -}
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -	printf("%s", relative_path(argv[1], argv[2], &sb));
     -	strbuf_release(&sb);
     -	return 0;
    -+	return run_update_command(ud, subforce, err);
    - }
    - 
    +-}
    +-
      static const char *remote_submodule_branch(const char *path)
    + {
    + 	const struct submodule *sub;
     @@ builtin/submodule--helper.c: static int push_check(int argc, const char **argv, const char *prefix)
      	return 0;
      }
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +static int update_submodule(struct update_data *update_data)
     +{
     +	char *prefixed_path;
    -+	struct string_list err = STRING_LIST_INIT_DUP;
     +
     +	ensure_core_worktree(update_data->sm_path);
     +
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +	}
     +
     +	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
    -+		if (run_update_procedure(update_data, &err))
    ++		if (run_update_procedure(update_data))
     +			return 1;
     +
     +	if (update_data->recursive) {
    -+		int res;
     +		struct child_process cp = CHILD_PROCESS_INIT;
     +		struct update_data next = *update_data;
    -+		char *die_msg = xstrfmt(_("Failed to recurse into submodule path '%s'"),
    -+					update_data->displaypath);
    ++		int res;
     +
     +		if (update_data->recursive_prefix)
     +			prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix,
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +		update_data_to_args(&next, &cp.args);
     +
     +		/* die() if child process die()'d */
    -+		if ((res = run_command(&cp)) == 128)
    -+			die("%s", die_msg);
    -+		if (res)
    -+			string_list_append(&err, die_msg);
    -+
    -+		free(die_msg);
    -+	}
    -+
    -+	if (err.nr) {
    -+		struct string_list_item *item;
    -+		for_each_string_list_item(item, &err)
    -+			fputs(item->string, stderr);
    -+		string_list_clear(&err, 0);
    -+		return 1;
    ++		res = run_command(&cp);
    ++		if (!res)
    ++			return 0;
    ++		die_message(_("Failed to recurse into submodule path '%s'"),
    ++			    update_data->displaypath);
    ++		if (res == 128)
    ++			exit(res);
    ++		else if (res)
    ++			return 1;
     +	}
     +
     +	return 0;
-- 
2.35.0.914.ge5c8aab0d5b
Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 1/9] submodule--helper: get remote names from any repository

From: Atharva Raykar <raykar.ath@gmail.com>

`get_default_remote()` retrieves the name of a remote by resolving the refs from of the current repository's ref store.

Thus in order to use it for retrieving the remote name of a submodule, we have to start a new subprocess which runs from the submodule directory.

Let's instead introduce a function called `repo_get_default_remote()` which takes any repository object and retrieves the remote accordingly.

`get_default_remote()` is then defined as a call to `repo_get_default_remote()` with 'the_repository' passed to it.

Now that we have `repo_get_default_remote()`, we no longer have to start a subprocess that called `submodule--helper get-default-remote` from within the submodule directory.

So let's make a function called `get_default_remote_submodule()` which takes a submodule path, and returns the default remote for that submodule, all within the same process.

We can now use this function to save an unnecessary subprocess spawn in `sync_submodule()`, and also in the next patch, which will require this functionality.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Helped-by: Glen Choo <chooglen@google.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 39 +++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 15 deletions(-)
Show changes to builtin/submodule--helper.c +24 −15
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c5d3fc3817f..4c7c1e1432d 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -29,11 +29,14 @@
 typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
 				  void *cb_data);
 
-static char *get_default_remote(void)
+static char *repo_get_default_remote(struct repository *repo)
 {
 	char *dest = NULL, *ret;
 	struct strbuf sb = STRBUF_INIT;
-	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+	struct ref_store *store = get_main_ref_store(repo);
+	int ignore_errno;
+	const char *refname = refs_resolve_ref_unsafe(store, "HEAD", 0, NULL,
+						      NULL, &ignore_errno);
 
 	if (!refname)
 		die(_("No such ref: %s"), "HEAD");
@@ -46,7 +49,7 @@ static char *get_default_remote(void)
 		die(_("Expecting a full ref name, got %s"), refname);
 
 	strbuf_addf(&sb, "branch.%s.remote", refname);
-	if (git_config_get_string(sb.buf, &dest))
+	if (repo_config_get_string(repo, sb.buf, &dest))
 		ret = xstrdup("origin");
 	else
 		ret = dest;
@@ -55,6 +58,19 @@ static char *get_default_remote(void)
 	return ret;
 }
 
+static char *get_default_remote_submodule(const char *module_path)
+{
+	struct repository subrepo;
+
+	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
+	return repo_get_default_remote(&subrepo);
+}
+
+static char *get_default_remote(void)
+{
+	return repo_get_default_remote(the_repository);
+}
+
 static int print_default_remote(int argc, const char **argv, const char *prefix)
 {
 	char *remote;
@@ -1341,9 +1357,8 @@ static void sync_submodule(const char *path, const char *prefix,
 {
 	const struct submodule *sub;
 	char *remote_key = NULL;
-	char *sub_origin_url, *super_config_url, *displaypath;
+	char *sub_origin_url, *super_config_url, *displaypath, *default_remote;
 	struct strbuf sb = STRBUF_INIT;
-	struct child_process cp = CHILD_PROCESS_INIT;
 	char *sub_config_path = NULL;
 
 	if (!is_submodule_active(the_repository, path))
@@ -1382,21 +1397,15 @@ static void sync_submodule(const char *path, const char *prefix,
 	if (!is_submodule_populated_gently(path, NULL))
 		goto cleanup;
 
-	prepare_submodule_repo_env(&cp.env_array);
-	cp.git_cmd = 1;
-	cp.dir = path;
-	strvec_pushl(&cp.args, "submodule--helper",
-		     "print-default-remote", NULL);
-
 	strbuf_reset(&sb);
-	if (capture_command(&cp, &sb, 0))
+	default_remote = get_default_remote_submodule(path);
+	if (!default_remote)
 		die(_("failed to get the default remote for submodule '%s'"),
 		      path);
 
-	strbuf_strip_suffix(&sb, "\n");
-	remote_key = xstrfmt("remote.%s.url", sb.buf);
+	remote_key = xstrfmt("remote.%s.url", default_remote);
+	free(default_remote);
 
-	strbuf_reset(&sb);
 	submodule_to_gitdir(&sb, path);
 	strbuf_addstr(&sb, "/config");
 
-- 
2.35.0.914.ge5c8aab0d5b
Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 2/9] submodule--helper: refactor get_submodule_displaypath()

From: Atharva Raykar <raykar.ath@gmail.com>

We create a function called `do_get_submodule_displaypath()` that generates the display path required by several submodule functions, and takes a custom superprefix parameter, instead of reading it from the environment.

We then redefine the existing `get_submodule_displaypath()` function as a call to this new function, where the superprefix is obtained from the environment.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +8 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 4c7c1e1432d..5efceb9d46c 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -261,11 +261,8 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr
 	return 0;
 }
 
-/* the result should be freed by the caller. */
-static char *get_submodule_displaypath(const char *path, const char *prefix)
+static char *do_get_submodule_displaypath(const char *path, const char *prefix, const char *super_prefix)
 {
-	const char *super_prefix = get_super_prefix();
-
 	if (prefix && super_prefix) {
 		BUG("cannot have prefix '%s' and superprefix '%s'",
 		    prefix, super_prefix);
@@ -281,6 +278,13 @@ static char *get_submodule_displaypath(const char *path, const char *prefix)
 	}
 }
 
+/* the result should be freed by the caller. */
+static char *get_submodule_displaypath(const char *path, const char *prefix)
+{
+	const char *super_prefix = get_super_prefix();
+	return do_get_submodule_displaypath(path, prefix, super_prefix);
+}
+
 static char *compute_rev_name(const char *sub_path, const char* object_id)
 {
 	struct strbuf sb = STRBUF_INIT;
-- 
2.35.0.914.ge5c8aab0d5b
Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 3/9] submodule--helper: allow setting superprefix for init_submodule()

From: Atharva Raykar <raykar.ath@gmail.com>

We allow callers of the `init_submodule()` function to optionally override the superprefix from the environment.

We need to enable this option because in our conversion of the update command that will follow, the '--init' option will be handled through this API. We will need to change the superprefix at that time to ensure the display paths show correctly in the output messages.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)
Show changes to builtin/submodule--helper.c +7 −3
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 5efceb9d46c..09cda67c1ea 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -606,18 +606,22 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
 
 struct init_cb {
 	const char *prefix;
+	const char *superprefix;
 	unsigned int flags;
 };
 #define INIT_CB_INIT { 0 }
 
 static void init_submodule(const char *path, const char *prefix,
-			   unsigned int flags)
+			   const char *superprefix, unsigned int flags)
 {
 	const struct submodule *sub;
 	struct strbuf sb = STRBUF_INIT;
 	char *upd = NULL, *url = NULL, *displaypath;
 
-	displaypath = get_submodule_displaypath(path, prefix);
+	/* try superprefix from the environment, if it is not passed explicitly */
+	if (!superprefix)
+		superprefix = get_super_prefix();
+	displaypath = do_get_submodule_displaypath(path, prefix, superprefix);
 
 	sub = submodule_from_path(the_repository, null_oid(), path);
 
@@ -691,7 +695,7 @@ static void init_submodule(const char *path, const char *prefix,
 static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
 {
 	struct init_cb *info = cb_data;
-	init_submodule(list_item->name, info->prefix, info->flags);
+	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
 }
 
 static int module_init(int argc, const char **argv, const char *prefix)
-- 
2.35.0.914.ge5c8aab0d5b
Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 4/9] submodule--helper: run update using child process struct

From: Atharva Raykar <raykar.ath@gmail.com>

We switch to using the run-command API function that takes a 'struct child process', since we are using a lot of the options. This will also make it simple to switch over to using 'capture_command()' when we start handling the output of the command completely in C.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 34 ++++++++++++++++------------------
 1 file changed, 16 insertions(+), 18 deletions(-)
Show changes to builtin/submodule--helper.c +16 −18
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 09cda67c1ea..db71e6f4ec8 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2344,47 +2344,45 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 
 static int run_update_command(struct update_data *ud, int subforce)
 {
-	struct strvec args = STRVEC_INIT;
-	struct strvec child_env = STRVEC_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
 	int must_die_on_failure = 0;
-	int git_cmd;
 
 	switch (ud->update_strategy.type) {
 	case SM_UPDATE_CHECKOUT:
-		git_cmd = 1;
-		strvec_pushl(&args, "checkout", "-q", NULL);
+		cp.git_cmd = 1;
+		strvec_pushl(&cp.args, "checkout", "-q", NULL);
 		if (subforce)
-			strvec_push(&args, "-f");
+			strvec_push(&cp.args, "-f");
 		break;
 	case SM_UPDATE_REBASE:
-		git_cmd = 1;
-		strvec_push(&args, "rebase");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "rebase");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_MERGE:
-		git_cmd = 1;
-		strvec_push(&args, "merge");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "merge");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_COMMAND:
-		git_cmd = 0;
-		strvec_push(&args, ud->update_strategy.command);
+		cp.use_shell = 1;
+		strvec_push(&cp.args, ud->update_strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
 		    submodule_strategy_to_string(&ud->update_strategy));
 	}
-	strvec_push(&args, oid);
+	strvec_push(&cp.args, oid);
 
-	prepare_submodule_repo_env(&child_env);
-	if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
-				     ud->sm_path, child_env.v)) {
+	cp.dir = xstrdup(ud->sm_path);
+	prepare_submodule_repo_env(&cp.env_array);
+	if (run_command(&cp)) {
 		switch (ud->update_strategy.type) {
 		case SM_UPDATE_CHECKOUT:
 			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-- 
2.35.0.914.ge5c8aab0d5b
Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 5/9] builtin/submodule--helper.c: reformat designated initializers

From: Atharva Raykar <raykar.ath@gmail.com>

The second hunk here will make a subsequent commit's diff smaller, and let's do the first and third hunks while we're at it so that we consistently format all of these.

Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)
Show changes to builtin/submodule--helper.c +10 −3
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index db71e6f4ec8..9f79bdf4d51 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1654,7 +1654,10 @@ struct module_clone_data {
 	unsigned int require_init: 1;
 	int single_branch;
 };
-#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 }
+#define MODULE_CLONE_DATA_INIT { \
+	.reference = STRING_LIST_INIT_NODUP, \
+	.single_branch = -1, \
+}
 
 struct submodule_alternate_setup {
 	const char *submodule_name;
@@ -2047,7 +2050,9 @@ struct update_data {
 	unsigned int nofetch: 1;
 	unsigned int just_cloned: 1;
 };
-#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
+#define UPDATE_DATA_INIT { \
+	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+}
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
 		struct strbuf *out, const char *displaypath)
@@ -3013,7 +3018,9 @@ struct add_data {
 	unsigned int progress: 1;
 	unsigned int dissociate: 1;
 };
-#define ADD_DATA_INIT { .depth = -1 }
+#define ADD_DATA_INIT { \
+	.depth = -1, \
+}
 
 static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path)
 {
-- 
2.35.0.914.ge5c8aab0d5b
Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 6/9] builtin/submodule--helper.c: rename option variables to "opt"

Rename the "suc" variable in update_clone() to "opt", and do the same for the "update_data" variable in run_update_procedure().

The only reason for this change is to make the subsequent commit's diff smaller, by doing this rename we can "anchor" the diff better, as it "borrow" most of the options declared here as-is as far as the diff rename detection is concerned.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 74 ++++++++++++++++++-------------------
 1 file changed, 37 insertions(+), 37 deletions(-)
Show changes to builtin/submodule--helper.c +37 −37
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 9f79bdf4d51..c2d4fd0347a 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2517,36 +2517,36 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 {
 	const char *update = NULL;
 	struct pathspec pathspec;
-	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
 
 	struct option module_update_clone_options[] = {
 		OPT_STRING(0, "prefix", &prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
 			   N_("path"),
 			   N_("path into the working tree, across nested "
 			      "submodule boundaries")),
 		OPT_STRING(0, "update", &update,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
+		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
 			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &suc.dissociate,
+		OPT_BOOL(0, "dissociate", &opt.dissociate,
 			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &suc.depth, "<depth>",
+		OPT_STRING(0, "depth", &opt.depth, "<depth>",
 			   N_("create a shallow clone truncated to the "
 			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &suc.max_jobs,
+		OPT_INTEGER('j', "jobs", &opt.max_jobs,
 			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
+		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
 			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &suc.progress,
+		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &opt.progress,
 			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &suc.require_init,
+		OPT_BOOL(0, "require-init", &opt.require_init,
 			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &suc.single_branch,
+		OPT_BOOL(0, "single-branch", &opt.single_branch,
 			 N_("clone only one branch, HEAD or --branch")),
 		OPT_END()
 	};
@@ -2555,32 +2555,32 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
 		NULL
 	};
-	suc.prefix = prefix;
+	opt.prefix = prefix;
 
-	update_clone_config_from_gitmodules(&suc.max_jobs);
-	git_config(git_update_clone_config, &suc.max_jobs);
+	update_clone_config_from_gitmodules(&opt.max_jobs);
+	git_config(git_update_clone_config, &opt.max_jobs);
 
 	argc = parse_options(argc, argv, prefix, module_update_clone_options,
 			     git_submodule_helper_usage, 0);
 
 	if (update)
-		if (parse_submodule_update_strategy(update, &suc.update) < 0)
+		if (parse_submodule_update_strategy(update, &opt.update) < 0)
 			die(_("bad value for update parameter"));
 
-	if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
+	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
 		return 1;
 
 	if (pathspec.nr)
-		suc.warn_if_uninitialized = 1;
+		opt.warn_if_uninitialized = 1;
 
-	return update_submodules(&suc);
+	return update_submodules(&opt);
 }
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
 	char *prefixed_path, *update = NULL;
-	struct update_data update_data = UPDATE_DATA_INIT;
+	struct update_data opt = UPDATE_DATA_INIT;
 
 	struct option options[] = {
 		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
@@ -2589,20 +2589,20 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 			 N_("don't fetch new objects from the remote site")),
 		OPT_BOOL(0, "just-cloned", &just_cloned,
 			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
+		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
 		OPT_STRING(0, "prefix", &prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
 		OPT_STRING(0, "update", &update,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
 			   N_("path into the working tree, across nested "
 			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
+		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
 			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
+		OPT_CALLBACK_F(0, "suboid", &opt.suboid, N_("subsha1"),
 			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
 		OPT_END()
@@ -2618,27 +2618,27 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	if (argc != 1)
 		usage_with_options(usage, options);
 
-	update_data.force = !!force;
-	update_data.quiet = !!quiet;
-	update_data.nofetch = !!nofetch;
-	update_data.just_cloned = !!just_cloned;
-	update_data.sm_path = argv[0];
+	opt.force = !!force;
+	opt.quiet = !!quiet;
+	opt.nofetch = !!nofetch;
+	opt.just_cloned = !!just_cloned;
+	opt.sm_path = argv[0];
 
-	if (update_data.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
+	if (opt.recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", opt.recursive_prefix, opt.sm_path);
 	else
-		prefixed_path = xstrdup(update_data.sm_path);
+		prefixed_path = xstrdup(opt.sm_path);
 
-	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
+	opt.displaypath = get_submodule_displaypath(prefixed_path, prefix);
 
-	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
-					    update_data.sm_path, update,
-					    &update_data.update_strategy);
+	determine_submodule_update_strategy(the_repository, opt.just_cloned,
+					    opt.sm_path, update,
+					    &opt.update_strategy);
 
 	free(prefixed_path);
 
-	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data);
+	if (!oideq(&opt.oid, &opt.suboid) || opt.force)
+		return do_run_update_procedure(&opt);
 
 	return 3;
 }
-- 
2.35.0.914.ge5c8aab0d5b
Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 7/9] submodule--helper: don't use bitfield indirection for parse_options()

Do away with the indirection of local variables added in c51f8f94e5b (submodule--helper: run update procedures from C, 2021-08-24).

These were only needed because in C you can't get a pointer to a single bit, so we were using intermediate variables instead.

This will also make a subsequent large commit's diff smaller.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 23 ++++++++++-------------
 1 file changed, 10 insertions(+), 13 deletions(-)
Show changes to builtin/submodule--helper.c +10 −13
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c2d4fd0347a..4a0890954e9 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2045,10 +2045,10 @@ struct update_data {
 	struct object_id suboid;
 	struct submodule_update_strategy update_strategy;
 	int depth;
-	unsigned int force: 1;
-	unsigned int quiet: 1;
-	unsigned int nofetch: 1;
-	unsigned int just_cloned: 1;
+	unsigned int force;
+	unsigned int quiet;
+	unsigned int nofetch;
+	unsigned int just_cloned;
 };
 #define UPDATE_DATA_INIT { \
 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
@@ -2578,16 +2578,17 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
-	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
 	char *prefixed_path, *update = NULL;
 	struct update_data opt = UPDATE_DATA_INIT;
 
 	struct option options[] = {
-		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&force, N_("force checkout updates"), 0),
-		OPT_BOOL('N', "no-fetch", &nofetch,
+		OPT__QUIET(&opt.quiet,
+			   N_("suppress output for update by rebase or merge")),
+		OPT__FORCE(&opt.force, N_("force checkout updates"),
+			   0),
+		OPT_BOOL('N', "no-fetch", &opt.nofetch,
 			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &just_cloned,
+		OPT_BOOL(0, "just-cloned", &opt.just_cloned,
 			 N_("overrides update mode in case the repository is a fresh clone")),
 		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
 		OPT_STRING(0, "prefix", &prefix,
@@ -2618,10 +2619,6 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	if (argc != 1)
 		usage_with_options(usage, options);
 
-	opt.force = !!force;
-	opt.quiet = !!quiet;
-	opt.nofetch = !!nofetch;
-	opt.just_cloned = !!just_cloned;
 	opt.sm_path = argv[0];
 
 	if (opt.recursive_prefix)
-- 
2.35.0.914.ge5c8aab0d5b
Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 8/9] submodule tests: test for init and update failure output

Amend some submodule tests to test for the failure output of "git submodule [update|init]". The lack of such tests hid a regression in an earlier version of a subsequent commit.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 t/t7406-submodule-update.sh    | 14 ++++++++++++--
 t/t7408-submodule-reference.sh | 14 +++++++++++++-
 2 files changed, 25 insertions(+), 3 deletions(-)
Show changes to 2 files +25 −3

t/t7406-submodule-update.sh, t/t7408-submodule-reference.sh

diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 11cccbb333b..7764c1c3cb6 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -205,8 +205,18 @@ test_expect_success 'submodule update should fail due to local changes' '
 	 (cd submodule &&
 	  compare_head
 	 ) &&
-	 test_must_fail git submodule update submodule
-	)
+	 test_must_fail git submodule update submodule 2>../actual.raw
+	) &&
+	sed "s/^> //" >expect <<-\EOF &&
+	> error: Your local changes to the following files would be overwritten by checkout:
+	> 	file
+	> Please commit your changes or stash them before you switch branches.
+	> Aborting
+	> fatal: Unable to checkout OID in submodule path '\''submodule'\''
+	EOF
+	sed -e "s/checkout $SQ[^$SQ]*$SQ/checkout OID/" <actual.raw >actual &&
+	test_cmp expect actual
+
 '
 test_expect_success 'submodule update should throw away changes with --force ' '
 	(cd super &&
diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh
index a3892f494b6..c3a45455106 100755
--- a/t/t7408-submodule-reference.sh
+++ b/t/t7408-submodule-reference.sh
@@ -193,7 +193,19 @@ test_expect_success 'missing nested submodule alternate fails clone and submodul
 		cd supersuper-clone &&
 		check_that_two_of_three_alternates_are_used &&
 		# update of the submodule fails
-		test_must_fail git submodule update --init --recursive
+		cat >expect <<-\EOF &&
+		fatal: submodule '\''sub'\'' cannot add alternate: path ... does not exist
+		Failed to clone '\''sub'\''. Retry scheduled
+		fatal: submodule '\''sub-dissociate'\'' cannot add alternate: path ... does not exist
+		Failed to clone '\''sub-dissociate'\''. Retry scheduled
+		fatal: submodule '\''sub'\'' cannot add alternate: path ... does not exist
+		Failed to clone '\''sub'\'' a second time, aborting
+		fatal: Failed to recurse into submodule path ...
+		EOF
+		test_must_fail git submodule update --init --recursive 2>err &&
+		grep -e fatal: -e ^Failed err >actual.raw &&
+		sed -e "s/path $SQ[^$SQ]*$SQ/path .../" <actual.raw >actual &&
+		test_cmp expect actual
 	)
 '
 
-- 
2.35.0.914.ge5c8aab0d5b
Ævar Arnfjörð Bjarmason· Jan 28, 2022, 12:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v5 9/9] submodule: move core cmd_update() logic to C

From: Atharva Raykar <raykar.ath@gmail.com>

This patch completes the conversion past the flag parsing of `submodule update` by introducing a helper subcommand called `submodule--helper update`. The behaviour of `submodule update` should remain the same after this patch.

We add more fields to the `struct update_data` that are required by `struct submodule_update_clone` to be able to perform a clone, when that is needed to be done.

Recursing on a submodule is done by calling a subprocess that launches `submodule--helper update`, with a modified `--recursive-prefix` and `--prefix` parameter.

We also introduce `update_submodules2()` and `update_submodule2()` which will supersede `update_submodules()` and `update_submodule()`.

When the `--init` flag is passed to the subcommand, we do not spawn a new subprocess and call `submodule--helper init` on the submodule paths, because the Git machinery is not able to pick up the configuration changes introduced by that init call[1]. So we instead run the `init_submodule_cb()` callback over each submodule in the same process.

While we are at it, we also remove the fetch_in_submodule() shell function since it is no longer used anywhere.

[1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/submodule--helper.c | 599 +++++++++++++++++++++---------------
 git-submodule.sh            | 145 +--------
 2 files changed, 356 insertions(+), 388 deletions(-)
Show changes to 2 files +356 −388

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 4a0890954e9..1c28b6f479c 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -71,21 +71,6 @@ static char *get_default_remote(void)
 	return repo_get_default_remote(the_repository);
 }
 
-static int print_default_remote(int argc, const char **argv, const char *prefix)
-{
-	char *remote;
-
-	if (argc != 1)
-		die(_("submodule--helper print-default-remote takes no arguments"));
-
-	remote = get_default_remote();
-	if (remote)
-		printf("%s\n", remote);
-
-	free(remote);
-	return 0;
-}
-
 static int starts_with_dot_slash(const char *str)
 {
 	return str[0] == '.' && is_dir_sep(str[1]);
@@ -1965,29 +1950,6 @@ static void determine_submodule_update_strategy(struct repository *r,
 	free(key);
 }
 
-static int module_update_module_mode(int argc, const char **argv, const char *prefix)
-{
-	const char *path, *update = NULL;
-	int just_cloned;
-	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
-
-	if (argc < 3 || argc > 4)
-		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
-
-	just_cloned = git_config_int("just_cloned", argv[1]);
-	path = argv[2];
-
-	if (argc == 4)
-		update = argv[3];
-
-	determine_submodule_update_strategy(the_repository,
-					    just_cloned, path, update,
-					    &update_strategy);
-	fputs(submodule_strategy_to_string(&update_strategy), stdout);
-
-	return 0;
-}
-
 struct update_clone_data {
 	const struct submodule *sub;
 	struct object_id oid;
@@ -2015,7 +1977,6 @@ struct submodule_update_clone {
 	const char *prefix;
 	int single_branch;
 
-	/* to be consumed by git-submodule.sh */
 	struct update_clone_data *update_clone;
 	int update_clone_nr; int update_clone_alloc;
 
@@ -2038,6 +1999,7 @@ struct submodule_update_clone {
 }
 
 struct update_data {
+	const char *prefix;
 	const char *recursive_prefix;
 	const char *sm_path;
 	const char *displaypath;
@@ -2045,13 +2007,54 @@ struct update_data {
 	struct object_id suboid;
 	struct submodule_update_strategy update_strategy;
 	int depth;
+	int recommend_shallow;
+	int single_branch;
+	int max_jobs;
+	unsigned int init;
 	unsigned int force;
 	unsigned int quiet;
 	unsigned int nofetch;
 	unsigned int just_cloned;
+	unsigned int remote;
+	unsigned int recursive;
+	unsigned int progress;
+	unsigned int dissociate;
+	unsigned int require_init;
+	unsigned warn_if_uninitialized ;
+	struct string_list references;
+	struct module_list list;
 };
 #define UPDATE_DATA_INIT { \
+	.list = MODULE_LIST_INIT, \
 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+	.recommend_shallow = -1, \
+	.references = STRING_LIST_INIT_DUP, \
+	.single_branch = -1, \
+	.max_jobs = 1, \
+}
+
+static void update_clone_from_update_data(struct submodule_update_clone *suc,
+					  struct update_data *update_data)
+{
+	suc->prefix = update_data->prefix;
+	suc->recursive_prefix = update_data->recursive_prefix;
+	suc->max_jobs = update_data->max_jobs;
+	suc->progress = update_data->progress;
+	suc->quiet = update_data->quiet;
+	suc->dissociate = update_data->dissociate;
+	suc->require_init = update_data->require_init;
+	suc->single_branch = update_data->single_branch;
+	suc->warn_if_uninitialized = update_data->warn_if_uninitialized;
+	suc->list = update_data->list;
+	suc->update = update_data->update_strategy;
+	suc->recommend_shallow = update_data->recommend_shallow;
+	if (update_data->depth)
+		suc->depth = xstrfmt("--depth=%d", update_data->depth);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			string_list_append(&suc->references, item->string);
+	}
 }
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
@@ -2352,8 +2355,15 @@ static int run_update_command(struct update_data *ud, int subforce)
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
 	int must_die_on_failure = 0;
+	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
 
-	switch (ud->update_strategy.type) {
+	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
+		determine_submodule_update_strategy(the_repository, ud->just_cloned,
+						    ud->sm_path, NULL, &strategy);
+	else
+		strategy = ud->update_strategy;
+
+	switch (strategy.type) {
 	case SM_UPDATE_CHECKOUT:
 		cp.git_cmd = 1;
 		strvec_pushl(&cp.args, "checkout", "-q", NULL);
@@ -2376,55 +2386,54 @@ static int run_update_command(struct update_data *ud, int subforce)
 		break;
 	case SM_UPDATE_COMMAND:
 		cp.use_shell = 1;
-		strvec_push(&cp.args, ud->update_strategy.command);
+		strvec_push(&cp.args, strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+		    submodule_strategy_to_string(&strategy));
 	}
 	strvec_push(&cp.args, oid);
 
 	cp.dir = xstrdup(ud->sm_path);
 	prepare_submodule_repo_env(&cp.env_array);
 	if (run_command(&cp)) {
-		switch (ud->update_strategy.type) {
+		switch (strategy.type) {
 		case SM_UPDATE_CHECKOUT:
-			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			die_message(_("Unable to checkout '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_REBASE:
-			printf(_("Unable to rebase '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			if (!must_die_on_failure)
+				break;
+			die(_("Unable to rebase '%s' in submodule path '%s'"),
+			    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_MERGE:
-			printf(_("Unable to merge '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			if (!must_die_on_failure)
+				break;
+			die(_("Unable to merge '%s' in submodule path '%s'"),
+			    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_COMMAND:
-			printf(_("Execution of '%s %s' failed in submodule path '%s'"),
-			       ud->update_strategy.command, oid, ud->displaypath);
+			if (!must_die_on_failure)
+				break;
+			die(_("Execution of '%s %s' failed in submodule path '%s'"),
+			    strategy.command, oid, ud->displaypath);
 			break;
 		default:
 			BUG("unexpected update strategy type: %s",
-			    submodule_strategy_to_string(&ud->update_strategy));
+			    submodule_strategy_to_string(&strategy));
 		}
-		/*
-		 * NEEDSWORK: We are currently printing to stdout with error
-		 * return so that the shell caller handles the error output
-		 * properly. Once we start handling the error messages within
-		 * C, we should use die() instead.
-		 */
-		if (must_die_on_failure)
-			return 2;
-		/*
-		 * This signifies to the caller in shell that the command
-		 * failed without dying
-		 */
+
+		/* the command failed, but update must continue */
 		return 1;
 	}
 
-	switch (ud->update_strategy.type) {
+	if (ud->quiet)
+		return 0;
+
+	switch (strategy.type) {
 	case SM_UPDATE_CHECKOUT:
 		printf(_("Submodule path '%s': checked out '%s'\n"),
 		       ud->displaypath, oid);
@@ -2439,17 +2448,17 @@ static int run_update_command(struct update_data *ud, int subforce)
 		break;
 	case SM_UPDATE_COMMAND:
 		printf(_("Submodule path '%s': '%s %s'\n"),
-		       ud->displaypath, ud->update_strategy.command, oid);
+		       ud->displaypath, strategy.command, oid);
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+		    submodule_strategy_to_string(&strategy));
 	}
 
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud)
+static int run_update_procedure(struct update_data *ud)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2479,178 +2488,6 @@ static int do_run_update_procedure(struct update_data *ud)
 	return run_update_command(ud, subforce);
 }
 
-static void update_submodule(struct update_clone_data *ucd)
-{
-	fprintf(stdout, "dummy %s %d\t%s\n",
-		oid_to_hex(&ucd->oid),
-		ucd->just_cloned,
-		ucd->sub->path);
-}
-
-static int update_submodules(struct submodule_update_clone *suc)
-{
-	int i;
-
-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
-				   update_clone_start_failure,
-				   update_clone_task_finished, suc, "submodule",
-				   "parallel/update");
-
-	/*
-	 * We saved the output and put it out all at once now.
-	 * That means:
-	 * - the listener does not have to interleave their (checkout)
-	 *   work with our fetching.  The writes involved in a
-	 *   checkout involve more straightforward sequential I/O.
-	 * - the listener can avoid doing any work if fetching failed.
-	 */
-	if (suc->quickstop)
-		return 1;
-
-	for (i = 0; i < suc->update_clone_nr; i++)
-		update_submodule(&suc->update_clone[i]);
-
-	return 0;
-}
-
-static int update_clone(int argc, const char **argv, const char *prefix)
-{
-	const char *update = NULL;
-	struct pathspec pathspec;
-	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
-
-	struct option module_update_clone_options[] = {
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
-			   N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
-			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &opt.dissociate,
-			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &opt.depth, "<depth>",
-			   N_("create a shallow clone truncated to the "
-			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &opt.max_jobs,
-			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
-			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &opt.progress,
-			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &opt.require_init,
-			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &opt.single_branch,
-			 N_("clone only one branch, HEAD or --branch")),
-		OPT_END()
-	};
-
-	const char *const git_submodule_helper_usage[] = {
-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
-		NULL
-	};
-	opt.prefix = prefix;
-
-	update_clone_config_from_gitmodules(&opt.max_jobs);
-	git_config(git_update_clone_config, &opt.max_jobs);
-
-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
-			     git_submodule_helper_usage, 0);
-
-	if (update)
-		if (parse_submodule_update_strategy(update, &opt.update) < 0)
-			die(_("bad value for update parameter"));
-
-	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
-		return 1;
-
-	if (pathspec.nr)
-		opt.warn_if_uninitialized = 1;
-
-	return update_submodules(&opt);
-}
-
-static int run_update_procedure(int argc, const char **argv, const char *prefix)
-{
-	char *prefixed_path, *update = NULL;
-	struct update_data opt = UPDATE_DATA_INIT;
-
-	struct option options[] = {
-		OPT__QUIET(&opt.quiet,
-			   N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&opt.force, N_("force checkout updates"),
-			   0),
-		OPT_BOOL('N', "no-fetch", &opt.nofetch,
-			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &opt.just_cloned,
-			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
-			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &opt.suboid, N_("subsha1"),
-			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_END()
-	};
-
-	const char *const usage[] = {
-		N_("git submodule--helper run-update-procedure [<options>] <path>"),
-		NULL
-	};
-
-	argc = parse_options(argc, argv, prefix, options, usage, 0);
-
-	if (argc != 1)
-		usage_with_options(usage, options);
-
-	opt.sm_path = argv[0];
-
-	if (opt.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", opt.recursive_prefix, opt.sm_path);
-	else
-		prefixed_path = xstrdup(opt.sm_path);
-
-	opt.displaypath = get_submodule_displaypath(prefixed_path, prefix);
-
-	determine_submodule_update_strategy(the_repository, opt.just_cloned,
-					    opt.sm_path, update,
-					    &opt.update_strategy);
-
-	free(prefixed_path);
-
-	if (!oideq(&opt.oid, &opt.suboid) || opt.force)
-		return do_run_update_procedure(&opt);
-
-	return 3;
-}
-
-static int resolve_relative_path(int argc, const char **argv, const char *prefix)
-{
-	struct strbuf sb = STRBUF_INIT;
-	if (argc != 3)
-		die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
-
-	printf("%s", relative_path(argv[1], argv[2], &sb));
-	strbuf_release(&sb);
-	return 0;
-}
-
 static const char *remote_submodule_branch(const char *path)
 {
 	const struct submodule *sub;
@@ -2783,17 +2620,11 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+static void ensure_core_worktree(const char *path)
 {
-	const char *path;
 	const char *cw;
 	struct repository subrepo;
 
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-
 	if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
 		die(_("could not get a repository handle for submodule '%s'"), path);
 
@@ -2813,8 +2644,6 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
 		free(abs_path);
 		strbuf_release(&sb);
 	}
-
-	return 0;
 }
 
 static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
@@ -3001,6 +2830,271 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 	return !!ret;
 }
 
+static void update_data_to_args(struct update_data *update_data, struct strvec *args)
+{
+	const char *update = submodule_strategy_to_string(&update_data->update_strategy);
+
+	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
+	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
+	if (update_data->prefix)
+		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
+	if (update_data->recursive_prefix)
+		strvec_pushl(args, "--recursive-prefix",
+			     update_data->recursive_prefix, NULL);
+	if (update_data->quiet)
+		strvec_push(args, "--quiet");
+	if (update_data->force)
+		strvec_push(args, "--force");
+	if (update_data->init)
+		strvec_push(args, "--init");
+	if (update_data->remote)
+		strvec_push(args, "--remote");
+	if (update_data->nofetch)
+		strvec_push(args, "--no-fetch");
+	if (update_data->dissociate)
+		strvec_push(args, "--dissociate");
+	if (update_data->progress)
+		strvec_push(args, "--progress");
+	if (update_data->require_init)
+		strvec_push(args, "--require-init");
+	if (update_data->depth)
+		strvec_pushf(args, "--depth=%d", update_data->depth);
+	if (update)
+		strvec_pushl(args, "--update", update, NULL);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			strvec_pushl(args, "--reference", item->string, NULL);
+	}
+	if (update_data->recommend_shallow == 0)
+		strvec_push(args, "--no-recommend-shallow");
+	else if (update_data->recommend_shallow == 1)
+		strvec_push(args, "--recommend-shallow");
+	if (update_data->single_branch >= 0)
+		strvec_push(args, "--single-branch");
+}
+
+static int update_submodule(struct update_data *update_data)
+{
+	char *prefixed_path;
+
+	ensure_core_worktree(update_data->sm_path);
+
+	if (update_data->recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
+					update_data->sm_path);
+	else
+		prefixed_path = xstrdup(update_data->sm_path);
+
+	update_data->displaypath = get_submodule_displaypath(prefixed_path,
+							     update_data->prefix);
+	free(prefixed_path);
+
+	if (update_data->just_cloned) {
+		oidcpy(&update_data->suboid, null_oid());
+	} else {
+		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+			die(_("Unable to find current revision in submodule path '%s'"),
+			    update_data->displaypath);
+	}
+
+	if (update_data->remote) {
+		char *remote_name = get_default_remote_submodule(update_data->sm_path);
+		const char *branch = remote_submodule_branch(update_data->sm_path);
+		char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch);
+
+		if (!update_data->nofetch) {
+			if (fetch_in_submodule(update_data->sm_path, update_data->depth,
+					      0, NULL))
+				die(_("Unable to fetch in submodule path '%s'"),
+				    update_data->sm_path);
+		}
+
+		if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
+			die(_("Unable to find %s revision in submodule path '%s'"),
+			    remote_ref, update_data->sm_path);
+
+		free(remote_ref);
+	}
+
+	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
+		if (run_update_procedure(update_data))
+			return 1;
+
+	if (update_data->recursive) {
+		struct child_process cp = CHILD_PROCESS_INIT;
+		struct update_data next = *update_data;
+		int res;
+
+		if (update_data->recursive_prefix)
+			prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix,
+						update_data->sm_path);
+		else
+			prefixed_path = xstrfmt("%s/", update_data->sm_path);
+
+		next.recursive_prefix = get_submodule_displaypath(prefixed_path,
+								  update_data->prefix);
+		next.prefix = NULL;
+		oidcpy(&next.oid, null_oid());
+		oidcpy(&next.suboid, null_oid());
+
+		cp.dir = update_data->sm_path;
+		cp.git_cmd = 1;
+		prepare_submodule_repo_env(&cp.env_array);
+		update_data_to_args(&next, &cp.args);
+
+		/* die() if child process die()'d */
+		res = run_command(&cp);
+		if (!res)
+			return 0;
+		die_message(_("Failed to recurse into submodule path '%s'"),
+			    update_data->displaypath);
+		if (res == 128)
+			exit(res);
+		else if (res)
+			return 1;
+	}
+
+	return 0;
+}
+
+static int update_submodules(struct update_data *update_data)
+{
+	int i, res = 0;
+	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+
+	update_clone_from_update_data(&suc, update_data);
+	run_processes_parallel_tr2(suc.max_jobs, update_clone_get_next_task,
+				   update_clone_start_failure,
+				   update_clone_task_finished, &suc, "submodule",
+				   "parallel/update");
+
+	/*
+	 * We saved the output and put it out all at once now.
+	 * That means:
+	 * - the listener does not have to interleave their (checkout)
+	 *   work with our fetching.  The writes involved in a
+	 *   checkout involve more straightforward sequential I/O.
+	 * - the listener can avoid doing any work if fetching failed.
+	 */
+	if (suc.quickstop) {
+		res = 1;
+		goto cleanup;
+	}
+
+	for (i = 0; i < suc.update_clone_nr; i++) {
+		struct update_clone_data ucd = suc.update_clone[i];
+
+		oidcpy(&update_data->oid, &ucd.oid);
+		update_data->just_cloned = ucd.just_cloned;
+		update_data->sm_path = ucd.sub->path;
+
+		if (update_submodule(update_data))
+			res = 1;
+	}
+
+cleanup:
+	string_list_clear(&update_data->references, 0);
+	return res;
+}
+
+static int module_update(int argc, const char **argv, const char *prefix)
+{
+	const char *update = NULL;
+	struct pathspec pathspec;
+	struct update_data opt = UPDATE_DATA_INIT;
+
+	struct option module_update_clone_options[] = {
+		OPT__FORCE(&opt.force, N_("force checkout updates"), 0),
+		OPT_BOOL(0, "init", &opt.init,
+			 N_("initialize uninitialized submodules before update")),
+		OPT_BOOL(0, "remote", &opt.remote,
+			 N_("use SHA-1 of submodule's remote tracking branch")),
+		OPT_BOOL(0, "recursive", &opt.recursive,
+			 N_("traverse submodules recursively")),
+		OPT_BOOL('N', "no-fetch", &opt.nofetch,
+			 N_("don't fetch new objects from the remote site")),
+		OPT_STRING(0, "prefix", &opt.prefix,
+			   N_("path"),
+			   N_("path into the working tree")),
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
+			   N_("path"),
+			   N_("path into the working tree, across nested "
+			      "submodule boundaries")),
+		OPT_STRING(0, "update", &update,
+			   N_("string"),
+			   N_("rebase, merge, checkout or none")),
+		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
+				N_("reference repository")),
+		OPT_BOOL(0, "dissociate", &opt.dissociate,
+			 N_("use --reference only while cloning")),
+		OPT_INTEGER(0, "depth", &opt.depth,
+			    N_("create a shallow clone truncated to the "
+			       "specified number of revisions")),
+		OPT_INTEGER('j', "jobs", &opt.max_jobs,
+			    N_("parallel jobs")),
+		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
+			 N_("whether the initial clone should follow the shallow recommendation")),
+		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &opt.progress,
+			 N_("force cloning progress")),
+		OPT_BOOL(0, "require-init", &opt.require_init,
+			 N_("disallow cloning into non-empty directory")),
+		OPT_BOOL(0, "single-branch", &opt.single_branch,
+			 N_("clone only one branch, HEAD or --branch")),
+		OPT_END()
+	};
+
+	const char *const git_submodule_helper_usage[] = {
+		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
+		NULL
+	};
+
+	update_clone_config_from_gitmodules(&opt.max_jobs);
+	git_config(git_update_clone_config, &opt.max_jobs);
+
+	argc = parse_options(argc, argv, prefix, module_update_clone_options,
+			     git_submodule_helper_usage, 0);
+	oidcpy(&opt.oid, null_oid());
+	oidcpy(&opt.suboid, null_oid());
+
+	if (update)
+		if (parse_submodule_update_strategy(update,
+						    &opt.update_strategy) < 0)
+			die(_("bad value for update parameter"));
+
+	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
+		return 1;
+
+	if (pathspec.nr)
+		opt.warn_if_uninitialized = 1;
+
+	if (opt.init) {
+		struct module_list list = MODULE_LIST_INIT;
+		struct init_cb info = INIT_CB_INIT;
+
+		if (module_list_compute(argc, argv, opt.prefix,
+					&pathspec, &list) < 0)
+			return 1;
+
+		/*
+		 * If there are no path args and submodule.active is set then,
+		 * by default, only initialize 'active' modules.
+		 */
+		if (!argc && git_config_get_value_multi("submodule.active"))
+			module_list_active(&list);
+
+		info.prefix = opt.prefix;
+		info.superprefix = opt.recursive_prefix;
+		if (opt.quiet)
+			info.flags |= OPT_QUIET;
+
+		for_each_listed_submodule(&list, init_submodule_cb, &info);
+	}
+
+	return update_submodules(&opt);
+}
+
 struct add_data {
 	const char *prefix;
 	const char *branch;
@@ -3388,16 +3482,11 @@ static struct cmd_struct commands[] = {
 	{"name", module_name, 0},
 	{"clone", module_clone, 0},
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
-	{"update-module-mode", module_update_module_mode, 0},
-	{"update-clone", update_clone, 0},
-	{"run-update-procedure", run_update_procedure, 0},
-	{"ensure-core-worktree", ensure_core_worktree, 0},
-	{"relative-path", resolve_relative_path, 0},
+	{"update", module_update, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
 	{"init", module_init, SUPPORT_SUPER_PREFIX},
 	{"status", module_status, SUPPORT_SUPER_PREFIX},
-	{"print-default-remote", print_default_remote, 0},
 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
 	{"deinit", module_deinit, 0},
 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index 652861aa66a..bcd8b92aabd 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -246,20 +246,6 @@ cmd_deinit()
 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
 }
 
-# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
-# Because arguments are positional, use an empty string to omit <depth>
-# but include <sha1>.
-fetch_in_submodule () (
-	sanitize_submodule_env &&
-	cd "$1" &&
-	if test $# -eq 3
-	then
-		echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
-	else
-		git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
-	fi
-)
-
 #
 # Update each submodule path to correct revision, using clone and checkout as needed
 #
@@ -361,133 +347,26 @@ cmd_update()
 		shift
 	done
 
-	if test -n "$init"
-	then
-		cmd_init "--" "$@" || return
-	fi
-
-	{
-	git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
-		${progress:+"--progress"} \
+	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
+		${GIT_QUIET:+--quiet} \
+		${force:+--force} \
+		${progress:+--progress} \
+		${dissociate:+--dissociate} \
+		${remote:+--remote} \
+		${recursive:+--recursive} \
+		${init:+--init} \
+		${require_init:+--require-init} \
+		${nofetch:+--no-fetch} \
 		${wt_prefix:+--prefix "$wt_prefix"} \
 		${prefix:+--recursive-prefix "$prefix"} \
 		${update:+--update "$update"} \
 		${reference:+"$reference"} \
-		${dissociate:+"--dissociate"} \
-		${depth:+--depth "$depth"} \
-		${require_init:+--require-init} \
+		${depth:+"$depth"} \
 		$single_branch \
 		$recommend_shallow \
 		$jobs \
 		-- \
-		"$@" || echo "#unmatched" $?
-	} | {
-	err=
-	while read -r quickabort sha1 just_cloned sm_path
-	do
-		die_if_unmatched "$quickabort" "$sha1"
-
-		git submodule--helper ensure-core-worktree "$sm_path" || exit 1
-
-		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
-
-		if test $just_cloned -eq 1
-		then
-			subsha1=
-		else
-			just_cloned=
-			subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify HEAD) ||
-			die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
-		fi
-
-		if test -n "$remote"
-		then
-			branch=$(git submodule--helper remote-branch "$sm_path")
-			if test -z "$nofetch"
-			then
-				# Fetch remote before determining tracking $sha1
-				fetch_in_submodule "$sm_path" $depth ||
-				die "fatal: $(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
-			fi
-			remote_name=$(sanitize_submodule_env; cd "$sm_path" && git submodule--helper print-default-remote)
-			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify "${remote_name}/${branch}") ||
-			die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
-		fi
-
-		out=$(git submodule--helper run-update-procedure \
-			  ${wt_prefix:+--prefix "$wt_prefix"} \
-			  ${GIT_QUIET:+--quiet} \
-			  ${force:+--force} \
-			  ${just_cloned:+--just-cloned} \
-			  ${nofetch:+--no-fetch} \
-			  ${depth:+"$depth"} \
-			  ${update:+--update "$update"} \
-			  ${prefix:+--recursive-prefix "$prefix"} \
-			  ${sha1:+--oid "$sha1"} \
-			  ${subsha1:+--suboid "$subsha1"} \
-			  "--" \
-			  "$sm_path")
-
-		# exit codes for run-update-procedure:
-		# 0: update was successful, say command output
-		# 1: update procedure failed, but should not die
-		# 2 or 128: subcommand died during execution
-		# 3: no update procedure was run
-		res="$?"
-		case $res in
-		0)
-			say "$out"
-			;;
-		1)
-			err="${err};fatal: $out"
-			continue
-			;;
-		2|128)
-			die_with_status $res "fatal: $out"
-			;;
-		esac
-
-		if test -n "$recursive"
-		then
-			(
-				prefix=$(git submodule--helper relative-path "$prefix$sm_path/" "$wt_prefix")
-				wt_prefix=
-				sanitize_submodule_env
-				cd "$sm_path" &&
-				eval cmd_update
-			)
-			res=$?
-			if test $res -gt 0
-			then
-				die_msg="fatal: $(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
-				if test $res -ne 2
-				then
-					err="${err};$die_msg"
-					continue
-				else
-					die_with_status $res "$die_msg"
-				fi
-			fi
-		fi
-	done
-
-	if test -n "$err"
-	then
-		OIFS=$IFS
-		IFS=';'
-		for e in $err
-		do
-			if test -n "$e"
-			then
-				echo >&2 "$e"
-			fi
-		done
-		IFS=$OIFS
-		exit 1
-	fi
-	}
+		"$@"
 }
 
 #
-- 
2.35.0.914.ge5c8aab0d5b
Glen Choo· Feb 3, 2022, 00:18 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v5 9/9] submodule: move core cmd_update() logic to C

I mentioned (out-of-mailing-list) that I was still looking at this, but that the big sh -> c conversion is quite challenging for me to parse personally. I'm still looking at it, but it will take some time..

I'm of the opinion that this patch would be a lot easier to review if it were broken up into more patches, but it has always looked like this [1]. The only real difference from [1] to this version is that this also removes all of the dead code, which doesn't really hinder reviewability.

I don't think you have to go through the effort of splitting it up - after all you were able to review [1], so given enough time I should be able to read through this patch too :) That said, I wish that we already had a split up patch for at least 2 other reasons:

- Junio pointed out that this conflicts with
  es/superproject-aware-submodules [2]. I'm not sure which should be
  based on which. If this does end up being based on
  es/superproject-aware-submodules, it would probably be easier to
  rebase as a series of smaller patches. Atharva noted that the
  conflicts are mild though, so maybe it's not so bad.
- Besides making sure that the sh -> c is faithful, a thorough review
  should hopefully catch unintentional mistakes. The size of this patch
  makes such mistakes difficult to spot. For instance, here's something
  I spotted only after trying to split the patch myself..
  > +static int module_update(int argc, const char **argv, const char *prefix)
  > +{
  > +	const char *update = NULL;
  > +	struct pathspec pathspec;
  > +	struct update_data opt = UPDATE_DATA_INIT;
  > +
  > +	struct option module_update_clone_options[] = {
  [...]
  > +	};
  > +
  > +	const char *const git_submodule_helper_usage[] = {
  > +		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
  > +		NULL
  > +	};
  > +
  > +	update_clone_config_from_gitmodules(&opt.max_jobs);
  > +	git_config(git_update_clone_config, &opt.max_jobs);
  Notice that we copy-pasted the option parsing from update-clone into
  module_update() but forgot to update the names.
My ideal patch organization would be something like:
- wrap some existing command in "git submodule--helper update" (probably
  run-update-procedure)
- absorb the surrounding sh code into "git submodule--helper
  update" one command at-a-time i.e. deprecating and removing the
  commands one at a time - instead of deprecating and removing them all
  at once (like this patch), or deprecating all at once and removing
  them one at a time (like v1).

I don't know if it's feasible or not; Atharva noted upthread that there are some technical reasons why some things can be done in-process and some cannot, but it might be a useful exercise.

Here's what I propose:
- If you think this alternative organization would be helpful for you
  too, I will attempt it. This will take a while, but by the end you and
  I will have effectively reviewed all of the code, so it should be easy
  to finish up the review.
- Otherwise e.g. maybe this is a huge waste of time, or you're already
  really confident in the correctness of the sh -> c when you reviewed
  the original patch, etc, I'll just review this patch as-is. I'd
  appreciate any tips and tricks that might help :)
- Orthogonal to patch organization, I'm still not sure if this will be
  rebased on es/superproject-aware-submodules or vice-versa, and I don't
  want either of us to sink too much effort before knowing the answer.

[1] https://lore.kernel.org/git/20210907115932.36068-7-raykar.ath@gmail.com/ [2] https://lore.kernel.org/git/YWiXL+plA7GHfuVv@google.com/

Ævar Arnfjörð Bjarmason· Feb 3, 2022, 02:26 UTC · re: Glen Choo · lore

Re: [PATCH v5 9/9] submodule: move core cmd_update() logic to C

On Wed, Feb 02 2022, Glen Choo wrote:
Show 6 quoted lines
> - Junio pointed out that this conflicts with
>   es/superproject-aware-submodules [2]. I'm not sure which should be
>   based on which. If this does end up being based on
>   es/superproject-aware-submodules, it would probably be easier to
>   rebase as a series of smaller patches. Atharva noted that the
>   conflicts are mild though, so maybe it's not so bad.

I think it makes sense to get this series through first, i.e. the (supposedly) no-behavior-changing one, and then one that introduces new submodule behavior.

Particularly because for es/superproject-aware-submodules the main selling point is a performance improvement, which as I noted in the review for it I've been unable to observe once the C<->sh layer goes away.

I'm not saying it's not there, just that I don't think it's been shown so far, IIRC there was some reference to some Google-internal network FS that might or might not be helped by it...

Show 35 quoted lines
> - Besides making sure that the sh -> c is faithful, a thorough review
>   should hopefully catch unintentional mistakes. The size of this patch
>   makes such mistakes difficult to spot. For instance, here's something
>   I spotted only after trying to split the patch myself..
>
>   > +static int module_update(int argc, const char **argv, const char *prefix)
>   > +{
>   > +	const char *update = NULL;
>   > +	struct pathspec pathspec;
>   > +	struct update_data opt = UPDATE_DATA_INIT;
>   > +
>   > +	struct option module_update_clone_options[] = {
>   [...]
>   > +	};
>   > +
>   > +	const char *const git_submodule_helper_usage[] = {
>   > +		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
>   > +		NULL
>   > +	};
>   > +
>   > +	update_clone_config_from_gitmodules(&opt.max_jobs);
>   > +	git_config(git_update_clone_config, &opt.max_jobs);
>
>   Notice that we copy-pasted the option parsing from update-clone into
>   module_update() but forgot to update the names.
>
> My ideal patch organization would be something like:
>
> - wrap some existing command in "git submodule--helper update" (probably
>   run-update-procedure)
> - absorb the surrounding sh code into "git submodule--helper
>   update" one command at-a-time i.e. deprecating and removing the
>   commands one at a time - instead of deprecating and removing them all
>   at once (like this patch), or deprecating all at once and removing
>   them one at a time (like v1).

I do think atomic changes that don't leave dead code for removal later are easier to read & reason about, whatever else is reorganized.

I.e. not to have something where we replace all the running code, and then remove already-unused code later.

On that topic, I noticed this series could/should have [1] fixed up into it.

> - If you think this alternative organization would be helpful for you
>   too, I will attempt it. This will take a while, but by the end you and
>   I will have effectively reviewed all of the code, so it should be easy
>   to finish up the review.

I think it might, but I really don't know. We'll just have to see, so if you want to take a stab at it that would be great.

Maybe it's a good way forward. E.g. as af first small step we could turn:
    while read -r quickabort sha1 just_cloned sm_path
    [...]
    die_if_unmatched "$quickabort" "$sha1"

into version where we fold that die_if_unmatched() logic into the C code, and then ensure-core-worktree etc.

> - Otherwise e.g. maybe this is a huge waste of time, or you're already
>   really confident in the correctness of the sh -> c when you reviewed
>   the original patch, etc, I'll just review this patch as-is. I'd
>   appreciate any tips and tricks that might help :)
I'm not really confident in it.

I've read it, tested it as well as I could manage etc. but it's still a very large change.

1.
Show changes to 2 files +0 −26

git-sh-setup.sh, git-submodule.sh

diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index b93f39288ce..756c2a67c72 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -286,13 +286,6 @@ get_author_ident_from_commit () {
 	parse_ident_from_commit author AUTHOR
 }
 
-# Clear repo-local GIT_* environment variables. Useful when switching to
-# another repository (e.g. when entering a submodule). See also the env
-# list in git_connect()
-clear_local_git_env() {
-	unset $(git rev-parse --local-env-vars)
-}
-
 # Generate a virtual base file for a two-file merge. Uses git apply to
 # remove lines from $1 that are not in $2, leaving only common lines.
 create_virtual_base() {
diff --git a/git-submodule.sh b/git-submodule.sh
index bcd8b92aabd..6c91b9e2403 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -50,30 +50,11 @@ single_branch=
 jobs=
 recommend_shallow=
 
-die_if_unmatched ()
-{
-	if test "$1" = "#unmatched"
-	then
-		exit ${2:-1}
-	fi
-}
-
 isnumber()
 {
 	n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
 }
 
-# Sanitize the local git environment for use within a submodule. We
-# can't simply use clear_local_git_env since we want to preserve some
-# of the settings from GIT_CONFIG_PARAMETERS.
-sanitize_submodule_env()
-{
-	save_config=$GIT_CONFIG_PARAMETERS
-	clear_local_git_env
-	GIT_CONFIG_PARAMETERS=$save_config
-	export GIT_CONFIG_PARAMETERS
-}
-
 #
 # Add a new submodule to the working tree, .gitmodules and the index
 #
Ævar Arnfjörð Bjarmason· Feb 3, 2022, 08:15 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v5 9/9] submodule: move core cmd_update() logic to C

On Thu, Feb 03 2022, Ævar Arnfjörð Bjarmason wrote:
Show 83 quoted lines
> On Wed, Feb 02 2022, Glen Choo wrote:
>
>> - Junio pointed out that this conflicts with
>>   es/superproject-aware-submodules [2]. I'm not sure which should be
>>   based on which. If this does end up being based on
>>   es/superproject-aware-submodules, it would probably be easier to
>>   rebase as a series of smaller patches. Atharva noted that the
>>   conflicts are mild though, so maybe it's not so bad.
>
> I think it makes sense to get this series through first, i.e. the
> (supposedly) no-behavior-changing one, and then one that introduces new
> submodule behavior.
>
> Particularly because for es/superproject-aware-submodules the main
> selling point is a performance improvement, which as I noted in the
> review for it I've been unable to observe once the C<->sh layer goes
> away.
>
> I'm not saying it's not there, just that I don't think it's been shown
> so far, IIRC there was some reference to some Google-internal network FS
> that might or might not be helped by it...
>
>> - Besides making sure that the sh -> c is faithful, a thorough review
>>   should hopefully catch unintentional mistakes. The size of this patch
>>   makes such mistakes difficult to spot. For instance, here's something
>>   I spotted only after trying to split the patch myself..
>>
>>   > +static int module_update(int argc, const char **argv, const char *prefix)
>>   > +{
>>   > +	const char *update = NULL;
>>   > +	struct pathspec pathspec;
>>   > +	struct update_data opt = UPDATE_DATA_INIT;
>>   > +
>>   > +	struct option module_update_clone_options[] = {
>>   [...]
>>   > +	};
>>   > +
>>   > +	const char *const git_submodule_helper_usage[] = {
>>   > +		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
>>   > +		NULL
>>   > +	};
>>   > +
>>   > +	update_clone_config_from_gitmodules(&opt.max_jobs);
>>   > +	git_config(git_update_clone_config, &opt.max_jobs);
>>
>>   Notice that we copy-pasted the option parsing from update-clone into
>>   module_update() but forgot to update the names.
>>
>> My ideal patch organization would be something like:
>>
>> - wrap some existing command in "git submodule--helper update" (probably
>>   run-update-procedure)
>> - absorb the surrounding sh code into "git submodule--helper
>>   update" one command at-a-time i.e. deprecating and removing the
>>   commands one at a time - instead of deprecating and removing them all
>>   at once (like this patch), or deprecating all at once and removing
>>   them one at a time (like v1).
>
> I do think atomic changes that don't leave dead code for removal later
> are easier to read & reason about, whatever else is reorganized.
>
> I.e. not to have something where we replace all the running code, and
> then remove already-unused code later.
>
> On that topic, I noticed this series could/should have [1] fixed up into
> it.
>
>> - If you think this alternative organization would be helpful for you
>>   too, I will attempt it. This will take a while, but by the end you and
>>   I will have effectively reviewed all of the code, so it should be easy
>>   to finish up the review.
>
> I think it might, but I really don't know. We'll just have to see, so if
> you want to take a stab at it that would be great.
>
> Maybe it's a good way forward. E.g. as af first small step we could turn:
>
>     while read -r quickabort sha1 just_cloned sm_path
>     [...]
>     die_if_unmatched "$quickabort" "$sha1"
>
> into version where we fold that die_if_unmatched() logic into the C
> code, and then ensure-core-worktree etc.

Sorry, that one makes no sense since it's an artifact of the shellscript implementation.

But I tested the below on top of master, and it passes all tests, which isn't very promising...

Show changes to 2 files +0 −37

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 4a0890954e9..e749008f13a 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2783,40 +2783,6 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
-{
-	const char *path;
-	const char *cw;
-	struct repository subrepo;
-
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-
-	if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
-		die(_("could not get a repository handle for submodule '%s'"), path);
-
-	if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) {
-		char *cfg_file, *abs_path;
-		const char *rel_path;
-		struct strbuf sb = STRBUF_INIT;
-
-		cfg_file = repo_git_path(&subrepo, "config");
-
-		abs_path = absolute_pathdup(path);
-		rel_path = relative_path(abs_path, subrepo.gitdir, &sb);
-
-		git_config_set_in_file(cfg_file, "core.worktree", rel_path);
-
-		free(cfg_file);
-		free(abs_path);
-		strbuf_release(&sb);
-	}
-
-	return 0;
-}
-
 static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
 {
 	int i;
@@ -3391,7 +3357,6 @@ static struct cmd_struct commands[] = {
 	{"update-module-mode", module_update_module_mode, 0},
 	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
-	{"ensure-core-worktree", ensure_core_worktree, 0},
 	{"relative-path", resolve_relative_path, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index 652861aa66a..460cbd4e265 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -387,8 +387,6 @@ cmd_update()
 	do
 		die_if_unmatched "$quickabort" "$sha1"
 
-		git submodule--helper ensure-core-worktree "$sm_path" || exit 1
-
 		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
 
 		if test $just_cloned -eq 1
Glen Choo· Feb 3, 2022, 17:35 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v5 9/9] submodule: move core cmd_update() logic to C

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 23 quoted lines
> On Thu, Feb 03 2022, Ævar Arnfjörð Bjarmason wrote:
>
>> On Wed, Feb 02 2022, Glen Choo wrote:
>>
>>> - Junio pointed out that this conflicts with
>>>   es/superproject-aware-submodules [2]. I'm not sure which should be
>>>   based on which. If this does end up being based on
>>>   es/superproject-aware-submodules, it would probably be easier to
>>>   rebase as a series of smaller patches. Atharva noted that the
>>>   conflicts are mild though, so maybe it's not so bad.
>>
>> I think it makes sense to get this series through first, i.e. the
>> (supposedly) no-behavior-changing one, and then one that introduces new
>> submodule behavior.
>>
>> Particularly because for es/superproject-aware-submodules the main
>> selling point is a performance improvement, which as I noted in the
>> review for it I've been unable to observe once the C<->sh layer goes
>> away.
>>
>> I'm not saying it's not there, just that I don't think it's been shown
>> so far, IIRC there was some reference to some Google-internal network FS
>> that might or might not be helped by it...

I'll let the experts chime in, I don't think I can add anything useful to the discussion.

Show 15 quoted lines
>>> My ideal patch organization would be something like:
>>>
>>> - wrap some existing command in "git submodule--helper update" (probably
>>>   run-update-procedure)
>>> - absorb the surrounding sh code into "git submodule--helper
>>>   update" one command at-a-time i.e. deprecating and removing the
>>>   commands one at a time - instead of deprecating and removing them all
>>>   at once (like this patch), or deprecating all at once and removing
>>>   them one at a time (like v1).
>>
>> I do think atomic changes that don't leave dead code for removal later
>> are easier to read & reason about, whatever else is reorganized.
>>
>> I.e. not to have something where we replace all the running code, and
>> then remove already-unused code later.
I agree - otherwise patches aren't self-contianed and harder to merge.
Show 19 quoted lines
>>> - If you think this alternative organization would be helpful for you
>>>   too, I will attempt it. This will take a while, but by the end you and
>>>   I will have effectively reviewed all of the code, so it should be easy
>>>   to finish up the review.
>>
>> I think it might, but I really don't know. We'll just have to see, so if
>> you want to take a stab at it that would be great.
>>
>> Maybe it's a good way forward. E.g. as af first small step we could turn:
>>
>>     while read -r quickabort sha1 just_cloned sm_path
>>     [...]
>>     die_if_unmatched "$quickabort" "$sha1"
>>
>> into version where we fold that die_if_unmatched() logic into the C
>> code, and then ensure-core-worktree etc.
>
> Sorry, that one makes no sense since it's an artifact of the shellscript
> implementation.

Whether or not it makes sense, I think it gets the point across i.e. that we think folding into C can be done incrementally.

Show 9 quoted lines
>>> - Otherwise e.g. maybe this is a huge waste of time, or you're already
>>>   really confident in the correctness of the sh -> c when you reviewed
>>>   the original patch, etc, I'll just review this patch as-is. I'd
>>>   appreciate any tips and tricks that might help :)
>>
>> I'm not really confident in it.
>>
>> I've read it, tested it as well as I could manage etc. but it's still a
>> very large change.
[...]
> But I tested the below on top of master, and it passes all tests, which
> isn't very promising...

A good enough (i.e. extremely comprehensive) test suite will all but guarantee that no behavior has changed. Our test suite is nowhere near that level and probably never will be, so we can't trust that things are correct even if it passes tests.

So, for the sake of reviewability, I'll take a stab at reorganizing. I'll be taking a long flight anyway, so I'll have big chunk of non-company time to spend on this ;)

Glen Choo· Feb 8, 2022, 08:39 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v6 00/16] submodule: convert the rest of 'update' to C

Here is a split-up of the last patch as I promised :) The intent was to minimize the diff vs v5 instead of getting to a perfect end state; this will almost certainly need a reroll.

This split-up isn't as complete as I had hoped - the last patch is still rather large, but breaking that up already took more time than everything else combined, and I think this organization is already easier to review than v5. I'd appreciate any suggestions on how this could be done [1] :) If inspiration strikes, I'll break it up further in a reroll, but as of right now, I'm out of ideas.

I did some minimal review of the implementation, but I plan to do more after sending this to the list e.g. I have not revisited the earlier preparatory patches to see if my patches make sense with them. Where I found issues, I left NEEDSWORK comments instead of fixing them because the result would be easier to diff with v5 (the only differences between this tree and v5's is the NEEDSWORK comments). I plan to fix implementation issues (once we've spotted them in reivew) and the NEEDSWORKs in the next reroll.

Let me know if the trailers make sense - this is my first time reworking someone else's changes, so I don't think I did a perfect job at attributing work. I also had to fiddle with the Signed-off-by to make them resemble the v5 patches' Signed-off-by (ar/submodule-update seemed to order them differently from v5, I'm not sure why).

[1] The difficulty in the last patch comes from the fact that we have the --recursive flag handling in shell, and we are piping update-clone into {run-update-procedure + --recursive flag handling}. Because of how "git submodule" parses args, it is tricky to implement --recursive without bypassing "git submodule" with "git submodule--helper update" (the final patch does this bypass). On the other hand, it's difficult to leave --recursive for last, because we would need to mimic the piping and exiting behavior around update-clone and run-update-procedure.

Changes in v6:
- Split up last patch of v5 into patches 9-16.
- Patches 9-10 are preparatory work on submodule--helper.c.
- Patches 11-14 contain sh -> c conversions of the git-submodule.sh code
  surrounding update-clone and run-update-procedure.
- Patch 15 moves functions around in order to shrink patch 16's diff
  - If reviewers prefer, we could choose _not_ to move the functions
    around. If so, we can drop patch 15.
- Patch 16 introduces "submodule--helper update". Change the commit
  message to drop references to work done in previous patches.
- Add NEEDSWORK comments for potential issues. These will be cleaned up
  in the next version.
Atharva Raykar (6):
  submodule--helper: get remote names from any repository
  submodule--helper: refactor get_submodule_displaypath()
  submodule--helper: allow setting superprefix for init_submodule()
  submodule--helper: run update using child process struct
  builtin/submodule--helper.c: reformat designated initializers
  submodule: move core cmd_update() logic to C
Glen Choo (7):
  submodule--helper: remove update-module-mode
  submodule--helper: reorganize code for sh to C conversion
  submodule--helper run-update-procedure: remove --suboid
  submodule--helper run-update-procedure: learn --remote
  submodule--helper: remove ensure-core-worktree
  submodule--helper update-clone: learn --init
  submodule--helper: move functions around
Ævar Arnfjörð Bjarmason (3):
  builtin/submodule--helper.c: rename option variables to "opt"
  submodule--helper: don't use bitfield indirection for parse_options()
  submodule tests: test for init and update failure output
 builtin/submodule--helper.c    | 709 +++++++++++++++++++--------------
 git-submodule.sh               | 145 +------
 t/t7406-submodule-update.sh    |  14 +-
 t/t7408-submodule-reference.sh |  14 +-
 4 files changed, 447 insertions(+), 435 deletions(-)
Range-diff against v5:
 1:  b45e49810a =  1:  86ffb53742 submodule--helper: get remote names from any repository
 2:  8974329fc5 =  2:  2a40266b7a submodule--helper: refactor get_submodule_displaypath()
 3:  bd92e00c05 =  3:  cd851c8eb5 submodule--helper: allow setting superprefix for init_submodule()
 4:  909cb60cd4 =  4:  bfe5cad136 submodule--helper: run update using child process struct
 5:  36575b2f25 =  5:  72c257fdbf builtin/submodule--helper.c: reformat designated initializers
 6:  a743003e09 =  6:  4b5f703fde builtin/submodule--helper.c: rename option variables to "opt"
 7:  3a449d00b9 =  7:  f1d21f5b1c submodule--helper: don't use bitfield indirection for parse_options()
 8:  7da45dde67 =  8:  9d32a73fc3 submodule tests: test for init and update failure output
 -:  ---------- >  9:  087bf43aba submodule--helper: remove update-module-mode
 -:  ---------- > 10:  4eb2893a19 submodule--helper: reorganize code for sh to C conversion
 -:  ---------- > 11:  c08e7781e3 submodule--helper run-update-procedure: remove --suboid
 -:  ---------- > 12:  2419c37184 submodule--helper run-update-procedure: learn --remote
 -:  ---------- > 13:  6691fd3648 submodule--helper: remove ensure-core-worktree
 -:  ---------- > 14:  d2c9c356e9 submodule--helper update-clone: learn --init
 -:  ---------- > 15:  a5cde5e084 submodule--helper: move functions around
 9:  251a0b4241 ! 16:  f0551a37e5 submodule: move core cmd_update() logic to C
    @@ Commit message
         `submodule--helper update`, with a modified `--recursive-prefix` and
         `--prefix` parameter.
     
    -    We also introduce `update_submodules2()` and `update_submodule2()`
    -    which will supersede `update_submodules()` and `update_submodule()`.
    -
    -    When the `--init` flag is passed to the subcommand, we do not spawn a
    -    new subprocess and call `submodule--helper init` on the submodule paths,
    -    because the Git machinery is not able to pick up the configuration
    -    changes introduced by that init call[1]. So we instead run the
    -    `init_submodule_cb()` callback over each submodule in the same process.
    -
    -    While we are at it, we also remove the fetch_in_submodule() shell
    -    function since it is no longer used anywhere.
    -
    -    [1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
    -
         Mentored-by: Christian Couder <christian.couder@gmail.com>
         Mentored-by: Shourya Shukla <periperidip@gmail.com>
         Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    Signed-off-by: Glen Choo <chooglen@google.com>
     
      ## builtin/submodule--helper.c ##
    -@@ builtin/submodule--helper.c: static char *get_default_remote(void)
    - 	return repo_get_default_remote(the_repository);
    - }
    - 
    --static int print_default_remote(int argc, const char **argv, const char *prefix)
    --{
    --	char *remote;
    --
    --	if (argc != 1)
    --		die(_("submodule--helper print-default-remote takes no arguments"));
    --
    --	remote = get_default_remote();
    --	if (remote)
    --		printf("%s\n", remote);
    --
    --	free(remote);
    --	return 0;
    --}
    --
    - static int starts_with_dot_slash(const char *str)
    - {
    - 	return str[0] == '.' && is_dir_sep(str[1]);
    -@@ builtin/submodule--helper.c: static void determine_submodule_update_strategy(struct repository *r,
    - 	free(key);
    - }
    - 
    --static int module_update_module_mode(int argc, const char **argv, const char *prefix)
    --{
    --	const char *path, *update = NULL;
    --	int just_cloned;
    --	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
    --
    --	if (argc < 3 || argc > 4)
    --		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
    --
    --	just_cloned = git_config_int("just_cloned", argv[1]);
    --	path = argv[2];
    --
    --	if (argc == 4)
    --		update = argv[3];
    --
    --	determine_submodule_update_strategy(the_repository,
    --					    just_cloned, path, update,
    --					    &update_strategy);
    --	fputs(submodule_strategy_to_string(&update_strategy), stdout);
    --
    --	return 0;
    --}
    --
    - struct update_clone_data {
    - 	const struct submodule *sub;
    - 	struct object_id oid;
     @@ builtin/submodule--helper.c: struct submodule_update_clone {
      	const char *prefix;
      	int single_branch;
    @@ builtin/submodule--helper.c: struct submodule_update_clone {
      	struct update_clone_data *update_clone;
      	int update_clone_nr; int update_clone_alloc;
      
    +@@ builtin/submodule--helper.c: struct submodule_update_clone {
    + 	int failed_clones_nr, failed_clones_alloc;
    + 
    + 	int max_jobs;
    +-	unsigned int init;
    + };
    + #define SUBMODULE_UPDATE_CLONE_INIT { \
    + 	.list = MODULE_LIST_INIT, \
     @@ builtin/submodule--helper.c: struct submodule_update_clone {
      }
      
    @@ builtin/submodule--helper.c: struct update_data {
      	unsigned int quiet;
      	unsigned int nofetch;
      	unsigned int just_cloned;
    -+	unsigned int remote;
    + 	unsigned int remote;
     +	unsigned int recursive;
     +	unsigned int progress;
     +	unsigned int dissociate;
    @@ builtin/submodule--helper.c: static int run_update_command(struct update_data *u
      	char *oid = oid_to_hex(&ud->oid);
      	int must_die_on_failure = 0;
     +	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
    - 
    --	switch (ud->update_strategy.type) {
    ++
     +	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
     +		determine_submodule_update_strategy(the_repository, ud->just_cloned,
     +						    ud->sm_path, NULL, &strategy);
     +	else
     +		strategy = ud->update_strategy;
    -+
    + 
    +-	switch (ud->update_strategy.type) {
     +	switch (strategy.type) {
      	case SM_UPDATE_CHECKOUT:
      		cp.git_cmd = 1;
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -		ucd->sub->path);
     -}
     -
    --static int update_submodules(struct submodule_update_clone *suc)
    --{
    --	int i;
    --
    --	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
    --				   update_clone_start_failure,
    --				   update_clone_task_finished, suc, "submodule",
    --				   "parallel/update");
    --
    --	/*
    --	 * We saved the output and put it out all at once now.
    --	 * That means:
    --	 * - the listener does not have to interleave their (checkout)
    --	 *   work with our fetching.  The writes involved in a
    --	 *   checkout involve more straightforward sequential I/O.
    --	 * - the listener can avoid doing any work if fetching failed.
    --	 */
    --	if (suc->quickstop)
    --		return 1;
    --
    --	for (i = 0; i < suc->update_clone_nr; i++)
    --		update_submodule(&suc->update_clone[i]);
    --
    --	return 0;
    --}
    --
    --static int update_clone(int argc, const char **argv, const char *prefix)
    --{
    --	const char *update = NULL;
    --	struct pathspec pathspec;
    --	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
    --
    --	struct option module_update_clone_options[] = {
    --		OPT_STRING(0, "prefix", &prefix,
    --			   N_("path"),
    --			   N_("path into the working tree")),
    --		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
    --			   N_("path"),
    --			   N_("path into the working tree, across nested "
    --			      "submodule boundaries")),
    --		OPT_STRING(0, "update", &update,
    --			   N_("string"),
    --			   N_("rebase, merge, checkout or none")),
    --		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
    --			   N_("reference repository")),
    --		OPT_BOOL(0, "dissociate", &opt.dissociate,
    --			   N_("use --reference only while cloning")),
    --		OPT_STRING(0, "depth", &opt.depth, "<depth>",
    --			   N_("create a shallow clone truncated to the "
    --			      "specified number of revisions")),
    --		OPT_INTEGER('j', "jobs", &opt.max_jobs,
    --			    N_("parallel jobs")),
    --		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
    --			    N_("whether the initial clone should follow the shallow recommendation")),
    --		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
    --		OPT_BOOL(0, "progress", &opt.progress,
    --			    N_("force cloning progress")),
    --		OPT_BOOL(0, "require-init", &opt.require_init,
    --			   N_("disallow cloning into non-empty directory")),
    --		OPT_BOOL(0, "single-branch", &opt.single_branch,
    --			 N_("clone only one branch, HEAD or --branch")),
    --		OPT_END()
    --	};
    --
    --	const char *const git_submodule_helper_usage[] = {
    --		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
    --		NULL
    --	};
    --	opt.prefix = prefix;
    --
    --	update_clone_config_from_gitmodules(&opt.max_jobs);
    --	git_config(git_update_clone_config, &opt.max_jobs);
    --
    --	argc = parse_options(argc, argv, prefix, module_update_clone_options,
    --			     git_submodule_helper_usage, 0);
    --
    --	if (update)
    --		if (parse_submodule_update_strategy(update, &opt.update) < 0)
    --			die(_("bad value for update parameter"));
    --
    --	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
    --		return 1;
    --
    --	if (pathspec.nr)
    --		opt.warn_if_uninitialized = 1;
    --
    --	return update_submodules(&opt);
    --}
    --
    +-/*
    +- * NEEDSWORK: Use a forward declaration to avoid moving
    +- * run_update_procedure() (which will be removed soon).
    +- */
    +-static int update_submodule2(struct update_data *update_data);
     -static int run_update_procedure(int argc, const char **argv, const char *prefix)
     -{
     -	char *prefixed_path, *update = NULL;
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
     -			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
     -			       parse_opt_object_id),
    --		OPT_CALLBACK_F(0, "suboid", &opt.suboid, N_("subsha1"),
    --			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
    --			       parse_opt_object_id),
    +-		OPT_BOOL(0, "remote", &opt.remote,
    +-			 N_("use SHA-1 of submodule's remote tracking branch")),
     -		OPT_END()
     -	};
     -
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -					    &opt.update_strategy);
     -
     -	free(prefixed_path);
    --
    --	if (!oideq(&opt.oid, &opt.suboid) || opt.force)
    --		return do_run_update_procedure(&opt);
    --
    --	return 3;
    +-	return update_submodule2(&opt);
     -}
     -
     -static int resolve_relative_path(int argc, const char **argv, const char *prefix)
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
      static const char *remote_submodule_branch(const char *path)
      {
      	const struct submodule *sub;
    -@@ builtin/submodule--helper.c: static int push_check(int argc, const char **argv, const char *prefix)
    - 	return 0;
    - }
    - 
    --static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
    -+static void ensure_core_worktree(const char *path)
    - {
    --	const char *path;
    - 	const char *cw;
    - 	struct repository subrepo;
    - 
    --	if (argc != 2)
    --		BUG("submodule--helper ensure-core-worktree <path>");
    --
    --	path = argv[1];
    --
    - 	if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
    - 		die(_("could not get a repository handle for submodule '%s'"), path);
    - 
    -@@ builtin/submodule--helper.c: static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
    - 		free(abs_path);
    - 		strbuf_release(&sb);
    - 	}
    --
    --	return 0;
    - }
    - 
    - static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
     @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char **argv, const char *prefix)
      	return !!ret;
      }
      
    +-/* NEEDSWORK: this is a temporary name until we delete update_submodule() */
    +-static int update_submodule2(struct update_data *update_data)
     +static void update_data_to_args(struct update_data *update_data, struct strvec *args)
     +{
     +	const char *update = submodule_strategy_to_string(&update_data->update_strategy);
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +}
     +
     +static int update_submodule(struct update_data *update_data)
    -+{
    + {
     +	char *prefixed_path;
     +
    -+	ensure_core_worktree(update_data->sm_path);
    -+
    + 	ensure_core_worktree(update_data->sm_path);
    + 
     +	if (update_data->recursive_prefix)
     +		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
     +					update_data->sm_path);
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +							     update_data->prefix);
     +	free(prefixed_path);
     +
    -+	if (update_data->just_cloned) {
    -+		oidcpy(&update_data->suboid, null_oid());
    -+	} else {
    -+		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
    -+			die(_("Unable to find current revision in submodule path '%s'"),
    -+			    update_data->displaypath);
    -+	}
    -+
    -+	if (update_data->remote) {
    -+		char *remote_name = get_default_remote_submodule(update_data->sm_path);
    -+		const char *branch = remote_submodule_branch(update_data->sm_path);
    -+		char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch);
    -+
    -+		if (!update_data->nofetch) {
    -+			if (fetch_in_submodule(update_data->sm_path, update_data->depth,
    -+					      0, NULL))
    -+				die(_("Unable to fetch in submodule path '%s'"),
    -+				    update_data->sm_path);
    -+		}
    -+
    -+		if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
    -+			die(_("Unable to find %s revision in submodule path '%s'"),
    -+			    remote_ref, update_data->sm_path);
    -+
    -+		free(remote_ref);
    -+	}
    -+
    -+	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
    + 	/* NEEDSWORK: fix the style issues e.g. braces */
    + 	if (update_data->just_cloned) {
    + 		oidcpy(&update_data->suboid, null_oid());
    +@@ builtin/submodule--helper.c: static int update_submodule2(struct update_data *update_data)
    + 	}
    + 
    + 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
    +-		return do_run_update_procedure(update_data);
     +		if (run_update_procedure(update_data))
     +			return 1;
    -+
    + 
    +-	return 3;
     +	if (update_data->recursive) {
     +		struct child_process cp = CHILD_PROCESS_INIT;
     +		struct update_data next = *update_data;
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +	}
     +
     +	return 0;
    -+}
    -+
    + }
    + 
    +-static int update_submodules(struct submodule_update_clone *suc)
     +static int update_submodules(struct update_data *update_data)
    -+{
    + {
    +-	int i;
     +	int i, res = 0;
     +	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
    -+
    + 
    +-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
     +	update_clone_from_update_data(&suc, update_data);
     +	run_processes_parallel_tr2(suc.max_jobs, update_clone_get_next_task,
    -+				   update_clone_start_failure,
    + 				   update_clone_start_failure,
    +-				   update_clone_task_finished, suc, "submodule",
     +				   update_clone_task_finished, &suc, "submodule",
    -+				   "parallel/update");
    -+
    -+	/*
    -+	 * We saved the output and put it out all at once now.
    -+	 * That means:
    -+	 * - the listener does not have to interleave their (checkout)
    -+	 *   work with our fetching.  The writes involved in a
    -+	 *   checkout involve more straightforward sequential I/O.
    -+	 * - the listener can avoid doing any work if fetching failed.
    -+	 */
    + 				   "parallel/update");
    + 
    + 	/*
    +@@ builtin/submodule--helper.c: static int update_submodules(struct submodule_update_clone *suc)
    + 	 *   checkout involve more straightforward sequential I/O.
    + 	 * - the listener can avoid doing any work if fetching failed.
    + 	 */
    +-	if (suc->quickstop)
    +-		return 1;
     +	if (suc.quickstop) {
     +		res = 1;
     +		goto cleanup;
     +	}
    -+
    + 
    +-	for (i = 0; i < suc->update_clone_nr; i++)
    +-		update_submodule(&suc->update_clone[i]);
     +	for (i = 0; i < suc.update_clone_nr; i++) {
     +		struct update_clone_data ucd = suc.update_clone[i];
    -+
    + 
    +-	return 0;
     +		oidcpy(&update_data->oid, &ucd.oid);
     +		update_data->just_cloned = ucd.just_cloned;
     +		update_data->sm_path = ucd.sub->path;
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +cleanup:
     +	string_list_clear(&update_data->references, 0);
     +	return res;
    -+}
    -+
    + }
    + 
    +-static int update_clone(int argc, const char **argv, const char *prefix)
     +static int module_update(int argc, const char **argv, const char *prefix)
    -+{
    -+	const char *update = NULL;
    -+	struct pathspec pathspec;
    + {
    + 	const char *update = NULL;
    + 	struct pathspec pathspec;
    +-	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
     +	struct update_data opt = UPDATE_DATA_INIT;
    -+
    -+	struct option module_update_clone_options[] = {
    + 
    ++	/* NEEDSWORK: update names and strings */
    + 	struct option module_update_clone_options[] = {
     +		OPT__FORCE(&opt.force, N_("force checkout updates"), 0),
    -+		OPT_BOOL(0, "init", &opt.init,
    -+			 N_("initialize uninitialized submodules before update")),
    + 		OPT_BOOL(0, "init", &opt.init,
    + 			 N_("initialize uninitialized submodules before update")),
    +-		OPT_STRING(0, "prefix", &prefix,
     +		OPT_BOOL(0, "remote", &opt.remote,
     +			 N_("use SHA-1 of submodule's remote tracking branch")),
     +		OPT_BOOL(0, "recursive", &opt.recursive,
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +		OPT_BOOL('N', "no-fetch", &opt.nofetch,
     +			 N_("don't fetch new objects from the remote site")),
     +		OPT_STRING(0, "prefix", &opt.prefix,
    -+			   N_("path"),
    -+			   N_("path into the working tree")),
    -+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
    -+			   N_("path"),
    -+			   N_("path into the working tree, across nested "
    -+			      "submodule boundaries")),
    -+		OPT_STRING(0, "update", &update,
    -+			   N_("string"),
    -+			   N_("rebase, merge, checkout or none")),
    -+		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
    + 			   N_("path"),
    + 			   N_("path into the working tree")),
    + 		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
    +@@ builtin/submodule--helper.c: static int update_clone(int argc, const char **argv, const char *prefix)
    + 			   N_("string"),
    + 			   N_("rebase, merge, checkout or none")),
    + 		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
    +-			   N_("reference repository")),
     +				N_("reference repository")),
    -+		OPT_BOOL(0, "dissociate", &opt.dissociate,
    + 		OPT_BOOL(0, "dissociate", &opt.dissociate,
    +-			   N_("use --reference only while cloning")),
    +-		OPT_STRING(0, "depth", &opt.depth, "<depth>",
    +-			   N_("create a shallow clone truncated to the "
    +-			      "specified number of revisions")),
     +			 N_("use --reference only while cloning")),
     +		OPT_INTEGER(0, "depth", &opt.depth,
     +			    N_("create a shallow clone truncated to the "
     +			       "specified number of revisions")),
    -+		OPT_INTEGER('j', "jobs", &opt.max_jobs,
    -+			    N_("parallel jobs")),
    -+		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
    + 		OPT_INTEGER('j', "jobs", &opt.max_jobs,
    + 			    N_("parallel jobs")),
    + 		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
    +-			    N_("whether the initial clone should follow the shallow recommendation")),
     +			 N_("whether the initial clone should follow the shallow recommendation")),
    -+		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
    -+		OPT_BOOL(0, "progress", &opt.progress,
    + 		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
    + 		OPT_BOOL(0, "progress", &opt.progress,
    +-			    N_("force cloning progress")),
     +			 N_("force cloning progress")),
    -+		OPT_BOOL(0, "require-init", &opt.require_init,
    + 		OPT_BOOL(0, "require-init", &opt.require_init,
    +-			   N_("disallow cloning into non-empty directory")),
     +			 N_("disallow cloning into non-empty directory")),
    -+		OPT_BOOL(0, "single-branch", &opt.single_branch,
    -+			 N_("clone only one branch, HEAD or --branch")),
    -+		OPT_END()
    -+	};
    -+
    -+	const char *const git_submodule_helper_usage[] = {
    -+		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
    -+		NULL
    -+	};
    -+
    -+	update_clone_config_from_gitmodules(&opt.max_jobs);
    -+	git_config(git_update_clone_config, &opt.max_jobs);
    -+
    -+	argc = parse_options(argc, argv, prefix, module_update_clone_options,
    -+			     git_submodule_helper_usage, 0);
    + 		OPT_BOOL(0, "single-branch", &opt.single_branch,
    + 			 N_("clone only one branch, HEAD or --branch")),
    + 		OPT_END()
    +@@ builtin/submodule--helper.c: static int update_clone(int argc, const char **argv, const char *prefix)
    + 		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
    + 		NULL
    + 	};
    +-	opt.prefix = prefix;
    + 
    + 	update_clone_config_from_gitmodules(&opt.max_jobs);
    + 	git_config(git_update_clone_config, &opt.max_jobs);
    + 
    + 	argc = parse_options(argc, argv, prefix, module_update_clone_options,
    + 			     git_submodule_helper_usage, 0);
     +	oidcpy(&opt.oid, null_oid());
     +	oidcpy(&opt.suboid, null_oid());
    -+
    -+	if (update)
    + 
    + 	if (update)
    +-		if (parse_submodule_update_strategy(update, &opt.update) < 0)
     +		if (parse_submodule_update_strategy(update,
     +						    &opt.update_strategy) < 0)
    -+			die(_("bad value for update parameter"));
    -+
    -+	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
    -+		return 1;
    -+
    -+	if (pathspec.nr)
    -+		opt.warn_if_uninitialized = 1;
    -+
    -+	if (opt.init) {
    -+		struct module_list list = MODULE_LIST_INIT;
    -+		struct init_cb info = INIT_CB_INIT;
    -+
    -+		if (module_list_compute(argc, argv, opt.prefix,
    -+					&pathspec, &list) < 0)
    -+			return 1;
    -+
    -+		/*
    -+		 * If there are no path args and submodule.active is set then,
    -+		 * by default, only initialize 'active' modules.
    -+		 */
    -+		if (!argc && git_config_get_value_multi("submodule.active"))
    -+			module_list_active(&list);
    -+
    -+		info.prefix = opt.prefix;
    -+		info.superprefix = opt.recursive_prefix;
    -+		if (opt.quiet)
    -+			info.flags |= OPT_QUIET;
    -+
    -+		for_each_listed_submodule(&list, init_submodule_cb, &info);
    -+	}
    -+
    -+	return update_submodules(&opt);
    -+}
    -+
    - struct add_data {
    - 	const char *prefix;
    - 	const char *branch;
    + 			die(_("bad value for update parameter"));
    + 
    + 	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
     @@ builtin/submodule--helper.c: static struct cmd_struct commands[] = {
      	{"name", module_name, 0},
      	{"clone", module_clone, 0},
      	{"add", module_add, SUPPORT_SUPER_PREFIX},
    --	{"update-module-mode", module_update_module_mode, 0},
     -	{"update-clone", update_clone, 0},
     -	{"run-update-procedure", run_update_procedure, 0},
    --	{"ensure-core-worktree", ensure_core_worktree, 0},
     -	{"relative-path", resolve_relative_path, 0},
     +	{"update", module_update, 0},
      	{"resolve-relative-url-test", resolve_relative_url_test, 0},
      	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
      	{"init", module_init, SUPPORT_SUPER_PREFIX},
    - 	{"status", module_status, SUPPORT_SUPER_PREFIX},
    --	{"print-default-remote", print_default_remote, 0},
    - 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
    - 	{"deinit", module_deinit, 0},
    - 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
     
      ## git-submodule.sh ##
    -@@ git-submodule.sh: cmd_deinit()
    - 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
    - }
    - 
    --# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
    --# Because arguments are positional, use an empty string to omit <depth>
    --# but include <sha1>.
    --fetch_in_submodule () (
    --	sanitize_submodule_env &&
    --	cd "$1" &&
    --	if test $# -eq 3
    --	then
    --		echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
    --	else
    --		git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
    --	fi
    --)
    --
    - #
    - # Update each submodule path to correct revision, using clone and checkout as needed
    - #
     @@ git-submodule.sh: cmd_update()
      		shift
      	done
      
    --	if test -n "$init"
    --	then
    --		cmd_init "--" "$@" || return
    --	fi
    --
     -	{
    --	git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
    --		${progress:+"--progress"} \
    +-	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update-clone \
     +	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
    -+		${GIT_QUIET:+--quiet} \
    + 		${GIT_QUIET:+--quiet} \
    +-		${progress:+"--progress"} \
     +		${force:+--force} \
     +		${progress:+--progress} \
     +		${dissociate:+--dissociate} \
     +		${remote:+--remote} \
     +		${recursive:+--recursive} \
    -+		${init:+--init} \
    + 		${init:+--init} \
     +		${require_init:+--require-init} \
     +		${nofetch:+--no-fetch} \
      		${wt_prefix:+--prefix "$wt_prefix"} \
    @@ git-submodule.sh: cmd_update()
     -	do
     -		die_if_unmatched "$quickabort" "$sha1"
     -
    --		git submodule--helper ensure-core-worktree "$sm_path" || exit 1
    --
     -		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
     -
    --		if test $just_cloned -eq 1
    +-		if test $just_cloned -eq 0
     -		then
    --			subsha1=
    --		else
     -			just_cloned=
    --			subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
    --				git rev-parse --verify HEAD) ||
    --			die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
    --		fi
    --
    --		if test -n "$remote"
    --		then
    --			branch=$(git submodule--helper remote-branch "$sm_path")
    --			if test -z "$nofetch"
    --			then
    --				# Fetch remote before determining tracking $sha1
    --				fetch_in_submodule "$sm_path" $depth ||
    --				die "fatal: $(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
    --			fi
    --			remote_name=$(sanitize_submodule_env; cd "$sm_path" && git submodule--helper print-default-remote)
    --			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
    --				git rev-parse --verify "${remote_name}/${branch}") ||
    --			die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
     -		fi
     -
     -		out=$(git submodule--helper run-update-procedure \
    @@ git-submodule.sh: cmd_update()
     -			  ${update:+--update "$update"} \
     -			  ${prefix:+--recursive-prefix "$prefix"} \
     -			  ${sha1:+--oid "$sha1"} \
    --			  ${subsha1:+--suboid "$subsha1"} \
    +-			  ${remote:+--remote} \
     -			  "--" \
     -			  "$sm_path")
     -
base-commit: b23dac905bde28da47543484320db16312c87551
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 01/16] submodule--helper: get remote names from any repository

From: Atharva Raykar <raykar.ath@gmail.com>

`get_default_remote()` retrieves the name of a remote by resolving the refs from of the current repository's ref store.

Thus in order to use it for retrieving the remote name of a submodule, we have to start a new subprocess which runs from the submodule directory.

Let's instead introduce a function called `repo_get_default_remote()` which takes any repository object and retrieves the remote accordingly.

`get_default_remote()` is then defined as a call to `repo_get_default_remote()` with 'the_repository' passed to it.

Now that we have `repo_get_default_remote()`, we no longer have to start a subprocess that called `submodule--helper get-default-remote` from within the submodule directory.

So let's make a function called `get_default_remote_submodule()` which takes a submodule path, and returns the default remote for that submodule, all within the same process.

We can now use this function to save an unnecessary subprocess spawn in `sync_submodule()`, and also in the next patch, which will require this functionality.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Helped-by: Glen Choo <chooglen@google.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 39 +++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 15 deletions(-)
Show changes to builtin/submodule--helper.c +24 −15
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c5d3fc3817..4c7c1e1432 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -29,11 +29,14 @@
 typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
 				  void *cb_data);
 
-static char *get_default_remote(void)
+static char *repo_get_default_remote(struct repository *repo)
 {
 	char *dest = NULL, *ret;
 	struct strbuf sb = STRBUF_INIT;
-	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+	struct ref_store *store = get_main_ref_store(repo);
+	int ignore_errno;
+	const char *refname = refs_resolve_ref_unsafe(store, "HEAD", 0, NULL,
+						      NULL, &ignore_errno);
 
 	if (!refname)
 		die(_("No such ref: %s"), "HEAD");
@@ -46,7 +49,7 @@ static char *get_default_remote(void)
 		die(_("Expecting a full ref name, got %s"), refname);
 
 	strbuf_addf(&sb, "branch.%s.remote", refname);
-	if (git_config_get_string(sb.buf, &dest))
+	if (repo_config_get_string(repo, sb.buf, &dest))
 		ret = xstrdup("origin");
 	else
 		ret = dest;
@@ -55,6 +58,19 @@ static char *get_default_remote(void)
 	return ret;
 }
 
+static char *get_default_remote_submodule(const char *module_path)
+{
+	struct repository subrepo;
+
+	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
+	return repo_get_default_remote(&subrepo);
+}
+
+static char *get_default_remote(void)
+{
+	return repo_get_default_remote(the_repository);
+}
+
 static int print_default_remote(int argc, const char **argv, const char *prefix)
 {
 	char *remote;
@@ -1341,9 +1357,8 @@ static void sync_submodule(const char *path, const char *prefix,
 {
 	const struct submodule *sub;
 	char *remote_key = NULL;
-	char *sub_origin_url, *super_config_url, *displaypath;
+	char *sub_origin_url, *super_config_url, *displaypath, *default_remote;
 	struct strbuf sb = STRBUF_INIT;
-	struct child_process cp = CHILD_PROCESS_INIT;
 	char *sub_config_path = NULL;
 
 	if (!is_submodule_active(the_repository, path))
@@ -1382,21 +1397,15 @@ static void sync_submodule(const char *path, const char *prefix,
 	if (!is_submodule_populated_gently(path, NULL))
 		goto cleanup;
 
-	prepare_submodule_repo_env(&cp.env_array);
-	cp.git_cmd = 1;
-	cp.dir = path;
-	strvec_pushl(&cp.args, "submodule--helper",
-		     "print-default-remote", NULL);
-
 	strbuf_reset(&sb);
-	if (capture_command(&cp, &sb, 0))
+	default_remote = get_default_remote_submodule(path);
+	if (!default_remote)
 		die(_("failed to get the default remote for submodule '%s'"),
 		      path);
 
-	strbuf_strip_suffix(&sb, "\n");
-	remote_key = xstrfmt("remote.%s.url", sb.buf);
+	remote_key = xstrfmt("remote.%s.url", default_remote);
+	free(default_remote);
 
-	strbuf_reset(&sb);
 	submodule_to_gitdir(&sb, path);
 	strbuf_addstr(&sb, "/config");
 
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 02/16] submodule--helper: refactor get_submodule_displaypath()

From: Atharva Raykar <raykar.ath@gmail.com>

We create a function called `do_get_submodule_displaypath()` that generates the display path required by several submodule functions, and takes a custom superprefix parameter, instead of reading it from the environment.

We then redefine the existing `get_submodule_displaypath()` function as a call to this new function, where the superprefix is obtained from the environment.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +8 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 4c7c1e1432..5efceb9d46 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -261,11 +261,8 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr
 	return 0;
 }
 
-/* the result should be freed by the caller. */
-static char *get_submodule_displaypath(const char *path, const char *prefix)
+static char *do_get_submodule_displaypath(const char *path, const char *prefix, const char *super_prefix)
 {
-	const char *super_prefix = get_super_prefix();
-
 	if (prefix && super_prefix) {
 		BUG("cannot have prefix '%s' and superprefix '%s'",
 		    prefix, super_prefix);
@@ -281,6 +278,13 @@ static char *get_submodule_displaypath(const char *path, const char *prefix)
 	}
 }
 
+/* the result should be freed by the caller. */
+static char *get_submodule_displaypath(const char *path, const char *prefix)
+{
+	const char *super_prefix = get_super_prefix();
+	return do_get_submodule_displaypath(path, prefix, super_prefix);
+}
+
 static char *compute_rev_name(const char *sub_path, const char* object_id)
 {
 	struct strbuf sb = STRBUF_INIT;
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 04/16] submodule--helper: run update using child process struct

From: Atharva Raykar <raykar.ath@gmail.com>

We switch to using the run-command API function that takes a 'struct child process', since we are using a lot of the options. This will also make it simple to switch over to using 'capture_command()' when we start handling the output of the command completely in C.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 34 ++++++++++++++++------------------
 1 file changed, 16 insertions(+), 18 deletions(-)
Show changes to builtin/submodule--helper.c +16 −18
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 09cda67c1e..db71e6f4ec 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2344,47 +2344,45 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 
 static int run_update_command(struct update_data *ud, int subforce)
 {
-	struct strvec args = STRVEC_INIT;
-	struct strvec child_env = STRVEC_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
 	int must_die_on_failure = 0;
-	int git_cmd;
 
 	switch (ud->update_strategy.type) {
 	case SM_UPDATE_CHECKOUT:
-		git_cmd = 1;
-		strvec_pushl(&args, "checkout", "-q", NULL);
+		cp.git_cmd = 1;
+		strvec_pushl(&cp.args, "checkout", "-q", NULL);
 		if (subforce)
-			strvec_push(&args, "-f");
+			strvec_push(&cp.args, "-f");
 		break;
 	case SM_UPDATE_REBASE:
-		git_cmd = 1;
-		strvec_push(&args, "rebase");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "rebase");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_MERGE:
-		git_cmd = 1;
-		strvec_push(&args, "merge");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "merge");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_COMMAND:
-		git_cmd = 0;
-		strvec_push(&args, ud->update_strategy.command);
+		cp.use_shell = 1;
+		strvec_push(&cp.args, ud->update_strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
 		    submodule_strategy_to_string(&ud->update_strategy));
 	}
-	strvec_push(&args, oid);
+	strvec_push(&cp.args, oid);
 
-	prepare_submodule_repo_env(&child_env);
-	if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
-				     ud->sm_path, child_env.v)) {
+	cp.dir = xstrdup(ud->sm_path);
+	prepare_submodule_repo_env(&cp.env_array);
+	if (run_command(&cp)) {
 		switch (ud->update_strategy.type) {
 		case SM_UPDATE_CHECKOUT:
 			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 03/16] submodule--helper: allow setting superprefix for init_submodule()

From: Atharva Raykar <raykar.ath@gmail.com>

We allow callers of the `init_submodule()` function to optionally override the superprefix from the environment.

We need to enable this option because in our conversion of the update command that will follow, the '--init' option will be handled through this API. We will need to change the superprefix at that time to ensure the display paths show correctly in the output messages.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)
Show changes to builtin/submodule--helper.c +7 −3
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 5efceb9d46..09cda67c1e 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -606,18 +606,22 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
 
 struct init_cb {
 	const char *prefix;
+	const char *superprefix;
 	unsigned int flags;
 };
 #define INIT_CB_INIT { 0 }
 
 static void init_submodule(const char *path, const char *prefix,
-			   unsigned int flags)
+			   const char *superprefix, unsigned int flags)
 {
 	const struct submodule *sub;
 	struct strbuf sb = STRBUF_INIT;
 	char *upd = NULL, *url = NULL, *displaypath;
 
-	displaypath = get_submodule_displaypath(path, prefix);
+	/* try superprefix from the environment, if it is not passed explicitly */
+	if (!superprefix)
+		superprefix = get_super_prefix();
+	displaypath = do_get_submodule_displaypath(path, prefix, superprefix);
 
 	sub = submodule_from_path(the_repository, null_oid(), path);
 
@@ -691,7 +695,7 @@ static void init_submodule(const char *path, const char *prefix,
 static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
 {
 	struct init_cb *info = cb_data;
-	init_submodule(list_item->name, info->prefix, info->flags);
+	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
 }
 
 static int module_init(int argc, const char **argv, const char *prefix)
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 05/16] builtin/submodule--helper.c: reformat designated initializers

From: Atharva Raykar <raykar.ath@gmail.com>

The second hunk here will make a subsequent commit's diff smaller, and let's do the first and third hunks while we're at it so that we consistently format all of these.

Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)
Show changes to builtin/submodule--helper.c +10 −3
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index db71e6f4ec..9f79bdf4d5 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1654,7 +1654,10 @@ struct module_clone_data {
 	unsigned int require_init: 1;
 	int single_branch;
 };
-#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 }
+#define MODULE_CLONE_DATA_INIT { \
+	.reference = STRING_LIST_INIT_NODUP, \
+	.single_branch = -1, \
+}
 
 struct submodule_alternate_setup {
 	const char *submodule_name;
@@ -2047,7 +2050,9 @@ struct update_data {
 	unsigned int nofetch: 1;
 	unsigned int just_cloned: 1;
 };
-#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
+#define UPDATE_DATA_INIT { \
+	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+}
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
 		struct strbuf *out, const char *displaypath)
@@ -3013,7 +3018,9 @@ struct add_data {
 	unsigned int progress: 1;
 	unsigned int dissociate: 1;
 };
-#define ADD_DATA_INIT { .depth = -1 }
+#define ADD_DATA_INIT { \
+	.depth = -1, \
+}
 
 static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path)
 {
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 07/16] submodule--helper: don't use bitfield indirection for parse_options()

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Do away with the indirection of local variables added in c51f8f94e5b (submodule--helper: run update procedures from C, 2021-08-24).

These were only needed because in C you can't get a pointer to a single bit, so we were using intermediate variables instead.

This will also make a subsequent large commit's diff smaller.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 23 ++++++++++-------------
 1 file changed, 10 insertions(+), 13 deletions(-)
Show changes to builtin/submodule--helper.c +10 −13
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c2d4fd0347..4a0890954e 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2045,10 +2045,10 @@ struct update_data {
 	struct object_id suboid;
 	struct submodule_update_strategy update_strategy;
 	int depth;
-	unsigned int force: 1;
-	unsigned int quiet: 1;
-	unsigned int nofetch: 1;
-	unsigned int just_cloned: 1;
+	unsigned int force;
+	unsigned int quiet;
+	unsigned int nofetch;
+	unsigned int just_cloned;
 };
 #define UPDATE_DATA_INIT { \
 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
@@ -2578,16 +2578,17 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
-	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
 	char *prefixed_path, *update = NULL;
 	struct update_data opt = UPDATE_DATA_INIT;
 
 	struct option options[] = {
-		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&force, N_("force checkout updates"), 0),
-		OPT_BOOL('N', "no-fetch", &nofetch,
+		OPT__QUIET(&opt.quiet,
+			   N_("suppress output for update by rebase or merge")),
+		OPT__FORCE(&opt.force, N_("force checkout updates"),
+			   0),
+		OPT_BOOL('N', "no-fetch", &opt.nofetch,
 			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &just_cloned,
+		OPT_BOOL(0, "just-cloned", &opt.just_cloned,
 			 N_("overrides update mode in case the repository is a fresh clone")),
 		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
 		OPT_STRING(0, "prefix", &prefix,
@@ -2618,10 +2619,6 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	if (argc != 1)
 		usage_with_options(usage, options);
 
-	opt.force = !!force;
-	opt.quiet = !!quiet;
-	opt.nofetch = !!nofetch;
-	opt.just_cloned = !!just_cloned;
 	opt.sm_path = argv[0];
 
 	if (opt.recursive_prefix)
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 08/16] submodule tests: test for init and update failure output

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Amend some submodule tests to test for the failure output of "git submodule [update|init]". The lack of such tests hid a regression in an earlier version of a subsequent commit.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t7406-submodule-update.sh    | 14 ++++++++++++--
 t/t7408-submodule-reference.sh | 14 +++++++++++++-
 2 files changed, 25 insertions(+), 3 deletions(-)
Show changes to 2 files +25 −3

t/t7406-submodule-update.sh, t/t7408-submodule-reference.sh

diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 11cccbb333..7764c1c3cb 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -205,8 +205,18 @@ test_expect_success 'submodule update should fail due to local changes' '
 	 (cd submodule &&
 	  compare_head
 	 ) &&
-	 test_must_fail git submodule update submodule
-	)
+	 test_must_fail git submodule update submodule 2>../actual.raw
+	) &&
+	sed "s/^> //" >expect <<-\EOF &&
+	> error: Your local changes to the following files would be overwritten by checkout:
+	> 	file
+	> Please commit your changes or stash them before you switch branches.
+	> Aborting
+	> fatal: Unable to checkout OID in submodule path '\''submodule'\''
+	EOF
+	sed -e "s/checkout $SQ[^$SQ]*$SQ/checkout OID/" <actual.raw >actual &&
+	test_cmp expect actual
+
 '
 test_expect_success 'submodule update should throw away changes with --force ' '
 	(cd super &&
diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh
index a3892f494b..c3a4545510 100755
--- a/t/t7408-submodule-reference.sh
+++ b/t/t7408-submodule-reference.sh
@@ -193,7 +193,19 @@ test_expect_success 'missing nested submodule alternate fails clone and submodul
 		cd supersuper-clone &&
 		check_that_two_of_three_alternates_are_used &&
 		# update of the submodule fails
-		test_must_fail git submodule update --init --recursive
+		cat >expect <<-\EOF &&
+		fatal: submodule '\''sub'\'' cannot add alternate: path ... does not exist
+		Failed to clone '\''sub'\''. Retry scheduled
+		fatal: submodule '\''sub-dissociate'\'' cannot add alternate: path ... does not exist
+		Failed to clone '\''sub-dissociate'\''. Retry scheduled
+		fatal: submodule '\''sub'\'' cannot add alternate: path ... does not exist
+		Failed to clone '\''sub'\'' a second time, aborting
+		fatal: Failed to recurse into submodule path ...
+		EOF
+		test_must_fail git submodule update --init --recursive 2>err &&
+		grep -e fatal: -e ^Failed err >actual.raw &&
+		sed -e "s/path $SQ[^$SQ]*$SQ/path .../" <actual.raw >actual &&
+		test_cmp expect actual
 	)
 '
 
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 09/16] submodule--helper: remove update-module-mode

This is dead code - it has not been used since c51f8f94e5 (submodule--helper: run update procedures from C, 2021-08-24).

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 24 ------------------------
 1 file changed, 24 deletions(-)
Show changes to builtin/submodule--helper.c +0 −24
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 4a0890954e..e0cc1c1b79 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1965,29 +1965,6 @@ static void determine_submodule_update_strategy(struct repository *r,
 	free(key);
 }
 
-static int module_update_module_mode(int argc, const char **argv, const char *prefix)
-{
-	const char *path, *update = NULL;
-	int just_cloned;
-	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
-
-	if (argc < 3 || argc > 4)
-		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
-
-	just_cloned = git_config_int("just_cloned", argv[1]);
-	path = argv[2];
-
-	if (argc == 4)
-		update = argv[3];
-
-	determine_submodule_update_strategy(the_repository,
-					    just_cloned, path, update,
-					    &update_strategy);
-	fputs(submodule_strategy_to_string(&update_strategy), stdout);
-
-	return 0;
-}
-
 struct update_clone_data {
 	const struct submodule *sub;
 	struct object_id oid;
@@ -3388,7 +3365,6 @@ static struct cmd_struct commands[] = {
 	{"name", module_name, 0},
 	{"clone", module_clone, 0},
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
-	{"update-module-mode", module_update_module_mode, 0},
 	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
 	{"ensure-core-worktree", ensure_core_worktree, 0},
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 06/16] builtin/submodule--helper.c: rename option variables to "opt"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Rename the "suc" variable in update_clone() to "opt", and do the same for the "update_data" variable in run_update_procedure().

The only reason for this change is to make the subsequent commit's diff smaller, by doing this rename we can "anchor" the diff better, as it "borrow" most of the options declared here as-is as far as the diff rename detection is concerned.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 74 ++++++++++++++++++-------------------
 1 file changed, 37 insertions(+), 37 deletions(-)
Show changes to builtin/submodule--helper.c +37 −37
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 9f79bdf4d5..c2d4fd0347 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2517,36 +2517,36 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 {
 	const char *update = NULL;
 	struct pathspec pathspec;
-	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
 
 	struct option module_update_clone_options[] = {
 		OPT_STRING(0, "prefix", &prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
 			   N_("path"),
 			   N_("path into the working tree, across nested "
 			      "submodule boundaries")),
 		OPT_STRING(0, "update", &update,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
+		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
 			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &suc.dissociate,
+		OPT_BOOL(0, "dissociate", &opt.dissociate,
 			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &suc.depth, "<depth>",
+		OPT_STRING(0, "depth", &opt.depth, "<depth>",
 			   N_("create a shallow clone truncated to the "
 			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &suc.max_jobs,
+		OPT_INTEGER('j', "jobs", &opt.max_jobs,
 			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
+		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
 			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &suc.progress,
+		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &opt.progress,
 			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &suc.require_init,
+		OPT_BOOL(0, "require-init", &opt.require_init,
 			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &suc.single_branch,
+		OPT_BOOL(0, "single-branch", &opt.single_branch,
 			 N_("clone only one branch, HEAD or --branch")),
 		OPT_END()
 	};
@@ -2555,32 +2555,32 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
 		NULL
 	};
-	suc.prefix = prefix;
+	opt.prefix = prefix;
 
-	update_clone_config_from_gitmodules(&suc.max_jobs);
-	git_config(git_update_clone_config, &suc.max_jobs);
+	update_clone_config_from_gitmodules(&opt.max_jobs);
+	git_config(git_update_clone_config, &opt.max_jobs);
 
 	argc = parse_options(argc, argv, prefix, module_update_clone_options,
 			     git_submodule_helper_usage, 0);
 
 	if (update)
-		if (parse_submodule_update_strategy(update, &suc.update) < 0)
+		if (parse_submodule_update_strategy(update, &opt.update) < 0)
 			die(_("bad value for update parameter"));
 
-	if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
+	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
 		return 1;
 
 	if (pathspec.nr)
-		suc.warn_if_uninitialized = 1;
+		opt.warn_if_uninitialized = 1;
 
-	return update_submodules(&suc);
+	return update_submodules(&opt);
 }
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
 	char *prefixed_path, *update = NULL;
-	struct update_data update_data = UPDATE_DATA_INIT;
+	struct update_data opt = UPDATE_DATA_INIT;
 
 	struct option options[] = {
 		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
@@ -2589,20 +2589,20 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 			 N_("don't fetch new objects from the remote site")),
 		OPT_BOOL(0, "just-cloned", &just_cloned,
 			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
+		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
 		OPT_STRING(0, "prefix", &prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
 		OPT_STRING(0, "update", &update,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
 			   N_("path into the working tree, across nested "
 			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
+		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
 			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
+		OPT_CALLBACK_F(0, "suboid", &opt.suboid, N_("subsha1"),
 			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
 		OPT_END()
@@ -2618,27 +2618,27 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	if (argc != 1)
 		usage_with_options(usage, options);
 
-	update_data.force = !!force;
-	update_data.quiet = !!quiet;
-	update_data.nofetch = !!nofetch;
-	update_data.just_cloned = !!just_cloned;
-	update_data.sm_path = argv[0];
+	opt.force = !!force;
+	opt.quiet = !!quiet;
+	opt.nofetch = !!nofetch;
+	opt.just_cloned = !!just_cloned;
+	opt.sm_path = argv[0];
 
-	if (update_data.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
+	if (opt.recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", opt.recursive_prefix, opt.sm_path);
 	else
-		prefixed_path = xstrdup(update_data.sm_path);
+		prefixed_path = xstrdup(opt.sm_path);
 
-	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
+	opt.displaypath = get_submodule_displaypath(prefixed_path, prefix);
 
-	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
-					    update_data.sm_path, update,
-					    &update_data.update_strategy);
+	determine_submodule_update_strategy(the_repository, opt.just_cloned,
+					    opt.sm_path, update,
+					    &opt.update_strategy);
 
 	free(prefixed_path);
 
-	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data);
+	if (!oideq(&opt.oid, &opt.suboid) || opt.force)
+		return do_run_update_procedure(&opt);
 
 	return 3;
 }
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 10/16] submodule--helper: reorganize code for sh to C conversion

Introduce a function, update_submodule2(), that performs an update for one submodule. This function will implement the functionality of run-update-procedure and its surrounding shell code in submodule.sh.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)
Show changes to builtin/submodule--helper.c +15 −5
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index e0cc1c1b79..0b5120734a 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2553,6 +2553,11 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 	return update_submodules(&opt);
 }
 
+/*
+ * NEEDSWORK: Use a forward declaration to avoid moving
+ * run_update_procedure() (which will be removed soon).
+ */
+static int update_submodule2(struct update_data *update_data);
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
 	char *prefixed_path, *update = NULL;
@@ -2610,11 +2615,7 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 					    &opt.update_strategy);
 
 	free(prefixed_path);
-
-	if (!oideq(&opt.oid, &opt.suboid) || opt.force)
-		return do_run_update_procedure(&opt);
-
-	return 3;
+	return update_submodule2(&opt);
 }
 
 static int resolve_relative_path(int argc, const char **argv, const char *prefix)
@@ -2978,6 +2979,15 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 	return !!ret;
 }
 
+/* NEEDSWORK: this is a temporary name until we delete update_submodule() */
+static int update_submodule2(struct update_data *update_data)
+{
+	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
+		return do_run_update_procedure(update_data);
+
+	return 3;
+}
+
 struct add_data {
 	const char *prefix;
 	const char *branch;
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 12/16] submodule--helper run-update-procedure: learn --remote

Teach run-update-procedure to handle --remote instead of parsing --remote in git-submodule.sh. As a result, "git submodule--helper print-default-remote" has no more callers, so remove it.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 39 ++++++++++++++++++++++---------------
 git-submodule.sh            | 30 +---------------------------
 2 files changed, 24 insertions(+), 45 deletions(-)
Show changes to 2 files +24 −45

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index a26477ce04..15ae986692 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -71,21 +71,6 @@ static char *get_default_remote(void)
 	return repo_get_default_remote(the_repository);
 }
 
-static int print_default_remote(int argc, const char **argv, const char *prefix)
-{
-	char *remote;
-
-	if (argc != 1)
-		die(_("submodule--helper print-default-remote takes no arguments"));
-
-	remote = get_default_remote();
-	if (remote)
-		printf("%s\n", remote);
-
-	free(remote);
-	return 0;
-}
-
 static int starts_with_dot_slash(const char *str)
 {
 	return str[0] == '.' && is_dir_sep(str[1]);
@@ -2026,6 +2011,7 @@ struct update_data {
 	unsigned int quiet;
 	unsigned int nofetch;
 	unsigned int just_cloned;
+	unsigned int remote;
 };
 #define UPDATE_DATA_INIT { \
 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
@@ -2585,6 +2571,8 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
 			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
+		OPT_BOOL(0, "remote", &opt.remote,
+			 N_("use SHA-1 of submodule's remote tracking branch")),
 		OPT_END()
 	};
 
@@ -2988,6 +2976,25 @@ static int update_submodule2(struct update_data *update_data)
 			    update_data->displaypath);
 	}
 
+	if (update_data->remote) {
+		char *remote_name = get_default_remote_submodule(update_data->sm_path);
+		const char *branch = remote_submodule_branch(update_data->sm_path);
+		char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch);
+
+		if (!update_data->nofetch) {
+			if (fetch_in_submodule(update_data->sm_path, update_data->depth,
+					      0, NULL))
+				die(_("Unable to fetch in submodule path '%s'"),
+				    update_data->sm_path);
+		}
+
+		if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
+			die(_("Unable to find %s revision in submodule path '%s'"),
+			    remote_ref, update_data->sm_path);
+
+		free(remote_ref);
+	}
+
 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
 		return do_run_update_procedure(update_data);
 
@@ -3389,10 +3396,10 @@ static struct cmd_struct commands[] = {
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
 	{"init", module_init, SUPPORT_SUPER_PREFIX},
 	{"status", module_status, SUPPORT_SUPER_PREFIX},
-	{"print-default-remote", print_default_remote, 0},
 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
 	{"deinit", module_deinit, 0},
 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
+	/* NEEDSWORK: remote-branch is also obsolete */
 	{"remote-branch", resolve_remote_submodule_branch, 0},
 	{"push-check", push_check, 0},
 	{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index d48c314f01..29fd69250d 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -246,20 +246,6 @@ cmd_deinit()
 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
 }
 
-# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
-# Because arguments are positional, use an empty string to omit <depth>
-# but include <sha1>.
-fetch_in_submodule () (
-	sanitize_submodule_env &&
-	cd "$1" &&
-	if test $# -eq 3
-	then
-		echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
-	else
-		git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
-	fi
-)
-
 #
 # Update each submodule path to correct revision, using clone and checkout as needed
 #
@@ -396,21 +382,6 @@ cmd_update()
 			just_cloned=
 		fi
 
-		if test -n "$remote"
-		then
-			branch=$(git submodule--helper remote-branch "$sm_path")
-			if test -z "$nofetch"
-			then
-				# Fetch remote before determining tracking $sha1
-				fetch_in_submodule "$sm_path" $depth ||
-				die "fatal: $(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
-			fi
-			remote_name=$(sanitize_submodule_env; cd "$sm_path" && git submodule--helper print-default-remote)
-			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify "${remote_name}/${branch}") ||
-			die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
-		fi
-
 		out=$(git submodule--helper run-update-procedure \
 			  ${wt_prefix:+--prefix "$wt_prefix"} \
 			  ${GIT_QUIET:+--quiet} \
@@ -421,6 +392,7 @@ cmd_update()
 			  ${update:+--update "$update"} \
 			  ${prefix:+--recursive-prefix "$prefix"} \
 			  ${sha1:+--oid "$sha1"} \
+			  ${remote:+--remote} \
 			  "--" \
 			  "$sm_path")
 
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 13/16] submodule--helper: remove ensure-core-worktree

Move the logic of "git submodule--helper ensure-core-worktree" into run-update-procedure. Since the ensure-core-worktree command is obsolete, remove it.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 13 +++----------
 git-submodule.sh            |  2 --
 2 files changed, 3 insertions(+), 12 deletions(-)
Show changes to 2 files +3 −12

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 15ae986692..a05aea5cd6 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2746,17 +2746,11 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+static void ensure_core_worktree(const char *path)
 {
-	const char *path;
 	const char *cw;
 	struct repository subrepo;
 
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-
 	if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
 		die(_("could not get a repository handle for submodule '%s'"), path);
 
@@ -2776,8 +2770,6 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
 		free(abs_path);
 		strbuf_release(&sb);
 	}
-
-	return 0;
 }
 
 static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
@@ -2967,6 +2959,8 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 /* NEEDSWORK: this is a temporary name until we delete update_submodule() */
 static int update_submodule2(struct update_data *update_data)
 {
+	ensure_core_worktree(update_data->sm_path);
+
 	/* NEEDSWORK: fix the style issues e.g. braces */
 	if (update_data->just_cloned) {
 		oidcpy(&update_data->suboid, null_oid());
@@ -3390,7 +3384,6 @@ static struct cmd_struct commands[] = {
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
 	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
-	{"ensure-core-worktree", ensure_core_worktree, 0},
 	{"relative-path", resolve_relative_path, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index 29fd69250d..aa9c898e1c 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -373,8 +373,6 @@ cmd_update()
 	do
 		die_if_unmatched "$quickabort" "$sha1"
 
-		git submodule--helper ensure-core-worktree "$sm_path" || exit 1
-
 		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
 
 		if test $just_cloned -eq 0
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 11/16] submodule--helper run-update-procedure: remove --suboid

Teach run-update-procedure to determine the oid of the submodule's HEAD instead of doing it in git-subomdule.sh.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 12 +++++++++---
 git-submodule.sh            |  8 +-------
 2 files changed, 10 insertions(+), 10 deletions(-)
Show changes to 2 files +10 −10

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 0b5120734a..a26477ce04 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2585,9 +2585,6 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
 			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &opt.suboid, N_("subsha1"),
-			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
 		OPT_END()
 	};
 
@@ -2982,6 +2979,15 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 /* NEEDSWORK: this is a temporary name until we delete update_submodule() */
 static int update_submodule2(struct update_data *update_data)
 {
+	/* NEEDSWORK: fix the style issues e.g. braces */
+	if (update_data->just_cloned) {
+		oidcpy(&update_data->suboid, null_oid());
+	} else {
+		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+			die(_("Unable to find current revision in submodule path '%s'"),
+			    update_data->displaypath);
+	}
+
 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
 		return do_run_update_procedure(update_data);
 
diff --git a/git-submodule.sh b/git-submodule.sh
index 652861aa66..d48c314f01 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -391,14 +391,9 @@ cmd_update()
 
 		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
 
-		if test $just_cloned -eq 1
+		if test $just_cloned -eq 0
 		then
-			subsha1=
-		else
 			just_cloned=
-			subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify HEAD) ||
-			die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
 		fi
 
 		if test -n "$remote"
@@ -426,7 +421,6 @@ cmd_update()
 			  ${update:+--update "$update"} \
 			  ${prefix:+--recursive-prefix "$prefix"} \
 			  ${sha1:+--oid "$sha1"} \
-			  ${subsha1:+--suboid "$subsha1"} \
 			  "--" \
 			  "$sm_path")
 
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 14/16] submodule--helper update-clone: learn --init

Teach "git submodule--helper update-clone" the --init flag and remove the corresponding shell code.

When the `--init` flag is passed to the subcommand, we do not spawn a new subprocess and call `submodule--helper init` on the submodule paths, because the Git machinery is not able to pick up the configuration changes introduced by that init call. So we instead run the `init_submodule_cb()` callback over each submodule in the same process.

[1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 26 ++++++++++++++++++++++++++
 git-submodule.sh            |  9 +++------
 2 files changed, 29 insertions(+), 6 deletions(-)
Show changes to 2 files +29 −6

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index a05aea5cd6..5635f0c48b 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1989,6 +1989,7 @@ struct submodule_update_clone {
 	int failed_clones_nr, failed_clones_alloc;
 
 	int max_jobs;
+	unsigned int init;
 };
 #define SUBMODULE_UPDATE_CLONE_INIT { \
 	.list = MODULE_LIST_INIT, \
@@ -2483,6 +2484,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
 
 	struct option module_update_clone_options[] = {
+		OPT_BOOL(0, "init", &opt.init,
+			 N_("initialize uninitialized submodules before update")),
 		OPT_STRING(0, "prefix", &prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
@@ -2536,6 +2539,29 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 	if (pathspec.nr)
 		opt.warn_if_uninitialized = 1;
 
+	if (opt.init) {
+		struct module_list list = MODULE_LIST_INIT;
+		struct init_cb info = INIT_CB_INIT;
+
+		if (module_list_compute(argc, argv, opt.prefix,
+					&pathspec, &list) < 0)
+			return 1;
+
+		/*
+		 * If there are no path args and submodule.active is set then,
+		 * by default, only initialize 'active' modules.
+		 */
+		if (!argc && git_config_get_value_multi("submodule.active"))
+			module_list_active(&list);
+
+		info.prefix = opt.prefix;
+		info.superprefix = opt.recursive_prefix;
+		if (opt.quiet)
+			info.flags |= OPT_QUIET;
+
+		for_each_listed_submodule(&list, init_submodule_cb, &info);
+	}
+
 	return update_submodules(&opt);
 }
 
diff --git a/git-submodule.sh b/git-submodule.sh
index aa9c898e1c..3ccf2388bf 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -347,14 +347,11 @@ cmd_update()
 		shift
 	done
 
-	if test -n "$init"
-	then
-		cmd_init "--" "$@" || return
-	fi
-
 	{
-	git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
+	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update-clone \
+		${GIT_QUIET:+--quiet} \
 		${progress:+"--progress"} \
+		${init:+--init} \
 		${wt_prefix:+--prefix "$wt_prefix"} \
 		${prefix:+--recursive-prefix "$prefix"} \
 		${update:+--update "$update"} \
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 15/16] submodule--helper: move functions around

The next commit will change the internals of several functions and arrange them in a more logical manner. Move these functions to their final positions so that the diff is smaller.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 228 ++++++++++++++++++------------------
 1 file changed, 114 insertions(+), 114 deletions(-)
Show changes to builtin/submodule--helper.c +114 −114
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 5635f0c48b..0ab8f9d49f 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2451,120 +2451,6 @@ static void update_submodule(struct update_clone_data *ucd)
 		ucd->sub->path);
 }
 
-static int update_submodules(struct submodule_update_clone *suc)
-{
-	int i;
-
-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
-				   update_clone_start_failure,
-				   update_clone_task_finished, suc, "submodule",
-				   "parallel/update");
-
-	/*
-	 * We saved the output and put it out all at once now.
-	 * That means:
-	 * - the listener does not have to interleave their (checkout)
-	 *   work with our fetching.  The writes involved in a
-	 *   checkout involve more straightforward sequential I/O.
-	 * - the listener can avoid doing any work if fetching failed.
-	 */
-	if (suc->quickstop)
-		return 1;
-
-	for (i = 0; i < suc->update_clone_nr; i++)
-		update_submodule(&suc->update_clone[i]);
-
-	return 0;
-}
-
-static int update_clone(int argc, const char **argv, const char *prefix)
-{
-	const char *update = NULL;
-	struct pathspec pathspec;
-	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
-
-	struct option module_update_clone_options[] = {
-		OPT_BOOL(0, "init", &opt.init,
-			 N_("initialize uninitialized submodules before update")),
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
-			   N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
-			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &opt.dissociate,
-			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &opt.depth, "<depth>",
-			   N_("create a shallow clone truncated to the "
-			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &opt.max_jobs,
-			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
-			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &opt.progress,
-			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &opt.require_init,
-			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &opt.single_branch,
-			 N_("clone only one branch, HEAD or --branch")),
-		OPT_END()
-	};
-
-	const char *const git_submodule_helper_usage[] = {
-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
-		NULL
-	};
-	opt.prefix = prefix;
-
-	update_clone_config_from_gitmodules(&opt.max_jobs);
-	git_config(git_update_clone_config, &opt.max_jobs);
-
-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
-			     git_submodule_helper_usage, 0);
-
-	if (update)
-		if (parse_submodule_update_strategy(update, &opt.update) < 0)
-			die(_("bad value for update parameter"));
-
-	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
-		return 1;
-
-	if (pathspec.nr)
-		opt.warn_if_uninitialized = 1;
-
-	if (opt.init) {
-		struct module_list list = MODULE_LIST_INIT;
-		struct init_cb info = INIT_CB_INIT;
-
-		if (module_list_compute(argc, argv, opt.prefix,
-					&pathspec, &list) < 0)
-			return 1;
-
-		/*
-		 * If there are no path args and submodule.active is set then,
-		 * by default, only initialize 'active' modules.
-		 */
-		if (!argc && git_config_get_value_multi("submodule.active"))
-			module_list_active(&list);
-
-		info.prefix = opt.prefix;
-		info.superprefix = opt.recursive_prefix;
-		if (opt.quiet)
-			info.flags |= OPT_QUIET;
-
-		for_each_listed_submodule(&list, init_submodule_cb, &info);
-	}
-
-	return update_submodules(&opt);
-}
-
 /*
  * NEEDSWORK: Use a forward declaration to avoid moving
  * run_update_procedure() (which will be removed soon).
@@ -3021,6 +2907,120 @@ static int update_submodule2(struct update_data *update_data)
 	return 3;
 }
 
+static int update_submodules(struct submodule_update_clone *suc)
+{
+	int i;
+
+	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
+				   update_clone_start_failure,
+				   update_clone_task_finished, suc, "submodule",
+				   "parallel/update");
+
+	/*
+	 * We saved the output and put it out all at once now.
+	 * That means:
+	 * - the listener does not have to interleave their (checkout)
+	 *   work with our fetching.  The writes involved in a
+	 *   checkout involve more straightforward sequential I/O.
+	 * - the listener can avoid doing any work if fetching failed.
+	 */
+	if (suc->quickstop)
+		return 1;
+
+	for (i = 0; i < suc->update_clone_nr; i++)
+		update_submodule(&suc->update_clone[i]);
+
+	return 0;
+}
+
+static int update_clone(int argc, const char **argv, const char *prefix)
+{
+	const char *update = NULL;
+	struct pathspec pathspec;
+	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
+
+	struct option module_update_clone_options[] = {
+		OPT_BOOL(0, "init", &opt.init,
+			 N_("initialize uninitialized submodules before update")),
+		OPT_STRING(0, "prefix", &prefix,
+			   N_("path"),
+			   N_("path into the working tree")),
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
+			   N_("path"),
+			   N_("path into the working tree, across nested "
+			      "submodule boundaries")),
+		OPT_STRING(0, "update", &update,
+			   N_("string"),
+			   N_("rebase, merge, checkout or none")),
+		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
+			   N_("reference repository")),
+		OPT_BOOL(0, "dissociate", &opt.dissociate,
+			   N_("use --reference only while cloning")),
+		OPT_STRING(0, "depth", &opt.depth, "<depth>",
+			   N_("create a shallow clone truncated to the "
+			      "specified number of revisions")),
+		OPT_INTEGER('j', "jobs", &opt.max_jobs,
+			    N_("parallel jobs")),
+		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
+			    N_("whether the initial clone should follow the shallow recommendation")),
+		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &opt.progress,
+			    N_("force cloning progress")),
+		OPT_BOOL(0, "require-init", &opt.require_init,
+			   N_("disallow cloning into non-empty directory")),
+		OPT_BOOL(0, "single-branch", &opt.single_branch,
+			 N_("clone only one branch, HEAD or --branch")),
+		OPT_END()
+	};
+
+	const char *const git_submodule_helper_usage[] = {
+		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
+		NULL
+	};
+	opt.prefix = prefix;
+
+	update_clone_config_from_gitmodules(&opt.max_jobs);
+	git_config(git_update_clone_config, &opt.max_jobs);
+
+	argc = parse_options(argc, argv, prefix, module_update_clone_options,
+			     git_submodule_helper_usage, 0);
+
+	if (update)
+		if (parse_submodule_update_strategy(update, &opt.update) < 0)
+			die(_("bad value for update parameter"));
+
+	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
+		return 1;
+
+	if (pathspec.nr)
+		opt.warn_if_uninitialized = 1;
+
+	if (opt.init) {
+		struct module_list list = MODULE_LIST_INIT;
+		struct init_cb info = INIT_CB_INIT;
+
+		if (module_list_compute(argc, argv, opt.prefix,
+					&pathspec, &list) < 0)
+			return 1;
+
+		/*
+		 * If there are no path args and submodule.active is set then,
+		 * by default, only initialize 'active' modules.
+		 */
+		if (!argc && git_config_get_value_multi("submodule.active"))
+			module_list_active(&list);
+
+		info.prefix = opt.prefix;
+		info.superprefix = opt.recursive_prefix;
+		if (opt.quiet)
+			info.flags |= OPT_QUIET;
+
+		for_each_listed_submodule(&list, init_submodule_cb, &info);
+	}
+
+	return update_submodules(&opt);
+}
+
 struct add_data {
 	const char *prefix;
 	const char *branch;
-- 
2.33.GIT
Glen Choo· Feb 8, 2022, 08:39 UTC · re: Glen Choo · lore

[PATCH v6 16/16] submodule: move core cmd_update() logic to C

From: Atharva Raykar <raykar.ath@gmail.com>

This patch completes the conversion past the flag parsing of `submodule update` by introducing a helper subcommand called `submodule--helper update`. The behaviour of `submodule update` should remain the same after this patch.

We add more fields to the `struct update_data` that are required by `struct submodule_update_clone` to be able to perform a clone, when that is needed to be done.

Recursing on a submodule is done by calling a subprocess that launches `submodule--helper update`, with a modified `--recursive-prefix` and `--prefix` parameter.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 360 ++++++++++++++++++++++--------------
 git-submodule.sh            | 102 +---------
 2 files changed, 227 insertions(+), 235 deletions(-)
Show changes to 2 files +227 −235

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 0ab8f9d49f..5085b28a2d 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1977,7 +1977,6 @@ struct submodule_update_clone {
 	const char *prefix;
 	int single_branch;
 
-	/* to be consumed by git-submodule.sh */
 	struct update_clone_data *update_clone;
 	int update_clone_nr; int update_clone_alloc;
 
@@ -1989,7 +1988,6 @@ struct submodule_update_clone {
 	int failed_clones_nr, failed_clones_alloc;
 
 	int max_jobs;
-	unsigned int init;
 };
 #define SUBMODULE_UPDATE_CLONE_INIT { \
 	.list = MODULE_LIST_INIT, \
@@ -2001,6 +1999,7 @@ struct submodule_update_clone {
 }
 
 struct update_data {
+	const char *prefix;
 	const char *recursive_prefix;
 	const char *sm_path;
 	const char *displaypath;
@@ -2008,14 +2007,54 @@ struct update_data {
 	struct object_id suboid;
 	struct submodule_update_strategy update_strategy;
 	int depth;
+	int recommend_shallow;
+	int single_branch;
+	int max_jobs;
+	unsigned int init;
 	unsigned int force;
 	unsigned int quiet;
 	unsigned int nofetch;
 	unsigned int just_cloned;
 	unsigned int remote;
+	unsigned int recursive;
+	unsigned int progress;
+	unsigned int dissociate;
+	unsigned int require_init;
+	unsigned warn_if_uninitialized ;
+	struct string_list references;
+	struct module_list list;
 };
 #define UPDATE_DATA_INIT { \
+	.list = MODULE_LIST_INIT, \
 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+	.recommend_shallow = -1, \
+	.references = STRING_LIST_INIT_DUP, \
+	.single_branch = -1, \
+	.max_jobs = 1, \
+}
+
+static void update_clone_from_update_data(struct submodule_update_clone *suc,
+					  struct update_data *update_data)
+{
+	suc->prefix = update_data->prefix;
+	suc->recursive_prefix = update_data->recursive_prefix;
+	suc->max_jobs = update_data->max_jobs;
+	suc->progress = update_data->progress;
+	suc->quiet = update_data->quiet;
+	suc->dissociate = update_data->dissociate;
+	suc->require_init = update_data->require_init;
+	suc->single_branch = update_data->single_branch;
+	suc->warn_if_uninitialized = update_data->warn_if_uninitialized;
+	suc->list = update_data->list;
+	suc->update = update_data->update_strategy;
+	suc->recommend_shallow = update_data->recommend_shallow;
+	if (update_data->depth)
+		suc->depth = xstrfmt("--depth=%d", update_data->depth);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			string_list_append(&suc->references, item->string);
+	}
 }
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
@@ -2316,8 +2355,15 @@ static int run_update_command(struct update_data *ud, int subforce)
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
 	int must_die_on_failure = 0;
+	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
+
+	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
+		determine_submodule_update_strategy(the_repository, ud->just_cloned,
+						    ud->sm_path, NULL, &strategy);
+	else
+		strategy = ud->update_strategy;
 
-	switch (ud->update_strategy.type) {
+	switch (strategy.type) {
 	case SM_UPDATE_CHECKOUT:
 		cp.git_cmd = 1;
 		strvec_pushl(&cp.args, "checkout", "-q", NULL);
@@ -2340,55 +2386,54 @@ static int run_update_command(struct update_data *ud, int subforce)
 		break;
 	case SM_UPDATE_COMMAND:
 		cp.use_shell = 1;
-		strvec_push(&cp.args, ud->update_strategy.command);
+		strvec_push(&cp.args, strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+		    submodule_strategy_to_string(&strategy));
 	}
 	strvec_push(&cp.args, oid);
 
 	cp.dir = xstrdup(ud->sm_path);
 	prepare_submodule_repo_env(&cp.env_array);
 	if (run_command(&cp)) {
-		switch (ud->update_strategy.type) {
+		switch (strategy.type) {
 		case SM_UPDATE_CHECKOUT:
-			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			die_message(_("Unable to checkout '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_REBASE:
-			printf(_("Unable to rebase '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			if (!must_die_on_failure)
+				break;
+			die(_("Unable to rebase '%s' in submodule path '%s'"),
+			    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_MERGE:
-			printf(_("Unable to merge '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			if (!must_die_on_failure)
+				break;
+			die(_("Unable to merge '%s' in submodule path '%s'"),
+			    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_COMMAND:
-			printf(_("Execution of '%s %s' failed in submodule path '%s'"),
-			       ud->update_strategy.command, oid, ud->displaypath);
+			if (!must_die_on_failure)
+				break;
+			die(_("Execution of '%s %s' failed in submodule path '%s'"),
+			    strategy.command, oid, ud->displaypath);
 			break;
 		default:
 			BUG("unexpected update strategy type: %s",
-			    submodule_strategy_to_string(&ud->update_strategy));
+			    submodule_strategy_to_string(&strategy));
 		}
-		/*
-		 * NEEDSWORK: We are currently printing to stdout with error
-		 * return so that the shell caller handles the error output
-		 * properly. Once we start handling the error messages within
-		 * C, we should use die() instead.
-		 */
-		if (must_die_on_failure)
-			return 2;
-		/*
-		 * This signifies to the caller in shell that the command
-		 * failed without dying
-		 */
+
+		/* the command failed, but update must continue */
 		return 1;
 	}
 
-	switch (ud->update_strategy.type) {
+	if (ud->quiet)
+		return 0;
+
+	switch (strategy.type) {
 	case SM_UPDATE_CHECKOUT:
 		printf(_("Submodule path '%s': checked out '%s'\n"),
 		       ud->displaypath, oid);
@@ -2403,17 +2448,17 @@ static int run_update_command(struct update_data *ud, int subforce)
 		break;
 	case SM_UPDATE_COMMAND:
 		printf(_("Submodule path '%s': '%s %s'\n"),
-		       ud->displaypath, ud->update_strategy.command, oid);
+		       ud->displaypath, strategy.command, oid);
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&ud->update_strategy));
+		    submodule_strategy_to_string(&strategy));
 	}
 
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud)
+static int run_update_procedure(struct update_data *ud)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2443,89 +2488,6 @@ static int do_run_update_procedure(struct update_data *ud)
 	return run_update_command(ud, subforce);
 }
 
-static void update_submodule(struct update_clone_data *ucd)
-{
-	fprintf(stdout, "dummy %s %d\t%s\n",
-		oid_to_hex(&ucd->oid),
-		ucd->just_cloned,
-		ucd->sub->path);
-}
-
-/*
- * NEEDSWORK: Use a forward declaration to avoid moving
- * run_update_procedure() (which will be removed soon).
- */
-static int update_submodule2(struct update_data *update_data);
-static int run_update_procedure(int argc, const char **argv, const char *prefix)
-{
-	char *prefixed_path, *update = NULL;
-	struct update_data opt = UPDATE_DATA_INIT;
-
-	struct option options[] = {
-		OPT__QUIET(&opt.quiet,
-			   N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&opt.force, N_("force checkout updates"),
-			   0),
-		OPT_BOOL('N', "no-fetch", &opt.nofetch,
-			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &opt.just_cloned,
-			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
-			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_BOOL(0, "remote", &opt.remote,
-			 N_("use SHA-1 of submodule's remote tracking branch")),
-		OPT_END()
-	};
-
-	const char *const usage[] = {
-		N_("git submodule--helper run-update-procedure [<options>] <path>"),
-		NULL
-	};
-
-	argc = parse_options(argc, argv, prefix, options, usage, 0);
-
-	if (argc != 1)
-		usage_with_options(usage, options);
-
-	opt.sm_path = argv[0];
-
-	if (opt.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", opt.recursive_prefix, opt.sm_path);
-	else
-		prefixed_path = xstrdup(opt.sm_path);
-
-	opt.displaypath = get_submodule_displaypath(prefixed_path, prefix);
-
-	determine_submodule_update_strategy(the_repository, opt.just_cloned,
-					    opt.sm_path, update,
-					    &opt.update_strategy);
-
-	free(prefixed_path);
-	return update_submodule2(&opt);
-}
-
-static int resolve_relative_path(int argc, const char **argv, const char *prefix)
-{
-	struct strbuf sb = STRBUF_INIT;
-	if (argc != 3)
-		die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
-
-	printf("%s", relative_path(argv[1], argv[2], &sb));
-	strbuf_release(&sb);
-	return 0;
-}
-
 static const char *remote_submodule_branch(const char *path)
 {
 	const struct submodule *sub;
@@ -2868,11 +2830,66 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 	return !!ret;
 }
 
-/* NEEDSWORK: this is a temporary name until we delete update_submodule() */
-static int update_submodule2(struct update_data *update_data)
+static void update_data_to_args(struct update_data *update_data, struct strvec *args)
+{
+	const char *update = submodule_strategy_to_string(&update_data->update_strategy);
+
+	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
+	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
+	if (update_data->prefix)
+		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
+	if (update_data->recursive_prefix)
+		strvec_pushl(args, "--recursive-prefix",
+			     update_data->recursive_prefix, NULL);
+	if (update_data->quiet)
+		strvec_push(args, "--quiet");
+	if (update_data->force)
+		strvec_push(args, "--force");
+	if (update_data->init)
+		strvec_push(args, "--init");
+	if (update_data->remote)
+		strvec_push(args, "--remote");
+	if (update_data->nofetch)
+		strvec_push(args, "--no-fetch");
+	if (update_data->dissociate)
+		strvec_push(args, "--dissociate");
+	if (update_data->progress)
+		strvec_push(args, "--progress");
+	if (update_data->require_init)
+		strvec_push(args, "--require-init");
+	if (update_data->depth)
+		strvec_pushf(args, "--depth=%d", update_data->depth);
+	if (update)
+		strvec_pushl(args, "--update", update, NULL);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			strvec_pushl(args, "--reference", item->string, NULL);
+	}
+	if (update_data->recommend_shallow == 0)
+		strvec_push(args, "--no-recommend-shallow");
+	else if (update_data->recommend_shallow == 1)
+		strvec_push(args, "--recommend-shallow");
+	if (update_data->single_branch >= 0)
+		strvec_push(args, "--single-branch");
+}
+
+static int update_submodule(struct update_data *update_data)
 {
+	char *prefixed_path;
+
 	ensure_core_worktree(update_data->sm_path);
 
+	if (update_data->recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
+					update_data->sm_path);
+	else
+		prefixed_path = xstrdup(update_data->sm_path);
+
+	update_data->displaypath = get_submodule_displaypath(prefixed_path,
+							     update_data->prefix);
+	free(prefixed_path);
+
 	/* NEEDSWORK: fix the style issues e.g. braces */
 	if (update_data->just_cloned) {
 		oidcpy(&update_data->suboid, null_oid());
@@ -2902,18 +2919,55 @@ static int update_submodule2(struct update_data *update_data)
 	}
 
 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
-		return do_run_update_procedure(update_data);
+		if (run_update_procedure(update_data))
+			return 1;
 
-	return 3;
+	if (update_data->recursive) {
+		struct child_process cp = CHILD_PROCESS_INIT;
+		struct update_data next = *update_data;
+		int res;
+
+		if (update_data->recursive_prefix)
+			prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix,
+						update_data->sm_path);
+		else
+			prefixed_path = xstrfmt("%s/", update_data->sm_path);
+
+		next.recursive_prefix = get_submodule_displaypath(prefixed_path,
+								  update_data->prefix);
+		next.prefix = NULL;
+		oidcpy(&next.oid, null_oid());
+		oidcpy(&next.suboid, null_oid());
+
+		cp.dir = update_data->sm_path;
+		cp.git_cmd = 1;
+		prepare_submodule_repo_env(&cp.env_array);
+		update_data_to_args(&next, &cp.args);
+
+		/* die() if child process die()'d */
+		res = run_command(&cp);
+		if (!res)
+			return 0;
+		die_message(_("Failed to recurse into submodule path '%s'"),
+			    update_data->displaypath);
+		if (res == 128)
+			exit(res);
+		else if (res)
+			return 1;
+	}
+
+	return 0;
 }
 
-static int update_submodules(struct submodule_update_clone *suc)
+static int update_submodules(struct update_data *update_data)
 {
-	int i;
+	int i, res = 0;
+	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
 
-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
+	update_clone_from_update_data(&suc, update_data);
+	run_processes_parallel_tr2(suc.max_jobs, update_clone_get_next_task,
 				   update_clone_start_failure,
-				   update_clone_task_finished, suc, "submodule",
+				   update_clone_task_finished, &suc, "submodule",
 				   "parallel/update");
 
 	/*
@@ -2924,25 +2978,45 @@ static int update_submodules(struct submodule_update_clone *suc)
 	 *   checkout involve more straightforward sequential I/O.
 	 * - the listener can avoid doing any work if fetching failed.
 	 */
-	if (suc->quickstop)
-		return 1;
+	if (suc.quickstop) {
+		res = 1;
+		goto cleanup;
+	}
 
-	for (i = 0; i < suc->update_clone_nr; i++)
-		update_submodule(&suc->update_clone[i]);
+	for (i = 0; i < suc.update_clone_nr; i++) {
+		struct update_clone_data ucd = suc.update_clone[i];
 
-	return 0;
+		oidcpy(&update_data->oid, &ucd.oid);
+		update_data->just_cloned = ucd.just_cloned;
+		update_data->sm_path = ucd.sub->path;
+
+		if (update_submodule(update_data))
+			res = 1;
+	}
+
+cleanup:
+	string_list_clear(&update_data->references, 0);
+	return res;
 }
 
-static int update_clone(int argc, const char **argv, const char *prefix)
+static int module_update(int argc, const char **argv, const char *prefix)
 {
 	const char *update = NULL;
 	struct pathspec pathspec;
-	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
+	struct update_data opt = UPDATE_DATA_INIT;
 
+	/* NEEDSWORK: update names and strings */
 	struct option module_update_clone_options[] = {
+		OPT__FORCE(&opt.force, N_("force checkout updates"), 0),
 		OPT_BOOL(0, "init", &opt.init,
 			 N_("initialize uninitialized submodules before update")),
-		OPT_STRING(0, "prefix", &prefix,
+		OPT_BOOL(0, "remote", &opt.remote,
+			 N_("use SHA-1 of submodule's remote tracking branch")),
+		OPT_BOOL(0, "recursive", &opt.recursive,
+			 N_("traverse submodules recursively")),
+		OPT_BOOL('N', "no-fetch", &opt.nofetch,
+			 N_("don't fetch new objects from the remote site")),
+		OPT_STRING(0, "prefix", &opt.prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
 		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
@@ -2953,21 +3027,21 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
 		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
-			   N_("reference repository")),
+				N_("reference repository")),
 		OPT_BOOL(0, "dissociate", &opt.dissociate,
-			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &opt.depth, "<depth>",
-			   N_("create a shallow clone truncated to the "
-			      "specified number of revisions")),
+			 N_("use --reference only while cloning")),
+		OPT_INTEGER(0, "depth", &opt.depth,
+			    N_("create a shallow clone truncated to the "
+			       "specified number of revisions")),
 		OPT_INTEGER('j', "jobs", &opt.max_jobs,
 			    N_("parallel jobs")),
 		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
-			    N_("whether the initial clone should follow the shallow recommendation")),
+			 N_("whether the initial clone should follow the shallow recommendation")),
 		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
 		OPT_BOOL(0, "progress", &opt.progress,
-			    N_("force cloning progress")),
+			 N_("force cloning progress")),
 		OPT_BOOL(0, "require-init", &opt.require_init,
-			   N_("disallow cloning into non-empty directory")),
+			 N_("disallow cloning into non-empty directory")),
 		OPT_BOOL(0, "single-branch", &opt.single_branch,
 			 N_("clone only one branch, HEAD or --branch")),
 		OPT_END()
@@ -2977,16 +3051,18 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
 		NULL
 	};
-	opt.prefix = prefix;
 
 	update_clone_config_from_gitmodules(&opt.max_jobs);
 	git_config(git_update_clone_config, &opt.max_jobs);
 
 	argc = parse_options(argc, argv, prefix, module_update_clone_options,
 			     git_submodule_helper_usage, 0);
+	oidcpy(&opt.oid, null_oid());
+	oidcpy(&opt.suboid, null_oid());
 
 	if (update)
-		if (parse_submodule_update_strategy(update, &opt.update) < 0)
+		if (parse_submodule_update_strategy(update,
+						    &opt.update_strategy) < 0)
 			die(_("bad value for update parameter"));
 
 	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
@@ -3408,9 +3484,7 @@ static struct cmd_struct commands[] = {
 	{"name", module_name, 0},
 	{"clone", module_clone, 0},
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
-	{"update-clone", update_clone, 0},
-	{"run-update-procedure", run_update_procedure, 0},
-	{"relative-path", resolve_relative_path, 0},
+	{"update", module_update, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
 	{"init", module_init, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index 3ccf2388bf..bcd8b92aab 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -347,108 +347,26 @@ cmd_update()
 		shift
 	done
 
-	{
-	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update-clone \
+	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
 		${GIT_QUIET:+--quiet} \
-		${progress:+"--progress"} \
+		${force:+--force} \
+		${progress:+--progress} \
+		${dissociate:+--dissociate} \
+		${remote:+--remote} \
+		${recursive:+--recursive} \
 		${init:+--init} \
+		${require_init:+--require-init} \
+		${nofetch:+--no-fetch} \
 		${wt_prefix:+--prefix "$wt_prefix"} \
 		${prefix:+--recursive-prefix "$prefix"} \
 		${update:+--update "$update"} \
 		${reference:+"$reference"} \
-		${dissociate:+"--dissociate"} \
-		${depth:+--depth "$depth"} \
-		${require_init:+--require-init} \
+		${depth:+"$depth"} \
 		$single_branch \
 		$recommend_shallow \
 		$jobs \
 		-- \
-		"$@" || echo "#unmatched" $?
-	} | {
-	err=
-	while read -r quickabort sha1 just_cloned sm_path
-	do
-		die_if_unmatched "$quickabort" "$sha1"
-
-		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
-
-		if test $just_cloned -eq 0
-		then
-			just_cloned=
-		fi
-
-		out=$(git submodule--helper run-update-procedure \
-			  ${wt_prefix:+--prefix "$wt_prefix"} \
-			  ${GIT_QUIET:+--quiet} \
-			  ${force:+--force} \
-			  ${just_cloned:+--just-cloned} \
-			  ${nofetch:+--no-fetch} \
-			  ${depth:+"$depth"} \
-			  ${update:+--update "$update"} \
-			  ${prefix:+--recursive-prefix "$prefix"} \
-			  ${sha1:+--oid "$sha1"} \
-			  ${remote:+--remote} \
-			  "--" \
-			  "$sm_path")
-
-		# exit codes for run-update-procedure:
-		# 0: update was successful, say command output
-		# 1: update procedure failed, but should not die
-		# 2 or 128: subcommand died during execution
-		# 3: no update procedure was run
-		res="$?"
-		case $res in
-		0)
-			say "$out"
-			;;
-		1)
-			err="${err};fatal: $out"
-			continue
-			;;
-		2|128)
-			die_with_status $res "fatal: $out"
-			;;
-		esac
-
-		if test -n "$recursive"
-		then
-			(
-				prefix=$(git submodule--helper relative-path "$prefix$sm_path/" "$wt_prefix")
-				wt_prefix=
-				sanitize_submodule_env
-				cd "$sm_path" &&
-				eval cmd_update
-			)
-			res=$?
-			if test $res -gt 0
-			then
-				die_msg="fatal: $(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
-				if test $res -ne 2
-				then
-					err="${err};$die_msg"
-					continue
-				else
-					die_with_status $res "$die_msg"
-				fi
-			fi
-		fi
-	done
-
-	if test -n "$err"
-	then
-		OIFS=$IFS
-		IFS=';'
-		for e in $err
-		do
-			if test -n "$e"
-			then
-				echo >&2 "$e"
-			fi
-		done
-		IFS=$OIFS
-		exit 1
-	fi
-	}
+		"$@"
 }
 
 #
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 00/20] submodule: convert the rest of 'update' to C

This reroll contains another 'easy' preparatory patch and the fixups I alluded to in v6 [1]. This isn't the split-up I described in the footnote of v6, but it gets the big patch (patch 17) to what I think is a reviewable state.

The diff between v7 and v5 is no longer just NEEDSWORK comments, but I think it is easier to reason about. Patch 17 resembles v5 the most (I will include a diff in a reply to that patch); everything after patch 17 is fixups (I did not squash them in because they would grow the diff even more).

I will also leave a review on patch 17 since the changes were not originally authored by me.

[1] https://lore.kernel.org/git/20220208083952.35036-1-chooglen@google.com
Changes in v7:
- Split the last patch of v6 (the big one) into patches 16-17.
- Patch 16 moves logic out of run_update_procedure() (because the
  command is going away), removing some noise from patch 17. This makes
  the update_strategy parsing easier to reason about, but at the cost of
  growing the diff vis-a-vis v5
- Patches 18-20 are fixups that address NEEDSWORK comments from earlier
  patches. Once maintaining a small diff vis-a-vis v5 stops making
  sense, I will squash them in.
Atharva Raykar (6):
  submodule--helper: get remote names from any repository
  submodule--helper: refactor get_submodule_displaypath()
  submodule--helper: allow setting superprefix for init_submodule()
  submodule--helper: run update using child process struct
  builtin/submodule--helper.c: reformat designated initializers
  submodule: move core cmd_update() logic to C
Glen Choo (11):
  submodule--helper: remove update-module-mode
  submodule--helper: reorganize code for sh to C conversion
  submodule--helper run-update-procedure: remove --suboid
  submodule--helper run-update-procedure: learn --remote
  submodule--helper: remove ensure-core-worktree
  submodule--helper update-clone: learn --init
  submodule--helper: move functions around
  submodule--helper: reduce logic in run_update_procedure()
  fixup! submodule--helper run-update-procedure: remove --suboid
  fixup! submodule--helper run-update-procedure: learn --remote
  fixup! submodule: move core cmd_update() logic to C
Ævar Arnfjörð Bjarmason (3):
  builtin/submodule--helper.c: rename option variables to "opt"
  submodule--helper: don't use bitfield indirection for parse_options()
  submodule tests: test for init and update failure output
 builtin/submodule--helper.c    | 695 ++++++++++++++++++---------------
 git-submodule.sh               | 153 +-------
 t/t7406-submodule-update.sh    |  14 +-
 t/t7408-submodule-reference.sh |  14 +-
 4 files changed, 423 insertions(+), 453 deletions(-)
Range-diff against v6:
 1:  79c522cf56 =  1:  86ffb53742 submodule--helper: get remote names from any repository
 2:  0b97034d89 =  2:  2a40266b7a submodule--helper: refactor get_submodule_displaypath()
 3:  fedbed87a3 =  3:  cd851c8eb5 submodule--helper: allow setting superprefix for init_submodule()
 4:  8e7868c5d1 =  4:  bfe5cad136 submodule--helper: run update using child process struct
 5:  3dee4b9b15 =  5:  72c257fdbf builtin/submodule--helper.c: reformat designated initializers
 6:  e2cc866e6b =  6:  4b5f703fde builtin/submodule--helper.c: rename option variables to "opt"
 7:  4831695dc6 =  7:  f1d21f5b1c submodule--helper: don't use bitfield indirection for parse_options()
 8:  c2dadfffb2 =  8:  9d32a73fc3 submodule tests: test for init and update failure output
 9:  79ceb88dee =  9:  087bf43aba submodule--helper: remove update-module-mode
10:  6fe25e24da = 10:  4eb2893a19 submodule--helper: reorganize code for sh to C conversion
11:  52c997f97b = 11:  c08e7781e3 submodule--helper run-update-procedure: remove --suboid
12:  61a5b02472 = 12:  2419c37184 submodule--helper run-update-procedure: learn --remote
13:  f76627a078 = 13:  6691fd3648 submodule--helper: remove ensure-core-worktree
14:  2d93c4232b = 14:  d2c9c356e9 submodule--helper update-clone: learn --init
15:  d4899c5635 ! 15:  c8945fcc6f submodule--helper: move functions around
    @@ Metadata
      ## Commit message ##
         submodule--helper: move functions around
     
    -    The next commit will change the internals of several functions and
    +    A subsequent commit will change the internals of several functions and
         arrange them in a more logical manner. Move these functions to their
         final positions so that the diff is smaller.
     
 -:  ---------- > 16:  10af533ae0 submodule--helper: reduce logic in run_update_procedure()
16:  edf752da8d ! 17:  19143f6009 submodule: move core cmd_update() logic to C
    @@ builtin/submodule--helper.c: struct submodule_update_clone {
      };
      #define SUBMODULE_UPDATE_CLONE_INIT { \
      	.list = MODULE_LIST_INIT, \
    -@@ builtin/submodule--helper.c: struct submodule_update_clone {
    - }
    - 
    - struct update_data {
    -+	const char *prefix;
    - 	const char *recursive_prefix;
    - 	const char *sm_path;
    - 	const char *displaypath;
     @@ builtin/submodule--helper.c: struct update_data {
      	struct object_id suboid;
      	struct submodule_update_strategy update_strategy;
    @@ builtin/submodule--helper.c: struct update_data {
      
      static void next_submodule_warn_missing(struct submodule_update_clone *suc,
     @@ builtin/submodule--helper.c: static int run_update_command(struct update_data *ud, int subforce)
    - 	struct child_process cp = CHILD_PROCESS_INIT;
    - 	char *oid = oid_to_hex(&ud->oid);
    - 	int must_die_on_failure = 0;
    -+	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
    -+
    -+	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
    -+		determine_submodule_update_strategy(the_repository, ud->just_cloned,
    -+						    ud->sm_path, NULL, &strategy);
    -+	else
    -+		strategy = ud->update_strategy;
    - 
    --	switch (ud->update_strategy.type) {
    -+	switch (strategy.type) {
    - 	case SM_UPDATE_CHECKOUT:
    - 		cp.git_cmd = 1;
    - 		strvec_pushl(&cp.args, "checkout", "-q", NULL);
    -@@ builtin/submodule--helper.c: static int run_update_command(struct update_data *ud, int subforce)
    - 		break;
    - 	case SM_UPDATE_COMMAND:
    - 		cp.use_shell = 1;
    --		strvec_push(&cp.args, ud->update_strategy.command);
    -+		strvec_push(&cp.args, strategy.command);
    - 		must_die_on_failure = 1;
    - 		break;
    - 	default:
    - 		BUG("unexpected update strategy type: %s",
    --		    submodule_strategy_to_string(&ud->update_strategy));
    -+		    submodule_strategy_to_string(&strategy));
    - 	}
    - 	strvec_push(&cp.args, oid);
    - 
    - 	cp.dir = xstrdup(ud->sm_path);
    - 	prepare_submodule_repo_env(&cp.env_array);
      	if (run_command(&cp)) {
    --		switch (ud->update_strategy.type) {
    -+		switch (strategy.type) {
    + 		switch (ud->update_strategy.type) {
      		case SM_UPDATE_CHECKOUT:
     -			printf(_("Unable to checkout '%s' in submodule path '%s'"),
     -			       oid, ud->displaypath);
    @@ builtin/submodule--helper.c: static int run_update_command(struct update_data *u
      		case SM_UPDATE_REBASE:
     -			printf(_("Unable to rebase '%s' in submodule path '%s'"),
     -			       oid, ud->displaypath);
    -+			if (!must_die_on_failure)
    -+				break;
    -+			die(_("Unable to rebase '%s' in submodule path '%s'"),
    ++			die_message(_("Unable to rebase '%s' in submodule path '%s'"),
     +			    oid, ud->displaypath);
      			break;
      		case SM_UPDATE_MERGE:
     -			printf(_("Unable to merge '%s' in submodule path '%s'"),
     -			       oid, ud->displaypath);
    -+			if (!must_die_on_failure)
    -+				break;
    -+			die(_("Unable to merge '%s' in submodule path '%s'"),
    ++			die_message(_("Unable to merge '%s' in submodule path '%s'"),
     +			    oid, ud->displaypath);
      			break;
      		case SM_UPDATE_COMMAND:
     -			printf(_("Execution of '%s %s' failed in submodule path '%s'"),
     -			       ud->update_strategy.command, oid, ud->displaypath);
    -+			if (!must_die_on_failure)
    -+				break;
    -+			die(_("Execution of '%s %s' failed in submodule path '%s'"),
    -+			    strategy.command, oid, ud->displaypath);
    ++			die_message(_("Execution of '%s %s' failed in submodule path '%s'"),
    ++			    ud->update_strategy.command, oid, ud->displaypath);
      			break;
      		default:
      			BUG("unexpected update strategy type: %s",
    --			    submodule_strategy_to_string(&ud->update_strategy));
    -+			    submodule_strategy_to_string(&strategy));
    + 			    submodule_strategy_to_string(&ud->update_strategy));
      		}
     -		/*
     -		 * NEEDSWORK: We are currently printing to stdout with error
    @@ builtin/submodule--helper.c: static int run_update_command(struct update_data *u
     -		 * properly. Once we start handling the error messages within
     -		 * C, we should use die() instead.
     -		 */
    --		if (must_die_on_failure)
    + 		if (must_die_on_failure)
     -			return 2;
     -		/*
     -		 * This signifies to the caller in shell that the command
     -		 * failed without dying
     -		 */
    ++			exit(128);
     +
     +		/* the command failed, but update must continue */
      		return 1;
      	}
      
    --	switch (ud->update_strategy.type) {
     +	if (ud->quiet)
     +		return 0;
     +
    -+	switch (strategy.type) {
    + 	switch (ud->update_strategy.type) {
      	case SM_UPDATE_CHECKOUT:
      		printf(_("Submodule path '%s': checked out '%s'\n"),
    - 		       ud->displaypath, oid);
     @@ builtin/submodule--helper.c: static int run_update_command(struct update_data *ud, int subforce)
    - 		break;
    - 	case SM_UPDATE_COMMAND:
    - 		printf(_("Submodule path '%s': '%s %s'\n"),
    --		       ud->displaypath, ud->update_strategy.command, oid);
    -+		       ud->displaypath, strategy.command, oid);
    - 		break;
    - 	default:
    - 		BUG("unexpected update strategy type: %s",
    --		    submodule_strategy_to_string(&ud->update_strategy));
    -+		    submodule_strategy_to_string(&strategy));
    - 	}
    - 
      	return 0;
      }
      
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -static int update_submodule2(struct update_data *update_data);
     -static int run_update_procedure(int argc, const char **argv, const char *prefix)
     -{
    --	char *prefixed_path, *update = NULL;
     -	struct update_data opt = UPDATE_DATA_INIT;
     -
     -	struct option options[] = {
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -		OPT_BOOL(0, "just-cloned", &opt.just_cloned,
     -			 N_("overrides update mode in case the repository is a fresh clone")),
     -		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
    --		OPT_STRING(0, "prefix", &prefix,
    +-		OPT_STRING(0, "prefix", &opt.prefix,
     -			   N_("path"),
     -			   N_("path into the working tree")),
    --		OPT_STRING(0, "update", &update,
    +-		OPT_STRING(0, "update", &opt.update_default,
     -			   N_("string"),
     -			   N_("rebase, merge, checkout or none")),
     -		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
    @@ builtin/submodule--helper.c: static int do_run_update_procedure(struct update_da
     -
     -	opt.sm_path = argv[0];
     -
    --	if (opt.recursive_prefix)
    --		prefixed_path = xstrfmt("%s%s", opt.recursive_prefix, opt.sm_path);
    --	else
    --		prefixed_path = xstrdup(opt.sm_path);
    --
    --	opt.displaypath = get_submodule_displaypath(prefixed_path, prefix);
    --
    --	determine_submodule_update_strategy(the_repository, opt.just_cloned,
    --					    opt.sm_path, update,
    --					    &opt.update_strategy);
    --
    --	free(prefixed_path);
     -	return update_submodule2(&opt);
     -}
     -
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     -static int update_submodule2(struct update_data *update_data)
     +static void update_data_to_args(struct update_data *update_data, struct strvec *args)
     +{
    -+	const char *update = submodule_strategy_to_string(&update_data->update_strategy);
    -+
     +	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
     +	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
    ++	/*
    ++	 * NEEDSWORK: the equivalent code in git-submodule.sh does not
    ++	 * pass --prefix, so this shouldn't either
    ++	*/
     +	if (update_data->prefix)
     +		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
     +	if (update_data->recursive_prefix)
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +		strvec_push(args, "--require-init");
     +	if (update_data->depth)
     +		strvec_pushf(args, "--depth=%d", update_data->depth);
    -+	if (update)
    -+		strvec_pushl(args, "--update", update, NULL);
    ++	if (update_data->update_default)
    ++		strvec_pushl(args, "--update", update_data->update_default, NULL);
     +	if (update_data->references.nr) {
     +		struct string_list_item *item;
     +		for_each_string_list_item(item, &update_data->references)
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +
     +static int update_submodule(struct update_data *update_data)
      {
    -+	char *prefixed_path;
    -+
    - 	ensure_core_worktree(update_data->sm_path);
    + 	char *prefixed_path;
      
    -+	if (update_data->recursive_prefix)
    -+		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
    -+					update_data->sm_path);
    -+	else
    -+		prefixed_path = xstrdup(update_data->sm_path);
    -+
    -+	update_data->displaypath = get_submodule_displaypath(prefixed_path,
    -+							     update_data->prefix);
    -+	free(prefixed_path);
    -+
    - 	/* NEEDSWORK: fix the style issues e.g. braces */
    - 	if (update_data->just_cloned) {
    - 		oidcpy(&update_data->suboid, null_oid());
     @@ builtin/submodule--helper.c: static int update_submodule2(struct update_data *update_data)
      	}
      
    @@ builtin/submodule--helper.c: static int update_submodule2(struct update_data *up
     -		return do_run_update_procedure(update_data);
     +		if (run_update_procedure(update_data))
     +			return 1;
    - 
    --	return 3;
    ++
     +	if (update_data->recursive) {
     +		struct child_process cp = CHILD_PROCESS_INIT;
     +		struct update_data next = *update_data;
    @@ builtin/submodule--helper.c: static int update_submodule2(struct update_data *up
     +		cp.git_cmd = 1;
     +		prepare_submodule_repo_env(&cp.env_array);
     +		update_data_to_args(&next, &cp.args);
    -+
    + 
    +-	return 3;
     +		/* die() if child process die()'d */
     +		res = run_command(&cp);
     +		if (!res)
    @@ builtin/submodule--helper.c: static int update_submodules(struct submodule_updat
     -static int update_clone(int argc, const char **argv, const char *prefix)
     +static int module_update(int argc, const char **argv, const char *prefix)
      {
    - 	const char *update = NULL;
    +-	const char *update = NULL;
      	struct pathspec pathspec;
     -	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
     +	struct update_data opt = UPDATE_DATA_INIT;
    @@ builtin/submodule--helper.c: static int update_submodules(struct submodule_updat
      			   N_("path"),
      			   N_("path into the working tree")),
      		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
    -@@ builtin/submodule--helper.c: static int update_clone(int argc, const char **argv, const char *prefix)
    + 			   N_("path"),
    + 			   N_("path into the working tree, across nested "
    + 			      "submodule boundaries")),
    +-		OPT_STRING(0, "update", &update,
    ++		OPT_STRING(0, "update", &opt.update_default,
      			   N_("string"),
      			   N_("rebase, merge, checkout or none")),
      		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
    @@ builtin/submodule--helper.c: static int update_clone(int argc, const char **argv
     +	oidcpy(&opt.oid, null_oid());
     +	oidcpy(&opt.suboid, null_oid());
      
    - 	if (update)
    +-	if (update)
     -		if (parse_submodule_update_strategy(update, &opt.update) < 0)
    -+		if (parse_submodule_update_strategy(update,
    ++	if (opt.update_default)
    ++		if (parse_submodule_update_strategy(opt.update_default,
     +						    &opt.update_strategy) < 0)
      			die(_("bad value for update parameter"));
      
    @@ builtin/submodule--helper.c: static struct cmd_struct commands[] = {
      	{"init", module_init, SUPPORT_SUPER_PREFIX},
     
      ## git-submodule.sh ##
    +@@ git-submodule.sh: single_branch=
    + jobs=
    + recommend_shallow=
    + 
    ++# NEEDSWORK this is now unused
    + die_if_unmatched ()
    + {
    + 	if test "$1" = "#unmatched"
     @@ git-submodule.sh: cmd_update()
      		shift
      	done
      
     -	{
     -	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update-clone \
    ++	# NEEDSWORK --super-prefix isn't actually supported by this
    ++	# command - we just pass the $prefix to --recursive-prefix.
     +	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
      		${GIT_QUIET:+--quiet} \
     -		${progress:+"--progress"} \
 -:  ---------- > 18:  9d0afc60ec fixup! submodule--helper run-update-procedure: remove --suboid
 -:  ---------- > 19:  e700861239 fixup! submodule--helper run-update-procedure: learn --remote
 -:  ---------- > 20:  aef1a03b44 fixup! submodule: move core cmd_update() logic to C
base-commit: b23dac905bde28da47543484320db16312c87551
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 01/20] submodule--helper: get remote names from any repository

From: Atharva Raykar <raykar.ath@gmail.com>

`get_default_remote()` retrieves the name of a remote by resolving the refs from of the current repository's ref store.

Thus in order to use it for retrieving the remote name of a submodule, we have to start a new subprocess which runs from the submodule directory.

Let's instead introduce a function called `repo_get_default_remote()` which takes any repository object and retrieves the remote accordingly.

`get_default_remote()` is then defined as a call to `repo_get_default_remote()` with 'the_repository' passed to it.

Now that we have `repo_get_default_remote()`, we no longer have to start a subprocess that called `submodule--helper get-default-remote` from within the submodule directory.

So let's make a function called `get_default_remote_submodule()` which takes a submodule path, and returns the default remote for that submodule, all within the same process.

We can now use this function to save an unnecessary subprocess spawn in `sync_submodule()`, and also in the next patch, which will require this functionality.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Helped-by: Glen Choo <chooglen@google.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 39 +++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 15 deletions(-)
Show changes to builtin/submodule--helper.c +24 −15
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c5d3fc3817..4c7c1e1432 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -29,11 +29,14 @@
 typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
 				  void *cb_data);
 
-static char *get_default_remote(void)
+static char *repo_get_default_remote(struct repository *repo)
 {
 	char *dest = NULL, *ret;
 	struct strbuf sb = STRBUF_INIT;
-	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+	struct ref_store *store = get_main_ref_store(repo);
+	int ignore_errno;
+	const char *refname = refs_resolve_ref_unsafe(store, "HEAD", 0, NULL,
+						      NULL, &ignore_errno);
 
 	if (!refname)
 		die(_("No such ref: %s"), "HEAD");
@@ -46,7 +49,7 @@ static char *get_default_remote(void)
 		die(_("Expecting a full ref name, got %s"), refname);
 
 	strbuf_addf(&sb, "branch.%s.remote", refname);
-	if (git_config_get_string(sb.buf, &dest))
+	if (repo_config_get_string(repo, sb.buf, &dest))
 		ret = xstrdup("origin");
 	else
 		ret = dest;
@@ -55,6 +58,19 @@ static char *get_default_remote(void)
 	return ret;
 }
 
+static char *get_default_remote_submodule(const char *module_path)
+{
+	struct repository subrepo;
+
+	repo_submodule_init(&subrepo, the_repository, module_path, null_oid());
+	return repo_get_default_remote(&subrepo);
+}
+
+static char *get_default_remote(void)
+{
+	return repo_get_default_remote(the_repository);
+}
+
 static int print_default_remote(int argc, const char **argv, const char *prefix)
 {
 	char *remote;
@@ -1341,9 +1357,8 @@ static void sync_submodule(const char *path, const char *prefix,
 {
 	const struct submodule *sub;
 	char *remote_key = NULL;
-	char *sub_origin_url, *super_config_url, *displaypath;
+	char *sub_origin_url, *super_config_url, *displaypath, *default_remote;
 	struct strbuf sb = STRBUF_INIT;
-	struct child_process cp = CHILD_PROCESS_INIT;
 	char *sub_config_path = NULL;
 
 	if (!is_submodule_active(the_repository, path))
@@ -1382,21 +1397,15 @@ static void sync_submodule(const char *path, const char *prefix,
 	if (!is_submodule_populated_gently(path, NULL))
 		goto cleanup;
 
-	prepare_submodule_repo_env(&cp.env_array);
-	cp.git_cmd = 1;
-	cp.dir = path;
-	strvec_pushl(&cp.args, "submodule--helper",
-		     "print-default-remote", NULL);
-
 	strbuf_reset(&sb);
-	if (capture_command(&cp, &sb, 0))
+	default_remote = get_default_remote_submodule(path);
+	if (!default_remote)
 		die(_("failed to get the default remote for submodule '%s'"),
 		      path);
 
-	strbuf_strip_suffix(&sb, "\n");
-	remote_key = xstrfmt("remote.%s.url", sb.buf);
+	remote_key = xstrfmt("remote.%s.url", default_remote);
+	free(default_remote);
 
-	strbuf_reset(&sb);
 	submodule_to_gitdir(&sb, path);
 	strbuf_addstr(&sb, "/config");
 
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 02/20] submodule--helper: refactor get_submodule_displaypath()

From: Atharva Raykar <raykar.ath@gmail.com>

We create a function called `do_get_submodule_displaypath()` that generates the display path required by several submodule functions, and takes a custom superprefix parameter, instead of reading it from the environment.

We then redefine the existing `get_submodule_displaypath()` function as a call to this new function, where the superprefix is obtained from the environment.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
Show changes to builtin/submodule--helper.c +8 −4
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 4c7c1e1432..5efceb9d46 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -261,11 +261,8 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr
 	return 0;
 }
 
-/* the result should be freed by the caller. */
-static char *get_submodule_displaypath(const char *path, const char *prefix)
+static char *do_get_submodule_displaypath(const char *path, const char *prefix, const char *super_prefix)
 {
-	const char *super_prefix = get_super_prefix();
-
 	if (prefix && super_prefix) {
 		BUG("cannot have prefix '%s' and superprefix '%s'",
 		    prefix, super_prefix);
@@ -281,6 +278,13 @@ static char *get_submodule_displaypath(const char *path, const char *prefix)
 	}
 }
 
+/* the result should be freed by the caller. */
+static char *get_submodule_displaypath(const char *path, const char *prefix)
+{
+	const char *super_prefix = get_super_prefix();
+	return do_get_submodule_displaypath(path, prefix, super_prefix);
+}
+
 static char *compute_rev_name(const char *sub_path, const char* object_id)
 {
 	struct strbuf sb = STRBUF_INIT;
-- 
2.33.GIT
Ævar Arnfjörð Bjarmason· Feb 12, 2022, 14:24 UTC · re: Glen Choo · lore

Re: [PATCH v7 02/20] submodule--helper: refactor get_submodule_displaypath()

On Thu, Feb 10 2022, Glen Choo wrote:
Show 31 quoted lines
> From: Atharva Raykar <raykar.ath@gmail.com>
>
> We create a function called `do_get_submodule_displaypath()` that
> generates the display path required by several submodule functions, and
> takes a custom superprefix parameter, instead of reading it from the
> environment.
>
> We then redefine the existing `get_submodule_displaypath()` function
> as a call to this new function, where the superprefix is obtained from
> the environment.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  builtin/submodule--helper.c | 12 ++++++++----
>  1 file changed, 8 insertions(+), 4 deletions(-)
>
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index 4c7c1e1432..5efceb9d46 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -261,11 +261,8 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr
>  	return 0;
>  }
>  
> -/* the result should be freed by the caller. */
> -static char *get_submodule_displaypath(const char *path, const char *prefix)
> +static char *do_get_submodule_displaypath(const char *path, const char *prefix, const char *super_prefix)
Nit: overly long line (also in my v5, but since we're applying some
final polishing touches...)
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 03/20] submodule--helper: allow setting superprefix for init_submodule()

From: Atharva Raykar <raykar.ath@gmail.com>

We allow callers of the `init_submodule()` function to optionally override the superprefix from the environment.

We need to enable this option because in our conversion of the update command that will follow, the '--init' option will be handled through this API. We will need to change the superprefix at that time to ensure the display paths show correctly in the output messages.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)
Show changes to builtin/submodule--helper.c +7 −3
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 5efceb9d46..09cda67c1e 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -606,18 +606,22 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
 
 struct init_cb {
 	const char *prefix;
+	const char *superprefix;
 	unsigned int flags;
 };
 #define INIT_CB_INIT { 0 }
 
 static void init_submodule(const char *path, const char *prefix,
-			   unsigned int flags)
+			   const char *superprefix, unsigned int flags)
 {
 	const struct submodule *sub;
 	struct strbuf sb = STRBUF_INIT;
 	char *upd = NULL, *url = NULL, *displaypath;
 
-	displaypath = get_submodule_displaypath(path, prefix);
+	/* try superprefix from the environment, if it is not passed explicitly */
+	if (!superprefix)
+		superprefix = get_super_prefix();
+	displaypath = do_get_submodule_displaypath(path, prefix, superprefix);
 
 	sub = submodule_from_path(the_repository, null_oid(), path);
 
@@ -691,7 +695,7 @@ static void init_submodule(const char *path, const char *prefix,
 static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
 {
 	struct init_cb *info = cb_data;
-	init_submodule(list_item->name, info->prefix, info->flags);
+	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
 }
 
 static int module_init(int argc, const char **argv, const char *prefix)
-- 
2.33.GIT
Ævar Arnfjörð Bjarmason· Feb 12, 2022, 14:30 UTC · re: Glen Choo · lore

Re: [PATCH v7 03/20] submodule--helper: allow setting superprefix for init_submodule()

On Thu, Feb 10 2022, Glen Choo wrote:
Show 57 quoted lines
> From: Atharva Raykar <raykar.ath@gmail.com>
>
> We allow callers of the `init_submodule()` function to optionally
> override the superprefix from the environment.
>
> We need to enable this option because in our conversion of the update
> command that will follow, the '--init' option will be handled through
> this API. We will need to change the superprefix at that time to ensure
> the display paths show correctly in the output messages.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  builtin/submodule--helper.c | 10 +++++++---
>  1 file changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index 5efceb9d46..09cda67c1e 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -606,18 +606,22 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
>  
>  struct init_cb {
>  	const char *prefix;
> +	const char *superprefix;
>  	unsigned int flags;
>  };
>  #define INIT_CB_INIT { 0 }
>  
>  static void init_submodule(const char *path, const char *prefix,
> -			   unsigned int flags)
> +			   const char *superprefix, unsigned int flags)
>  {
>  	const struct submodule *sub;
>  	struct strbuf sb = STRBUF_INIT;
>  	char *upd = NULL, *url = NULL, *displaypath;
>  
> -	displaypath = get_submodule_displaypath(path, prefix);
> +	/* try superprefix from the environment, if it is not passed explicitly */
> +	if (!superprefix)
> +		superprefix = get_super_prefix();
> +	displaypath = do_get_submodule_displaypath(path, prefix, superprefix);
>  
>  	sub = submodule_from_path(the_repository, null_oid(), path);
>  
> @@ -691,7 +695,7 @@ static void init_submodule(const char *path, const char *prefix,
>  static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
>  {
>  	struct init_cb *info = cb_data;
> -	init_submodule(list_item->name, info->prefix, info->flags);
> +	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
>  }
>  
>  static int module_init(int argc, const char **argv, const char *prefix)

Note/nit on existing (pre this series) code, I wonder why we ended up with this init_submodule() v.s. init_submodule_cb() indirection, v.s. just doing:

Show changes to builtin/submodule--helper.c +7 −8
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 09cda67c1ea..aa82abeb37a 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -611,9 +611,14 @@ struct init_cb {
 };
 #define INIT_CB_INIT { 0 }
 
-static void init_submodule(const char *path, const char *prefix,
-			   const char *superprefix, unsigned int flags)
+static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
 {
+	const char *path = list_item->name;
+	struct init_cb *info = cb_data;
+	const char *prefix = info->prefix;
+	const char *superprefix = info->superprefix ? info->superprefix :
+		get_super_prefix();
+	unsigned int flags = info->flags;
 	const struct submodule *sub;
 	struct strbuf sb = STRBUF_INIT;
 	char *upd = NULL, *url = NULL, *displaypath;
@@ -692,12 +697,6 @@ static void init_submodule(const char *path, const char *prefix,
 	free(upd);
 }
 
-static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data)
-{
-	struct init_cb *info = cb_data;
-	init_submodule(list_item->name, info->prefix, info->superprefix, info->flags);
-}
-
 static int module_init(int argc, const char **argv, const char *prefix)
 {
 	struct init_cb info = INIT_CB_INIT;

Maybe it's worth it to declare the variables in the argument list
v.s. the function.
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 04/20] submodule--helper: run update using child process struct

From: Atharva Raykar <raykar.ath@gmail.com>

We switch to using the run-command API function that takes a 'struct child process', since we are using a lot of the options. This will also make it simple to switch over to using 'capture_command()' when we start handling the output of the command completely in C.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 34 ++++++++++++++++------------------
 1 file changed, 16 insertions(+), 18 deletions(-)
Show changes to builtin/submodule--helper.c +16 −18
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 09cda67c1e..db71e6f4ec 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2344,47 +2344,45 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
 
 static int run_update_command(struct update_data *ud, int subforce)
 {
-	struct strvec args = STRVEC_INIT;
-	struct strvec child_env = STRVEC_INIT;
+	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
 	int must_die_on_failure = 0;
-	int git_cmd;
 
 	switch (ud->update_strategy.type) {
 	case SM_UPDATE_CHECKOUT:
-		git_cmd = 1;
-		strvec_pushl(&args, "checkout", "-q", NULL);
+		cp.git_cmd = 1;
+		strvec_pushl(&cp.args, "checkout", "-q", NULL);
 		if (subforce)
-			strvec_push(&args, "-f");
+			strvec_push(&cp.args, "-f");
 		break;
 	case SM_UPDATE_REBASE:
-		git_cmd = 1;
-		strvec_push(&args, "rebase");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "rebase");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_MERGE:
-		git_cmd = 1;
-		strvec_push(&args, "merge");
+		cp.git_cmd = 1;
+		strvec_push(&cp.args, "merge");
 		if (ud->quiet)
-			strvec_push(&args, "--quiet");
+			strvec_push(&cp.args, "--quiet");
 		must_die_on_failure = 1;
 		break;
 	case SM_UPDATE_COMMAND:
-		git_cmd = 0;
-		strvec_push(&args, ud->update_strategy.command);
+		cp.use_shell = 1;
+		strvec_push(&cp.args, ud->update_strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
 		    submodule_strategy_to_string(&ud->update_strategy));
 	}
-	strvec_push(&args, oid);
+	strvec_push(&cp.args, oid);
 
-	prepare_submodule_repo_env(&child_env);
-	if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
-				     ud->sm_path, child_env.v)) {
+	cp.dir = xstrdup(ud->sm_path);
+	prepare_submodule_repo_env(&cp.env_array);
+	if (run_command(&cp)) {
 		switch (ud->update_strategy.type) {
 		case SM_UPDATE_CHECKOUT:
 			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-- 
2.33.GIT
Ævar Arnfjörð Bjarmason· Feb 12, 2022, 14:33 UTC · re: Glen Choo · lore

Re: [PATCH v7 04/20] submodule--helper: run update using child process struct

On Thu, Feb 10 2022, Glen Choo wrote:
Show 84 quoted lines
> From: Atharva Raykar <raykar.ath@gmail.com>
>
> We switch to using the run-command API function that takes a
> 'struct child process', since we are using a lot of the options. This
> will also make it simple to switch over to using 'capture_command()'
> when we start handling the output of the command completely in C.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Shourya Shukla <periperidip@gmail.com>
> Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  builtin/submodule--helper.c | 34 ++++++++++++++++------------------
>  1 file changed, 16 insertions(+), 18 deletions(-)
>
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index 09cda67c1e..db71e6f4ec 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -2344,47 +2344,45 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str
>  
>  static int run_update_command(struct update_data *ud, int subforce)
>  {
> -	struct strvec args = STRVEC_INIT;
> -	struct strvec child_env = STRVEC_INIT;
> +	struct child_process cp = CHILD_PROCESS_INIT;
>  	char *oid = oid_to_hex(&ud->oid);
>  	int must_die_on_failure = 0;
> -	int git_cmd;
>  
>  	switch (ud->update_strategy.type) {
>  	case SM_UPDATE_CHECKOUT:
> -		git_cmd = 1;
> -		strvec_pushl(&args, "checkout", "-q", NULL);
> +		cp.git_cmd = 1;
> +		strvec_pushl(&cp.args, "checkout", "-q", NULL);
>  		if (subforce)
> -			strvec_push(&args, "-f");
> +			strvec_push(&cp.args, "-f");
>  		break;
>  	case SM_UPDATE_REBASE:
> -		git_cmd = 1;
> -		strvec_push(&args, "rebase");
> +		cp.git_cmd = 1;
> +		strvec_push(&cp.args, "rebase");
>  		if (ud->quiet)
> -			strvec_push(&args, "--quiet");
> +			strvec_push(&cp.args, "--quiet");
>  		must_die_on_failure = 1;
>  		break;
>  	case SM_UPDATE_MERGE:
> -		git_cmd = 1;
> -		strvec_push(&args, "merge");
> +		cp.git_cmd = 1;
> +		strvec_push(&cp.args, "merge");
>  		if (ud->quiet)
> -			strvec_push(&args, "--quiet");
> +			strvec_push(&cp.args, "--quiet");
>  		must_die_on_failure = 1;
>  		break;
>  	case SM_UPDATE_COMMAND:
> -		git_cmd = 0;
> -		strvec_push(&args, ud->update_strategy.command);
> +		cp.use_shell = 1;
> +		strvec_push(&cp.args, ud->update_strategy.command);
>  		must_die_on_failure = 1;
>  		break;
>  	default:
>  		BUG("unexpected update strategy type: %s",
>  		    submodule_strategy_to_string(&ud->update_strategy));
>  	}
> -	strvec_push(&args, oid);
> +	strvec_push(&cp.args, oid);
>  
> -	prepare_submodule_repo_env(&child_env);
> -	if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL,
> -				     ud->sm_path, child_env.v)) {
> +	cp.dir = xstrdup(ud->sm_path);
> +	prepare_submodule_repo_env(&cp.env_array);
> +	if (run_command(&cp)) {
>  		switch (ud->update_strategy.type) {
>  		case SM_UPDATE_CHECKOUT:
>  			printf(_("Unable to checkout '%s' in submodule path '%s'"),

If this series is re-arranged so that this comes first, this compiles just fine & passes all tests.

So I wonder if we can peel this and perhaps other such easy to review prep changes off into its own series, since this one has grown to 20 patches.

We could then hopefully fast-track those easy-to-review prep changes, which would make the "real" series to follow smaller and easier to review/grok.

Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 05/20] builtin/submodule--helper.c: reformat designated initializers

From: Atharva Raykar <raykar.ath@gmail.com>

The second hunk here will make a subsequent commit's diff smaller, and let's do the first and third hunks while we're at it so that we consistently format all of these.

Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)
Show changes to builtin/submodule--helper.c +10 −3
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index db71e6f4ec..9f79bdf4d5 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1654,7 +1654,10 @@ struct module_clone_data {
 	unsigned int require_init: 1;
 	int single_branch;
 };
-#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 }
+#define MODULE_CLONE_DATA_INIT { \
+	.reference = STRING_LIST_INIT_NODUP, \
+	.single_branch = -1, \
+}
 
 struct submodule_alternate_setup {
 	const char *submodule_name;
@@ -2047,7 +2050,9 @@ struct update_data {
 	unsigned int nofetch: 1;
 	unsigned int just_cloned: 1;
 };
-#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT }
+#define UPDATE_DATA_INIT { \
+	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+}
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
 		struct strbuf *out, const char *displaypath)
@@ -3013,7 +3018,9 @@ struct add_data {
 	unsigned int progress: 1;
 	unsigned int dissociate: 1;
 };
-#define ADD_DATA_INIT { .depth = -1 }
+#define ADD_DATA_INIT { \
+	.depth = -1, \
+}
 
 static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path)
 {
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 06/20] builtin/submodule--helper.c: rename option variables to "opt"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Rename the "suc" variable in update_clone() to "opt", and do the same for the "update_data" variable in run_update_procedure().

The only reason for this change is to make the subsequent commit's diff smaller, by doing this rename we can "anchor" the diff better, as it "borrow" most of the options declared here as-is as far as the diff rename detection is concerned.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 74 ++++++++++++++++++-------------------
 1 file changed, 37 insertions(+), 37 deletions(-)
Show changes to builtin/submodule--helper.c +37 −37
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 9f79bdf4d5..c2d4fd0347 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2517,36 +2517,36 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 {
 	const char *update = NULL;
 	struct pathspec pathspec;
-	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
 
 	struct option module_update_clone_options[] = {
 		OPT_STRING(0, "prefix", &prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
 			   N_("path"),
 			   N_("path into the working tree, across nested "
 			      "submodule boundaries")),
 		OPT_STRING(0, "update", &update,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
+		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
 			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &suc.dissociate,
+		OPT_BOOL(0, "dissociate", &opt.dissociate,
 			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &suc.depth, "<depth>",
+		OPT_STRING(0, "depth", &opt.depth, "<depth>",
 			   N_("create a shallow clone truncated to the "
 			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &suc.max_jobs,
+		OPT_INTEGER('j', "jobs", &opt.max_jobs,
 			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
+		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
 			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &suc.progress,
+		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &opt.progress,
 			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &suc.require_init,
+		OPT_BOOL(0, "require-init", &opt.require_init,
 			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &suc.single_branch,
+		OPT_BOOL(0, "single-branch", &opt.single_branch,
 			 N_("clone only one branch, HEAD or --branch")),
 		OPT_END()
 	};
@@ -2555,32 +2555,32 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
 		NULL
 	};
-	suc.prefix = prefix;
+	opt.prefix = prefix;
 
-	update_clone_config_from_gitmodules(&suc.max_jobs);
-	git_config(git_update_clone_config, &suc.max_jobs);
+	update_clone_config_from_gitmodules(&opt.max_jobs);
+	git_config(git_update_clone_config, &opt.max_jobs);
 
 	argc = parse_options(argc, argv, prefix, module_update_clone_options,
 			     git_submodule_helper_usage, 0);
 
 	if (update)
-		if (parse_submodule_update_strategy(update, &suc.update) < 0)
+		if (parse_submodule_update_strategy(update, &opt.update) < 0)
 			die(_("bad value for update parameter"));
 
-	if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
+	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
 		return 1;
 
 	if (pathspec.nr)
-		suc.warn_if_uninitialized = 1;
+		opt.warn_if_uninitialized = 1;
 
-	return update_submodules(&suc);
+	return update_submodules(&opt);
 }
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
 	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
 	char *prefixed_path, *update = NULL;
-	struct update_data update_data = UPDATE_DATA_INIT;
+	struct update_data opt = UPDATE_DATA_INIT;
 
 	struct option options[] = {
 		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
@@ -2589,20 +2589,20 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 			 N_("don't fetch new objects from the remote site")),
 		OPT_BOOL(0, "just-cloned", &just_cloned,
 			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")),
+		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
 		OPT_STRING(0, "prefix", &prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
 		OPT_STRING(0, "update", &update,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"),
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
 			   N_("path into the working tree, across nested "
 			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"),
+		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
 			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"),
+		OPT_CALLBACK_F(0, "suboid", &opt.suboid, N_("subsha1"),
 			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
 		OPT_END()
@@ -2618,27 +2618,27 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	if (argc != 1)
 		usage_with_options(usage, options);
 
-	update_data.force = !!force;
-	update_data.quiet = !!quiet;
-	update_data.nofetch = !!nofetch;
-	update_data.just_cloned = !!just_cloned;
-	update_data.sm_path = argv[0];
+	opt.force = !!force;
+	opt.quiet = !!quiet;
+	opt.nofetch = !!nofetch;
+	opt.just_cloned = !!just_cloned;
+	opt.sm_path = argv[0];
 
-	if (update_data.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path);
+	if (opt.recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", opt.recursive_prefix, opt.sm_path);
 	else
-		prefixed_path = xstrdup(update_data.sm_path);
+		prefixed_path = xstrdup(opt.sm_path);
 
-	update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix);
+	opt.displaypath = get_submodule_displaypath(prefixed_path, prefix);
 
-	determine_submodule_update_strategy(the_repository, update_data.just_cloned,
-					    update_data.sm_path, update,
-					    &update_data.update_strategy);
+	determine_submodule_update_strategy(the_repository, opt.just_cloned,
+					    opt.sm_path, update,
+					    &opt.update_strategy);
 
 	free(prefixed_path);
 
-	if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force)
-		return do_run_update_procedure(&update_data);
+	if (!oideq(&opt.oid, &opt.suboid) || opt.force)
+		return do_run_update_procedure(&opt);
 
 	return 3;
 }
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 07/20] submodule--helper: don't use bitfield indirection for parse_options()

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Do away with the indirection of local variables added in c51f8f94e5b (submodule--helper: run update procedures from C, 2021-08-24).

These were only needed because in C you can't get a pointer to a single bit, so we were using intermediate variables instead.

This will also make a subsequent large commit's diff smaller.
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/submodule--helper.c | 23 ++++++++++-------------
 1 file changed, 10 insertions(+), 13 deletions(-)
Show changes to builtin/submodule--helper.c +10 −13
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c2d4fd0347..4a0890954e 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2045,10 +2045,10 @@ struct update_data {
 	struct object_id suboid;
 	struct submodule_update_strategy update_strategy;
 	int depth;
-	unsigned int force: 1;
-	unsigned int quiet: 1;
-	unsigned int nofetch: 1;
-	unsigned int just_cloned: 1;
+	unsigned int force;
+	unsigned int quiet;
+	unsigned int nofetch;
+	unsigned int just_cloned;
 };
 #define UPDATE_DATA_INIT { \
 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
@@ -2578,16 +2578,17 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
-	int force = 0, quiet = 0, nofetch = 0, just_cloned = 0;
 	char *prefixed_path, *update = NULL;
 	struct update_data opt = UPDATE_DATA_INIT;
 
 	struct option options[] = {
-		OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&force, N_("force checkout updates"), 0),
-		OPT_BOOL('N', "no-fetch", &nofetch,
+		OPT__QUIET(&opt.quiet,
+			   N_("suppress output for update by rebase or merge")),
+		OPT__FORCE(&opt.force, N_("force checkout updates"),
+			   0),
+		OPT_BOOL('N', "no-fetch", &opt.nofetch,
 			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &just_cloned,
+		OPT_BOOL(0, "just-cloned", &opt.just_cloned,
 			 N_("overrides update mode in case the repository is a fresh clone")),
 		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
 		OPT_STRING(0, "prefix", &prefix,
@@ -2618,10 +2619,6 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 	if (argc != 1)
 		usage_with_options(usage, options);
 
-	opt.force = !!force;
-	opt.quiet = !!quiet;
-	opt.nofetch = !!nofetch;
-	opt.just_cloned = !!just_cloned;
 	opt.sm_path = argv[0];
 
 	if (opt.recursive_prefix)
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 08/20] submodule tests: test for init and update failure output

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Amend some submodule tests to test for the failure output of "git submodule [update|init]". The lack of such tests hid a regression in an earlier version of a subsequent commit.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t7406-submodule-update.sh    | 14 ++++++++++++--
 t/t7408-submodule-reference.sh | 14 +++++++++++++-
 2 files changed, 25 insertions(+), 3 deletions(-)
Show changes to 2 files +25 −3

t/t7406-submodule-update.sh, t/t7408-submodule-reference.sh

diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 11cccbb333..7764c1c3cb 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -205,8 +205,18 @@ test_expect_success 'submodule update should fail due to local changes' '
 	 (cd submodule &&
 	  compare_head
 	 ) &&
-	 test_must_fail git submodule update submodule
-	)
+	 test_must_fail git submodule update submodule 2>../actual.raw
+	) &&
+	sed "s/^> //" >expect <<-\EOF &&
+	> error: Your local changes to the following files would be overwritten by checkout:
+	> 	file
+	> Please commit your changes or stash them before you switch branches.
+	> Aborting
+	> fatal: Unable to checkout OID in submodule path '\''submodule'\''
+	EOF
+	sed -e "s/checkout $SQ[^$SQ]*$SQ/checkout OID/" <actual.raw >actual &&
+	test_cmp expect actual
+
 '
 test_expect_success 'submodule update should throw away changes with --force ' '
 	(cd super &&
diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh
index a3892f494b..c3a4545510 100755
--- a/t/t7408-submodule-reference.sh
+++ b/t/t7408-submodule-reference.sh
@@ -193,7 +193,19 @@ test_expect_success 'missing nested submodule alternate fails clone and submodul
 		cd supersuper-clone &&
 		check_that_two_of_three_alternates_are_used &&
 		# update of the submodule fails
-		test_must_fail git submodule update --init --recursive
+		cat >expect <<-\EOF &&
+		fatal: submodule '\''sub'\'' cannot add alternate: path ... does not exist
+		Failed to clone '\''sub'\''. Retry scheduled
+		fatal: submodule '\''sub-dissociate'\'' cannot add alternate: path ... does not exist
+		Failed to clone '\''sub-dissociate'\''. Retry scheduled
+		fatal: submodule '\''sub'\'' cannot add alternate: path ... does not exist
+		Failed to clone '\''sub'\'' a second time, aborting
+		fatal: Failed to recurse into submodule path ...
+		EOF
+		test_must_fail git submodule update --init --recursive 2>err &&
+		grep -e fatal: -e ^Failed err >actual.raw &&
+		sed -e "s/path $SQ[^$SQ]*$SQ/path .../" <actual.raw >actual &&
+		test_cmp expect actual
 	)
 '
 
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 09/20] submodule--helper: remove update-module-mode

This is dead code - it has not been used since c51f8f94e5 (submodule--helper: run update procedures from C, 2021-08-24).

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 24 ------------------------
 1 file changed, 24 deletions(-)
Show changes to builtin/submodule--helper.c +0 −24
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 4a0890954e..e0cc1c1b79 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1965,29 +1965,6 @@ static void determine_submodule_update_strategy(struct repository *r,
 	free(key);
 }
 
-static int module_update_module_mode(int argc, const char **argv, const char *prefix)
-{
-	const char *path, *update = NULL;
-	int just_cloned;
-	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
-
-	if (argc < 3 || argc > 4)
-		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
-
-	just_cloned = git_config_int("just_cloned", argv[1]);
-	path = argv[2];
-
-	if (argc == 4)
-		update = argv[3];
-
-	determine_submodule_update_strategy(the_repository,
-					    just_cloned, path, update,
-					    &update_strategy);
-	fputs(submodule_strategy_to_string(&update_strategy), stdout);
-
-	return 0;
-}
-
 struct update_clone_data {
 	const struct submodule *sub;
 	struct object_id oid;
@@ -3388,7 +3365,6 @@ static struct cmd_struct commands[] = {
 	{"name", module_name, 0},
 	{"clone", module_clone, 0},
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
-	{"update-module-mode", module_update_module_mode, 0},
 	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
 	{"ensure-core-worktree", ensure_core_worktree, 0},
-- 
2.33.GIT
Ævar Arnfjörð Bjarmason· Feb 12, 2022, 14:35 UTC · re: Glen Choo · lore

Re: [PATCH v7 09/20] submodule--helper: remove update-module-mode

On Thu, Feb 10 2022, Glen Choo wrote:
Show 50 quoted lines
> This is dead code - it has not been used since c51f8f94e5
> (submodule--helper: run update procedures from C, 2021-08-24).
>
> Signed-off-by: Glen Choo <chooglen@google.com>
> ---
>  builtin/submodule--helper.c | 24 ------------------------
>  1 file changed, 24 deletions(-)
>
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index 4a0890954e..e0cc1c1b79 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -1965,29 +1965,6 @@ static void determine_submodule_update_strategy(struct repository *r,
>  	free(key);
>  }
>  
> -static int module_update_module_mode(int argc, const char **argv, const char *prefix)
> -{
> -	const char *path, *update = NULL;
> -	int just_cloned;
> -	struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT };
> -
> -	if (argc < 3 || argc > 4)
> -		die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]");
> -
> -	just_cloned = git_config_int("just_cloned", argv[1]);
> -	path = argv[2];
> -
> -	if (argc == 4)
> -		update = argv[3];
> -
> -	determine_submodule_update_strategy(the_repository,
> -					    just_cloned, path, update,
> -					    &update_strategy);
> -	fputs(submodule_strategy_to_string(&update_strategy), stdout);
> -
> -	return 0;
> -}
> -
>  struct update_clone_data {
>  	const struct submodule *sub;
>  	struct object_id oid;
> @@ -3388,7 +3365,6 @@ static struct cmd_struct commands[] = {
>  	{"name", module_name, 0},
>  	{"clone", module_clone, 0},
>  	{"add", module_add, SUPPORT_SUPER_PREFIX},
> -	{"update-module-mode", module_update_module_mode, 0},
>  	{"update-clone", update_clone, 0},
>  	{"run-update-procedure", run_update_procedure, 0},
>  	{"ensure-core-worktree", ensure_core_worktree, 0},
Nice catch!

Re my comment on 04/20 in <220212.86y22gxig0.gmgdl@evledraar.gmail.com> at least 04..09/20 could be split into such a "trivial refactors for later changes" series, and it would make sense to lead with this (and any other deletions of already-dead code).

Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 11/20] submodule--helper run-update-procedure: remove --suboid

Teach run-update-procedure to determine the oid of the submodule's HEAD instead of doing it in git-subomdule.sh.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 12 +++++++++---
 git-submodule.sh            |  8 +-------
 2 files changed, 10 insertions(+), 10 deletions(-)
Show changes to 2 files +10 −10

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 0b5120734a..a26477ce04 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2585,9 +2585,6 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
 			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
-		OPT_CALLBACK_F(0, "suboid", &opt.suboid, N_("subsha1"),
-			       N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
 		OPT_END()
 	};
 
@@ -2982,6 +2979,15 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 /* NEEDSWORK: this is a temporary name until we delete update_submodule() */
 static int update_submodule2(struct update_data *update_data)
 {
+	/* NEEDSWORK: fix the style issues e.g. braces */
+	if (update_data->just_cloned) {
+		oidcpy(&update_data->suboid, null_oid());
+	} else {
+		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+			die(_("Unable to find current revision in submodule path '%s'"),
+			    update_data->displaypath);
+	}
+
 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
 		return do_run_update_procedure(update_data);
 
diff --git a/git-submodule.sh b/git-submodule.sh
index 652861aa66..d48c314f01 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -391,14 +391,9 @@ cmd_update()
 
 		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
 
-		if test $just_cloned -eq 1
+		if test $just_cloned -eq 0
 		then
-			subsha1=
-		else
 			just_cloned=
-			subsha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify HEAD) ||
-			die "fatal: $(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")"
 		fi
 
 		if test -n "$remote"
@@ -426,7 +421,6 @@ cmd_update()
 			  ${update:+--update "$update"} \
 			  ${prefix:+--recursive-prefix "$prefix"} \
 			  ${sha1:+--oid "$sha1"} \
-			  ${subsha1:+--suboid "$subsha1"} \
 			  "--" \
 			  "$sm_path")
 
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 10/20] submodule--helper: reorganize code for sh to C conversion

Introduce a function, update_submodule2(), that performs an update for one submodule. This function will implement the functionality of run-update-procedure and its surrounding shell code in submodule.sh.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)
Show changes to builtin/submodule--helper.c +15 −5
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index e0cc1c1b79..0b5120734a 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2553,6 +2553,11 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 	return update_submodules(&opt);
 }
 
+/*
+ * NEEDSWORK: Use a forward declaration to avoid moving
+ * run_update_procedure() (which will be removed soon).
+ */
+static int update_submodule2(struct update_data *update_data);
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
 	char *prefixed_path, *update = NULL;
@@ -2610,11 +2615,7 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 					    &opt.update_strategy);
 
 	free(prefixed_path);
-
-	if (!oideq(&opt.oid, &opt.suboid) || opt.force)
-		return do_run_update_procedure(&opt);
-
-	return 3;
+	return update_submodule2(&opt);
 }
 
 static int resolve_relative_path(int argc, const char **argv, const char *prefix)
@@ -2978,6 +2979,15 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 	return !!ret;
 }
 
+/* NEEDSWORK: this is a temporary name until we delete update_submodule() */
+static int update_submodule2(struct update_data *update_data)
+{
+	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
+		return do_run_update_procedure(update_data);
+
+	return 3;
+}
+
 struct add_data {
 	const char *prefix;
 	const char *branch;
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 12/20] submodule--helper run-update-procedure: learn --remote

Teach run-update-procedure to handle --remote instead of parsing --remote in git-submodule.sh. As a result, "git submodule--helper print-default-remote" has no more callers, so remove it.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 39 ++++++++++++++++++++++---------------
 git-submodule.sh            | 30 +---------------------------
 2 files changed, 24 insertions(+), 45 deletions(-)
Show changes to 2 files +24 −45

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index a26477ce04..15ae986692 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -71,21 +71,6 @@ static char *get_default_remote(void)
 	return repo_get_default_remote(the_repository);
 }
 
-static int print_default_remote(int argc, const char **argv, const char *prefix)
-{
-	char *remote;
-
-	if (argc != 1)
-		die(_("submodule--helper print-default-remote takes no arguments"));
-
-	remote = get_default_remote();
-	if (remote)
-		printf("%s\n", remote);
-
-	free(remote);
-	return 0;
-}
-
 static int starts_with_dot_slash(const char *str)
 {
 	return str[0] == '.' && is_dir_sep(str[1]);
@@ -2026,6 +2011,7 @@ struct update_data {
 	unsigned int quiet;
 	unsigned int nofetch;
 	unsigned int just_cloned;
+	unsigned int remote;
 };
 #define UPDATE_DATA_INIT { \
 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
@@ -2585,6 +2571,8 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
 			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
 			       parse_opt_object_id),
+		OPT_BOOL(0, "remote", &opt.remote,
+			 N_("use SHA-1 of submodule's remote tracking branch")),
 		OPT_END()
 	};
 
@@ -2988,6 +2976,25 @@ static int update_submodule2(struct update_data *update_data)
 			    update_data->displaypath);
 	}
 
+	if (update_data->remote) {
+		char *remote_name = get_default_remote_submodule(update_data->sm_path);
+		const char *branch = remote_submodule_branch(update_data->sm_path);
+		char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch);
+
+		if (!update_data->nofetch) {
+			if (fetch_in_submodule(update_data->sm_path, update_data->depth,
+					      0, NULL))
+				die(_("Unable to fetch in submodule path '%s'"),
+				    update_data->sm_path);
+		}
+
+		if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
+			die(_("Unable to find %s revision in submodule path '%s'"),
+			    remote_ref, update_data->sm_path);
+
+		free(remote_ref);
+	}
+
 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
 		return do_run_update_procedure(update_data);
 
@@ -3389,10 +3396,10 @@ static struct cmd_struct commands[] = {
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
 	{"init", module_init, SUPPORT_SUPER_PREFIX},
 	{"status", module_status, SUPPORT_SUPER_PREFIX},
-	{"print-default-remote", print_default_remote, 0},
 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
 	{"deinit", module_deinit, 0},
 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
+	/* NEEDSWORK: remote-branch is also obsolete */
 	{"remote-branch", resolve_remote_submodule_branch, 0},
 	{"push-check", push_check, 0},
 	{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index d48c314f01..29fd69250d 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -246,20 +246,6 @@ cmd_deinit()
 	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@"
 }
 
-# usage: fetch_in_submodule <module_path> [<depth>] [<sha1>]
-# Because arguments are positional, use an empty string to omit <depth>
-# but include <sha1>.
-fetch_in_submodule () (
-	sanitize_submodule_env &&
-	cd "$1" &&
-	if test $# -eq 3
-	then
-		echo "$3" | git fetch ${GIT_QUIET:+--quiet} --stdin ${2:+"$2"}
-	else
-		git fetch ${GIT_QUIET:+--quiet} ${2:+"$2"}
-	fi
-)
-
 #
 # Update each submodule path to correct revision, using clone and checkout as needed
 #
@@ -396,21 +382,6 @@ cmd_update()
 			just_cloned=
 		fi
 
-		if test -n "$remote"
-		then
-			branch=$(git submodule--helper remote-branch "$sm_path")
-			if test -z "$nofetch"
-			then
-				# Fetch remote before determining tracking $sha1
-				fetch_in_submodule "$sm_path" $depth ||
-				die "fatal: $(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
-			fi
-			remote_name=$(sanitize_submodule_env; cd "$sm_path" && git submodule--helper print-default-remote)
-			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
-				git rev-parse --verify "${remote_name}/${branch}") ||
-			die "fatal: $(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
-		fi
-
 		out=$(git submodule--helper run-update-procedure \
 			  ${wt_prefix:+--prefix "$wt_prefix"} \
 			  ${GIT_QUIET:+--quiet} \
@@ -421,6 +392,7 @@ cmd_update()
 			  ${update:+--update "$update"} \
 			  ${prefix:+--recursive-prefix "$prefix"} \
 			  ${sha1:+--oid "$sha1"} \
+			  ${remote:+--remote} \
 			  "--" \
 			  "$sm_path")
 
-- 
2.33.GIT
Ævar Arnfjörð Bjarmason· Feb 12, 2022, 14:38 UTC · re: Glen Choo · lore

Re: [PATCH v7 12/20] submodule--helper run-update-procedure: learn --remote

On Thu, Feb 10 2022, Glen Choo wrote:
Show 8 quoted lines
>  #define UPDATE_DATA_INIT { \
>  	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
> @@ -2585,6 +2571,8 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
>  		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
>  			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
>  			       parse_opt_object_id),
> +		OPT_BOOL(0, "remote", &opt.remote,
> +			 N_("use SHA-1 of submodule's remote tracking branch")),

For some things the references to SHA-1 are faithfully copying existing test, but for new things let's use "object" instead.

Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 13/20] submodule--helper: remove ensure-core-worktree

Move the logic of "git submodule--helper ensure-core-worktree" into run-update-procedure. Since the ensure-core-worktree command is obsolete, remove it.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 13 +++----------
 git-submodule.sh            |  2 --
 2 files changed, 3 insertions(+), 12 deletions(-)
Show changes to 2 files +3 −12

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 15ae986692..a05aea5cd6 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2746,17 +2746,11 @@ static int push_check(int argc, const char **argv, const char *prefix)
 	return 0;
 }
 
-static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
+static void ensure_core_worktree(const char *path)
 {
-	const char *path;
 	const char *cw;
 	struct repository subrepo;
 
-	if (argc != 2)
-		BUG("submodule--helper ensure-core-worktree <path>");
-
-	path = argv[1];
-
 	if (repo_submodule_init(&subrepo, the_repository, path, null_oid()))
 		die(_("could not get a repository handle for submodule '%s'"), path);
 
@@ -2776,8 +2770,6 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
 		free(abs_path);
 		strbuf_release(&sb);
 	}
-
-	return 0;
 }
 
 static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
@@ -2967,6 +2959,8 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 /* NEEDSWORK: this is a temporary name until we delete update_submodule() */
 static int update_submodule2(struct update_data *update_data)
 {
+	ensure_core_worktree(update_data->sm_path);
+
 	/* NEEDSWORK: fix the style issues e.g. braces */
 	if (update_data->just_cloned) {
 		oidcpy(&update_data->suboid, null_oid());
@@ -3390,7 +3384,6 @@ static struct cmd_struct commands[] = {
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
 	{"update-clone", update_clone, 0},
 	{"run-update-procedure", run_update_procedure, 0},
-	{"ensure-core-worktree", ensure_core_worktree, 0},
 	{"relative-path", resolve_relative_path, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index 29fd69250d..aa9c898e1c 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -373,8 +373,6 @@ cmd_update()
 	do
 		die_if_unmatched "$quickabort" "$sha1"
 
-		git submodule--helper ensure-core-worktree "$sm_path" || exit 1
-
 		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
 
 		if test $just_cloned -eq 0
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 14/20] submodule--helper update-clone: learn --init

Teach "git submodule--helper update-clone" the --init flag and remove the corresponding shell code.

When the `--init` flag is passed to the subcommand, we do not spawn a new subprocess and call `submodule--helper init` on the submodule paths, because the Git machinery is not able to pick up the configuration changes introduced by that init call. So we instead run the `init_submodule_cb()` callback over each submodule in the same process.

[1] https://lore.kernel.org/git/CAP8UFD0NCQ5w_3GtT_xHr35i7h8BuLX4UcHNY6VHPGREmDVObA@mail.gmail.com/
Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 26 ++++++++++++++++++++++++++
 git-submodule.sh            |  9 +++------
 2 files changed, 29 insertions(+), 6 deletions(-)
Show changes to 2 files +29 −6

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index a05aea5cd6..5635f0c48b 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1989,6 +1989,7 @@ struct submodule_update_clone {
 	int failed_clones_nr, failed_clones_alloc;
 
 	int max_jobs;
+	unsigned int init;
 };
 #define SUBMODULE_UPDATE_CLONE_INIT { \
 	.list = MODULE_LIST_INIT, \
@@ -2483,6 +2484,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
 
 	struct option module_update_clone_options[] = {
+		OPT_BOOL(0, "init", &opt.init,
+			 N_("initialize uninitialized submodules before update")),
 		OPT_STRING(0, "prefix", &prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
@@ -2536,6 +2539,29 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 	if (pathspec.nr)
 		opt.warn_if_uninitialized = 1;
 
+	if (opt.init) {
+		struct module_list list = MODULE_LIST_INIT;
+		struct init_cb info = INIT_CB_INIT;
+
+		if (module_list_compute(argc, argv, opt.prefix,
+					&pathspec, &list) < 0)
+			return 1;
+
+		/*
+		 * If there are no path args and submodule.active is set then,
+		 * by default, only initialize 'active' modules.
+		 */
+		if (!argc && git_config_get_value_multi("submodule.active"))
+			module_list_active(&list);
+
+		info.prefix = opt.prefix;
+		info.superprefix = opt.recursive_prefix;
+		if (opt.quiet)
+			info.flags |= OPT_QUIET;
+
+		for_each_listed_submodule(&list, init_submodule_cb, &info);
+	}
+
 	return update_submodules(&opt);
 }
 
diff --git a/git-submodule.sh b/git-submodule.sh
index aa9c898e1c..3ccf2388bf 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -347,14 +347,11 @@ cmd_update()
 		shift
 	done
 
-	if test -n "$init"
-	then
-		cmd_init "--" "$@" || return
-	fi
-
 	{
-	git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
+	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update-clone \
+		${GIT_QUIET:+--quiet} \
 		${progress:+"--progress"} \
+		${init:+--init} \
 		${wt_prefix:+--prefix "$wt_prefix"} \
 		${prefix:+--recursive-prefix "$prefix"} \
 		${update:+--update "$update"} \
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 15/20] submodule--helper: move functions around

A subsequent commit will change the internals of several functions and arrange them in a more logical manner. Move these functions to their final positions so that the diff is smaller.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 228 ++++++++++++++++++------------------
 1 file changed, 114 insertions(+), 114 deletions(-)
Show changes to builtin/submodule--helper.c +114 −114
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 5635f0c48b..0ab8f9d49f 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2451,120 +2451,6 @@ static void update_submodule(struct update_clone_data *ucd)
 		ucd->sub->path);
 }
 
-static int update_submodules(struct submodule_update_clone *suc)
-{
-	int i;
-
-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
-				   update_clone_start_failure,
-				   update_clone_task_finished, suc, "submodule",
-				   "parallel/update");
-
-	/*
-	 * We saved the output and put it out all at once now.
-	 * That means:
-	 * - the listener does not have to interleave their (checkout)
-	 *   work with our fetching.  The writes involved in a
-	 *   checkout involve more straightforward sequential I/O.
-	 * - the listener can avoid doing any work if fetching failed.
-	 */
-	if (suc->quickstop)
-		return 1;
-
-	for (i = 0; i < suc->update_clone_nr; i++)
-		update_submodule(&suc->update_clone[i]);
-
-	return 0;
-}
-
-static int update_clone(int argc, const char **argv, const char *prefix)
-{
-	const char *update = NULL;
-	struct pathspec pathspec;
-	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
-
-	struct option module_update_clone_options[] = {
-		OPT_BOOL(0, "init", &opt.init,
-			 N_("initialize uninitialized submodules before update")),
-		OPT_STRING(0, "prefix", &prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
-			   N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_STRING(0, "update", &update,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
-			   N_("reference repository")),
-		OPT_BOOL(0, "dissociate", &opt.dissociate,
-			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &opt.depth, "<depth>",
-			   N_("create a shallow clone truncated to the "
-			      "specified number of revisions")),
-		OPT_INTEGER('j', "jobs", &opt.max_jobs,
-			    N_("parallel jobs")),
-		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
-			    N_("whether the initial clone should follow the shallow recommendation")),
-		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
-		OPT_BOOL(0, "progress", &opt.progress,
-			    N_("force cloning progress")),
-		OPT_BOOL(0, "require-init", &opt.require_init,
-			   N_("disallow cloning into non-empty directory")),
-		OPT_BOOL(0, "single-branch", &opt.single_branch,
-			 N_("clone only one branch, HEAD or --branch")),
-		OPT_END()
-	};
-
-	const char *const git_submodule_helper_usage[] = {
-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
-		NULL
-	};
-	opt.prefix = prefix;
-
-	update_clone_config_from_gitmodules(&opt.max_jobs);
-	git_config(git_update_clone_config, &opt.max_jobs);
-
-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
-			     git_submodule_helper_usage, 0);
-
-	if (update)
-		if (parse_submodule_update_strategy(update, &opt.update) < 0)
-			die(_("bad value for update parameter"));
-
-	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
-		return 1;
-
-	if (pathspec.nr)
-		opt.warn_if_uninitialized = 1;
-
-	if (opt.init) {
-		struct module_list list = MODULE_LIST_INIT;
-		struct init_cb info = INIT_CB_INIT;
-
-		if (module_list_compute(argc, argv, opt.prefix,
-					&pathspec, &list) < 0)
-			return 1;
-
-		/*
-		 * If there are no path args and submodule.active is set then,
-		 * by default, only initialize 'active' modules.
-		 */
-		if (!argc && git_config_get_value_multi("submodule.active"))
-			module_list_active(&list);
-
-		info.prefix = opt.prefix;
-		info.superprefix = opt.recursive_prefix;
-		if (opt.quiet)
-			info.flags |= OPT_QUIET;
-
-		for_each_listed_submodule(&list, init_submodule_cb, &info);
-	}
-
-	return update_submodules(&opt);
-}
-
 /*
  * NEEDSWORK: Use a forward declaration to avoid moving
  * run_update_procedure() (which will be removed soon).
@@ -3021,6 +2907,120 @@ static int update_submodule2(struct update_data *update_data)
 	return 3;
 }
 
+static int update_submodules(struct submodule_update_clone *suc)
+{
+	int i;
+
+	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
+				   update_clone_start_failure,
+				   update_clone_task_finished, suc, "submodule",
+				   "parallel/update");
+
+	/*
+	 * We saved the output and put it out all at once now.
+	 * That means:
+	 * - the listener does not have to interleave their (checkout)
+	 *   work with our fetching.  The writes involved in a
+	 *   checkout involve more straightforward sequential I/O.
+	 * - the listener can avoid doing any work if fetching failed.
+	 */
+	if (suc->quickstop)
+		return 1;
+
+	for (i = 0; i < suc->update_clone_nr; i++)
+		update_submodule(&suc->update_clone[i]);
+
+	return 0;
+}
+
+static int update_clone(int argc, const char **argv, const char *prefix)
+{
+	const char *update = NULL;
+	struct pathspec pathspec;
+	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
+
+	struct option module_update_clone_options[] = {
+		OPT_BOOL(0, "init", &opt.init,
+			 N_("initialize uninitialized submodules before update")),
+		OPT_STRING(0, "prefix", &prefix,
+			   N_("path"),
+			   N_("path into the working tree")),
+		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
+			   N_("path"),
+			   N_("path into the working tree, across nested "
+			      "submodule boundaries")),
+		OPT_STRING(0, "update", &update,
+			   N_("string"),
+			   N_("rebase, merge, checkout or none")),
+		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
+			   N_("reference repository")),
+		OPT_BOOL(0, "dissociate", &opt.dissociate,
+			   N_("use --reference only while cloning")),
+		OPT_STRING(0, "depth", &opt.depth, "<depth>",
+			   N_("create a shallow clone truncated to the "
+			      "specified number of revisions")),
+		OPT_INTEGER('j', "jobs", &opt.max_jobs,
+			    N_("parallel jobs")),
+		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
+			    N_("whether the initial clone should follow the shallow recommendation")),
+		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
+		OPT_BOOL(0, "progress", &opt.progress,
+			    N_("force cloning progress")),
+		OPT_BOOL(0, "require-init", &opt.require_init,
+			   N_("disallow cloning into non-empty directory")),
+		OPT_BOOL(0, "single-branch", &opt.single_branch,
+			 N_("clone only one branch, HEAD or --branch")),
+		OPT_END()
+	};
+
+	const char *const git_submodule_helper_usage[] = {
+		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
+		NULL
+	};
+	opt.prefix = prefix;
+
+	update_clone_config_from_gitmodules(&opt.max_jobs);
+	git_config(git_update_clone_config, &opt.max_jobs);
+
+	argc = parse_options(argc, argv, prefix, module_update_clone_options,
+			     git_submodule_helper_usage, 0);
+
+	if (update)
+		if (parse_submodule_update_strategy(update, &opt.update) < 0)
+			die(_("bad value for update parameter"));
+
+	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
+		return 1;
+
+	if (pathspec.nr)
+		opt.warn_if_uninitialized = 1;
+
+	if (opt.init) {
+		struct module_list list = MODULE_LIST_INIT;
+		struct init_cb info = INIT_CB_INIT;
+
+		if (module_list_compute(argc, argv, opt.prefix,
+					&pathspec, &list) < 0)
+			return 1;
+
+		/*
+		 * If there are no path args and submodule.active is set then,
+		 * by default, only initialize 'active' modules.
+		 */
+		if (!argc && git_config_get_value_multi("submodule.active"))
+			module_list_active(&list);
+
+		info.prefix = opt.prefix;
+		info.superprefix = opt.recursive_prefix;
+		if (opt.quiet)
+			info.flags |= OPT_QUIET;
+
+		for_each_listed_submodule(&list, init_submodule_cb, &info);
+	}
+
+	return update_submodules(&opt);
+}
+
 struct add_data {
 	const char *prefix;
 	const char *branch;
-- 
2.33.GIT
Ævar Arnfjörð Bjarmason· Feb 12, 2022, 14:41 UTC · re: Glen Choo · lore

Re: [PATCH v7 15/20] submodule--helper: move functions around

On Thu, Feb 10 2022, Glen Choo wrote:
> A subsequent commit will change the internals of several functions and
> arrange them in a more logical manner. Move these functions to their
> final positions so that the diff is smaller.

Shouldn't we do this earlier & avoid the FIXME comment in 10/20? (maybe a subsequent fixup addresses it, haven't checked...)

Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 16/20] submodule--helper: reduce logic in run_update_procedure()

A later commit will combine the "update-clone" and "run-update-procedure" commands, so run_update_procedure() will be removed. Prepare for this by moving as much logic as possible out of run_update_procedure() and into update_submodule2().

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 35 ++++++++++++++++++++---------------
 1 file changed, 20 insertions(+), 15 deletions(-)
Show changes to builtin/submodule--helper.c +20 −15
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 0ab8f9d49f..ff7ee73e1a 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2001,9 +2001,11 @@ struct submodule_update_clone {
 }
 
 struct update_data {
+	const char *prefix;
 	const char *recursive_prefix;
 	const char *sm_path;
 	const char *displaypath;
+	const char *update_default;
 	struct object_id oid;
 	struct object_id suboid;
 	struct submodule_update_strategy update_strategy;
@@ -2458,7 +2460,6 @@ static void update_submodule(struct update_clone_data *ucd)
 static int update_submodule2(struct update_data *update_data);
 static int run_update_procedure(int argc, const char **argv, const char *prefix)
 {
-	char *prefixed_path, *update = NULL;
 	struct update_data opt = UPDATE_DATA_INIT;
 
 	struct option options[] = {
@@ -2471,10 +2472,10 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "just-cloned", &opt.just_cloned,
 			 N_("overrides update mode in case the repository is a fresh clone")),
 		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
-		OPT_STRING(0, "prefix", &prefix,
+		OPT_STRING(0, "prefix", &opt.prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
-		OPT_STRING(0, "update", &update,
+		OPT_STRING(0, "update", &opt.update_default,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
 		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
@@ -2500,18 +2501,6 @@ static int run_update_procedure(int argc, const char **argv, const char *prefix)
 
 	opt.sm_path = argv[0];
 
-	if (opt.recursive_prefix)
-		prefixed_path = xstrfmt("%s%s", opt.recursive_prefix, opt.sm_path);
-	else
-		prefixed_path = xstrdup(opt.sm_path);
-
-	opt.displaypath = get_submodule_displaypath(prefixed_path, prefix);
-
-	determine_submodule_update_strategy(the_repository, opt.just_cloned,
-					    opt.sm_path, update,
-					    &opt.update_strategy);
-
-	free(prefixed_path);
 	return update_submodule2(&opt);
 }
 
@@ -2871,8 +2860,24 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 /* NEEDSWORK: this is a temporary name until we delete update_submodule() */
 static int update_submodule2(struct update_data *update_data)
 {
+	char *prefixed_path;
+
 	ensure_core_worktree(update_data->sm_path);
 
+	if (update_data->recursive_prefix)
+		prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix,
+					update_data->sm_path);
+	else
+		prefixed_path = xstrdup(update_data->sm_path);
+
+	update_data->displaypath = get_submodule_displaypath(prefixed_path,
+							     update_data->prefix);
+	free(prefixed_path);
+
+	determine_submodule_update_strategy(the_repository, update_data->just_cloned,
+					    update_data->sm_path, update_data->update_default,
+					    &update_data->update_strategy);
+
 	/* NEEDSWORK: fix the style issues e.g. braces */
 	if (update_data->just_cloned) {
 		oidcpy(&update_data->suboid, null_oid());
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 17/20] submodule: move core cmd_update() logic to C

From: Atharva Raykar <raykar.ath@gmail.com>

This patch completes the conversion past the flag parsing of `submodule update` by introducing a helper subcommand called `submodule--helper update`. The behaviour of `submodule update` should remain the same after this patch.

We add more fields to the `struct update_data` that are required by `struct submodule_update_clone` to be able to perform a clone, when that is needed to be done.

Recursing on a submodule is done by calling a subprocess that launches `submodule--helper update`, with a modified `--recursive-prefix` and `--prefix` parameter.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Shourya Shukla <periperidip@gmail.com>
Signed-off-by: Atharva Raykar <raykar.ath@gmail.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 312 ++++++++++++++++++++++--------------
 git-submodule.sh            | 105 ++----------
 2 files changed, 201 insertions(+), 216 deletions(-)
Show changes to 2 files +201 −216

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index ff7ee73e1a..b895e88a64 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1977,7 +1977,6 @@ struct submodule_update_clone {
 	const char *prefix;
 	int single_branch;
 
-	/* to be consumed by git-submodule.sh */
 	struct update_clone_data *update_clone;
 	int update_clone_nr; int update_clone_alloc;
 
@@ -1989,7 +1988,6 @@ struct submodule_update_clone {
 	int failed_clones_nr, failed_clones_alloc;
 
 	int max_jobs;
-	unsigned int init;
 };
 #define SUBMODULE_UPDATE_CLONE_INIT { \
 	.list = MODULE_LIST_INIT, \
@@ -2010,14 +2008,54 @@ struct update_data {
 	struct object_id suboid;
 	struct submodule_update_strategy update_strategy;
 	int depth;
+	int recommend_shallow;
+	int single_branch;
+	int max_jobs;
+	unsigned int init;
 	unsigned int force;
 	unsigned int quiet;
 	unsigned int nofetch;
 	unsigned int just_cloned;
 	unsigned int remote;
+	unsigned int recursive;
+	unsigned int progress;
+	unsigned int dissociate;
+	unsigned int require_init;
+	unsigned warn_if_uninitialized ;
+	struct string_list references;
+	struct module_list list;
 };
 #define UPDATE_DATA_INIT { \
+	.list = MODULE_LIST_INIT, \
 	.update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \
+	.recommend_shallow = -1, \
+	.references = STRING_LIST_INIT_DUP, \
+	.single_branch = -1, \
+	.max_jobs = 1, \
+}
+
+static void update_clone_from_update_data(struct submodule_update_clone *suc,
+					  struct update_data *update_data)
+{
+	suc->prefix = update_data->prefix;
+	suc->recursive_prefix = update_data->recursive_prefix;
+	suc->max_jobs = update_data->max_jobs;
+	suc->progress = update_data->progress;
+	suc->quiet = update_data->quiet;
+	suc->dissociate = update_data->dissociate;
+	suc->require_init = update_data->require_init;
+	suc->single_branch = update_data->single_branch;
+	suc->warn_if_uninitialized = update_data->warn_if_uninitialized;
+	suc->list = update_data->list;
+	suc->update = update_data->update_strategy;
+	suc->recommend_shallow = update_data->recommend_shallow;
+	if (update_data->depth)
+		suc->depth = xstrfmt("--depth=%d", update_data->depth);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			string_list_append(&suc->references, item->string);
+	}
 }
 
 static void next_submodule_warn_missing(struct submodule_update_clone *suc,
@@ -2356,40 +2394,35 @@ static int run_update_command(struct update_data *ud, int subforce)
 	if (run_command(&cp)) {
 		switch (ud->update_strategy.type) {
 		case SM_UPDATE_CHECKOUT:
-			printf(_("Unable to checkout '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			die_message(_("Unable to checkout '%s' in submodule path '%s'"),
+				    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_REBASE:
-			printf(_("Unable to rebase '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			die_message(_("Unable to rebase '%s' in submodule path '%s'"),
+			    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_MERGE:
-			printf(_("Unable to merge '%s' in submodule path '%s'"),
-			       oid, ud->displaypath);
+			die_message(_("Unable to merge '%s' in submodule path '%s'"),
+			    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_COMMAND:
-			printf(_("Execution of '%s %s' failed in submodule path '%s'"),
-			       ud->update_strategy.command, oid, ud->displaypath);
+			die_message(_("Execution of '%s %s' failed in submodule path '%s'"),
+			    ud->update_strategy.command, oid, ud->displaypath);
 			break;
 		default:
 			BUG("unexpected update strategy type: %s",
 			    submodule_strategy_to_string(&ud->update_strategy));
 		}
-		/*
-		 * NEEDSWORK: We are currently printing to stdout with error
-		 * return so that the shell caller handles the error output
-		 * properly. Once we start handling the error messages within
-		 * C, we should use die() instead.
-		 */
 		if (must_die_on_failure)
-			return 2;
-		/*
-		 * This signifies to the caller in shell that the command
-		 * failed without dying
-		 */
+			exit(128);
+
+		/* the command failed, but update must continue */
 		return 1;
 	}
 
+	if (ud->quiet)
+		return 0;
+
 	switch (ud->update_strategy.type) {
 	case SM_UPDATE_CHECKOUT:
 		printf(_("Submodule path '%s': checked out '%s'\n"),
@@ -2415,7 +2448,7 @@ static int run_update_command(struct update_data *ud, int subforce)
 	return 0;
 }
 
-static int do_run_update_procedure(struct update_data *ud)
+static int run_update_procedure(struct update_data *ud)
 {
 	int subforce = is_null_oid(&ud->suboid) || ud->force;
 
@@ -2445,76 +2478,6 @@ static int do_run_update_procedure(struct update_data *ud)
 	return run_update_command(ud, subforce);
 }
 
-static void update_submodule(struct update_clone_data *ucd)
-{
-	fprintf(stdout, "dummy %s %d\t%s\n",
-		oid_to_hex(&ucd->oid),
-		ucd->just_cloned,
-		ucd->sub->path);
-}
-
-/*
- * NEEDSWORK: Use a forward declaration to avoid moving
- * run_update_procedure() (which will be removed soon).
- */
-static int update_submodule2(struct update_data *update_data);
-static int run_update_procedure(int argc, const char **argv, const char *prefix)
-{
-	struct update_data opt = UPDATE_DATA_INIT;
-
-	struct option options[] = {
-		OPT__QUIET(&opt.quiet,
-			   N_("suppress output for update by rebase or merge")),
-		OPT__FORCE(&opt.force, N_("force checkout updates"),
-			   0),
-		OPT_BOOL('N', "no-fetch", &opt.nofetch,
-			 N_("don't fetch new objects from the remote site")),
-		OPT_BOOL(0, "just-cloned", &opt.just_cloned,
-			 N_("overrides update mode in case the repository is a fresh clone")),
-		OPT_INTEGER(0, "depth", &opt.depth, N_("depth for shallow fetch")),
-		OPT_STRING(0, "prefix", &opt.prefix,
-			   N_("path"),
-			   N_("path into the working tree")),
-		OPT_STRING(0, "update", &opt.update_default,
-			   N_("string"),
-			   N_("rebase, merge, checkout or none")),
-		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"),
-			   N_("path into the working tree, across nested "
-			      "submodule boundaries")),
-		OPT_CALLBACK_F(0, "oid", &opt.oid, N_("sha1"),
-			       N_("SHA1 expected by superproject"), PARSE_OPT_NONEG,
-			       parse_opt_object_id),
-		OPT_BOOL(0, "remote", &opt.remote,
-			 N_("use SHA-1 of submodule's remote tracking branch")),
-		OPT_END()
-	};
-
-	const char *const usage[] = {
-		N_("git submodule--helper run-update-procedure [<options>] <path>"),
-		NULL
-	};
-
-	argc = parse_options(argc, argv, prefix, options, usage, 0);
-
-	if (argc != 1)
-		usage_with_options(usage, options);
-
-	opt.sm_path = argv[0];
-
-	return update_submodule2(&opt);
-}
-
-static int resolve_relative_path(int argc, const char **argv, const char *prefix)
-{
-	struct strbuf sb = STRBUF_INIT;
-	if (argc != 3)
-		die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
-
-	printf("%s", relative_path(argv[1], argv[2], &sb));
-	strbuf_release(&sb);
-	return 0;
-}
-
 static const char *remote_submodule_branch(const char *path)
 {
 	const struct submodule *sub;
@@ -2857,8 +2820,53 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 	return !!ret;
 }
 
-/* NEEDSWORK: this is a temporary name until we delete update_submodule() */
-static int update_submodule2(struct update_data *update_data)
+static void update_data_to_args(struct update_data *update_data, struct strvec *args)
+{
+	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
+	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
+	/*
+	 * NEEDSWORK: the equivalent code in git-submodule.sh does not
+	 * pass --prefix, so this shouldn't either
+	*/
+	if (update_data->prefix)
+		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
+	if (update_data->recursive_prefix)
+		strvec_pushl(args, "--recursive-prefix",
+			     update_data->recursive_prefix, NULL);
+	if (update_data->quiet)
+		strvec_push(args, "--quiet");
+	if (update_data->force)
+		strvec_push(args, "--force");
+	if (update_data->init)
+		strvec_push(args, "--init");
+	if (update_data->remote)
+		strvec_push(args, "--remote");
+	if (update_data->nofetch)
+		strvec_push(args, "--no-fetch");
+	if (update_data->dissociate)
+		strvec_push(args, "--dissociate");
+	if (update_data->progress)
+		strvec_push(args, "--progress");
+	if (update_data->require_init)
+		strvec_push(args, "--require-init");
+	if (update_data->depth)
+		strvec_pushf(args, "--depth=%d", update_data->depth);
+	if (update_data->update_default)
+		strvec_pushl(args, "--update", update_data->update_default, NULL);
+	if (update_data->references.nr) {
+		struct string_list_item *item;
+		for_each_string_list_item(item, &update_data->references)
+			strvec_pushl(args, "--reference", item->string, NULL);
+	}
+	if (update_data->recommend_shallow == 0)
+		strvec_push(args, "--no-recommend-shallow");
+	else if (update_data->recommend_shallow == 1)
+		strvec_push(args, "--recommend-shallow");
+	if (update_data->single_branch >= 0)
+		strvec_push(args, "--single-branch");
+}
+
+static int update_submodule(struct update_data *update_data)
 {
 	char *prefixed_path;
 
@@ -2907,18 +2915,55 @@ static int update_submodule2(struct update_data *update_data)
 	}
 
 	if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)
-		return do_run_update_procedure(update_data);
+		if (run_update_procedure(update_data))
+			return 1;
+
+	if (update_data->recursive) {
+		struct child_process cp = CHILD_PROCESS_INIT;
+		struct update_data next = *update_data;
+		int res;
+
+		if (update_data->recursive_prefix)
+			prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix,
+						update_data->sm_path);
+		else
+			prefixed_path = xstrfmt("%s/", update_data->sm_path);
+
+		next.recursive_prefix = get_submodule_displaypath(prefixed_path,
+								  update_data->prefix);
+		next.prefix = NULL;
+		oidcpy(&next.oid, null_oid());
+		oidcpy(&next.suboid, null_oid());
+
+		cp.dir = update_data->sm_path;
+		cp.git_cmd = 1;
+		prepare_submodule_repo_env(&cp.env_array);
+		update_data_to_args(&next, &cp.args);
 
-	return 3;
+		/* die() if child process die()'d */
+		res = run_command(&cp);
+		if (!res)
+			return 0;
+		die_message(_("Failed to recurse into submodule path '%s'"),
+			    update_data->displaypath);
+		if (res == 128)
+			exit(res);
+		else if (res)
+			return 1;
+	}
+
+	return 0;
 }
 
-static int update_submodules(struct submodule_update_clone *suc)
+static int update_submodules(struct update_data *update_data)
 {
-	int i;
+	int i, res = 0;
+	struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
 
-	run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
+	update_clone_from_update_data(&suc, update_data);
+	run_processes_parallel_tr2(suc.max_jobs, update_clone_get_next_task,
 				   update_clone_start_failure,
-				   update_clone_task_finished, suc, "submodule",
+				   update_clone_task_finished, &suc, "submodule",
 				   "parallel/update");
 
 	/*
@@ -2929,50 +2974,69 @@ static int update_submodules(struct submodule_update_clone *suc)
 	 *   checkout involve more straightforward sequential I/O.
 	 * - the listener can avoid doing any work if fetching failed.
 	 */
-	if (suc->quickstop)
-		return 1;
+	if (suc.quickstop) {
+		res = 1;
+		goto cleanup;
+	}
 
-	for (i = 0; i < suc->update_clone_nr; i++)
-		update_submodule(&suc->update_clone[i]);
+	for (i = 0; i < suc.update_clone_nr; i++) {
+		struct update_clone_data ucd = suc.update_clone[i];
 
-	return 0;
+		oidcpy(&update_data->oid, &ucd.oid);
+		update_data->just_cloned = ucd.just_cloned;
+		update_data->sm_path = ucd.sub->path;
+
+		if (update_submodule(update_data))
+			res = 1;
+	}
+
+cleanup:
+	string_list_clear(&update_data->references, 0);
+	return res;
 }
 
-static int update_clone(int argc, const char **argv, const char *prefix)
+static int module_update(int argc, const char **argv, const char *prefix)
 {
-	const char *update = NULL;
 	struct pathspec pathspec;
-	struct submodule_update_clone opt = SUBMODULE_UPDATE_CLONE_INIT;
+	struct update_data opt = UPDATE_DATA_INIT;
 
+	/* NEEDSWORK: update names and strings */
 	struct option module_update_clone_options[] = {
+		OPT__FORCE(&opt.force, N_("force checkout updates"), 0),
 		OPT_BOOL(0, "init", &opt.init,
 			 N_("initialize uninitialized submodules before update")),
-		OPT_STRING(0, "prefix", &prefix,
+		OPT_BOOL(0, "remote", &opt.remote,
+			 N_("use SHA-1 of submodule's remote tracking branch")),
+		OPT_BOOL(0, "recursive", &opt.recursive,
+			 N_("traverse submodules recursively")),
+		OPT_BOOL('N', "no-fetch", &opt.nofetch,
+			 N_("don't fetch new objects from the remote site")),
+		OPT_STRING(0, "prefix", &opt.prefix,
 			   N_("path"),
 			   N_("path into the working tree")),
 		OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix,
 			   N_("path"),
 			   N_("path into the working tree, across nested "
 			      "submodule boundaries")),
-		OPT_STRING(0, "update", &update,
+		OPT_STRING(0, "update", &opt.update_default,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
 		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
-			   N_("reference repository")),
+				N_("reference repository")),
 		OPT_BOOL(0, "dissociate", &opt.dissociate,
-			   N_("use --reference only while cloning")),
-		OPT_STRING(0, "depth", &opt.depth, "<depth>",
-			   N_("create a shallow clone truncated to the "
-			      "specified number of revisions")),
+			 N_("use --reference only while cloning")),
+		OPT_INTEGER(0, "depth", &opt.depth,
+			    N_("create a shallow clone truncated to the "
+			       "specified number of revisions")),
 		OPT_INTEGER('j', "jobs", &opt.max_jobs,
 			    N_("parallel jobs")),
 		OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow,
-			    N_("whether the initial clone should follow the shallow recommendation")),
+			 N_("whether the initial clone should follow the shallow recommendation")),
 		OPT__QUIET(&opt.quiet, N_("don't print cloning progress")),
 		OPT_BOOL(0, "progress", &opt.progress,
-			    N_("force cloning progress")),
+			 N_("force cloning progress")),
 		OPT_BOOL(0, "require-init", &opt.require_init,
-			   N_("disallow cloning into non-empty directory")),
+			 N_("disallow cloning into non-empty directory")),
 		OPT_BOOL(0, "single-branch", &opt.single_branch,
 			 N_("clone only one branch, HEAD or --branch")),
 		OPT_END()
@@ -2982,16 +3046,18 @@ static int update_clone(int argc, const char **argv, const char *prefix)
 		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
 		NULL
 	};
-	opt.prefix = prefix;
 
 	update_clone_config_from_gitmodules(&opt.max_jobs);
 	git_config(git_update_clone_config, &opt.max_jobs);
 
 	argc = parse_options(argc, argv, prefix, module_update_clone_options,
 			     git_submodule_helper_usage, 0);
+	oidcpy(&opt.oid, null_oid());
+	oidcpy(&opt.suboid, null_oid());
 
-	if (update)
-		if (parse_submodule_update_strategy(update, &opt.update) < 0)
+	if (opt.update_default)
+		if (parse_submodule_update_strategy(opt.update_default,
+						    &opt.update_strategy) < 0)
 			die(_("bad value for update parameter"));
 
 	if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0)
@@ -3413,9 +3479,7 @@ static struct cmd_struct commands[] = {
 	{"name", module_name, 0},
 	{"clone", module_clone, 0},
 	{"add", module_add, SUPPORT_SUPER_PREFIX},
-	{"update-clone", update_clone, 0},
-	{"run-update-procedure", run_update_procedure, 0},
-	{"relative-path", resolve_relative_path, 0},
+	{"update", module_update, 0},
 	{"resolve-relative-url-test", resolve_relative_url_test, 0},
 	{"foreach", module_foreach, SUPPORT_SUPER_PREFIX},
 	{"init", module_init, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index 3ccf2388bf..d176469fb1 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -50,6 +50,7 @@ single_branch=
 jobs=
 recommend_shallow=
 
+# NEEDSWORK this is now unused
 die_if_unmatched ()
 {
 	if test "$1" = "#unmatched"
@@ -347,108 +348,28 @@ cmd_update()
 		shift
 	done
 
-	{
-	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update-clone \
+	# NEEDSWORK --super-prefix isn't actually supported by this
+	# command - we just pass the $prefix to --recursive-prefix.
+	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
 		${GIT_QUIET:+--quiet} \
-		${progress:+"--progress"} \
+		${force:+--force} \
+		${progress:+--progress} \
+		${dissociate:+--dissociate} \
+		${remote:+--remote} \
+		${recursive:+--recursive} \
 		${init:+--init} \
+		${require_init:+--require-init} \
+		${nofetch:+--no-fetch} \
 		${wt_prefix:+--prefix "$wt_prefix"} \
 		${prefix:+--recursive-prefix "$prefix"} \
 		${update:+--update "$update"} \
 		${reference:+"$reference"} \
-		${dissociate:+"--dissociate"} \
-		${depth:+--depth "$depth"} \
-		${require_init:+--require-init} \
+		${depth:+"$depth"} \
 		$single_branch \
 		$recommend_shallow \
 		$jobs \
 		-- \
-		"$@" || echo "#unmatched" $?
-	} | {
-	err=
-	while read -r quickabort sha1 just_cloned sm_path
-	do
-		die_if_unmatched "$quickabort" "$sha1"
-
-		displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
-
-		if test $just_cloned -eq 0
-		then
-			just_cloned=
-		fi
-
-		out=$(git submodule--helper run-update-procedure \
-			  ${wt_prefix:+--prefix "$wt_prefix"} \
-			  ${GIT_QUIET:+--quiet} \
-			  ${force:+--force} \
-			  ${just_cloned:+--just-cloned} \
-			  ${nofetch:+--no-fetch} \
-			  ${depth:+"$depth"} \
-			  ${update:+--update "$update"} \
-			  ${prefix:+--recursive-prefix "$prefix"} \
-			  ${sha1:+--oid "$sha1"} \
-			  ${remote:+--remote} \
-			  "--" \
-			  "$sm_path")
-
-		# exit codes for run-update-procedure:
-		# 0: update was successful, say command output
-		# 1: update procedure failed, but should not die
-		# 2 or 128: subcommand died during execution
-		# 3: no update procedure was run
-		res="$?"
-		case $res in
-		0)
-			say "$out"
-			;;
-		1)
-			err="${err};fatal: $out"
-			continue
-			;;
-		2|128)
-			die_with_status $res "fatal: $out"
-			;;
-		esac
-
-		if test -n "$recursive"
-		then
-			(
-				prefix=$(git submodule--helper relative-path "$prefix$sm_path/" "$wt_prefix")
-				wt_prefix=
-				sanitize_submodule_env
-				cd "$sm_path" &&
-				eval cmd_update
-			)
-			res=$?
-			if test $res -gt 0
-			then
-				die_msg="fatal: $(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
-				if test $res -ne 2
-				then
-					err="${err};$die_msg"
-					continue
-				else
-					die_with_status $res "$die_msg"
-				fi
-			fi
-		fi
-	done
-
-	if test -n "$err"
-	then
-		OIFS=$IFS
-		IFS=';'
-		for e in $err
-		do
-			if test -n "$e"
-			then
-				echo >&2 "$e"
-			fi
-		done
-		IFS=$OIFS
-		exit 1
-	fi
-	}
+		"$@"
 }
 
 #
-- 
2.33.GIT
Glen Choo· Feb 10, 2022, 09:34 UTC · re: Glen Choo · lore

Re: [PATCH v7 17/20] submodule: move core cmd_update() logic to C

Here is the diff between this tree (+) and that of v5's [1] (-).
[1] https://lore.kernel.org/git/patch-v5-9.9-e8e57606ee9-20220128T125206Z-avarab@gmail.com
----- >8 --------- >8 --------- >8 --------- >8 --------- >8 ----
Show changes to 2 files +34 −33

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 1c28b6f479..b895e88a64 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2003,6 +2003,7 @@ struct update_data {
 	const char *recursive_prefix;
 	const char *sm_path;
 	const char *displaypath;
+	const char *update_default;
 	struct object_id oid;
 	struct object_id suboid;
 	struct submodule_update_strategy update_strategy;
@@ -2355,15 +2356,8 @@ static int run_update_command(struct update_data *ud, int subforce)
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *oid = oid_to_hex(&ud->oid);
 	int must_die_on_failure = 0;
-	struct submodule_update_strategy strategy = SUBMODULE_UPDATE_STRATEGY_INIT;
 
-	if (ud->update_strategy.type == SM_UPDATE_UNSPECIFIED || ud->just_cloned)
-		determine_submodule_update_strategy(the_repository, ud->just_cloned,
-						    ud->sm_path, NULL, &strategy);
-	else
-		strategy = ud->update_strategy;
-
-	switch (strategy.type) {
+	switch (ud->update_strategy.type) {
 	case SM_UPDATE_CHECKOUT:
 		cp.git_cmd = 1;
 		strvec_pushl(&cp.args, "checkout", "-q", NULL);
@@ -2386,45 +2380,41 @@ static int run_update_command(struct update_data *ud, int subforce)
 		break;
 	case SM_UPDATE_COMMAND:
 		cp.use_shell = 1;
-		strvec_push(&cp.args, strategy.command);
+		strvec_push(&cp.args, ud->update_strategy.command);
 		must_die_on_failure = 1;
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&strategy));
+		    submodule_strategy_to_string(&ud->update_strategy));
 	}
 	strvec_push(&cp.args, oid);
 
 	cp.dir = xstrdup(ud->sm_path);
 	prepare_submodule_repo_env(&cp.env_array);
 	if (run_command(&cp)) {
-		switch (strategy.type) {
+		switch (ud->update_strategy.type) {
 		case SM_UPDATE_CHECKOUT:
 			die_message(_("Unable to checkout '%s' in submodule path '%s'"),
 				    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_REBASE:
-			if (!must_die_on_failure)
-				break;
-			die(_("Unable to rebase '%s' in submodule path '%s'"),
+			die_message(_("Unable to rebase '%s' in submodule path '%s'"),
 			    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_MERGE:
-			if (!must_die_on_failure)
-				break;
-			die(_("Unable to merge '%s' in submodule path '%s'"),
+			die_message(_("Unable to merge '%s' in submodule path '%s'"),
 			    oid, ud->displaypath);
 			break;
 		case SM_UPDATE_COMMAND:
-			if (!must_die_on_failure)
-				break;
-			die(_("Execution of '%s %s' failed in submodule path '%s'"),
-			    strategy.command, oid, ud->displaypath);
+			die_message(_("Execution of '%s %s' failed in submodule path '%s'"),
+			    ud->update_strategy.command, oid, ud->displaypath);
 			break;
 		default:
 			BUG("unexpected update strategy type: %s",
-			    submodule_strategy_to_string(&strategy));
+			    submodule_strategy_to_string(&ud->update_strategy));
 		}
+		if (must_die_on_failure)
+			exit(128);
 
 		/* the command failed, but update must continue */
 		return 1;
@@ -2433,7 +2423,7 @@ static int run_update_command(struct update_data *ud, int subforce)
 	if (ud->quiet)
 		return 0;
 
-	switch (strategy.type) {
+	switch (ud->update_strategy.type) {
 	case SM_UPDATE_CHECKOUT:
 		printf(_("Submodule path '%s': checked out '%s'\n"),
 		       ud->displaypath, oid);
@@ -2448,11 +2438,11 @@ static int run_update_command(struct update_data *ud, int subforce)
 		break;
 	case SM_UPDATE_COMMAND:
 		printf(_("Submodule path '%s': '%s %s'\n"),
-		       ud->displaypath, strategy.command, oid);
+		       ud->displaypath, ud->update_strategy.command, oid);
 		break;
 	default:
 		BUG("unexpected update strategy type: %s",
-		    submodule_strategy_to_string(&strategy));
+		    submodule_strategy_to_string(&ud->update_strategy));
 	}
 
 	return 0;
@@ -2832,10 +2822,12 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
 
 static void update_data_to_args(struct update_data *update_data, struct strvec *args)
 {
-	const char *update = submodule_strategy_to_string(&update_data->update_strategy);
-
 	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
 	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
+	/*
+	 * NEEDSWORK: the equivalent code in git-submodule.sh does not
+	 * pass --prefix, so this shouldn't either
+	*/
 	if (update_data->prefix)
 		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
 	if (update_data->recursive_prefix)
@@ -2859,8 +2851,8 @@ static void update_data_to_args(struct update_data *update_data, struct strvec *
 		strvec_push(args, "--require-init");
 	if (update_data->depth)
 		strvec_pushf(args, "--depth=%d", update_data->depth);
-	if (update)
-		strvec_pushl(args, "--update", update, NULL);
+	if (update_data->update_default)
+		strvec_pushl(args, "--update", update_data->update_default, NULL);
 	if (update_data->references.nr) {
 		struct string_list_item *item;
 		for_each_string_list_item(item, &update_data->references)
@@ -2890,6 +2882,11 @@ static int update_submodule(struct update_data *update_data)
 							     update_data->prefix);
 	free(prefixed_path);
 
+	determine_submodule_update_strategy(the_repository, update_data->just_cloned,
+					    update_data->sm_path, update_data->update_default,
+					    &update_data->update_strategy);
+
+	/* NEEDSWORK: fix the style issues e.g. braces */
 	if (update_data->just_cloned) {
 		oidcpy(&update_data->suboid, null_oid());
 	} else {
@@ -3000,10 +2997,10 @@ static int update_submodules(struct update_data *update_data)
 
 static int module_update(int argc, const char **argv, const char *prefix)
 {
-	const char *update = NULL;
 	struct pathspec pathspec;
 	struct update_data opt = UPDATE_DATA_INIT;
 
+	/* NEEDSWORK: update names and strings */
 	struct option module_update_clone_options[] = {
 		OPT__FORCE(&opt.force, N_("force checkout updates"), 0),
 		OPT_BOOL(0, "init", &opt.init,
@@ -3021,7 +3018,7 @@ static int module_update(int argc, const char **argv, const char *prefix)
 			   N_("path"),
 			   N_("path into the working tree, across nested "
 			      "submodule boundaries")),
-		OPT_STRING(0, "update", &update,
+		OPT_STRING(0, "update", &opt.update_default,
 			   N_("string"),
 			   N_("rebase, merge, checkout or none")),
 		OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"),
@@ -3058,8 +3055,8 @@ static int module_update(int argc, const char **argv, const char *prefix)
 	oidcpy(&opt.oid, null_oid());
 	oidcpy(&opt.suboid, null_oid());
 
-	if (update)
-		if (parse_submodule_update_strategy(update,
+	if (opt.update_default)
+		if (parse_submodule_update_strategy(opt.update_default,
 						    &opt.update_strategy) < 0)
 			die(_("bad value for update parameter"));
 
@@ -3490,6 +3487,7 @@ static struct cmd_struct commands[] = {
 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
 	{"deinit", module_deinit, 0},
 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
+	/* NEEDSWORK: remote-branch is also obsolete */
 	{"remote-branch", resolve_remote_submodule_branch, 0},
 	{"push-check", push_check, 0},
 	{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
diff --git a/git-submodule.sh b/git-submodule.sh
index bcd8b92aab..d176469fb1 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -50,6 +50,7 @@ single_branch=
 jobs=
 recommend_shallow=
 
+# NEEDSWORK this is now unused
 die_if_unmatched ()
 {
 	if test "$1" = "#unmatched"
@@ -347,6 +348,8 @@ cmd_update()
 		shift
 	done
 
+	# NEEDSWORK --super-prefix isn't actually supported by this
+	# command - we just pass the $prefix to --recursive-prefix.
 	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
 		${GIT_QUIET:+--quiet} \
 		${force:+--force} \
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 18/20] fixup! submodule--helper run-update-procedure: remove --suboid

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)
Show changes to builtin/submodule--helper.c +4 −7
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index b895e88a64..98d8910930 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2886,14 +2886,11 @@ static int update_submodule(struct update_data *update_data)
 					    update_data->sm_path, update_data->update_default,
 					    &update_data->update_strategy);
 
-	/* NEEDSWORK: fix the style issues e.g. braces */
-	if (update_data->just_cloned) {
+	if (update_data->just_cloned)
 		oidcpy(&update_data->suboid, null_oid());
-	} else {
-		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
-			die(_("Unable to find current revision in submodule path '%s'"),
-			    update_data->displaypath);
-	}
+	else if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+		die(_("Unable to find current revision in submodule path '%s'"),
+			update_data->displaypath);
 
 	if (update_data->remote) {
 		char *remote_name = get_default_remote_submodule(update_data->sm_path);
-- 
2.33.GIT
Ævar Arnfjörð Bjarmason· Feb 12, 2022, 14:41 UTC · re: Glen Choo · lore

Re: [PATCH v7 18/20] fixup! submodule--helper run-update-procedure: remove --suboid

On Thu, Feb 10 2022, Glen Choo wrote:
Show 28 quoted lines
> Signed-off-by: Glen Choo <chooglen@google.com>
> ---
>  builtin/submodule--helper.c | 11 ++++-------
>  1 file changed, 4 insertions(+), 7 deletions(-)
>
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index b895e88a64..98d8910930 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -2886,14 +2886,11 @@ static int update_submodule(struct update_data *update_data)
>  					    update_data->sm_path, update_data->update_default,
>  					    &update_data->update_strategy);
>  
> -	/* NEEDSWORK: fix the style issues e.g. braces */
> -	if (update_data->just_cloned) {
> +	if (update_data->just_cloned)
>  		oidcpy(&update_data->suboid, null_oid());
> -	} else {
> -		if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
> -			die(_("Unable to find current revision in submodule path '%s'"),
> -			    update_data->displaypath);
> -	}
> +	else if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
> +		die(_("Unable to find current revision in submodule path '%s'"),
> +			update_data->displaypath);
>  
>  	if (update_data->remote) {
>  		char *remote_name = get_default_remote_submodule(update_data->sm_path);

This fixup looks good, let's apply this fix-up to the relevant preceding commit.

Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 19/20] fixup! submodule--helper run-update-procedure: learn --remote

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 19 -------------------
 1 file changed, 19 deletions(-)
Show changes to builtin/submodule--helper.c +0 −19
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 98d8910930..5d5302b50b 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2516,23 +2516,6 @@ static const char *remote_submodule_branch(const char *path)
 	return branch;
 }
 
-static int resolve_remote_submodule_branch(int argc, const char **argv,
-		const char *prefix)
-{
-	const char *ret;
-	struct strbuf sb = STRBUF_INIT;
-	if (argc != 2)
-		die("submodule--helper remote-branch takes exactly one arguments, got %d", argc);
-
-	ret = remote_submodule_branch(argv[1]);
-	if (!ret)
-		die("submodule %s doesn't exist", argv[1]);
-
-	printf("%s", ret);
-	strbuf_release(&sb);
-	return 0;
-}
-
 static int push_check(int argc, const char **argv, const char *prefix)
 {
 	struct remote *remote;
@@ -3484,8 +3467,6 @@ static struct cmd_struct commands[] = {
 	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
 	{"deinit", module_deinit, 0},
 	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
-	/* NEEDSWORK: remote-branch is also obsolete */
-	{"remote-branch", resolve_remote_submodule_branch, 0},
 	{"push-check", push_check, 0},
 	{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
 	{"is-active", is_active, 0},
-- 
2.33.GIT
Ævar Arnfjörð Bjarmason· Feb 12, 2022, 14:43 UTC · re: Glen Choo · lore

Re: [PATCH v7 19/20] fixup! submodule--helper run-update-procedure: learn --remote

On Thu, Feb 10 2022, Glen Choo wrote:
Show 42 quoted lines
> Signed-off-by: Glen Choo <chooglen@google.com>
> ---
>  builtin/submodule--helper.c | 19 -------------------
>  1 file changed, 19 deletions(-)
>
> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
> index 98d8910930..5d5302b50b 100644
> --- a/builtin/submodule--helper.c
> +++ b/builtin/submodule--helper.c
> @@ -2516,23 +2516,6 @@ static const char *remote_submodule_branch(const char *path)
>  	return branch;
>  }
>  
> -static int resolve_remote_submodule_branch(int argc, const char **argv,
> -		const char *prefix)
> -{
> -	const char *ret;
> -	struct strbuf sb = STRBUF_INIT;
> -	if (argc != 2)
> -		die("submodule--helper remote-branch takes exactly one arguments, got %d", argc);
> -
> -	ret = remote_submodule_branch(argv[1]);
> -	if (!ret)
> -		die("submodule %s doesn't exist", argv[1]);
> -
> -	printf("%s", ret);
> -	strbuf_release(&sb);
> -	return 0;
> -}
> -
>  static int push_check(int argc, const char **argv, const char *prefix)
>  {
>  	struct remote *remote;
> @@ -3484,8 +3467,6 @@ static struct cmd_struct commands[] = {
>  	{"sync", module_sync, SUPPORT_SUPER_PREFIX},
>  	{"deinit", module_deinit, 0},
>  	{"summary", module_summary, SUPPORT_SUPER_PREFIX},
> -	/* NEEDSWORK: remote-branch is also obsolete */
> -	{"remote-branch", resolve_remote_submodule_branch, 0},
>  	{"push-check", push_check, 0},
>  	{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
>  	{"is-active", is_active, 0},
Ditto good fix-up, should squash this into the relevant commit.
Glen Choo· Feb 10, 2022, 09:28 UTC · re: Glen Choo · lore

[PATCH v7 20/20] fixup! submodule: move core cmd_update() logic to C

Signed-off-by: Glen Choo <chooglen@google.com>
---
 builtin/submodule--helper.c | 13 +++----------
 git-submodule.sh            | 13 +------------
 2 files changed, 4 insertions(+), 22 deletions(-)
Show changes to 2 files +4 −22

builtin/submodule--helper.c, git-submodule.sh

diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 5d5302b50b..3367997973 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -2807,12 +2807,6 @@ static void update_data_to_args(struct update_data *update_data, struct strvec *
 {
 	strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL);
 	strvec_pushf(args, "--jobs=%d", update_data->max_jobs);
-	/*
-	 * NEEDSWORK: the equivalent code in git-submodule.sh does not
-	 * pass --prefix, so this shouldn't either
-	*/
-	if (update_data->prefix)
-		strvec_pushl(args, "--prefix", update_data->prefix, NULL);
 	if (update_data->recursive_prefix)
 		strvec_pushl(args, "--recursive-prefix",
 			     update_data->recursive_prefix, NULL);
@@ -2980,8 +2974,7 @@ static int module_update(int argc, const char **argv, const char *prefix)
 	struct pathspec pathspec;
 	struct update_data opt = UPDATE_DATA_INIT;
 
-	/* NEEDSWORK: update names and strings */
-	struct option module_update_clone_options[] = {
+	struct option module_update_options[] = {
 		OPT__FORCE(&opt.force, N_("force checkout updates"), 0),
 		OPT_BOOL(0, "init", &opt.init,
 			 N_("initialize uninitialized submodules before update")),
@@ -3023,14 +3016,14 @@ static int module_update(int argc, const char **argv, const char *prefix)
 	};
 
 	const char *const git_submodule_helper_usage[] = {
-		N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
+		N_("git submodule--helper update [--prefix=<path>] [<path>...]"),
 		NULL
 	};
 
 	update_clone_config_from_gitmodules(&opt.max_jobs);
 	git_config(git_update_clone_config, &opt.max_jobs);
 
-	argc = parse_options(argc, argv, prefix, module_update_clone_options,
+	argc = parse_options(argc, argv, prefix, module_update_options,
 			     git_submodule_helper_usage, 0);
 	oidcpy(&opt.oid, null_oid());
 	oidcpy(&opt.suboid, null_oid());
diff --git a/git-submodule.sh b/git-submodule.sh
index d176469fb1..89f5cf393e 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -50,15 +50,6 @@ single_branch=
 jobs=
 recommend_shallow=
 
-# NEEDSWORK this is now unused
-die_if_unmatched ()
-{
-	if test "$1" = "#unmatched"
-	then
-		exit ${2:-1}
-	fi
-}
-
 isnumber()
 {
 	n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
@@ -348,9 +339,7 @@ cmd_update()
 		shift
 	done
 
-	# NEEDSWORK --super-prefix isn't actually supported by this
-	# command - we just pass the $prefix to --recursive-prefix.
-	git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper update \
+	git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update \
 		${GIT_QUIET:+--quiet} \
 		${force:+--force} \
 		${progress:+--progress} \
-- 
2.33.GIT
Ævar Arnfjörð Bjarmason· Feb 12, 2022, 14:45 UTC · re: Glen Choo · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

On Thu, Feb 10 2022, Glen Choo wrote:
Show 51 quoted lines
> This reroll contains another 'easy' preparatory patch and the fixups I
> alluded to in v6 [1]. This isn't the split-up I described in the
> footnote of v6, but it gets the big patch (patch 17) to what I think is
> a reviewable state.
>
> The diff between v7 and v5 is no longer just NEEDSWORK comments, but I
> think it is easier to reason about. Patch 17 resembles v5 the most (I
> will include a diff in a reply to that patch); everything after patch 17
> is fixups (I did not squash them in because they would grow the diff
> even more).
>
> I will also leave a review on patch 17 since the changes were not
> originally authored by me.
>
> [1] https://lore.kernel.org/git/20220208083952.35036-1-chooglen@google.com
>
> Changes in v7:
> - Split the last patch of v6 (the big one) into patches 16-17.
> - Patch 16 moves logic out of run_update_procedure() (because the
>   command is going away), removing some noise from patch 17. This makes
>   the update_strategy parsing easier to reason about, but at the cost of
>   growing the diff vis-a-vis v5
> - Patches 18-20 are fixups that address NEEDSWORK comments from earlier
>   patches. Once maintaining a small diff vis-a-vis v5 stops making
>   sense, I will squash them in.
>
> Atharva Raykar (6):
>   submodule--helper: get remote names from any repository
>   submodule--helper: refactor get_submodule_displaypath()
>   submodule--helper: allow setting superprefix for init_submodule()
>   submodule--helper: run update using child process struct
>   builtin/submodule--helper.c: reformat designated initializers
>   submodule: move core cmd_update() logic to C
>
> Glen Choo (11):
>   submodule--helper: remove update-module-mode
>   submodule--helper: reorganize code for sh to C conversion
>   submodule--helper run-update-procedure: remove --suboid
>   submodule--helper run-update-procedure: learn --remote
>   submodule--helper: remove ensure-core-worktree
>   submodule--helper update-clone: learn --init
>   submodule--helper: move functions around
>   submodule--helper: reduce logic in run_update_procedure()
>   fixup! submodule--helper run-update-procedure: remove --suboid
>   fixup! submodule--helper run-update-procedure: learn --remote
>   fixup! submodule: move core cmd_update() logic to C
>
> Ævar Arnfjörð Bjarmason (3):
>   builtin/submodule--helper.c: rename option variables to "opt"
>   submodule--helper: don't use bitfield indirection for parse_options()
>   submodule tests: test for init and update failure output

Thanks a lot for picking this up! This split-up is much easier to read than my v5, particularly with the end diff-stat of the "main" patch being:

 2 files changed, 201 insertions(+), 216 deletions(-)
Instead of:
 2 files changed, 356 insertions(+), 388 deletions(-)

I think sending a version of this with the fixups squashed in as a v8 would be good, and perhaps addressing some of my comments.

I don't know if my suggested split-up of "prep fixes" into another series would be a good thing to pursue overall, perhaps Junio will chime in on how he'd be most comfortable in merging this down. I'd think splitting such trivial fixes into their own series be easier to review, but perhaps not.

For the Signed-off-by question on v6, I think you should add your SOB to
all the patches you submit. See this in SubmittingPatches:
    
    Notice that you can place your own `Signed-off-by` trailer when
    forwarding somebody else's patch with the above rules for
    D-C-O.  Indeed you are encouraged to do so.
Just running "git rebase -i -x 'git commit --amend --no-edit -s'" should
do it.
    
Glen Choo· Feb 17, 2022, 05:44 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 36 quoted lines
> On Thu, Feb 10 2022, Glen Choo wrote:
>
>> Atharva Raykar (6):
>>   submodule--helper: get remote names from any repository
>>   submodule--helper: refactor get_submodule_displaypath()
>>   submodule--helper: allow setting superprefix for init_submodule()
>>   submodule--helper: run update using child process struct
>>   builtin/submodule--helper.c: reformat designated initializers
>>   submodule: move core cmd_update() logic to C
>>
>> Glen Choo (11):
>>   submodule--helper: remove update-module-mode
>>   submodule--helper: reorganize code for sh to C conversion
>>   submodule--helper run-update-procedure: remove --suboid
>>   submodule--helper run-update-procedure: learn --remote
>>   submodule--helper: remove ensure-core-worktree
>>   submodule--helper update-clone: learn --init
>>   submodule--helper: move functions around
>>   submodule--helper: reduce logic in run_update_procedure()
>>   fixup! submodule--helper run-update-procedure: remove --suboid
>>   fixup! submodule--helper run-update-procedure: learn --remote
>>   fixup! submodule: move core cmd_update() logic to C
>>
>> Ævar Arnfjörð Bjarmason (3):
>>   builtin/submodule--helper.c: rename option variables to "opt"
>>   submodule--helper: don't use bitfield indirection for parse_options()
>>   submodule tests: test for init and update failure output
>
> I think sending a version of this with the fixups squashed in as a v8
> would be good, and perhaps addressing some of my comments.
>
> I don't know if my suggested split-up of "prep fixes" into another
> series would be a good thing to pursue overall, perhaps Junio will chime
> in on how he'd be most comfortable in merging this down. I'd think
> splitting such trivial fixes into their own series be easier to review,
> but perhaps not.

Combing through the patches again, I couldn't really convince myself that the patch 4..9 prep fixes make sense as obvious standalone fixes, except maybe:

- patch 4 submodule--helper: run update using child process struct
- patch 8 submodule tests: test for init and update failure output
- patch 9:  087bf43aba submodule--helper: remove update-module-mode

But, since the 'final' patch (ignoring the fixup!-s) is consuming a huge chunk of the work anyway, here's an alternative patch organization with the fixup!-s squashed:

= Move 'easy' and 'obviously correct' code from sh->C
- patches 8-9   Cleanup and introduce tests
- patches 1-4   Refactor existing functions, which enables..
- patches 10-14 Move 'obviously correct' pieces of logic from sh-> C
= Finalize move from sh->C
i.e. combine "run-update-procedure" and "update-clone" into "update"
- patches 5,7     Cleanup and prep
- patches 6,15-16 Shrinking the diff
- patch 17        Implement "git submodule--helper update" 
I'll send this if there are no objections :)
Ævar Arnfjörð Bjarmason· Feb 17, 2022, 09:17 UTC · re: Glen Choo · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

On Thu, Feb 17 2022, Glen Choo wrote:
Show 63 quoted lines
> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> On Thu, Feb 10 2022, Glen Choo wrote:
>>
>>> Atharva Raykar (6):
>>>   submodule--helper: get remote names from any repository
>>>   submodule--helper: refactor get_submodule_displaypath()
>>>   submodule--helper: allow setting superprefix for init_submodule()
>>>   submodule--helper: run update using child process struct
>>>   builtin/submodule--helper.c: reformat designated initializers
>>>   submodule: move core cmd_update() logic to C
>>>
>>> Glen Choo (11):
>>>   submodule--helper: remove update-module-mode
>>>   submodule--helper: reorganize code for sh to C conversion
>>>   submodule--helper run-update-procedure: remove --suboid
>>>   submodule--helper run-update-procedure: learn --remote
>>>   submodule--helper: remove ensure-core-worktree
>>>   submodule--helper update-clone: learn --init
>>>   submodule--helper: move functions around
>>>   submodule--helper: reduce logic in run_update_procedure()
>>>   fixup! submodule--helper run-update-procedure: remove --suboid
>>>   fixup! submodule--helper run-update-procedure: learn --remote
>>>   fixup! submodule: move core cmd_update() logic to C
>>>
>>> Ævar Arnfjörð Bjarmason (3):
>>>   builtin/submodule--helper.c: rename option variables to "opt"
>>>   submodule--helper: don't use bitfield indirection for parse_options()
>>>   submodule tests: test for init and update failure output
>>
>> I think sending a version of this with the fixups squashed in as a v8
>> would be good, and perhaps addressing some of my comments.
>>
>> I don't know if my suggested split-up of "prep fixes" into another
>> series would be a good thing to pursue overall, perhaps Junio will chime
>> in on how he'd be most comfortable in merging this down. I'd think
>> splitting such trivial fixes into their own series be easier to review,
>> but perhaps not.
>
> Combing through the patches again, I couldn't really convince myself
> that the patch 4..9 prep fixes make sense as obvious standalone fixes,
> except maybe:
>
> - patch 4 submodule--helper: run update using child process struct
> - patch 8 submodule tests: test for init and update failure output
> - patch 9:  087bf43aba submodule--helper: remove update-module-mode
>
> But, since the 'final' patch (ignoring the fixup!-s) is consuming a huge
> chunk of the work anyway, here's an alternative patch organization with
> the fixup!-s squashed:
>
> = Move 'easy' and 'obviously correct' code from sh->C
> - patches 8-9   Cleanup and introduce tests
> - patches 1-4   Refactor existing functions, which enables..
> - patches 10-14 Move 'obviously correct' pieces of logic from sh-> C
>
> = Finalize move from sh->C
> i.e. combine "run-update-procedure" and "update-clone" into "update"
> - patches 5,7     Cleanup and prep
> - patches 6,15-16 Shrinking the diff
> - patch 17        Implement "git submodule--helper update" 
>
> I'll send this if there are no objections :)

Yes that sounds good, or rather, I haven't re-looked at that in detail, but I think if you think it makes sense we should go for it.

Or rather, we should really be aiming to produce a patch series that makes sense in its current iteration, as opposed to optimizing for a diff against some ad-hoc re-roll I produced a few versions ago :)

Thanks again for working on this & picking this up. It's great to see progress in this area!

Glen Choo· Feb 17, 2022, 16:14 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 72 quoted lines
> On Thu, Feb 17 2022, Glen Choo wrote:
>
>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>>
>>> On Thu, Feb 10 2022, Glen Choo wrote:
>>>
>>>> Atharva Raykar (6):
>>>>   submodule--helper: get remote names from any repository
>>>>   submodule--helper: refactor get_submodule_displaypath()
>>>>   submodule--helper: allow setting superprefix for init_submodule()
>>>>   submodule--helper: run update using child process struct
>>>>   builtin/submodule--helper.c: reformat designated initializers
>>>>   submodule: move core cmd_update() logic to C
>>>>
>>>> Glen Choo (11):
>>>>   submodule--helper: remove update-module-mode
>>>>   submodule--helper: reorganize code for sh to C conversion
>>>>   submodule--helper run-update-procedure: remove --suboid
>>>>   submodule--helper run-update-procedure: learn --remote
>>>>   submodule--helper: remove ensure-core-worktree
>>>>   submodule--helper update-clone: learn --init
>>>>   submodule--helper: move functions around
>>>>   submodule--helper: reduce logic in run_update_procedure()
>>>>   fixup! submodule--helper run-update-procedure: remove --suboid
>>>>   fixup! submodule--helper run-update-procedure: learn --remote
>>>>   fixup! submodule: move core cmd_update() logic to C
>>>>
>>>> Ævar Arnfjörð Bjarmason (3):
>>>>   builtin/submodule--helper.c: rename option variables to "opt"
>>>>   submodule--helper: don't use bitfield indirection for parse_options()
>>>>   submodule tests: test for init and update failure output
>>>
>>> I think sending a version of this with the fixups squashed in as a v8
>>> would be good, and perhaps addressing some of my comments.
>>>
>>> I don't know if my suggested split-up of "prep fixes" into another
>>> series would be a good thing to pursue overall, perhaps Junio will chime
>>> in on how he'd be most comfortable in merging this down. I'd think
>>> splitting such trivial fixes into their own series be easier to review,
>>> but perhaps not.
>>
>> Combing through the patches again, I couldn't really convince myself
>> that the patch 4..9 prep fixes make sense as obvious standalone fixes,
>> except maybe:
>>
>> - patch 4 submodule--helper: run update using child process struct
>> - patch 8 submodule tests: test for init and update failure output
>> - patch 9:  087bf43aba submodule--helper: remove update-module-mode
>>
>> But, since the 'final' patch (ignoring the fixup!-s) is consuming a huge
>> chunk of the work anyway, here's an alternative patch organization with
>> the fixup!-s squashed:
>>
>> = Move 'easy' and 'obviously correct' code from sh->C
>> - patches 8-9   Cleanup and introduce tests
>> - patches 1-4   Refactor existing functions, which enables..
>> - patches 10-14 Move 'obviously correct' pieces of logic from sh-> C
>>
>> = Finalize move from sh->C
>> i.e. combine "run-update-procedure" and "update-clone" into "update"
>> - patches 5,7     Cleanup and prep
>> - patches 6,15-16 Shrinking the diff
>> - patch 17        Implement "git submodule--helper update" 
>>
>> I'll send this if there are no objections :)
>
> Yes that sounds good, or rather, I haven't re-looked at that in detail,
> but I think if you think it makes sense we should go for it.
>
> Or rather, we should really be aiming to produce a patch series that
> makes sense in its current iteration, as opposed to optimizing for a
> diff against some ad-hoc re-roll I produced a few versions ago :)
Agreed, makes sense.
> Thanks again for working on this & picking this up. It's great to see
> progress in this area!

Thanks to you too for getting the ball rolling and lending me your thoughts :)

Junio C Hamano· Feb 13, 2022, 05:54 UTC · re: Glen Choo · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

Glen Choo <chooglen@google.com> writes:
> This reroll contains another 'easy' preparatory patch and the fixups I
> alluded to in v6 [1]. This isn't the split-up I described in the
> footnote of v6, but it gets the big patch (patch 17) to what I think is
> a reviewable state.
Thanks.

This seems to heavily conflict with "clone, submodule: pass partial clone filters to submodules, 2022-02-04" by Josh Steadmon <690d2316ad518ea4551821b2b3aa652996858475.1644034886.git.steadmon@google.com> in both builtins/submodule--helper.c and git-submodule.sh.

It also removes the code that "submodule: record superproject gitdir during 'update', 2022-02-03" by Emily Shaffer <20220203215914.683922-5-emilyshaffer@google.com>, so what the other topic ends up adding to the shell script needs to eventually be redone in the C code.

I think "superproject aware" topic would see a reroll due to a slight redesign. I am not sure how solid the design of the "pass down partial clone filter" topic is at this moment.

I may try to eject them tentatively and see how well this topic plays with the rest of the topics in flight.

Thanks.
Junio C Hamano· Feb 13, 2022, 06:14 UTC · re: Junio C Hamano · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

Junio C Hamano <gitster@pobox.com> writes:
Show 28 quoted lines
> Glen Choo <chooglen@google.com> writes:
>
>> This reroll contains another 'easy' preparatory patch and the fixups I
>> alluded to in v6 [1]. This isn't the split-up I described in the
>> footnote of v6, but it gets the big patch (patch 17) to what I think is
>> a reviewable state.
>
> Thanks.
>
> This seems to heavily conflict with "clone, submodule: pass partial
> clone filters to submodules, 2022-02-04" by Josh Steadmon
> <690d2316ad518ea4551821b2b3aa652996858475.1644034886.git.steadmon@google.com>
> in both builtins/submodule--helper.c and git-submodule.sh.
>
> It also removes the code that "submodule: record superproject gitdir
> during 'update', 2022-02-03" by Emily Shaffer
> <20220203215914.683922-5-emilyshaffer@google.com>, so what the other
> topic ends up adding to the shell script needs to eventually be
> redone in the C code.
>
> I think "superproject aware" topic would see a reroll due to a
> slight redesign.  I am not sure how solid the design of the
> "pass down partial clone filter" topic is at this moment.
>
> I may try to eject them tentatively and see how well this topic
> plays with the rest of the topics in flight.
>
> Thanks.

I can merge this to seen minus the above two topics and get it compile, but it also seems to have some interaction with 961b130d (branch: add --recurse-submodules option for branch creation, 2022-01-28) and makes the t3207, tests added by that other topic, fail X-<.

Glen Choo· Feb 14, 2022, 16:37 UTC · re: Junio C Hamano · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

Junio C Hamano <gitster@pobox.com> writes:
Show 14 quoted lines
>> This seems to heavily conflict with "clone, submodule: pass partial
>> clone filters to submodules, 2022-02-04" by Josh Steadmon
>> <690d2316ad518ea4551821b2b3aa652996858475.1644034886.git.steadmon@google.com>
>> in both builtins/submodule--helper.c and git-submodule.sh.
>>
>> It also removes the code that "submodule: record superproject gitdir
>> during 'update', 2022-02-03" by Emily Shaffer
>> <20220203215914.683922-5-emilyshaffer@google.com>, so what the other
>> topic ends up adding to the shell script needs to eventually be
>> redone in the C code.
>>
>> I think "superproject aware" topic would see a reroll due to a
>> slight redesign.  I am not sure how solid the design of the
>> "pass down partial clone filter" topic is at this moment.

Hm, I haven't looked at where the conflicts are yet, but I'll get to it as I'm reviewing the rest of the feedback.

And on that note, what do you think of Ævar's suggestion to split off the 'easy to review' and 'obvious' patches into their own preparatory series? I wonder if this would make it harder or easier to manage the conflicts.

Show 5 quoted lines
> I can merge this to seen minus the above two topics and get it
> compile, but it also seems to have some interaction with 961b130d
> (branch: add --recurse-submodules option for branch creation,
> 2022-01-28) and makes the t3207, tests added by that other topic,
> fail X-<.

Oof, that's embarrassing of me, let me take a look at that. There's a nontrivial chance that the "branch --recurse-submodules" tests caught an actual regression.

Ævar Arnfjörð Bjarmason· Feb 14, 2022, 17:19 UTC · re: Glen Choo · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

On Tue, Feb 15 2022, Glen Choo wrote:
Show 19 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>>> This seems to heavily conflict with "clone, submodule: pass partial
>>> clone filters to submodules, 2022-02-04" by Josh Steadmon
>>> <690d2316ad518ea4551821b2b3aa652996858475.1644034886.git.steadmon@google.com>
>>> in both builtins/submodule--helper.c and git-submodule.sh.
>>>
>>> It also removes the code that "submodule: record superproject gitdir
>>> during 'update', 2022-02-03" by Emily Shaffer
>>> <20220203215914.683922-5-emilyshaffer@google.com>, so what the other
>>> topic ends up adding to the shell script needs to eventually be
>>> redone in the C code.
>>>
>>> I think "superproject aware" topic would see a reroll due to a
>>> slight redesign.  I am not sure how solid the design of the
>>> "pass down partial clone filter" topic is at this moment.
>
> Hm, I haven't looked at where the conflicts are yet, but I'll get to it
> as I'm reviewing the rest of the feedback.

To save you some time, my v4 CL summarizes the semantic conflict between the two: https://lore.kernel.org/git/cover-v4-0.7-00000000000-20220127T143552Z-avarab@gmail.com/

I.e. Atharva Raykar had working C code to do what an older version of that superproject config series was doing, but the semantics changed in a later version. It needs some new usage of path.c (or similar) API adjusted, but I didn't (and still don't) have time to look into it.

Show 10 quoted lines
> [...]
>> I can merge this to seen minus the above two topics and get it
>> compile, but it also seems to have some interaction with 961b130d
>> (branch: add --recurse-submodules option for branch creation,
>> 2022-01-28) and makes the t3207, tests added by that other topic,
>> fail X-<.
>
> Oof, that's embarrassing of me, let me take a look at that. There's a
> nontrivial chance that the "branch --recurse-submodules" tests caught an
> actual regression.

FWIW one thing I did as an extra sanity check was to run the whole test suite with --tee with/without this series (or rather, my earlier version), and diffing the full test-results output (which you'll need to save in-between the two runs, and IIRC hack t/Makefile to stop removing it on successful runs).

There's a lot of differences in output due to general issues in the test suite output not being reproducable (writing timestamps etc.), but I could not find any issues with the "git submodule" output being different, of course we may not have tests, or it may be piped to /dev/null....

But I've found it to be a helpful additional validation technique for this series & others where I'm not as confident in the test coverage.

Glen Choo· Feb 15, 2022, 09:34 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 30 quoted lines
> On Tue, Feb 15 2022, Glen Choo wrote:
>
>> Junio C Hamano <gitster@pobox.com> writes:
>>
>>>> This seems to heavily conflict with "clone, submodule: pass partial
>>>> clone filters to submodules, 2022-02-04" by Josh Steadmon
>>>> <690d2316ad518ea4551821b2b3aa652996858475.1644034886.git.steadmon@google.com>
>>>> in both builtins/submodule--helper.c and git-submodule.sh.
>>>>
>>>> It also removes the code that "submodule: record superproject gitdir
>>>> during 'update', 2022-02-03" by Emily Shaffer
>>>> <20220203215914.683922-5-emilyshaffer@google.com>, so what the other
>>>> topic ends up adding to the shell script needs to eventually be
>>>> redone in the C code.
>>>>
>>>> I think "superproject aware" topic would see a reroll due to a
>>>> slight redesign.  I am not sure how solid the design of the
>>>> "pass down partial clone filter" topic is at this moment.
>>
>> Hm, I haven't looked at where the conflicts are yet, but I'll get to it
>> as I'm reviewing the rest of the feedback.
>
> To save you some time, my v4 CL summarizes the semantic conflict between
> the two:
> https://lore.kernel.org/git/cover-v4-0.7-00000000000-20220127T143552Z-avarab@gmail.com/
>
> I.e. Atharva Raykar had working C code to do what an older version of
> that superproject config series was doing, but the semantics changed in
> a later version. It needs some new usage of path.c (or similar) API
> adjusted, but I didn't (and still don't) have time to look into it.
Ah, thanks for the reminder. That should help.
Show 24 quoted lines
>>> I can merge this to seen minus the above two topics and get it
>>> compile, but it also seems to have some interaction with 961b130d
>>> (branch: add --recurse-submodules option for branch creation,
>>> 2022-01-28) and makes the t3207, tests added by that other topic,
>>> fail X-<.
>>
>> Oof, that's embarrassing of me, let me take a look at that. There's a
>> nontrivial chance that the "branch --recurse-submodules" tests caught an
>> actual regression.
>
> FWIW one thing I did as an extra sanity check was to run the whole test
> suite with --tee with/without this series (or rather, my earlier
> version), and diffing the full test-results output (which you'll need to
> save in-between the two runs, and IIRC hack t/Makefile to stop removing
> it on successful runs).
>
> There's a lot of differences in output due to general issues in the test
> suite output not being reproducable (writing timestamps etc.), but I
> could not find any issues with the "git submodule" output being
> different, of course we may not have tests, or it may be piped to
> /dev/null....
>
> But I've found it to be a helpful additional validation technique for
> this series & others where I'm not as confident in the test coverage.

Thanks for the tip! I hadn't considered trying this, but this makes a lot of sense. I can see this being even more useful for this series since it's supposed to be a faithful conversion of sh->c.

Junio C Hamano· Feb 14, 2022, 17:34 UTC · re: Glen Choo · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

Glen Choo <chooglen@google.com> writes:
Show 7 quoted lines
> Hm, I haven't looked at where the conflicts are yet, but I'll get to it
> as I'm reviewing the rest of the feedback.
>
> And on that note, what do you think of Ævar's suggestion to split off
> the 'easy to review' and 'obvious' patches into their own preparatory
> series? I wonder if this would make it harder or easier to manage the
> conflicts.

It depends on how small an interaction the "obvious and easy" part has with topics in flight. In the best case, if there aren't any the preparatory series may even graduate before the other topics that interfere with the main part of this series becomes ready.

In a worse case, what the preparatory work to lay more solid foundation wants to do may contradict what some of these topics in flight want to do. Such semantic conflicts need to be resolved before the main part (and these interfering topics) can move forward, and with "split off", the core of the contradicting wish may become easier to see and what needs to be resolved may become clearer.

So, I do not think of a way for such a split to make things harder for later. Of course, the "easy to review" and "obvious" part has to be justifiable on its own, i.e. "a larger series wants to build on this foundation and for it to work this part must be done in this way", when the other topics wants to do the part in question differently, becomes a much weaker justification. But if it is truly "obvious", it is unlikely that the benefit of the change becomes harder to justify.

Glen Choo· Feb 15, 2022, 09:31 UTC · re: Junio C Hamano · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

Junio C Hamano <gitster@pobox.com> writes:
Show 31 quoted lines
> Glen Choo <chooglen@google.com> writes:
>
>> Hm, I haven't looked at where the conflicts are yet, but I'll get to it
>> as I'm reviewing the rest of the feedback.
>>
>> And on that note, what do you think of Ævar's suggestion to split off
>> the 'easy to review' and 'obvious' patches into their own preparatory
>> series? I wonder if this would make it harder or easier to manage the
>> conflicts.
>
> It depends on how small an interaction the "obvious and easy" part
> has with topics in flight.  In the best case, if there aren't any
> the preparatory series may even graduate before the other topics
> that interfere with the main part of this series becomes ready.
>
> In a worse case, what the preparatory work to lay more solid
> foundation wants to do may contradict what some of these topics in
> flight want to do.  Such semantic conflicts need to be resolved
> before the main part (and these interfering topics) can move
> forward, and with "split off", the core of the contradicting wish
> may become easier to see and what needs to be resolved may become
> clearer.
>
> So, I do not think of a way for such a split to make things harder
> for later.  Of course, the "easy to review" and "obvious" part has
> to be justifiable on its own, i.e. "a larger series wants to build
> on this foundation and for it to work this part must be done in this
> way", when the other topics wants to do the part in question
> differently, becomes a much weaker justification.  But if it is
> truly "obvious", it is unlikely that the benefit of the change
> becomes harder to justify.

Thanks for sharing your thought process. That makes sense, I'll do that :)

Glen Choo· Feb 15, 2022, 09:47 UTC · re: Glen Choo · lore

Re: [PATCH v7 00/20] submodule: convert the rest of 'update' to C

Glen Choo <chooglen@google.com> writes:
Show 11 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>> I can merge this to seen minus the above two topics and get it
>> compile, but it also seems to have some interaction with 961b130d
>> (branch: add --recurse-submodules option for branch creation,
>> 2022-01-28) and makes the t3207, tests added by that other topic,
>> fail X-<.
>
> Oof, that's embarrassing of me, let me take a look at that. There's a
> nontrivial chance that the "branch --recurse-submodules" tests caught an
> actual regression.

Looks like this is the case - t3207 caught a regression in how "git submodule update" sets the refspec of nested submodules.

Looks like our concerns about the test suite are well-founded..
Glen Choo· Oct 14, 2021, 21:50 UTC · re: Atharva Raykar · lore

Re: [PATCH 00/13] submodule: convert the rest of 'update' to C

> This series builds upon the previous conversion work on 'submodule update' and
> moves out all of that shell logic in 'git-submodule.sh' into
> 'builtin/submodule--helper.c'.

Hey Atharva! I'm working on a series that will teach "git branch" how to handle "--recurse-submodules". I plan to do this in-process because I think this will take less overall effort than using child processes, and to make it happen, I'm planning to add a helper function like "for_each_submodule()", which would call a C callback function on each submodule.

This is conceptually similar to "git submodule foreach" and for_each_listed_submodule() (though not exactly equivalent), so I'm reaching out to you in case this work is already on your radar. If so, and if it is coming soon, it might be easier to for me to base my work off yours instead of duplicating our efforts :)

Atharva Raykar· Oct 15, 2021, 09:13 UTC · re: Glen Choo · lore

Re: [PATCH 00/13] submodule: convert the rest of 'update' to C

Glen Choo <chooglen@google.com> writes:
Show 16 quoted lines
>> This series builds upon the previous conversion work on 'submodule update' and
>> moves out all of that shell logic in 'git-submodule.sh' into
>> 'builtin/submodule--helper.c'.
>
> Hey Atharva! I'm working on a series that will teach "git branch" how to
> handle "--recurse-submodules". I plan to do this in-process because I
> think this will take less overall effort than using child processes, and
> to make it happen, I'm planning to add a helper function like
> "for_each_submodule()", which would call a C callback function on each
> submodule.
>
> This is conceptually similar to "git submodule foreach" and
> for_each_listed_submodule() (though not exactly equivalent), so I'm
> reaching out to you in case this work is already on your radar. If so,
> and if it is coming soon, it might be easier to for me to base my work
> off yours instead of duplicating our efforts :)

Thanks for reaching out. I don't have anything like this on my radar, so feel free to go ahead with your plan :)

← back to recent threads