All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/2] branch: implement in-process --recurse-submodules
@ 2021-09-21 23:25 Glen Choo
  2021-09-21 23:25 ` [RFC PATCH 1/2] refs: pass struct repository *r through to write_ref_to_lockfile() Glen Choo
  2021-09-21 23:25 ` [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation Glen Choo
  0 siblings, 2 replies; 7+ messages in thread
From: Glen Choo @ 2021-09-21 23:25 UTC (permalink / raw)
  To: git; +Cc: Glen Choo

These patches are an attempt to implement git branch
--recurse-submodules in-process. This is part of the general submodule
UX improvements discussed in [1]. Specifically, this is the use case
discussed in the section titled "Create Mode (git switch -c / git
checkout -b)", but without checking out the newly created branch.

Doing this in-process allows "git switch -c" and "git checkout -b" to
reuse create_branch(), instead of relying on a child process to create
the branch. create_branch() nominally takes a struct repository *
parameter, but there is some hidden reliance on the_repository and
global state.

Broadly speaking, I'd like to know how we should approach
"--recurse-submodules", particularly when we are implementing [1]. I'm
sending these patches as an RFC because I think this is somewhat
indicative of submodule issues.

In this patchset, branching works slightly differently between the
superproject and submodule (skip ahead for specifics). There are two
very obvious alternatives that can address this:

* A: only implement --recurse-submodules behavior after we are able to
  eliminate any kind of dependence on the_repository/global state that
  shouldn't be shared.
* B: implement --recurse-submodules as child processes, which won't be
  bothered by global state.

My cursory thoughts on the matter are that A seems like a good direction
for code health, but it seems difficult to do in practice. B seems
hacky, but it might be a good stopgap while we work on A.

Obviously these aren't the only options and this isn't the end of the
discussion, so I'd appreciate any thoughts on the matter :)

Changes:
* refactor the refs.h functions to accept struct repository * where
  reasonable.
* add two new functions to refs.h that accept struct
  repository *, repo_ref_transaction_commit() and
  repo_ref_transaction_prepare().
* change ref_transaction_commit() and ref_transaction_prepare() to be
  thin wrappers of their repo_ counterparts.
* use repo_ref_transaction_commit() in create_branch()
* add a create_branches() function to builtin/branch.c that recursively
  creates branches in submodules

What doesn't work (marked with NEEDSWORK):
* branch tracking is disabled for submodules because remotes.c only
  holds state for a single repository
* similarly, bare repository checking does not work as expected because
  environment.c only holds state for a single repository

What is questionable (specific to these patches, not
--recurse-submodules in general):
* files-backend.c only uses the_repository to validate the oid being
  used and it is the only ref backend that does so. Instead of passing
  struct repository * through more places in refs.h, we might be able to
  eliminate this check altogether. This is discussed briefly in [2].
* create_branches() implements its own submodule iterating, which is
  similar to that of ls-files, but wholly different from
  submodule--helper.c.

[1] https://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/
[2] https://lore.kernel.org/git/20210916172432.1073546-1-jonathantanmy@google.com/

Glen Choo (2):
  refs: pass struct repository *r through to write_ref_to_lockfile()
  branch: add --recurse-submodules option for branch creation

 branch.c                  | 26 +++++++++------
 branch.h                  |  4 +--
 builtin/branch.c          | 69 ++++++++++++++++++++++++++++++++++++---
 builtin/checkout.c        |  4 +--
 refs.c                    | 21 ++++++------
 refs.h                    | 18 +++++++---
 refs/debug.c              | 14 ++++----
 refs/files-backend.c      | 30 ++++++++---------
 refs/packed-backend.c     |  7 ++--
 refs/refs-internal.h      |  7 ++--
 t/helper/test-ref-store.c |  2 +-
 t/t3200-branch.sh         | 58 ++++++++++++++++++++++++++++++++
 12 files changed, 199 insertions(+), 61 deletions(-)

-- 
2.33.0.464.g1972c5931b-goog


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [RFC PATCH 1/2] refs: pass struct repository *r through to write_ref_to_lockfile()
  2021-09-21 23:25 [RFC PATCH 0/2] branch: implement in-process --recurse-submodules Glen Choo
@ 2021-09-21 23:25 ` Glen Choo
  2021-09-21 23:25 ` [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation Glen Choo
  1 sibling, 0 replies; 7+ messages in thread
From: Glen Choo @ 2021-09-21 23:25 UTC (permalink / raw)
  To: git; +Cc: Glen Choo

In refs/files-backend.c, write_ref_to_lockfile() uses the_repository to
validate the oid of the ref being written. Thus, any function that
includes write_ref_to_lockfile() in its call chain has an implicit
dependence on the odb of the_repository. This is undesirable in the case
where we are using oids that are not in the_repository, e.g. when we are
updating refs in a submodule.

Let's fix this by passing struct repository * as a parameter through to
write_ref_to_lockfile().

To avoid breaking existing call sites, the existing function
signatures in refs.h are preserved. struct repository * is only passed
to new functions; these new functions have names prefixed with repo_.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 refs.c                    | 21 +++++++++++----------
 refs.h                    | 18 ++++++++++++++----
 refs/debug.c              | 14 ++++++++------
 refs/files-backend.c      | 30 +++++++++++++++---------------
 refs/packed-backend.c     |  7 ++++---
 refs/refs-internal.h      |  7 ++++---
 t/helper/test-ref-store.c |  2 +-
 7 files changed, 57 insertions(+), 42 deletions(-)

diff --git a/refs.c b/refs.c
index 8b9f7c3a80..c217512cd3 100644
--- a/refs.c
+++ b/refs.c
@@ -2105,7 +2105,7 @@ static int run_transaction_hook(struct ref_transaction *transaction,
 	return ret;
 }
 
-int ref_transaction_prepare(struct ref_transaction *transaction,
+int repo_ref_transaction_prepare(struct repository *r, struct ref_transaction *transaction,
 			    struct strbuf *err)
 {
 	struct ref_store *refs = transaction->ref_store;
@@ -2132,7 +2132,7 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
 		return -1;
 	}
 
-	ret = refs->be->transaction_prepare(refs, transaction, err);
+	ret = refs->be->transaction_prepare(r, refs, transaction, err);
 	if (ret)
 		return ret;
 
@@ -2172,7 +2172,8 @@ int ref_transaction_abort(struct ref_transaction *transaction,
 	return ret;
 }
 
-int ref_transaction_commit(struct ref_transaction *transaction,
+int repo_ref_transaction_commit(struct repository *r,
+			   struct ref_transaction *transaction,
 			   struct strbuf *err)
 {
 	struct ref_store *refs = transaction->ref_store;
@@ -2181,7 +2182,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,
 	switch (transaction->state) {
 	case REF_TRANSACTION_OPEN:
 		/* Need to prepare first. */
-		ret = ref_transaction_prepare(transaction, err);
+		ret = repo_ref_transaction_prepare(r, transaction, err);
 		if (ret)
 			return ret;
 		break;
@@ -2421,36 +2422,36 @@ int delete_refs(const char *msg, struct string_list *refnames,
 	return refs_delete_refs(get_main_ref_store(the_repository), msg, refnames, flags);
 }
 
-int refs_rename_ref(struct ref_store *refs, const char *oldref,
+int refs_rename_ref(struct repository *r, struct ref_store *refs, const char *oldref,
 		    const char *newref, const char *logmsg)
 {
 	char *msg;
 	int retval;
 
 	msg = normalize_reflog_message(logmsg);
-	retval = refs->be->rename_ref(refs, oldref, newref, msg);
+	retval = refs->be->rename_ref(r, refs, oldref, newref, msg);
 	free(msg);
 	return retval;
 }
 
 int rename_ref(const char *oldref, const char *newref, const char *logmsg)
 {
-	return refs_rename_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
+	return refs_rename_ref(the_repository, get_main_ref_store(the_repository), oldref, newref, logmsg);
 }
 
-int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
+int refs_copy_existing_ref(struct repository *r, struct ref_store *refs, const char *oldref,
 		    const char *newref, const char *logmsg)
 {
 	char *msg;
 	int retval;
 
 	msg = normalize_reflog_message(logmsg);
-	retval = refs->be->copy_ref(refs, oldref, newref, msg);
+	retval = refs->be->copy_ref(r, refs, oldref, newref, msg);
 	free(msg);
 	return retval;
 }
 
 int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
 {
-	return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
+	return refs_copy_existing_ref(the_repository, get_main_ref_store(the_repository), oldref, newref, logmsg);
 }
diff --git a/refs.h b/refs.h
index 48970dfc7e..157d14d7a3 100644
--- a/refs.h
+++ b/refs.h
@@ -524,13 +524,13 @@ char *refs_shorten_unambiguous_ref(struct ref_store *refs,
 char *shorten_unambiguous_ref(const char *refname, int strict);
 
 /** rename ref, return 0 on success **/
-int refs_rename_ref(struct ref_store *refs, const char *oldref,
+int refs_rename_ref(struct repository *r, struct ref_store *refs, const char *oldref,
 		    const char *newref, const char *logmsg);
 int rename_ref(const char *oldref, const char *newref,
 			const char *logmsg);
 
 /** copy ref, return 0 on success **/
-int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
+int refs_copy_existing_ref(struct repository *r, struct ref_store *refs, const char *oldref,
 		    const char *newref, const char *logmsg);
 int copy_existing_ref(const char *oldref, const char *newref,
 			const char *logmsg);
@@ -706,8 +706,13 @@ int ref_transaction_verify(struct ref_transaction *transaction,
  * Callers who don't need such fine-grained control over committing
  * reference transactions should just call `ref_transaction_commit()`.
  */
-int ref_transaction_prepare(struct ref_transaction *transaction,
+int repo_ref_transaction_prepare(struct repository *r, struct ref_transaction *transaction,
 			    struct strbuf *err);
+static inline int ref_transaction_prepare(struct ref_transaction *transaction,
+					  struct strbuf *err)
+{
+	return repo_ref_transaction_prepare(the_repository, transaction, err);
+}
 
 /*
  * Commit all of the changes that have been queued in transaction, as
@@ -716,8 +721,13 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
  * transaction, write an error message to `err`, and return one of the
  * `TRANSACTION_*` constants
  */
-int ref_transaction_commit(struct ref_transaction *transaction,
+int repo_ref_transaction_commit(struct repository *r, struct ref_transaction *transaction,
 			   struct strbuf *err);
+static inline int ref_transaction_commit(struct ref_transaction *transaction,
+					 struct strbuf *err)
+{
+	return repo_ref_transaction_commit(the_repository, transaction, err);
+}
 
 /*
  * Abort `transaction`, which has been begun and possibly prepared,
diff --git a/refs/debug.c b/refs/debug.c
index 1a7a9e11cf..224ee76563 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -38,14 +38,15 @@ static int debug_init_db(struct ref_store *refs, struct strbuf *err)
 	return res;
 }
 
-static int debug_transaction_prepare(struct ref_store *refs,
+static int debug_transaction_prepare(struct repository *r,
+				     struct ref_store *refs,
 				     struct ref_transaction *transaction,
 				     struct strbuf *err)
 {
 	struct debug_ref_store *drefs = (struct debug_ref_store *)refs;
 	int res;
 	transaction->ref_store = drefs->refs;
-	res = drefs->refs->be->transaction_prepare(drefs->refs, transaction,
+	res = drefs->refs->be->transaction_prepare(r, drefs->refs, transaction,
 						   err);
 	trace_printf_key(&trace_refs, "transaction_prepare: %d\n", res);
 	return res;
@@ -153,23 +154,24 @@ static int debug_delete_refs(struct ref_store *ref_store, const char *msg,
 	return res;
 }
 
-static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,
+static int debug_rename_ref(struct repository *r,
+			    struct ref_store *ref_store, const char *oldref,
 			    const char *newref, const char *logmsg)
 {
 	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
-	int res = drefs->refs->be->rename_ref(drefs->refs, oldref, newref,
+	int res = drefs->refs->be->rename_ref(r, drefs->refs, oldref, newref,
 					      logmsg);
 	trace_printf_key(&trace_refs, "rename_ref: %s -> %s \"%s\": %d\n", oldref, newref,
 		logmsg, res);
 	return res;
 }
 
-static int debug_copy_ref(struct ref_store *ref_store, const char *oldref,
+static int debug_copy_ref(struct repository *r, struct ref_store *ref_store, const char *oldref,
 			  const char *newref, const char *logmsg)
 {
 	struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
 	int res =
-		drefs->refs->be->copy_ref(drefs->refs, oldref, newref, logmsg);
+		drefs->refs->be->copy_ref(r, drefs->refs, oldref, newref, logmsg);
 	trace_printf_key(&trace_refs, "copy_ref: %s -> %s \"%s\": %d\n", oldref, newref,
 		logmsg, res);
 	return res;
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 74c0385873..2bd6115484 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -1308,14 +1308,14 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
 	return ret;
 }
 
-static int write_ref_to_lockfile(struct ref_lock *lock,
+static int write_ref_to_lockfile(struct repository *r, struct ref_lock *lock,
 				 const struct object_id *oid, struct strbuf *err);
 static int commit_ref_update(struct files_ref_store *refs,
 			     struct ref_lock *lock,
 			     const struct object_id *oid, const char *logmsg,
 			     struct strbuf *err);
 
-static int files_copy_or_rename_ref(struct ref_store *ref_store,
+static int files_copy_or_rename_ref(struct repository *r, struct ref_store *ref_store,
 			    const char *oldrefname, const char *newrefname,
 			    const char *logmsg, int copy)
 {
@@ -1427,7 +1427,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
 	}
 	oidcpy(&lock->old_oid, &orig_oid);
 
-	if (write_ref_to_lockfile(lock, &orig_oid, &err) ||
+	if (write_ref_to_lockfile(r, lock, &orig_oid, &err) ||
 	    commit_ref_update(refs, lock, &orig_oid, logmsg, &err)) {
 		error("unable to write current sha1 into %s: %s", newrefname, err.buf);
 		strbuf_release(&err);
@@ -1448,7 +1448,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
 
 	flag = log_all_ref_updates;
 	log_all_ref_updates = LOG_REFS_NONE;
-	if (write_ref_to_lockfile(lock, &orig_oid, &err) ||
+	if (write_ref_to_lockfile(r, lock, &orig_oid, &err) ||
 	    commit_ref_update(refs, lock, &orig_oid, NULL, &err)) {
 		error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
 		strbuf_release(&err);
@@ -1472,19 +1472,19 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
 	return ret;
 }
 
-static int files_rename_ref(struct ref_store *ref_store,
+static int files_rename_ref(struct repository *r, struct ref_store *ref_store,
 			    const char *oldrefname, const char *newrefname,
 			    const char *logmsg)
 {
-	return files_copy_or_rename_ref(ref_store, oldrefname,
+	return files_copy_or_rename_ref(r, ref_store, oldrefname,
 				 newrefname, logmsg, 0);
 }
 
-static int files_copy_ref(struct ref_store *ref_store,
+static int files_copy_ref(struct repository *r, struct ref_store *ref_store,
 			    const char *oldrefname, const char *newrefname,
 			    const char *logmsg)
 {
-	return files_copy_or_rename_ref(ref_store, oldrefname,
+	return files_copy_or_rename_ref(r, ref_store, oldrefname,
 				 newrefname, logmsg, 1);
 }
 
@@ -1683,14 +1683,14 @@ static int files_log_ref_write(struct files_ref_store *refs,
  * Write oid into the open lockfile, then close the lockfile. On
  * errors, rollback the lockfile, fill in *err and return -1.
  */
-static int write_ref_to_lockfile(struct ref_lock *lock,
+static int write_ref_to_lockfile(struct repository *r, struct ref_lock *lock,
 				 const struct object_id *oid, struct strbuf *err)
 {
 	static char term = '\n';
 	struct object *o;
 	int fd;
 
-	o = parse_object(the_repository, oid);
+	o = parse_object(r, oid);
 	if (!o) {
 		strbuf_addf(err,
 			    "trying to write ref '%s' with nonexistent object %s",
@@ -2390,7 +2390,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
  * - If it is an update of head_ref, add a corresponding REF_LOG_ONLY
  *   update of HEAD.
  */
-static int lock_ref_for_update(struct files_ref_store *refs,
+static int lock_ref_for_update(struct repository *r, struct files_ref_store *refs,
 			       struct ref_update *update,
 			       struct ref_transaction *transaction,
 			       const char *head_ref,
@@ -2496,7 +2496,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
 			 * The reference already has the desired
 			 * value, so we don't need to write it.
 			 */
-		} else if (write_ref_to_lockfile(lock, &update->new_oid,
+		} else if (write_ref_to_lockfile(r, lock, &update->new_oid,
 						 err)) {
 			char *write_err = strbuf_detach(err, NULL);
 
@@ -2577,7 +2577,7 @@ static void files_transaction_cleanup(struct files_ref_store *refs,
 	transaction->state = REF_TRANSACTION_CLOSED;
 }
 
-static int files_transaction_prepare(struct ref_store *ref_store,
+static int files_transaction_prepare(struct repository *r, struct ref_store *ref_store,
 				     struct ref_transaction *transaction,
 				     struct strbuf *err)
 {
@@ -2667,7 +2667,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
 	for (i = 0; i < transaction->nr; i++) {
 		struct ref_update *update = transaction->updates[i];
 
-		ret = lock_ref_for_update(refs, update, transaction,
+		ret = lock_ref_for_update(r, refs, update, transaction,
 					  head_ref, &affected_refnames, err);
 		if (ret)
 			goto cleanup;
@@ -2708,7 +2708,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
 
 		if (is_packed_transaction_needed(refs->packed_ref_store,
 						 packed_transaction)) {
-			ret = ref_transaction_prepare(packed_transaction, err);
+			ret = repo_ref_transaction_prepare(r, packed_transaction, err);
 			/*
 			 * A failure during the prepare step will abort
 			 * itself, but not free. Do that now, and disconnect
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index f8aa97d799..e6aa1841ba 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1403,7 +1403,8 @@ static void packed_transaction_cleanup(struct packed_ref_store *refs,
 	transaction->state = REF_TRANSACTION_CLOSED;
 }
 
-static int packed_transaction_prepare(struct ref_store *ref_store,
+static int packed_transaction_prepare(struct repository *r,
+				      struct ref_store *ref_store,
 				      struct ref_transaction *transaction,
 				      struct strbuf *err)
 {
@@ -1577,14 +1578,14 @@ static int packed_create_symref(struct ref_store *ref_store,
 	BUG("packed reference store does not support symrefs");
 }
 
-static int packed_rename_ref(struct ref_store *ref_store,
+static int packed_rename_ref(struct repository *r, struct ref_store *ref_store,
 			    const char *oldrefname, const char *newrefname,
 			    const char *logmsg)
 {
 	BUG("packed reference store does not support renaming references");
 }
 
-static int packed_copy_ref(struct ref_store *ref_store,
+static int packed_copy_ref(struct repository *r, struct ref_store *ref_store,
 			   const char *oldrefname, const char *newrefname,
 			   const char *logmsg)
 {
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 3155708345..1b7a68db8c 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -530,7 +530,8 @@ typedef struct ref_store *ref_store_init_fn(const char *gitdir,
 
 typedef int ref_init_db_fn(struct ref_store *refs, struct strbuf *err);
 
-typedef int ref_transaction_prepare_fn(struct ref_store *refs,
+typedef int ref_transaction_prepare_fn(struct repository *r,
+				       struct ref_store *refs,
 				       struct ref_transaction *transaction,
 				       struct strbuf *err);
 
@@ -553,10 +554,10 @@ typedef int create_symref_fn(struct ref_store *ref_store,
 			     const char *logmsg);
 typedef int delete_refs_fn(struct ref_store *ref_store, const char *msg,
 			   struct string_list *refnames, unsigned int flags);
-typedef int rename_ref_fn(struct ref_store *ref_store,
+typedef int rename_ref_fn(struct repository *r, struct ref_store *ref_store,
 			  const char *oldref, const char *newref,
 			  const char *logmsg);
-typedef int copy_ref_fn(struct ref_store *ref_store,
+typedef int copy_ref_fn(struct repository *r, struct ref_store *ref_store,
 			  const char *oldref, const char *newref,
 			  const char *logmsg);
 
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index b314b81a45..ad47488e5b 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -99,7 +99,7 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv)
 	const char *newref = notnull(*argv++, "newref");
 	const char *logmsg = *argv++;
 
-	return refs_rename_ref(refs, oldref, newref, logmsg);
+	return refs_rename_ref(the_repository, refs, oldref, newref, logmsg);
 }
 
 static int each_ref(const char *refname, const struct object_id *oid,
-- 
2.33.0.464.g1972c5931b-goog


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation
  2021-09-21 23:25 [RFC PATCH 0/2] branch: implement in-process --recurse-submodules Glen Choo
  2021-09-21 23:25 ` [RFC PATCH 1/2] refs: pass struct repository *r through to write_ref_to_lockfile() Glen Choo
@ 2021-09-21 23:25 ` Glen Choo
  2021-09-22 11:10   ` Ævar Arnfjörð Bjarmason
  2021-09-22 12:28   ` Philippe Blain
  1 sibling, 2 replies; 7+ messages in thread
From: Glen Choo @ 2021-09-21 23:25 UTC (permalink / raw)
  To: git; +Cc: Glen Choo

When working with a superproject and submodules, it can be helpful to
create topic branches to coordinate the work between each repository.

Teach cmd_branch to accept the --recurse-submodules option when creating
branches. When specified, like

  git branch --recurse-submodules topic

git-branch will create the "topic" branch in the superproject and all
submodules.

recurse_submodules is only supported for creating a branch. git-branch
will fail if the user attempts to use --recurse-submodules with anything
other than creating a branch. If a user has submodule.recurse in their
config, git-branch will use --recurse-submodules for creating a branch,
but will treat it as unset for any other operation.

There is no easy way to get the remotes of a submodule because remotes.c
stores state as static variables. As such, branch tracking is disabled
when using --recurse-submodules.

Signed-off-by: Glen Choo <chooglen@google.com>
---
 branch.c           | 26 ++++++++++-------
 branch.h           |  4 +--
 builtin/branch.c   | 69 ++++++++++++++++++++++++++++++++++++++++++----
 builtin/checkout.c |  4 +--
 t/t3200-branch.sh  | 58 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 142 insertions(+), 19 deletions(-)

diff --git a/branch.c b/branch.c
index 07a46430b3..1acf4aa407 100644
--- a/branch.c
+++ b/branch.c
@@ -137,6 +137,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 	struct tracking tracking;
 	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
 
+	if (track == BRANCH_TRACK_NEVER)
+		return;
+
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
 	if (for_each_remote(find_tracked_branch, &tracking))
@@ -183,12 +186,12 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
  * Return 1 if the named branch already exists; return 0 otherwise.
  * Fill ref with the full refname for the branch.
  */
-int validate_branchname(const char *name, struct strbuf *ref)
+int validate_branchname(struct repository *r, const char *name, struct strbuf *ref)
 {
 	if (strbuf_check_branch_ref(ref, name))
 		die(_("'%s' is not a valid branch name."), name);
 
-	return ref_exists(ref->buf);
+	return refs_ref_exists(get_main_ref_store(r), ref->buf);
 }
 
 /*
@@ -197,11 +200,11 @@ int validate_branchname(const char *name, struct strbuf *ref)
  * Return 1 if the named branch already exists; return 0 otherwise.
  * Fill ref with the full refname for the branch.
  */
-int validate_new_branchname(const char *name, struct strbuf *ref, int force)
+int validate_new_branchname(struct repository *r, const char *name, struct strbuf *ref, int force)
 {
 	const char *head;
 
-	if (!validate_branchname(name, ref))
+	if (!validate_branchname(r, name, ref))
 		return 0;
 
 	if (!force)
@@ -209,6 +212,9 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force)
 		    ref->buf + strlen("refs/heads/"));
 
 	head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+	/*
+	 * NEEDSWORK is_bare_repository() references the_repository.
+	 */
 	if (!is_bare_repository() && head && !strcmp(head, ref->buf))
 		die(_("Cannot force update the current branch."));
 
@@ -260,8 +266,8 @@ void create_branch(struct repository *r,
 		explicit_tracking = 1;
 
 	if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)
-	    ? validate_branchname(name, &ref)
-	    : validate_new_branchname(name, &ref, force)) {
+	    ? validate_branchname(r, name, &ref)
+	    : validate_new_branchname(r, name, &ref, force)) {
 		if (!force)
 			dont_change_ref = 1;
 		else
@@ -269,7 +275,7 @@ void create_branch(struct repository *r,
 	}
 
 	real_ref = NULL;
-	if (get_oid_mb(start_name, &oid)) {
+	if (repo_get_oid_mb(r, start_name, &oid)) {
 		if (explicit_tracking) {
 			if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {
 				error(_(upstream_missing), start_name);
@@ -281,7 +287,7 @@ void create_branch(struct repository *r,
 		die(_("Not a valid object name: '%s'."), start_name);
 	}
 
-	switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {
+	switch (repo_dwim_ref(r, start_name, strlen(start_name), &oid, &real_ref, 0)) {
 	case 0:
 		/* Not branching from any existing branch */
 		if (explicit_tracking)
@@ -319,12 +325,12 @@ void create_branch(struct repository *r,
 		else
 			msg = xstrfmt("branch: Created from %s", start_name);
 
-		transaction = ref_transaction_begin(&err);
+		transaction = ref_store_transaction_begin(get_main_ref_store(r), &err);
 		if (!transaction ||
 		    ref_transaction_update(transaction, ref.buf,
 					   &oid, forcing ? NULL : null_oid(),
 					   0, msg, &err) ||
-		    ref_transaction_commit(transaction, &err))
+		    repo_ref_transaction_commit(r, transaction, &err))
 			die("%s", err.buf);
 		ref_transaction_free(transaction);
 		strbuf_release(&err);
diff --git a/branch.h b/branch.h
index df0be61506..5fb9ed7e03 100644
--- a/branch.h
+++ b/branch.h
@@ -50,7 +50,7 @@ void create_branch(struct repository *r,
  * Return 1 if the named branch already exists; return 0 otherwise.
  * Fill ref with the full refname for the branch.
  */
-int validate_branchname(const char *name, struct strbuf *ref);
+int validate_branchname(struct repository *r, const char *name, struct strbuf *ref);
 
 /*
  * Check if a branch 'name' can be created as a new branch; die otherwise.
@@ -58,7 +58,7 @@ int validate_branchname(const char *name, struct strbuf *ref);
  * Return 1 if the named branch already exists; return 0 otherwise.
  * Fill ref with the full refname for the branch.
  */
-int validate_new_branchname(const char *name, struct strbuf *ref, int force);
+int validate_new_branchname(struct repository *r, const char *name, struct strbuf *ref, int force);
 
 /*
  * Remove information about the merge state on the current
diff --git a/builtin/branch.c b/builtin/branch.c
index 03c7b7253a..58a730b275 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -24,6 +24,7 @@
 #include "worktree.h"
 #include "help.h"
 #include "commit-reach.h"
+#include "submodule-config.h"
 
 static const char * const builtin_branch_usage[] = {
 	N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
@@ -38,6 +39,7 @@ static const char * const builtin_branch_usage[] = {
 
 static const char *head;
 static struct object_id head_oid;
+static int recurse_submodules = 0;
 
 static int branch_use_color = -1;
 static char branch_colors[][COLOR_MAXLEN] = {
@@ -100,6 +102,11 @@ static int git_branch_config(const char *var, const char *value, void *cb)
 			return config_error_nonbool(var);
 		return color_parse(value, branch_colors[slot]);
 	}
+	if (!strcmp(var, "submodule.recurse")) {
+		recurse_submodules = git_config_bool(var, value);
+		return 0;
+	}
+
 	return git_color_default_config(var, value, cb);
 }
 
@@ -190,6 +197,50 @@ static void delete_branch_config(const char *branchname)
 	strbuf_release(&buf);
 }
 
+static void create_branches(struct repository *r, const char *name,
+			   const char *start_name, int force,
+			   int clobber_head_ok, int reflog, int quiet,
+			   enum branch_track track)
+{
+	int i = 0;
+	struct repository subrepo;
+	const char *r_start_name;
+	const struct submodule *sub;
+
+	r_start_name = strcmp(start_name, "HEAD") ? start_name :
+		refs_resolve_refdup(get_main_ref_store(r), start_name, 0, NULL, NULL);
+	if (strcmp(r_start_name, "HEAD"))
+		skip_prefix(r_start_name, "refs/heads/", &r_start_name);
+	create_branch(r, name, r_start_name, force, clobber_head_ok, reflog,
+		      quiet, track);
+
+	if (!recurse_submodules) {
+		return;
+	}
+
+	if (repo_read_index(r) < 0)
+		die(_("index file corrupt"));
+
+	for (i = 0; i < r->index->cache_nr; i++) {
+		const struct cache_entry *ce = r->index->cache[i];
+		if (!S_ISGITLINK(ce->ce_mode))
+			continue;
+		sub = submodule_from_path(r, null_oid(), ce->name);
+		if (repo_submodule_init(&subrepo, r, sub) < 0) {
+			warning(_("Unable to initialize subrepo %s, skipping."), ce->name);
+			continue;
+		}
+		/*
+		 * NEEDSWORK: branch tracking is not supported for
+		 * submodules because it is not possible to get the
+		 * remotes of a submodule.
+		 */
+		create_branches(&subrepo, name, start_name, force,
+				clobber_head_ok, reflog, quiet, BRANCH_TRACK_NEVER);
+		repo_clear(&subrepo);
+	}
+}
+
 static int delete_branches(int argc, const char **argv, int force, int kinds,
 			   int quiet)
 {
@@ -531,9 +582,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 	 * cause the worktree to become inconsistent with HEAD, so allow it.
 	 */
 	if (!strcmp(oldname, newname))
-		validate_branchname(newname, &newref);
+		validate_branchname(the_repository, newname, &newref);
 	else
-		validate_new_branchname(newname, &newref, force);
+		validate_new_branchname(the_repository, newname, &newref, force);
 
 	reject_rebase_or_bisect_branch(oldref.buf);
 
@@ -626,6 +677,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 	int icase = 0;
 	static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
 	struct ref_format format = REF_FORMAT_INIT;
+	int recurse_submodules_explicit = 0;
 
 	struct option options[] = {
 		OPT_GROUP(N_("Generic options")),
@@ -669,6 +721,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"),
 			N_("print only branches of the object"), parse_opt_object_name),
 		OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
+		OPT_BOOL(0, "recurse-submodules", &recurse_submodules_explicit, N_("recurse through submodules")),
 		OPT_STRING(  0 , "format", &format.format, N_("format"), N_("format to use for the output")),
 		OPT_END(),
 	};
@@ -708,6 +761,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 	if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
 	    list + edit_description + unset_upstream > 1)
 		usage_with_options(builtin_branch_usage, options);
+	else if (recurse_submodules_explicit &&
+		 (delete || rename || copy || new_upstream || show_current || list
+		  || edit_description || unset_upstream))
+		die(_("--recurse-submodules can only be used to create branches"));
+
+	recurse_submodules |= !!recurse_submodules_explicit;
 
 	if (filter.abbrev == -1)
 		filter.abbrev = DEFAULT_ABBREV;
@@ -857,9 +916,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		if (track == BRANCH_TRACK_OVERRIDE)
 			die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
 
-		create_branch(the_repository,
-			      argv[0], (argc == 2) ? argv[1] : head,
-			      force, 0, reflog, quiet, track);
+		create_branches(the_repository,
+			      argv[0], (argc == 2) ? argv[1] : "HEAD",
+				force, 0, reflog, quiet, track);
 
 	} else
 		usage_with_options(builtin_branch_usage, options);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8c69dcdf72..8859076a14 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1761,10 +1761,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
 		struct strbuf buf = STRBUF_INIT;
 
 		if (opts->new_branch_force)
-			opts->branch_exists = validate_branchname(opts->new_branch, &buf);
+			opts->branch_exists = validate_branchname(the_repository, opts->new_branch, &buf);
 		else
 			opts->branch_exists =
-				validate_new_branchname(opts->new_branch, &buf, 0);
+				validate_new_branchname(the_repository, opts->new_branch, &buf, 0);
 		strbuf_release(&buf);
 	}
 
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index e575ffb4ff..858831d4cf 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1421,5 +1421,63 @@ test_expect_success 'invalid sort parameter in configuration' '
 		test_must_fail git branch
 	)
 '
+# Submodule tests
+
+test_expect_success 'setup superproject and submodule' '
+	git init parent &&
+	test_commit -C parent bar &&
+	git init child &&
+	test_commit -C child bar &&
+	git -C parent submodule add ../child sub &&
+	git -C parent commit -m "add submodule"
+'
+
+test_expect_success '--recurse-submodules should create branches' '
+	(
+		cd parent &&
+		git branch --recurse-submodules abc &&
+		test_path_is_file .git/refs/heads/abc &&
+		test_path_is_file .git/modules/sub/refs/heads/abc
+	)
+'
+
+test_expect_success '--recurse-submodules should fail when not creating branches' '
+	(
+		cd parent &&
+		test_must_fail git branch --recurse-submodules -D abc &&
+		test_path_is_file .git/refs/heads/abc &&
+		test_path_is_file .git/modules/sub/refs/heads/abc
+	)
+'
+
+test_expect_success 'should respect submodule.recurse when creating branches' '
+	(
+		cd parent &&
+		git -c submodule.recurse=true branch def &&
+		test_path_is_file .git/refs/heads/def &&
+		test_path_is_file .git/modules/sub/refs/heads/def
+	)
+'
+
+test_expect_success 'should ignore submodule.recurse when not creating branches' '
+	(
+		cd parent &&
+		git -c submodule.recurse=true branch -D def &&
+		test_path_is_missing .git/refs/heads/def &&
+		test_path_is_file .git/modules/sub/refs/heads/def
+	)
+'
+
+test_expect_success 'should not set up tracking for new submodule branches' '
+	(
+		cd parent &&
+		git -c branch.autoSetupMerge=always branch --recurse-submodules ghi main &&
+		test_path_is_file .git/modules/sub/refs/heads/ghi &&
+		test "$(git config branch.ghi.remote)" = . &&
+		test "$(git config branch.ghi.merge)" = refs/heads/main &&
+		test "z$(git -C ../child config branch.ghi.remote)" = z &&
+		test "z$(git -C ../child config branch.ghi.merge)" = z
+	)
+'
 
 test_done
-- 
2.33.0.464.g1972c5931b-goog


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation
  2021-09-21 23:25 ` [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation Glen Choo
@ 2021-09-22 11:10   ` Ævar Arnfjörð Bjarmason
  2021-09-22 16:55     ` Glen Choo
  2021-09-22 12:28   ` Philippe Blain
  1 sibling, 1 reply; 7+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-09-22 11:10 UTC (permalink / raw)
  To: Glen Choo; +Cc: git


On Tue, Sep 21 2021, Glen Choo wrote:

>  static const char *head;
>  static struct object_id head_oid;
> +static int recurse_submodules = 0;

Nit: just s/ = 0// will do here, and is the convention typically...

> +	r_start_name = strcmp(start_name, "HEAD") ? start_name :
> +		refs_resolve_refdup(get_main_ref_store(r), start_name, 0, NULL, NULL);

IMO clearer just as an if/else.

> +	if (strcmp(r_start_name, "HEAD"))
> +		skip_prefix(r_start_name, "refs/heads/", &r_start_name);


> +	create_branch(r, name, r_start_name, force, clobber_head_ok, reflog,
> +		      quiet, track);
> +
> +	if (!recurse_submodules) {
> +		return;
> +	}

Can lose the braces here...

> +
> +	if (repo_read_index(r) < 0)
> +		die(_("index file corrupt"));

...Just as you do here..

> +
> +	for (i = 0; i < r->index->cache_nr; i++) {
> +		const struct cache_entry *ce = r->index->cache[i];
> +		if (!S_ISGITLINK(ce->ce_mode))
> +			continue;
> +		sub = submodule_from_path(r, null_oid(), ce->name);
> +		if (repo_submodule_init(&subrepo, r, sub) < 0) {
> +			warning(_("Unable to initialize subrepo %s, skipping."), ce->name);
> +			continue;
> +		}
> +		/*
> +		 * NEEDSWORK: branch tracking is not supported for
> +		 * submodules because it is not possible to get the
> +		 * remotes of a submodule.
> +		 */

It isn't?

    $ git -C sha1collisiondetection/ remote -v
    origin  https://github.com/cr-marcstevens/sha1collisiondetection.git
> [...]

> +test_expect_success 'setup superproject and submodule' '
> +	git init parent &&
> +	test_commit -C parent bar &&
> +	git init child &&
> +	test_commit -C child bar &&
> +	git -C parent submodule add ../child sub &&
> +	git -C parent commit -m "add submodule"
> +'
> +
> +test_expect_success '--recurse-submodules should create branches' '
> +	(
> +		cd parent &&
> +		git branch --recurse-submodules abc &&
> +		test_path_is_file .git/refs/heads/abc &&
> +		test_path_is_file .git/modules/sub/refs/heads/abc
> +	)
> +'

All this manual file checking should depend on REFFILES, but better yet
is there a reason this can't use rev-parse? I.e. why can't we inpect
this state with 'for-each-ref', 'rev-parse' and the like? Does this test
need to assert that these files end up in these specific locations, or
just the ref store? Ditto for the later ones.

> [...]
> +		cd parent &&
> +		git -c branch.autoSetupMerge=always branch --recurse-submodules ghi main &&
> +		test_path_is_file .git/modules/sub/refs/heads/ghi &&
> +		test "$(git config branch.ghi.remote)" = . &&
> +		test "$(git config branch.ghi.merge)" = refs/heads/main &&
> +		test "z$(git -C ../child config branch.ghi.remote)" = z &&
> +		test "z$(git -C ../child config branch.ghi.merge)" = z

Use test_cmp, also for this sort of thing the test "x$y" = "x" idiom
isn't needed unless you've got a computer from the mid-90s or something
:)

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation
  2021-09-21 23:25 ` [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation Glen Choo
  2021-09-22 11:10   ` Ævar Arnfjörð Bjarmason
@ 2021-09-22 12:28   ` Philippe Blain
  2021-09-22 17:24     ` Glen Choo
  1 sibling, 1 reply; 7+ messages in thread
From: Philippe Blain @ 2021-09-22 12:28 UTC (permalink / raw)
  To: Glen Choo, git

Hi Glen,

Le 2021-09-21 à 19:25, Glen Choo a écrit :
> When working with a superproject and submodules, it can be helpful to
> create topic branches to coordinate the work between each repository.
> 
> Teach cmd_branch to accept the --recurse-submodules option when creating
> branches. When specified, like
> 
>    git branch --recurse-submodules topic
> 
> git-branch will create the "topic" branch in the superproject and all
> submodules.
> 
> recurse_submodules is only supported for creating a branch. git-branch

small nit: we usually don't refer to the dashed form of commands anymore: 'git branch'
would be preferable.

> will fail if the user attempts to use --recurse-submodules with anything
> other than creating a branch. If a user has submodule.recurse in their
> config, git-branch will use --recurse-submodules for creating a branch,
> but will treat it as unset for any other operation.

That seems like a good starting point.

However, I think that for this new feature, I would prefer *also* having
a new config 'branch.recurseSubmodules' (or similar) that would allow
more granular control than 'submodule.recurse', which influences several
commands. Personnally I have 'submodule.recurse' set to true in my '~/.gitconfig',
because I want the submodules working trees to be updated when I use 'git checkout'
to change branches, 'git grep' to search the working tree, etc., but I usually
do not *work* on the submodules in my project and I would not like new branches
being created in them every time I create a new branch in the superproject.
In other words, 'branch.recurseSubmodules=false' would have higher priority
than 'submodule.recurse=true'.

> 
> There is no easy way to get the remotes of a submodule because remotes.c
> stores state as static variables. As such, branch tracking is disabled
> when using --recurse-submodules.
> 
> Signed-off-by: Glen Choo <chooglen@google.com>
> ---


> diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
> index e575ffb4ff..858831d4cf 100755
> --- a/t/t3200-branch.sh
> +++ b/t/t3200-branch.sh
> @@ -1421,5 +1421,63 @@ test_expect_success 'invalid sort parameter in configuration' '
>   		test_must_fail git branch
>   	)
>   '
> +# Submodule tests
> +
> 

Most tests for submodules are usually in separate test files. I don't think
this is a set-in-stone rule, but if more tests are coming in the future, maybe
a new test file t????-branch-submodule.sh would be appropriate ? Just a small suggestion.


Thanks for working on this! It's exciting to see new developments
in the submodule area :)

Philippe.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation
  2021-09-22 11:10   ` Ævar Arnfjörð Bjarmason
@ 2021-09-22 16:55     ` Glen Choo
  0 siblings, 0 replies; 7+ messages in thread
From: Glen Choo @ 2021-09-22 16:55 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

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

>>  static const char *head;
>>  static struct object_id head_oid;
>> +static int recurse_submodules = 0;
>
> Nit: just s/ = 0// will do here, and is the convention typically...

Thanks for the catch!

>> +	r_start_name = strcmp(start_name, "HEAD") ? start_name :
>> +		refs_resolve_refdup(get_main_ref_store(r), start_name, 0, NULL, NULL);
>
> IMO clearer just as an if/else.

Sounds good, I'll use an if/else instead

>> +	if (!recurse_submodules) {
>> +		return;
>> +	}
>
> Can lose the braces here...

Ah, yes. Old habits die hard.. Thanks!

>> +
>> +	for (i = 0; i < r->index->cache_nr; i++) {
>> +		const struct cache_entry *ce = r->index->cache[i];
>> +		if (!S_ISGITLINK(ce->ce_mode))
>> +			continue;
>> +		sub = submodule_from_path(r, null_oid(), ce->name);
>> +		if (repo_submodule_init(&subrepo, r, sub) < 0) {
>> +			warning(_("Unable to initialize subrepo %s, skipping."), ce->name);
>> +			continue;
>> +		}
>> +		/*
>> +		 * NEEDSWORK: branch tracking is not supported for
>> +		 * submodules because it is not possible to get the
>> +		 * remotes of a submodule.
>> +		 */
>
> It isn't?
>
>     $ git -C sha1collisiondetection/ remote -v
>     origin  https://github.com/cr-marcstevens/sha1collisiondetection.git

Ah, my comment is ambiguous. I meant that we cannot get
submodule remotes in-process because remotes.c stores its state in
static variables I believe it implicitly refers to the remotes of
the_repository and can't be reused for submodules.

Of course I hope I am wrong, because that would make this task a lot
easier :)

> All this manual file checking should depend on REFFILES, but better yet
> is there a reason this can't use rev-parse? I.e. why can't we inpect
> this state with 'for-each-ref', 'rev-parse' and the like? Does this test
> need to assert that these files end up in these specific locations, or
> just the ref store? Ditto for the later ones.

> Use test_cmp, also for this sort of thing the test "x$y" = "x" idiom
> isn't needed unless you've got a computer from the mid-90s or something
> :)

The only reason the tests look this way is that I have copied the
surrounding tests. From your comments, it seems clear that these tests
are fairly out-of-date, so I should probably model them after something
else.

I will incorporate Philippe's suggestion

  Most tests for submodules are usually in separate test files. I don't think
  this is a set-in-stone rule, but if more tests are coming in the future, maybe
  a new test file t????-branch-submodule.sh would be appropriate ? Just a small suggestion.

Then at least my new tests won't look so out of place with the other
branch tests.

>> In this patchset, branching works slightly differently between the
>> superproject and submodule (skip ahead for specifics). There are two
>> very obvious alternatives that can address this:
 
>> * A: only implement --recurse-submodules behavior after we are able to
>>   eliminate any kind of dependence on the_repository/global state that
>>   shouldn't be shared.
>> * B: implement --recurse-submodules as child processes, which won't be
>>   bothered by global state.

I was wondering if you had thoughts on this bit in particular. It seems
unpleasant for branching to behave differently between superproject and
submodule, so I'd like to discard this RFC (or at least the 'disable
remotes' behavior) ASAP and start work on a version that serves us
better.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation
  2021-09-22 12:28   ` Philippe Blain
@ 2021-09-22 17:24     ` Glen Choo
  0 siblings, 0 replies; 7+ messages in thread
From: Glen Choo @ 2021-09-22 17:24 UTC (permalink / raw)
  To: Philippe Blain, git

Philippe Blain <levraiphilippeblain@gmail.com> writes:

>> git-branch will create the "topic" branch in the superproject and all
>> submodules.
>> 
>> recurse_submodules is only supported for creating a branch. git-branch
>
> small nit: we usually don't refer to the dashed form of commands anymore: 'git branch'
> would be preferable.

Ah, thanks! I really appreciate the style pointers.

> However, I think that for this new feature, I would prefer *also* having
> a new config 'branch.recurseSubmodules' (or similar) that would allow
> more granular control than 'submodule.recurse', which influences several
> commands. Personnally I have 'submodule.recurse' set to true in my '~/.gitconfig',
> because I want the submodules working trees to be updated when I use 'git checkout'
> to change branches, 'git grep' to search the working tree, etc., but I usually
> do not *work* on the submodules in my project and I would not like new branches
> being created in them every time I create a new branch in the superproject.
> In other words, 'branch.recurseSubmodules=false' would have higher priority
> than 'submodule.recurse=true'.

That's a good point. This behavior of git branch --recurse-submodules
has the potential to directly conflict with established workflows.
Opting out via 'branch.recurseSubmodules seems reasonable and I think we
definitely want to give users that fine-grained control. I wonder
if we should take this even further - perhaps --recurse-submodules is
too disruptive to be controlled by the config value as checkout, fetch,
pull, etc.

Perhaps submodule.recurse should be more fine-grained, so we can ship
good defaults for different workflows e.g. options that map to the
current true/false behavior and another option that includes branching
(I wish I could give these names, but I have no clue what to name them).

> Most tests for submodules are usually in separate test files. I don't think
> this is a set-in-stone rule, but if more tests are coming in the future, maybe
> a new test file t????-branch-submodule.sh would be appropriate ? Just a small suggestion.

I think that's a good call. It also looks like the tests in this file
are somewhat old, so it would be nice to start on a clean slate :)

>> In this patchset, branching works slightly differently between the
>> superproject and submodule (skip ahead for specifics). There are two
>> very obvious alternatives that can address this:
 
>> * A: only implement --recurse-submodules behavior after we are able to
>>   eliminate any kind of dependence on the_repository/global state that
>>   shouldn't be shared.
>> * B: implement --recurse-submodules as child processes, which won't be
>>   bothered by global state.

Your comments on the UX make a lot of sense, and I'll think about that
more carefully. Let me know if you also have thoughts on the
implementation itself, particular on in-process vs child process :)

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2021-09-22 17:24 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-21 23:25 [RFC PATCH 0/2] branch: implement in-process --recurse-submodules Glen Choo
2021-09-21 23:25 ` [RFC PATCH 1/2] refs: pass struct repository *r through to write_ref_to_lockfile() Glen Choo
2021-09-21 23:25 ` [RFC PATCH 2/2] branch: add --recurse-submodules option for branch creation Glen Choo
2021-09-22 11:10   ` Ævar Arnfjörð Bjarmason
2021-09-22 16:55     ` Glen Choo
2021-09-22 12:28   ` Philippe Blain
2021-09-22 17:24     ` Glen Choo

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.