{"thread":{"id":"56962","subject":"[PATCH 0/4] implement branch --recurse-submodules","startedAt":"2021-11-22T22:33:07Z","lastAt":"2022-02-04T18:10:13Z","messageCount":110,"participants":["Glen Choo","Jonathan Tan","Ævar Arnfjörð Bjarmason","Philippe Blain","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"442042","messageId":"20211122223252.19922-1-chooglen@google.com","threadId":"56962","inReplyTo":null,"subject":"[PATCH 0/4] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-22T22:32:48Z","receivedAt":"2021-11-22T22:33:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Submodule branching RFC:\nhttps://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/\n\nOriginal Submodule UX RFC/Discussion:\nhttps://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n\nContributor Summit submodules Notes:\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/\n\nSubmodule UX overhaul updates:\nhttps://lore.kernel.org/git/?q=Submodule+UX+overhaul+update\n\nThis series implements branch --recurse-submodules as laid out in the\nSubmodule branching RFC (linked above). If there are concerns about the\nUX/behavior, I would appreciate feedback on the RFC thread as well :)\n\nThis series uses child processes to support submodules. I initially\nhoped to do this in-core and [1] and [2] were meant to prepare for that.\nBut even though in-core is tantalizingly close, [1] showed that there\nis more work to be done on config.c before this is possible, and I would\nlike to get more feedback on the UX before converting this to in-core.\n\n[1] https://lore.kernel.org/git/20211111171643.13805-1-chooglen@google.com/\n[2] https://lore.kernel.org/git/20211118005325.64971-1-chooglen@google.com/\n\nGlen Choo (4):\n  submodule-config: add submodules_of_tree() helper\n  branch: refactor out branch validation from create_branch()\n  branch: add --dry-run option to branch\n  branch: add --recurse-submodules option for branch creation\n\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |   9 +\n Documentation/git-branch.txt       |   8 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 300 +++++++++++++++++++++--------\n branch.h                           |  41 +++-\n builtin/branch.c                   |  77 ++++++--\n builtin/submodule--helper.c        |  33 ++++\n submodule-config.c                 |  19 ++\n submodule-config.h                 |  13 ++\n t/t3200-branch.sh                  |  30 +++\n t/t3207-branch-submodule.sh        | 249 ++++++++++++++++++++++++\n 13 files changed, 678 insertions(+), 106 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\n-- \n2.33.GIT\n\n"},{"id":"442043","messageId":"20211122223252.19922-2-chooglen@google.com","threadId":"56962","inReplyTo":"20211122223252.19922-1-chooglen@google.com","subject":"[PATCH 1/4] submodule-config: add submodules_of_tree() helper","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-22T22:32:49Z","receivedAt":"2021-11-22T22:33:09Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"As we introduce a submodule UX with branches, we would like to be able\nto get the submodule commit ids in a superproject tree because those ids\nare the source of truth e.g. \"git branch --recurse-submodules topic\nstart-point\" should create branches based off the commit ids recorded in\nthe superproject's 'start-point' tree.\n\nTo make this easy, introduce a submodules_of_tree() helper function that\niterates through a tree and returns the tree's gitlink entries as a\nlist.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n submodule-config.c | 19 +++++++++++++++++++\n submodule-config.h | 13 +++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f95344028b..97da373301 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -7,6 +7,7 @@\n #include \"strbuf.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n+#include \"tree-walk.h\"\n \n /*\n  * submodule cache lookup structure\n@@ -726,6 +727,24 @@ const struct submodule *submodule_from_path(struct repository *r,\n \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n+struct submodule_entry_list *\n+submodules_of_tree(struct repository *r, const struct object_id *treeish_name)\n+{\n+\tstruct tree_desc tree;\n+\tstruct name_entry entry;\n+\tstruct submodule_entry_list *ret;\n+\n+\tCALLOC_ARRAY(ret, 1);\n+\tfill_tree_descriptor(r, &tree, treeish_name);\n+\twhile (tree_entry(&tree, &entry)) {\n+\t\tif (!S_ISGITLINK(entry.mode))\n+\t\t\tcontinue;\n+\t\tALLOC_GROW(ret->name_entries, ret->entry_nr + 1, ret->entry_alloc);\n+\t\tret->name_entries[ret->entry_nr++] = entry;\n+\t}\n+\treturn ret;\n+}\n+\n void submodule_free(struct repository *r)\n {\n \tif (r->submodule_cache)\ndiff --git a/submodule-config.h b/submodule-config.h\nindex 65875b94ea..4379ec77e3 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"submodule.h\"\n #include \"strbuf.h\"\n+#include \"tree-walk.h\"\n \n /**\n  * The submodule config cache API allows to read submodule\n@@ -67,6 +68,18 @@ const struct submodule *submodule_from_name(struct repository *r,\n \t\t\t\t\t    const struct object_id *commit_or_tree,\n \t\t\t\t\t    const char *name);\n \n+struct submodule_entry_list {\n+\tstruct name_entry *name_entries;\n+\tint entry_nr;\n+\tint entry_alloc;\n+};\n+\n+/**\n+ * Given a tree-ish, return all submodules in the tree.\n+ */\n+struct submodule_entry_list *\n+submodules_of_tree(struct repository *r, const struct object_id *treeish_name);\n+\n /**\n  * Given a tree-ish in the superproject and a path, return the submodule that\n  * is bound at the path in the named tree.\n-- \n2.33.GIT\n\n"},{"id":"442044","messageId":"20211122223252.19922-3-chooglen@google.com","threadId":"56962","inReplyTo":"20211122223252.19922-1-chooglen@google.com","subject":"[PATCH 2/4] branch: refactor out branch validation from create_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-22T22:32:50Z","receivedAt":"2021-11-22T22:33:11Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"In a subsequent commit, we would like to be able to validate whether or\nnot a branch name is valid before we create it (--dry-run). This is\nuseful for `git branch --recurse-submodules topic` because it allows Git\nto determine if the branch 'topic' can be created in all submodules\nwithout creating the branch 'topic'.\n\nA good starting point would be to refactor out the start point\nvalidation and dwim logic in create_branch() in a\nvalidate_branch_start() helper function. Once we do so, it becomes\nclear that create_branch() is more complex than it needs to be -\ncreate_branch() is also used to set tracking information when performing\n`git branch --set-upstream-to`. This made more sense when\n(the now unsupported) --set-upstream was first introduced in\n4fc5006676 (Add branch --set-upstream, 2010-01-18), because\nit would sometimes create a branch and sometimes update tracking\ninformation without creating a branch.\n\nRefactor out the branch validation and dwim logic from create_branch()\ninto validate_branch_start(), make it so that create_branch() always\ntries to create a branch, and replace the now-incorrect create_branch()\ncall with setup_tracking(). Since there were none, add tests for\ncreating a branch with `--force`.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\nIn this refactor, I preserved the existing behavior by making\nsetup_tracking() call validate_branch_start(). setup_tracking() needs\nthe dwim behavior e.g. to expand 'origin/main' into\n'refs/remotes/origin/main' but I'm doubtful that it needs the exact same\nset of validation behavior as creating a new branch e.g. validating that\nthe object_id is a commit.\n\n branch.c          | 177 ++++++++++++++++++++++++----------------------\n branch.h          |  13 +++-\n builtin/branch.c  |   7 +-\n t/t3200-branch.sh |  17 +++++\n 4 files changed, 121 insertions(+), 93 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 07a46430b3..f8b755513f 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -126,43 +126,6 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \treturn -1;\n }\n \n-/*\n- * This is called when new_ref is branched off of orig_ref, and tries\n- * to infer the settings for branch.<new_ref>.{remote,merge} from the\n- * config.\n- */\n-static void setup_tracking(const char *new_ref, const char *orig_ref,\n-\t\t\t   enum branch_track track, int quiet)\n-{\n-\tstruct tracking tracking;\n-\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n-\n-\tmemset(&tracking, 0, sizeof(tracking));\n-\ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n-\t\treturn;\n-\n-\tif (!tracking.matches)\n-\t\tswitch (track) {\n-\t\tcase BRANCH_TRACK_ALWAYS:\n-\t\tcase BRANCH_TRACK_EXPLICIT:\n-\t\tcase BRANCH_TRACK_OVERRIDE:\n-\t\t\tbreak;\n-\t\tdefault:\n-\t\t\treturn;\n-\t\t}\n-\n-\tif (tracking.matches > 1)\n-\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n-\t\t    orig_ref);\n-\n-\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n-\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n-\t\texit(-1);\n-\n-\tfree(tracking.src);\n-}\n-\n int read_branch_desc(struct strbuf *buf, const char *branch_name)\n {\n \tchar *v = NULL;\n@@ -243,33 +206,17 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+static void validate_branch_start(struct repository *r, const char *start_name,\n+\t\t\t\t  enum branch_track track,\n+\t\t\t\t  struct object_id *oid, char **full_ref)\n {\n \tstruct commit *commit;\n-\tstruct object_id oid;\n-\tchar *real_ref;\n-\tstruct strbuf ref = STRBUF_INIT;\n-\tint forcing = 0;\n-\tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n \n \tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n \t\texplicit_tracking = 1;\n \n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n-\t}\n-\n-\treal_ref = NULL;\n-\tif (get_oid_mb(start_name, &oid)) {\n+\tif (repo_get_oid_mb(r, start_name, oid)) {\n \t\tif (explicit_tracking) {\n \t\t\tif (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {\n \t\t\t\terror(_(upstream_missing), start_name);\n@@ -281,7 +228,8 @@ void create_branch(struct repository *r,\n \t\tdie(_(\"Not a valid object name: '%s'.\"), start_name);\n \t}\n \n-\tswitch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {\n+\tswitch (repo_dwim_ref(r, start_name, strlen(start_name), oid, full_ref,\n+\t\t\t      0)) {\n \tcase 0:\n \t\t/* Not branching from any existing branch */\n \t\tif (explicit_tracking)\n@@ -289,12 +237,12 @@ void create_branch(struct repository *r,\n \t\tbreak;\n \tcase 1:\n \t\t/* Unique completion -- good, only if it is a real branch */\n-\t\tif (!starts_with(real_ref, \"refs/heads/\") &&\n-\t\t    validate_remote_tracking_branch(real_ref)) {\n+\t\tif (!starts_with(*full_ref, \"refs/heads/\") &&\n+\t\t    validate_remote_tracking_branch(*full_ref)) {\n \t\t\tif (explicit_tracking)\n \t\t\t\tdie(_(upstream_not_branch), start_name);\n \t\t\telse\n-\t\t\t\tFREE_AND_NULL(real_ref);\n+\t\t\t\tFREE_AND_NULL(*full_ref);\n \t\t}\n \t\tbreak;\n \tdefault:\n@@ -302,37 +250,96 @@ void create_branch(struct repository *r,\n \t\tbreak;\n \t}\n \n-\tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n+\tif ((commit = lookup_commit_reference(r, oid)) == NULL)\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n-\toidcpy(&oid, &commit->object.oid);\n+\toidcpy(oid, &commit->object.oid);\n+}\n+\n+void setup_tracking(const char *new_ref, const char *orig_ref,\n+\t\t\t   enum branch_track track, int quiet, int expand_orig)\n+{\n+\tstruct tracking tracking;\n+\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n+\tchar *full_orig_ref;\n+\tstruct object_id unused_oid;\n+\n+\tmemset(&tracking, 0, sizeof(tracking));\n+\tif (expand_orig)\n+\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n+\telse\n+\t\tfull_orig_ref = xstrdup(orig_ref);\n+\n+\ttracking.spec.dst = full_orig_ref;\n+\tif (for_each_remote(find_tracked_branch, &tracking))\n+\t\tgoto cleanup;\n+\n+\tif (!tracking.matches)\n+\t\tswitch (track) {\n+\t\tcase BRANCH_TRACK_ALWAYS:\n+\t\tcase BRANCH_TRACK_EXPLICIT:\n+\t\tcase BRANCH_TRACK_OVERRIDE:\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\tif (tracking.matches > 1)\n+\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n+\t\t    full_orig_ref);\n+\n+\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n+\t\t\t      tracking.src ? tracking.src : full_orig_ref) < 0)\n+\t\texit(-1);\n+\n+cleanup:\n+\tfree(tracking.src);\n+\tfree(full_orig_ref);\n+}\n+\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track)\n+{\n+\tstruct object_id oid;\n+\tchar *real_ref;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tint forcing = 0;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *msg;\n+\n+\tif (clobber_head_ok && !force)\n+\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n+\n+\tif (clobber_head_ok ?\n+\t\t\t  validate_branchname(name, &ref) :\n+\t\t\t  validate_new_branchname(name, &ref, force)) {\n+\t\tforcing = 1;\n+\t}\n+\n+\tvalidate_branch_start(r, start_name, track, &oid, &real_ref);\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n-\tif (!dont_change_ref) {\n-\t\tstruct ref_transaction *transaction;\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\t\tchar *msg;\n-\n-\t\tif (forcing)\n-\t\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n-\t\telse\n-\t\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n-\n-\t\ttransaction = ref_transaction_begin(&err);\n-\t\tif (!transaction ||\n-\t\t    ref_transaction_update(transaction, ref.buf,\n-\t\t\t\t\t   &oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t   0, msg, &err) ||\n-\t\t    ref_transaction_commit(transaction, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\t\tref_transaction_free(transaction);\n-\t\tstrbuf_release(&err);\n-\t\tfree(msg);\n-\t}\n+\tif (forcing)\n+\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n+\telse\n+\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n+\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t\tref_transaction_update(transaction, ref.buf,\n+\t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n+\t\t\t\t\t0, msg, &err) ||\n+\t\tref_transaction_commit(transaction, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tfree(msg);\n \n \tif (real_ref && track)\n-\t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n+\t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet, 0);\n \n \tstrbuf_release(&ref);\n \tfree(real_ref);\ndiff --git a/branch.h b/branch.h\nindex df0be61506..75cefcdcbd 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -17,6 +17,15 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+/*\n+ * This sets the branch.<new_ref>.{remote,merge} config settings so that\n+ * branch 'new_ref' tracks 'orig_ref'. This is called when branches are\n+ * created, or when branch configs are updated (e.g. with\n+ * git branch --set-upstream-to).\n+ */\n+void setup_tracking(const char *new_ref, const char *orig_ref,\n+\t\t    enum branch_track track, int quiet, int expand_orig);\n+\n /*\n  * Creates a new branch, where:\n  *\n@@ -29,8 +38,8 @@ extern enum branch_track git_branch_track;\n  *\n  *   - force enables overwriting an existing (non-head) branch\n  *\n- *   - clobber_head_ok allows the currently checked out (hence existing)\n- *     branch to be overwritten; without 'force', it has no effect.\n+ *   - clobber_head_ok, when enabled with 'force', allows the currently\n+ *     checked out (head) branch to be overwritten\n  *\n  *   - reflog creates a reflog for the branch\n  *\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 0b7ed82654..eb5c117a6e 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -820,12 +820,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (!ref_exists(branch->refname))\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \n-\t\t/*\n-\t\t * create_branch takes care of setting up the tracking\n-\t\t * info and making sure new_upstream is correct\n-\t\t */\n-\t\tcreate_branch(the_repository, branch->name, new_upstream,\n-\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n+\t\tsetup_tracking(branch->name, new_upstream, BRANCH_TRACK_OVERRIDE, quiet, 1);\n \t} else if (unset_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n \t\tstruct strbuf buf = STRBUF_INIT;\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex e575ffb4ff..6bf95a1707 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n \tgit branch abc && test_path_is_file .git/refs/heads/abc\n '\n \n+test_expect_success 'git branch abc should fail when abc exists' '\n+\ttest_must_fail git branch abc\n+'\n+\n+test_expect_success 'git branch --force abc should fail when abc is checked out' '\n+\ttest_when_finished git switch main &&\n+\tgit switch abc &&\n+\ttest_must_fail git branch --force abc HEAD~1\n+'\n+\n+test_expect_success 'git branch --force abc should succeed when abc exists' '\n+\tgit rev-parse HEAD~1 >expect &&\n+\tgit branch --force abc HEAD~1 &&\n+\tgit rev-parse abc >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n '\n-- \n2.33.GIT\n\n"},{"id":"442045","messageId":"20211122223252.19922-4-chooglen@google.com","threadId":"56962","inReplyTo":"20211122223252.19922-1-chooglen@google.com","subject":"[PATCH 3/4] branch: add --dry-run option to branch","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-22T22:32:51Z","receivedAt":"2021-11-22T22:33:19Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"When running \"git branch --recurse-submodules topic\", it would be useful\nto know whether or not 'topic' is a valid branch for all repositories.\nCurrently there is no way to test this without actually creating the\nbranch.\n\nAdd a --dry-run option to branch creation that can check whether or not\na branch name and start point would be valid for a repository without\ncreating a branch. Refactor cmd_branch() to make the chosen action more\nobvious.\n\nIncidentally, fix an incorrect usage string that combined the 'list'\nusage of git branch (-l) with the 'create' usage; this string has been\nincorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\nparse_options., 2007-10-07).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\nThe --dry-run option is motivated mainly by --recurse-submodules. To my\nknowledge, there isn't a strong existing demand, but this might be\nmildly useful to some users.\n\n Documentation/git-branch.txt |  8 ++++++-\n branch.c                     |  6 ++---\n branch.h                     | 22 ++++++++++++++++++\n builtin/branch.c             | 44 ++++++++++++++++++++++++++----------\n t/t3200-branch.sh            | 13 +++++++++++\n 5 files changed, 77 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 5449767121..8cdc33c097 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track | --no-track] [-f] [--dry-run | -n] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -205,6 +205,12 @@ This option is only applicable in non-verbose mode.\n --no-abbrev::\n \tDisplay the full sha1s in the output listing rather than abbreviating them.\n \n+-n::\n+--dry-run::\n+\tCan only be used when creating a branch. If the branch creation\n+\twould fail, show the relevant error message. If the branch\n+\tcreation would succeed, show nothing.\n+\n -t::\n --track::\n \tWhen creating a new branch, set up `branch.<name>.remote` and\ndiff --git a/branch.c b/branch.c\nindex f8b755513f..528cb2d639 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -206,9 +206,9 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-static void validate_branch_start(struct repository *r, const char *start_name,\n-\t\t\t\t  enum branch_track track,\n-\t\t\t\t  struct object_id *oid, char **full_ref)\n+void validate_branch_start(struct repository *r, const char *start_name,\n+\t\t\t   enum branch_track track, struct object_id *oid,\n+\t\t\t   char **full_ref)\n {\n \tstruct commit *commit;\n \tint explicit_tracking = 0;\ndiff --git a/branch.h b/branch.h\nindex 75cefcdcbd..d8e5ff4e28 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -3,6 +3,7 @@\n \n struct repository;\n struct strbuf;\n+struct object_id;\n \n enum branch_track {\n \tBRANCH_TRACK_UNSPECIFIED = -1,\n@@ -17,6 +18,27 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+/*\n+ * Validates whether a ref is a valid starting point for a branch, where:\n+ *\n+ *   - r is the repository to validate the branch for\n+ *\n+ *   - start_name is the ref that we would like to test\n+ *\n+ *   - track is the tracking mode of the new branch. If tracking is\n+ *     explicitly requested, start_name must be a branch (because\n+ *     otherwise start_name cannot be tracked)\n+ *\n+ *   - oid is an out parameter containing the object_id of start_name\n+ *\n+ *   - full_ref is an out parameter containing the 'full' form of\n+ *     start_name e.g. refs/heads/main instead of main\n+ *\n+ */\n+void validate_branch_start(struct repository *r, const char *start_name,\n+\t\t\t   enum branch_track track, struct object_id *oid,\n+\t\t\t   char **full_ref);\n+\n /*\n  * This sets the branch.<new_ref>.{remote,merge} config settings so that\n  * branch 'new_ref' tracks 'orig_ref'. This is called when branches are\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex eb5c117a6e..5d4b9c82b4 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -27,7 +27,8 @@\n \n static const char * const builtin_branch_usage[] = {\n \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n-\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n+\tN_(\"git branch [<options>] [-f] [--dry-run | -n] <branch-name> [<start-point>]\"),\n \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n@@ -616,14 +617,14 @@ static int edit_branch_description(const char *branch_name)\n \n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n-\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n-\tint show_current = 0;\n-\tint reflog = 0, edit_description = 0;\n-\tint quiet = 0, unset_upstream = 0;\n+\t/* possible actions */\n+\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0, create = 0,\n+\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n+\t/* possible options */\n+\tint reflog = 0, quiet = 0, dry_run = 0, icase = 0;\n \tconst char *new_upstream = NULL;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n-\tint icase = 0;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n \tstruct ref_format format = REF_FORMAT_INIT;\n \n@@ -670,6 +671,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n+\t\tOPT__DRY_RUN(&dry_run, N_(\"show whether the branch would be created\")),\n \t\tOPT_END(),\n \t};\n \n@@ -705,10 +707,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)\n \t\tlist = 1;\n \n-\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n-\t    list + edit_description + unset_upstream > 1)\n+\tcreate = 1 - (!!delete + !!rename + !!copy + !!new_upstream +\n+\t\t      !!show_current + !!list + !!edit_description +\n+\t\t      !!unset_upstream);\n+\tif (create < 0)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n+\tif (dry_run && !create)\n+\t\tdie(_(\"--dry-run can only be used when creating branches\"));\n+\n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n \tfilter.ignore_case = icase;\n@@ -844,7 +851,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&buf, \"branch.%s.merge\", branch->name);\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n-\t} else if (argc > 0 && argc <= 2) {\n+\t} else if (create && argc > 0 && argc <= 2) {\n+\t\tconst char *branch_name = argv[0];\n+\t\tconst char *start_name = (argc == 2) ? argv[1] : head;\n+\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n@@ -852,10 +862,20 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (track == BRANCH_TRACK_OVERRIDE)\n \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n \n-\t\tcreate_branch(the_repository,\n-\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track);\n+\t\tif (dry_run) {\n+\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\t\tchar *unused_full_ref;\n+\t\t\tstruct object_id unused_oid;\n \n+\t\t\tvalidate_new_branchname(branch_name, &buf, force);\n+\t\t\tvalidate_branch_start(the_repository, start_name, track,\n+\t\t\t\t\t      &unused_oid, &unused_full_ref);\n+\t\t\tstrbuf_release(&buf);\n+\t\t\tFREE_AND_NULL(unused_full_ref);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n+\t\t\t      reflog, quiet, track);\n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\n \ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 6bf95a1707..653891736a 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -59,6 +59,19 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git branch --dry-run abc should fail when abc exists' '\n+\ttest_must_fail git branch --dry-run abc\n+'\n+\n+test_expect_success 'git branch --dry-run --force abc should succeed when abc exists' '\n+\tgit branch --dry-run --force abc\n+'\n+\n+test_expect_success 'git branch --dry-run def should not create a branch' '\n+\tgit branch --dry-run def &&\n+\ttest_must_fail git rev-parse def\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n '\n-- \n2.33.GIT\n\n"},{"id":"442046","messageId":"20211122223252.19922-5-chooglen@google.com","threadId":"56962","inReplyTo":"20211122223252.19922-1-chooglen@google.com","subject":"[PATCH 4/4] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-22T22:32:52Z","receivedAt":"2021-11-22T22:33:20Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a --recurse-submodules option when creating branches so that `git\nbranch --recurse-submodules topic` will create the \"topic\" branch in the\nsuperproject and all submodules. Guard this (and future submodule\nbranching) behavior behind a new configuration value\n'submodule.propagateBranches'.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |   9 ++\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 123 ++++++++++++++\n branch.h                           |   6 +\n builtin/branch.c                   |  28 +++-\n builtin/submodule--helper.c        |  33 ++++\n t/t3207-branch-submodule.sh        | 249 +++++++++++++++++++++++++++++\n 9 files changed, 452 insertions(+), 1 deletion(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex 063eec2511..e52262dc69 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -116,6 +116,9 @@ advice.*::\n \tsubmoduleAlternateErrorStrategyDie::\n \t\tAdvice shown when a submodule.alternateErrorStrategy option\n \t\tconfigured to \"die\" causes a fatal error.\n+\tsubmodulesNotUpdated::\n+\t\tAdvice shown when a user runs a submodule command that fails\n+\t\tbecause `git submodule update` was not run.\n \taddIgnoredFile::\n \t\tAdvice shown if a user attempts to add an ignored file to\n \t\tthe index.\ndiff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\nindex ee454f8126..c318b849aa 100644\n--- a/Documentation/config/submodule.txt\n+++ b/Documentation/config/submodule.txt\n@@ -72,6 +72,15 @@ submodule.recurse::\n \tFor these commands a workaround is to temporarily change the\n \tconfiguration value by using `git -c submodule.recurse=0`.\n \n+submodule.propagateBranches::\n+\t[EXPERIMENTAL] A boolean that enables branching support with\n+\tsubmodules. This allows certain commands to accept\n+\t`--recurse-submodules` (`git branch --recurse-submodules` will\n+\tcreate branches recursively), and certain commands that already\n+\taccept `--recurse-submodules` will now consider branches (`git\n+\tswitch --recurse-submodules` will switch to the correct branch\n+\tin all submodules).\n+\n submodule.fetchJobs::\n \tSpecifies how many submodules are fetched/cloned at the same time.\n \tA positive integer allows up to that number of submodules fetched\ndiff --git a/advice.c b/advice.c\nindex 1dfc91d176..e00d30254c 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -70,6 +70,7 @@ static struct {\n \t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n \t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n \t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n+\t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n };\ndiff --git a/advice.h b/advice.h\nindex 601265fd10..a7521d6087 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -44,6 +44,7 @@ struct string_list;\n \tADVICE_STATUS_HINTS,\n \tADVICE_STATUS_U_OPTION,\n \tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_SUBMODULES_NOT_UPDATED,\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\ndiff --git a/branch.c b/branch.c\nindex 528cb2d639..404766d01d 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -8,6 +8,8 @@\n #include \"sequencer.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n+#include \"submodule-config.h\"\n+#include \"run-command.h\"\n \n struct tracking {\n \tstruct refspec_item spec;\n@@ -345,6 +347,127 @@ void create_branch(struct repository *r, const char *name,\n \tfree(real_ref);\n }\n \n+static int submodule_validate_branchname(struct repository *r, const char *name,\n+\t\t\t\t\t const char *start_name, int force,\n+\t\t\t\t\t int quiet, char **err_msg)\n+{\n+\tint ret = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tstruct strbuf child_err = STRBUF_INIT;\n+\tchild.git_cmd = 1;\n+\tchild.err = -1;\n+\n+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n+\tstrvec_pushl(&child.args, \"branch\", \"--dry-run\", NULL);\n+\tif (force)\n+\t\tstrvec_push(&child.args, \"--force\");\n+\tif (quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\tstrvec_pushl(&child.args, name, start_name, NULL);\n+\n+\tif ((ret = start_command(&child)))\n+\t\treturn ret;\n+\tret = finish_command(&child);\n+\tstrbuf_read(&child_err, child.err, 0);\n+\t*err_msg = strbuf_detach(&child_err, NULL);\n+\treturn ret;\n+}\n+\n+static int submodule_create_branch(struct repository *r, const char *name,\n+\t\t\t\t   const char *start_oid,\n+\t\t\t\t   const char *start_name, int force,\n+\t\t\t\t   int reflog, int quiet,\n+\t\t\t\t   enum branch_track track, char **err_msg)\n+{\n+\tint ret = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tstruct strbuf child_err = STRBUF_INIT;\n+\tchild.git_cmd = 1;\n+\tchild.err = -1;\n+\n+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n+\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n+\tif (force)\n+\t\tstrvec_push(&child.args, \"--force\");\n+\tif (quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\tif (reflog)\n+\t\tstrvec_push(&child.args, \"--create-reflog\");\n+\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n+\t\tstrvec_push(&child.args, \"--track\");\n+\n+\tstrvec_pushl(&child.args, name, start_oid, start_name, NULL);\n+\n+\tif ((ret = start_command(&child)))\n+\t\treturn ret;\n+\tret = finish_command(&child);\n+\tstrbuf_read(&child_err, child.err, 0);\n+\t*err_msg = strbuf_detach(&child_err, NULL);\n+\treturn ret;\n+}\n+\n+void create_submodule_branches(struct repository *r, const char *name,\n+\t\t\t       const char *start_name, int force, int reflog,\n+\t\t\t       int quiet, enum branch_track track)\n+{\n+\tint i = 0;\n+\tchar *branch_point = NULL;\n+\tstruct repository *subrepos;\n+\tstruct submodule *submodules;\n+\tstruct object_id super_oid;\n+\tstruct submodule_entry_list *submodule_entry_list;\n+\tchar *err_msg = NULL;\n+\n+\tvalidate_branch_start(r, start_name, track, &super_oid, &branch_point);\n+\n+\tsubmodule_entry_list = submodules_of_tree(r, &super_oid);\n+\tCALLOC_ARRAY(subrepos, submodule_entry_list->entry_nr);\n+\tCALLOC_ARRAY(submodules, submodule_entry_list->entry_nr);\n+\n+\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n+\t\tsubmodules[i] = *submodule_from_path(\n+\t\t\tr, &super_oid,\n+\t\t\tsubmodule_entry_list->name_entries[i].path);\n+\n+\t\tif (repo_submodule_init(\n+\t\t\t    &subrepos[i], r,\n+\t\t\t    submodule_entry_list->name_entries[i].path,\n+\t\t\t    &super_oid)) {\n+\t\t\tdie(_(\"submodule %s: unable to find submodule\"),\n+\t\t\t    submodules[i].name);\n+\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n+\t\t\t\tadvise(_(\"You may try initializing the submodules using 'git checkout %s && git submodule update'\"),\n+\t\t\t\t       start_name);\n+\t\t}\n+\n+\t\tif (submodule_validate_branchname(\n+\t\t\t    &subrepos[i], name,\n+\t\t\t    oid_to_hex(\n+\t\t\t\t    &submodule_entry_list->name_entries[i].oid),\n+\t\t\t    force, quiet, &err_msg))\n+\t\t\tdie(_(\"submodule %s: could not create branch '%s'\\n\\t%s\"),\n+\t\t\t    submodules[i].name, name, err_msg);\n+\t}\n+\n+\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n+\t\t      track);\n+\n+\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n+\t\tprintf_ln(_(\"submodule %s: creating branch '%s'\"),\n+\t\t\t  submodules[i].name, name);\n+\t\tif (submodule_create_branch(\n+\t\t\t    &subrepos[i], name,\n+\t\t\t    oid_to_hex(\n+\t\t\t\t    &submodule_entry_list->name_entries[i].oid),\n+\t\t\t    branch_point, force, reflog, quiet, track,\n+\t\t\t    &err_msg))\n+\t\t\tdie(_(\"submodule %s: could not create branch '%s'\\n\\t%s\"),\n+\t\t\t    submodules[i].name, name, err_msg);\n+\n+\t\trepo_clear(&subrepos[i]);\n+\t}\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex d8e5ff4e28..1b4a635a2f 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -76,6 +76,12 @@ void create_branch(struct repository *r,\n \t\t   int force, int clobber_head_ok,\n \t\t   int reflog, int quiet, enum branch_track track);\n \n+/*\n+ * Creates a new branch in repository and its submodules.\n+ */\n+void create_submodule_branches(struct repository *r, const char *name,\n+\t\t\t       const char *start_name, int force, int reflog,\n+\t\t\t       int quiet, enum branch_track track);\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 5d4b9c82b4..6a16bdb1a3 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -39,6 +39,8 @@ static const char * const builtin_branch_usage[] = {\n \n static const char *head;\n static struct object_id head_oid;\n+static int recurse_submodules = 0;\n+static int submodule_propagate_branches = 0;\n \n static int branch_use_color = -1;\n static char branch_colors[][COLOR_MAXLEN] = {\n@@ -101,6 +103,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\t\treturn config_error_nonbool(var);\n \t\treturn color_parse(value, branch_colors[slot]);\n \t}\n+\tif (!strcmp(var, \"submodule.recurse\")) {\n+\t\trecurse_submodules = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcasecmp(var, \"submodule.propagateBranches\")) {\n+\t\tsubmodule_propagate_branches = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_color_default_config(var, value, cb);\n }\n \n@@ -621,7 +632,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tint delete = 0, rename = 0, copy = 0, force = 0, list = 0, create = 0,\n \t    unset_upstream = 0, show_current = 0, edit_description = 0;\n \t/* possible options */\n-\tint reflog = 0, quiet = 0, dry_run = 0, icase = 0;\n+\tint reflog = 0, quiet = 0, dry_run = 0, icase = 0,\n+\t    recurse_submodules_explicit = 0;\n \tconst char *new_upstream = NULL;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n@@ -670,6 +682,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n+\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT__DRY_RUN(&dry_run, N_(\"show whether the branch would be created\")),\n \t\tOPT_END(),\n@@ -713,9 +726,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tif (create < 0)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n+\tif (recurse_submodules_explicit && submodule_propagate_branches &&\n+\t    !create)\n+\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n \tif (dry_run && !create)\n \t\tdie(_(\"--dry-run can only be used when creating branches\"));\n \n+\trecurse_submodules =\n+\t\t(recurse_submodules || recurse_submodules_explicit) &&\n+\t\tsubmodule_propagate_branches;\n+\n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n \tfilter.ignore_case = icase;\n@@ -874,6 +894,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\t\tFREE_AND_NULL(unused_full_ref);\n \t\t\treturn 0;\n \t\t}\n+\t\tif (recurse_submodules) {\n+\t\t\tcreate_submodule_branches(the_repository, branch_name,\n+\t\t\t\t\t\t  start_name, force, reflog,\n+\t\t\t\t\t\t  quiet, track);\n+\t\t\treturn 0;\n+\t\t}\n \t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n \t\t\t      reflog, quiet, track);\n \t} else\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 6298cbdd4e..3ea1e8cc96 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -20,6 +20,7 @@\n #include \"diff.h\"\n #include \"object-store.h\"\n #include \"advice.h\"\n+#include \"branch.h\"\n \n #define OPT_QUIET (1 << 0)\n #define OPT_CACHED (1 << 1)\n@@ -2983,6 +2984,37 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n \treturn !!ret;\n }\n \n+static int module_create_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tenum branch_track track;\n+\tint quiet = 0, force = 0, reflog = 0;\n+\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n+\t\tOPT__FORCE(&force, N_(\"force creation\"), 0),\n+\t\tOPT_BOOL(0, \"create-reflog\", &reflog,\n+\t\t\t N_(\"create the branch's reflog\")),\n+\t\tOPT_SET_INT('t', \"track\", &track,\n+\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\t    BRANCH_TRACK_EXPLICIT),\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] <name> <start_oid> <start_name>\"),\n+\t\tNULL\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\n+\tif (argc != 3)\n+\t\tusage_with_options(usage, options);\n+\n+\tcreate_branch(the_repository, argv[0], argv[1], force, 0, reflog, quiet,\n+\t\t      BRANCH_TRACK_NEVER);\n+\tsetup_tracking(argv[0], argv[2], track, quiet, 0);\n+\n+\treturn 0;\n+}\n struct add_data {\n \tconst char *prefix;\n \tconst char *branch;\n@@ -3379,6 +3411,7 @@ static struct cmd_struct commands[] = {\n \t{\"config\", module_config, 0},\n \t{\"set-url\", module_set_url, 0},\n \t{\"set-branch\", module_set_branch, 0},\n+\t{\"create-branch\", module_create_branch, 0},\n };\n \n int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nnew file mode 100755\nindex 0000000000..14ff066e91\n--- /dev/null\n+++ b/t/t3207-branch-submodule.sh\n@@ -0,0 +1,249 @@\n+#!/bin/sh\n+\n+test_description='git branch submodule tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup superproject and submodule' '\n+\tgit init super &&\n+\ttest_commit foo &&\n+\tgit init sub-upstream &&\n+\ttest_commit -C sub-upstream foo &&\n+\tgit -C super submodule add ../sub-upstream sub &&\n+\tgit -C super commit -m \"add submodule\" &&\n+\tgit -C super config submodule.propagateBranches true\n+'\n+\n+cleanup_branches() {\n+\tsuper_dir=\"$1\"\n+\tshift\n+\t(\n+\t\tcd \"$super_dir\" &&\n+\t\tgit checkout main &&\n+\t\tfor branch_name in \"$@\"; do\n+\t\t\tgit branch -D \"$branch_name\"\n+\t\t\tgit submodule foreach \"(git checkout main && git branch -D $branch_name) || true\"\n+\t\tdone\n+\t)\n+} >/dev/null 2>/dev/null\n+\n+# Test the argument parsing\n+test_expect_success '--recurse-submodules should create branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should be ignored if submodule.propagateBranches is false' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c submodule.propagateBranches=false branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\ttest_must_fail git -C sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should fail when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n+\t\t# Assert that the branches were not deleted\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should respect submodule.recurse when creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c submodule.recurse=true branch branch-a &&\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should ignore submodule.recurse when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit -c submodule.recurse=true branch -D branch-a &&\n+\t\ttest_must_fail git rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+# Test branch creation behavior\n+test_expect_success 'should create branches based off commit id in superproject' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit checkout --recurse-submodules branch-a &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\t# Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a\n+\t\tgit -C sub checkout branch-a &&\n+\t\ttest_commit -C sub bar &&\n+\t\t# Create a new branch-b branch with start-point=branch-a\n+\t\tgit branch --recurse-submodules branch-b branch-a &&\n+\t\tgit rev-parse branch-b &&\n+\t\tgit -C sub rev-parse branch-b >actual &&\n+\t\t# Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should not create any branches if branch is not valid for all repos' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub branch branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_must_fail git rev-parse branch-a &&\n+\n+\t\tcat >expected <<EOF &&\n+fatal: submodule sub: could not create branch ${SQ}branch-a${SQ}\n+\tfatal: A branch named ${SQ}branch-a${SQ} already exists.\n+\n+EOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branches if branch exists and --force is given' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\ttest_commit -C sub baz &&\n+\t\tgit -C sub branch branch-a HEAD~1 &&\n+\t\tgit branch --recurse-submodules --force branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\t# assert that sub:branch-a was moved\n+\t\tgit -C sub rev-parse branch-a >actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branch when submodule is in .git/modules but not .gitmodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\" &&\n+\t\tgit checkout branch-a &&\n+\t\tgit branch --recurse-submodules branch-c branch-b &&\n+\t\tgit rev-parse branch-c &&\n+\t\tgit -C sub rev-parse branch-c &&\n+\t\tgit checkout --recurse-submodules branch-c &&\n+\t\tgit -C sub2 rev-parse branch-c\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with track=always' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with explicit track' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --track --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should not set up unnecessary tracking of local branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_expect_success 'setup remote-tracking tests' '\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\"\n+\t) &&\n+\t(\n+\t\tcd sub-upstream &&\n+\t\tgit branch branch-a\n+\t) &&\n+\tgit clone --branch main --recurse-submodules super super-clone &&\n+\tgit -C super-clone config submodule.propagateBranches true\n+'\n+\n+test_expect_success 'should not create branch when submodule is not in .git/modules' '\n+\t# The cleanup needs to delete sub2:branch-b in particular because main does not have sub2\n+\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n+\t\tcleanup_branches super-clone branch-a branch-b\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\t# This should succeed because super-clone has sub.\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\t# This should fail because super-clone does not have sub2.\n+\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n+\t\tcat >expected <<-EOF &&\n+\t\tfatal: submodule sub: unable to find submodule\n+\t\tYou may reinitialize the submodules using ${SQ}git checkout origin/branch-b && git submodule update${SQ}\n+\t\tEOF\n+\t\ttest_must_fail git rev-parse branch-b &&\n+\t\ttest_must_fail git -C sub rev-parse branch-b &&\n+\t\t# User can fix themselves by initializing the submodule\n+\t\tgit checkout origin/branch-b &&\n+\t\tgit submodule update &&\n+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of remote-tracking branches' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a\n+\t)\n+'\n+\n+test_expect_success 'should not fail when unable to set up tracking in submodule' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-b\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n+\t)\n+'\n+\n+test_done\n-- \n2.33.GIT\n\n"},{"id":"442085","messageId":"20211123021223.3472472-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20211122223252.19922-2-chooglen@google.com","subject":"Re: [PATCH 1/4] submodule-config: add submodules_of_tree() helper","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-11-23T02:12:23Z","receivedAt":"2021-11-23T02:12:28Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> +struct submodule_entry_list *\n> +submodules_of_tree(struct repository *r, const struct object_id *treeish_name)\n> +{\n> +\tstruct tree_desc tree;\n> +\tstruct name_entry entry;\n> +\tstruct submodule_entry_list *ret;\n> +\n> +\tCALLOC_ARRAY(ret, 1);\n> +\tfill_tree_descriptor(r, &tree, treeish_name);\n> +\twhile (tree_entry(&tree, &entry)) {\n\nI think that tree_entry() doesn't recurse into subtrees, but in any case we\nshould test this. (I looked at patch 4 and I think that the submodules are\nalways in the root tree.)\n\nThis reminded me of a similar thing when fetching submodules recursively and we\nneeded the \"before\" and \"after\" of submodule gitlinks. You can look at the code\n(collect_changed_submodules_cb() and the functions that use it in submodule.c)\nbut it may not be useful - in particular, that uses diff since we need to see\ndifferences there, but we don't need that here.\n\nI'll review the other patches tomorrow.\n"},{"id":"442103","messageId":"211123.86zgpvup6m.gmgdl@evledraar.gmail.com","threadId":"56962","inReplyTo":"20211122223252.19922-4-chooglen@google.com","subject":"Re: [PATCH 3/4] branch: add --dry-run option to branch","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T10:42:54Z","receivedAt":"2021-11-23T10:44:22Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Glen Choo wrote:\n\n> Add a --dry-run option to branch creation that can check whether or not\n> a branch name and start point would be valid for a repository without\n> creating a branch. Refactor cmd_branch() to make the chosen action more\n> obvious.\n> [...]\n> -'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n> +'git branch' [--track | --no-track] [-f] [--dry-run | -n] <branchname> [<start-point>]\n>  'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n>  'git branch' --unset-upstream [<branchname>]\n>  'git branch' (-m | -M) [<oldbranch>] <newbranch>\n> @@ -205,6 +205,12 @@ This option is only applicable in non-verbose mode.\n>  --no-abbrev::\n>  \tDisplay the full sha1s in the output listing rather than abbreviating them.\n>  \n> +-n::\n> +--dry-run::\n> +\tCan only be used when creating a branch. If the branch creation\n> +\twould fail, show the relevant error message. If the branch\n> +\tcreation would succeed, show nothing.\n> +\n\nThe usage & test show that we've got --dry-run for branch creation, but\nnot the \"creation\" we do on --copy or --move.\n\nThe former is just a \"create from source\", but \"move\" maybe not.\n\nIn any case, any reason to leave those out?\n"},{"id":"442104","messageId":"211123.86v90juovj.gmgdl@evledraar.gmail.com","threadId":"56962","inReplyTo":"20211122223252.19922-5-chooglen@google.com","subject":"Re: [PATCH 4/4] branch: add --recurse-submodules option for branch creation","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T10:45:06Z","receivedAt":"2021-11-23T10:51:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Glen Choo wrote:\n\n>  \tsubmoduleAlternateErrorStrategyDie::\n>  \t\tAdvice shown when a submodule.alternateErrorStrategy option\n>  \t\tconfigured to \"die\" causes a fatal error.\n> +\tsubmodulesNotUpdated::\n> +\t\tAdvice shown when a user runs a submodule command that fails\n> +\t\tbecause `git submodule update` was not run.\n>  \taddIgnoredFile::\n>  \t\tAdvice shown if a user attempts to add an ignored file to\n>  \t\tthe index.\n\nDoes it need to be submodule*s*NotUpdated? I.e. the existing error is\nsubmodule.. (non-plural), and we surely error on this per-submodule? The\nplural would make senes if the advice aggregates them to the end, let's\nlook at the implementation...\n\n> [...]\n> +\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n> +\t\tsubmodules[i] = *submodule_from_path(\n> +\t\t\tr, &super_oid,\n> +\t\t\tsubmodule_entry_list->name_entries[i].path);\n> +\n> +\t\tif (repo_submodule_init(\n> +\t\t\t    &subrepos[i], r,\n> +\t\t\t    submodule_entry_list->name_entries[i].path,\n> +\t\t\t    &super_oid)) {\n> +\t\t\tdie(_(\"submodule %s: unable to find submodule\"),\n> +\t\t\t    submodules[i].name);\n> +\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n> +\t\t\t\tadvise(_(\"You may try initializing the submodules using 'git checkout %s && git submodule update'\"),\n> +\t\t\t\t       start_name);\n> +\t\t}\n\nUh, a call to advise() after die()? :) That code isn't reachable.\n\nIt would be good to add test for what the output is in the next\niteration, which would be a forcing function for making sure this code\nworks.\n\nOne thing I find quite annoying about submodules currently is the\nverbosity of the output when we do N operations. E.g. I've got a repo\nwith 15-20 small submodules, cloning it prints out the usual \"git clone\"\nverbosity, which isn't so much when cloning one repo, but with 15-20 it\nfills up your screen.\n\nOperations like these should also behave more like \"git fetch --all\",\nsurely? I.e. let's try to run the operation on all, but if some failed\nalong the way let's have our exit code reflect that, and ideally print\nout an error summary, not N number of error() calls.\n\nThat would also justify the plural \"submodulesNotUpdated\", if such an\nadvise() printed out a summary of the N that failed at the end.\n\n"},{"id":"442105","messageId":"211123.86r1b7uoil.gmgdl@evledraar.gmail.com","threadId":"56962","inReplyTo":"20211122223252.19922-2-chooglen@google.com","subject":"Re: [PATCH 1/4] submodule-config: add submodules_of_tree() helper","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T10:53:15Z","receivedAt":"2021-11-23T10:58:46Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Nov 22 2021, Glen Choo wrote:\n\n> As we introduce a submodule UX with branches, we would like to be able\n> to get the submodule commit ids in a superproject tree because those ids\n> are the source of truth e.g. \"git branch --recurse-submodules topic\n> start-point\" should create branches based off the commit ids recorded in\n> the superproject's 'start-point' tree.\n>\n> To make this easy, introduce a submodules_of_tree() helper function that\n> iterates through a tree and returns the tree's gitlink entries as a\n> list.\n>\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  submodule-config.c | 19 +++++++++++++++++++\n>  submodule-config.h | 13 +++++++++++++\n>  2 files changed, 32 insertions(+)\n>\n> diff --git a/submodule-config.c b/submodule-config.c\n> index f95344028b..97da373301 100644\n> --- a/submodule-config.c\n> +++ b/submodule-config.c\n> @@ -7,6 +7,7 @@\n>  #include \"strbuf.h\"\n>  #include \"object-store.h\"\n>  #include \"parse-options.h\"\n> +#include \"tree-walk.h\"\n>  \n>  /*\n>   * submodule cache lookup structure\n> @@ -726,6 +727,24 @@ const struct submodule *submodule_from_path(struct repository *r,\n>  \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n>  }\n>  \n> +struct submodule_entry_list *\n> +submodules_of_tree(struct repository *r, const struct object_id *treeish_name)\n> +{\n> +\tstruct tree_desc tree;\n> +\tstruct name_entry entry;\n> +\tstruct submodule_entry_list *ret;\n> +\n> +\tCALLOC_ARRAY(ret, 1);\n> +\tfill_tree_descriptor(r, &tree, treeish_name);\n> +\twhile (tree_entry(&tree, &entry)) {\n> +\t\tif (!S_ISGITLINK(entry.mode))\n> +\t\t\tcontinue;\n> +\t\tALLOC_GROW(ret->name_entries, ret->entry_nr + 1, ret->entry_alloc);\n> +\t\tret->name_entries[ret->entry_nr++] = entry;\n> +\t}\n> +\treturn ret;\n> +}\n> +\n>  void submodule_free(struct repository *r)\n>  {\n>  \tif (r->submodule_cache)\n> diff --git a/submodule-config.h b/submodule-config.h\n> index 65875b94ea..4379ec77e3 100644\n> --- a/submodule-config.h\n> +++ b/submodule-config.h\n> @@ -6,6 +6,7 @@\n>  #include \"hashmap.h\"\n>  #include \"submodule.h\"\n>  #include \"strbuf.h\"\n> +#include \"tree-walk.h\"\n>  \n>  /**\n>   * The submodule config cache API allows to read submodule\n> @@ -67,6 +68,18 @@ const struct submodule *submodule_from_name(struct repository *r,\n>  \t\t\t\t\t    const struct object_id *commit_or_tree,\n>  \t\t\t\t\t    const char *name);\n>  \n> +struct submodule_entry_list {\n> +\tstruct name_entry *name_entries;\n> +\tint entry_nr;\n> +\tint entry_alloc;\n> +};\n> +\n> +/**\n> + * Given a tree-ish, return all submodules in the tree.\n> + */\n> +struct submodule_entry_list *\n> +submodules_of_tree(struct repository *r, const struct object_id *treeish_name);\n> +\n>  /**\n>   * Given a tree-ish in the superproject and a path, return the submodule that\n>   * is bound at the path in the named tree.\n\nHaving skimmed through this topic isn't this in 4/4 the only resulting caller:\n\t\n\t+void create_submodule_branches(struct repository *r, const char *name,\n\t+\t\t\t       const char *start_name, int force, int reflog,\n\t+\t\t\t       int quiet, enum branch_track track)\n\t+{\n\t+\tint i = 0;\n\t+\tchar *branch_point = NULL;\n\t+\tstruct repository *subrepos;\n\t+\tstruct submodule *submodules;\n\t+\tstruct object_id super_oid;\n\t+\tstruct submodule_entry_list *submodule_entry_list;\n\t+\tchar *err_msg = NULL;\n\t+\n\t+\tvalidate_branch_start(r, start_name, track, &super_oid, &branch_point);\n\t+\n\t+\tsubmodule_entry_list = submodules_of_tree(r, &super_oid);\n\t+\tCALLOC_ARRAY(subrepos, submodule_entry_list->entry_nr);\n\t+\tCALLOC_ARRAY(submodules, submodule_entry_list->entry_nr);\n\t+\n\t+\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n\nI think it would be better to just intorduce this function at the same\ntime as its (only?) user, which also makes it clear how it's used.\n\nIn this case this seems like quite a bit of over-allocation. I.e. we\nreturn a malloc'd pointer, and iterate with tree_entry(), the caller\nthen needs to loop over that and do its own allocations of \"struct\nrepository *\" and \"struct submodule *\".\n\nWouldn't it be better just to have this new submodule_entry_list contain\na list of not \"struct name_entry\", but:\n\n    struct new_thingy {\n        struct name_entry *entry;\n        struct repository *repo;\n        struct submodule *submodule;\n    }\n\nThen have the caller allocate the container on the stack, pass it to\nthis function.\n\nMaybe not, just musings while doing some light reading. I was surprised\nat what are effectively two loops over the same data, first allocating\n1/3, then the other doing the other 2/3...\n"},{"id":"442185","messageId":"kl6lk0gyk9ej.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"211123.86r1b7uoil.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 1/4] submodule-config: add submodules_of_tree() helper","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-23T18:35:16Z","receivedAt":"2021-11-23T18:35:20Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> Having skimmed through this topic isn't this in 4/4 the only resulting caller:\n> \t\n> \t+void create_submodule_branches(struct repository *r, const char *name,\n> \t+\t\t\t       const char *start_name, int force, int reflog,\n> \t+\t\t\t       int quiet, enum branch_track track)\n> \t+{\n> \t+\tint i = 0;\n> \t+\tchar *branch_point = NULL;\n> \t+\tstruct repository *subrepos;\n> \t+\tstruct submodule *submodules;\n> \t+\tstruct object_id super_oid;\n> \t+\tstruct submodule_entry_list *submodule_entry_list;\n> \t+\tchar *err_msg = NULL;\n> \t+\n> \t+\tvalidate_branch_start(r, start_name, track, &super_oid, &branch_point);\n> \t+\n> \t+\tsubmodule_entry_list = submodules_of_tree(r, &super_oid);\n> \t+\tCALLOC_ARRAY(subrepos, submodule_entry_list->entry_nr);\n> \t+\tCALLOC_ARRAY(submodules, submodule_entry_list->entry_nr);\n> \t+\n> \t+\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n>\n> I think it would be better to just intorduce this function at the same\n> time as its (only?) user, which also makes it clear how it's used.\n\nYes that makes sense. That is the only user (for now). \n\n> In this case this seems like quite a bit of over-allocation. I.e. we\n> return a malloc'd pointer, and iterate with tree_entry(), the caller\n> then needs to loop over that and do its own allocations of \"struct\n> repository *\" and \"struct submodule *\".\n>\n> Wouldn't it be better just to have this new submodule_entry_list contain\n> a list of not \"struct name_entry\", but:\n>\n>     struct new_thingy {\n>         struct name_entry *entry;\n>         struct repository *repo;\n>         struct submodule *submodule;\n>     }\n>\n> Then have the caller allocate the container on the stack, pass it to\n> this function.\n\nI thought about it as well. \"struct new_thingy\" is obviously the right\nstruct for create_submodule_branches(), but I'm not sure if it is the\nright thing for other future callers e.g. \"struct submodule\" is only\nused to give a submodule name to users in help messages.\n\nBut chances are, any caller that needs 'submodules of a tree' will need\nvery similar pieces of information, so it seems reasonable to do what\nyou said instead of over-allocating in all of the callers.\n\n> Maybe not, just musings while doing some light reading. I was surprised\n> at what are effectively two loops over the same data, first allocating\n> 1/3, then the other doing the other 2/3...\n\nThe first loop validates all submodules before creating any branches\n(and also happens to allocate). If we didn't have the validation step,\nallocation + creating branches could just be one loop :)\n"},{"id":"442187","messageId":"kl6lh7c2k92q.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"211123.86zgpvup6m.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 3/4] branch: add --dry-run option to branch","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-23T18:42:21Z","receivedAt":"2021-11-23T18:42:27Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Mon, Nov 22 2021, Glen Choo wrote:\n>\n>> Add a --dry-run option to branch creation that can check whether or not\n>> a branch name and start point would be valid for a repository without\n>> creating a branch. Refactor cmd_branch() to make the chosen action more\n>> obvious.\n>> [...]\n>> -'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n>> +'git branch' [--track | --no-track] [-f] [--dry-run | -n] <branchname> [<start-point>]\n>>  'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n>>  'git branch' --unset-upstream [<branchname>]\n>>  'git branch' (-m | -M) [<oldbranch>] <newbranch>\n>> @@ -205,6 +205,12 @@ This option is only applicable in non-verbose mode.\n>>  --no-abbrev::\n>>  \tDisplay the full sha1s in the output listing rather than abbreviating them.\n>>  \n>> +-n::\n>> +--dry-run::\n>> +\tCan only be used when creating a branch. If the branch creation\n>> +\twould fail, show the relevant error message. If the branch\n>> +\tcreation would succeed, show nothing.\n>> +\n>\n> The usage & test show that we've got --dry-run for branch creation, but\n> not the \"creation\" we do on --copy or --move.\n\nPerhaps this is more of a wording issue i.e. 'creating a branch' is too\nunspecific. Maybe \n\n\tCan only be used when creating a new branch (without copying or moving\n\tan existing branch). If the branch creation would fail, show the\n\trelevant error message. If the branch creation would succeed, show\n\tnothing.\n\n> In any case, any reason to leave those out?\n\nNo long term reason. I left those out because \"create brand new branch\nwith --dry-run\" was needed right now, but the others are not. For\nconsistency, we'd want --dry-run for all other actions, including copy,\nmove, delete, etc.\n"},{"id":"442189","messageId":"kl6lee76k8en.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"211123.86v90juovj.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 4/4] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-23T18:56:48Z","receivedAt":"2021-11-23T18:56:52Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Mon, Nov 22 2021, Glen Choo wrote:\n>\n>>  \tsubmoduleAlternateErrorStrategyDie::\n>>  \t\tAdvice shown when a submodule.alternateErrorStrategy option\n>>  \t\tconfigured to \"die\" causes a fatal error.\n>> +\tsubmodulesNotUpdated::\n>> +\t\tAdvice shown when a user runs a submodule command that fails\n>> +\t\tbecause `git submodule update` was not run.\n>>  \taddIgnoredFile::\n>>  \t\tAdvice shown if a user attempts to add an ignored file to\n>>  \t\tthe index.\n>\n> Does it need to be submodule*s*NotUpdated? I.e. the existing error is\n> submodule.. (non-plural), and we surely error on this per-submodule?\n\nFrom the user's perspective, failing on a per-submodule basis looks like\nan implementation detail. As a user, I am looking for advice on errors\nthat could be avoided by running \"git submodule update\"; I'd want the\nadvice option to describe what \"git submodule update\" does, which is\nupdate submodule*s*.\n\n>> [...]\n>> +\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n>> +\t\tsubmodules[i] = *submodule_from_path(\n>> +\t\t\tr, &super_oid,\n>> +\t\t\tsubmodule_entry_list->name_entries[i].path);\n>> +\n>> +\t\tif (repo_submodule_init(\n>> +\t\t\t    &subrepos[i], r,\n>> +\t\t\t    submodule_entry_list->name_entries[i].path,\n>> +\t\t\t    &super_oid)) {\n>> +\t\t\tdie(_(\"submodule %s: unable to find submodule\"),\n>> +\t\t\t    submodules[i].name);\n>> +\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n>> +\t\t\t\tadvise(_(\"You may try initializing the submodules using 'git checkout %s && git submodule update'\"),\n>> +\t\t\t\t       start_name);\n>> +\t\t}\n>\n> Uh, a call to advise() after die()? :) That code isn't reachable.\n>\n> It would be good to add test for what the output is in the next\n> iteration, which would be a forcing function for making sure this code\n> works.\n\nWhoops. Good catch. Yes I should test that.\n\n> One thing I find quite annoying about submodules currently is the\n> verbosity of the output when we do N operations. E.g. I've got a repo\n> with 15-20 small submodules, cloning it prints out the usual \"git clone\"\n> verbosity, which isn't so much when cloning one repo, but with 15-20 it\n> fills up your screen.\n>\n> Operations like these should also behave more like \"git fetch --all\",\n> surely? I.e. let's try to run the operation on all, but if some failed\n> along the way let's have our exit code reflect that, and ideally print\n> out an error summary, not N number of error() calls.\n\nThat's a valid criticism, and one we're concerned about as well. I'm\nnot 100% satisfied with how I've structured the output either, but as a\npractical matter, figuring out an *ideal* output format and\nstandardizing it takes up too much valuable iteration time that could be\nspent on improving the UX instead.\n\nSo the approach here is to have output that is 'good enough' for now,\nand to create better and standardized output when we've nailed down more\nof the UX.\n\n> That would also justify the plural \"submodulesNotUpdated\", if such an\n> advise() printed out a summary of the N that failed at the end.\n\nFair. We could treat an uninitialized submodule the same as any other\nfailure and summarize all failures.\n"},{"id":"442193","messageId":"3ad3941c-de18-41bf-2e44-4238ae868d79@gmail.com","threadId":"56962","inReplyTo":"20211122223252.19922-5-chooglen@google.com","subject":"Re: [PATCH 4/4] branch: add --recurse-submodules option for branch creation","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2021-11-23T19:41:06Z","receivedAt":"2021-11-23T19:41:11Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Glen,\n\nLe 2021-11-22 à 17:32, Glen Choo a écrit :\n> Add a --recurse-submodules option when creating branches so that `git\n> branch --recurse-submodules topic` will create the \"topic\" branch in the\n> superproject and all submodules. Guard this (and future submodule\n> branching) behavior behind a new configuration value\n> 'submodule.propagateBranches'.\n> \n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>   Documentation/config/advice.txt    |   3 +\n>   Documentation/config/submodule.txt |   9 ++\n\nWe would need to add the new flag to Documentation/git-branch.txt,\nand also probably update the documentation of 'submodule.recurse'\nin 'Documentation/config/submodule.txt'.\n\n>   advice.c                           |   1 +\n>   advice.h                           |   1 +\n>   branch.c                           | 123 ++++++++++++++\n>   branch.h                           |   6 +\n>   builtin/branch.c                   |  28 +++-\n>   builtin/submodule--helper.c        |  33 ++++\n>   t/t3207-branch-submodule.sh        | 249 +++++++++++++++++++++++++++++\n>   9 files changed, 452 insertions(+), 1 deletion(-)\n>   create mode 100755 t/t3207-branch-submodule.sh\n> \n> diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\n> index 063eec2511..e52262dc69 100644\n> --- a/Documentation/config/advice.txt\n> +++ b/Documentation/config/advice.txt\n> @@ -116,6 +116,9 @@ advice.*::\n>   \tsubmoduleAlternateErrorStrategyDie::\n>   \t\tAdvice shown when a submodule.alternateErrorStrategy option\n>   \t\tconfigured to \"die\" causes a fatal error.\n> +\tsubmodulesNotUpdated::\n> +\t\tAdvice shown when a user runs a submodule command that fails\n> +\t\tbecause `git submodule update` was not run.\n>   \taddIgnoredFile::\n>   \t\tAdvice shown if a user attempts to add an ignored file to\n>   \t\tthe index.\n> diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\n> index ee454f8126..c318b849aa 100644\n> --- a/Documentation/config/submodule.txt\n> +++ b/Documentation/config/submodule.txt\n> @@ -72,6 +72,15 @@ submodule.recurse::\n>   \tFor these commands a workaround is to temporarily change the\n>   \tconfiguration value by using `git -c submodule.recurse=0`.\n>   \n> +submodule.propagateBranches::\n> +\t[EXPERIMENTAL] A boolean that enables branching support with\n> +\tsubmodules. This allows certain commands to accept\n> +\t`--recurse-submodules` (`git branch --recurse-submodules` will\n> +\tcreate branches recursively), and certain commands that already\n> +\taccept `--recurse-submodules` will now consider branches (`git\n> +\tswitch --recurse-submodules` will switch to the correct branch\n> +\tin all submodules).\n\nLooking at the rest of the patch, this just implements 'branch --recurse-submodules', right ?\ni.e. 'git switch' and 'git checkout' are left alone for\nnow, so I think this addition to the doc should only mention 'git branch'.\n\n> +\n>   submodule.fetchJobs::\n>   \tSpecifies how many submodules are fetched/cloned at the same time.\n>   \tA positive integer allows up to that number of submodules fetched\n> diff --git a/advice.c b/advice.c\n> index 1dfc91d176..e00d30254c 100644\n> --- a/advice.c\n> +++ b/advice.c\n> @@ -70,6 +70,7 @@ static struct {\n>   \t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n>   \t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n>   \t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n> +\t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n>   \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n>   \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n>   };\n> diff --git a/advice.h b/advice.h\n> index 601265fd10..a7521d6087 100644\n> --- a/advice.h\n> +++ b/advice.h\n> @@ -44,6 +44,7 @@ struct string_list;\n>   \tADVICE_STATUS_HINTS,\n>   \tADVICE_STATUS_U_OPTION,\n>   \tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n> +\tADVICE_SUBMODULES_NOT_UPDATED,\n>   \tADVICE_UPDATE_SPARSE_PATH,\n>   \tADVICE_WAITING_FOR_EDITOR,\n>   \tADVICE_SKIPPED_CHERRY_PICKS,\n> diff --git a/branch.c b/branch.c\n> index 528cb2d639..404766d01d 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -8,6 +8,8 @@\n>   #include \"sequencer.h\"\n>   #include \"commit.h\"\n>   #include \"worktree.h\"\n> +#include \"submodule-config.h\"\n> +#include \"run-command.h\"\n>   \n>   struct tracking {\n>   \tstruct refspec_item spec;\n> @@ -345,6 +347,127 @@ void create_branch(struct repository *r, const char *name,\n>   \tfree(real_ref);\n>   }\n>   \n> +static int submodule_validate_branchname(struct repository *r, const char *name,\n> +\t\t\t\t\t const char *start_name, int force,\n> +\t\t\t\t\t int quiet, char **err_msg)\n> +{\n> +\tint ret = 0;\n> +\tstruct child_process child = CHILD_PROCESS_INIT;\n> +\tstruct strbuf child_err = STRBUF_INIT;\n> +\tchild.git_cmd = 1;\n> +\tchild.err = -1;\n> +\n> +\tprepare_other_repo_env(&child.env_array, r->gitdir);\n> +\tstrvec_pushl(&child.args, \"branch\", \"--dry-run\", NULL);\n> +\tif (force)\n> +\t\tstrvec_push(&child.args, \"--force\");\n> +\tif (quiet)\n> +\t\tstrvec_push(&child.args, \"--quiet\");\n> +\tstrvec_pushl(&child.args, name, start_name, NULL);\n> +\n> +\tif ((ret = start_command(&child)))\n> +\t\treturn ret;\n> +\tret = finish_command(&child);\n> +\tstrbuf_read(&child_err, child.err, 0);\n> +\t*err_msg = strbuf_detach(&child_err, NULL);\n> +\treturn ret;\n> +}\n> +\n> +static int submodule_create_branch(struct repository *r, const char *name,\n> +\t\t\t\t   const char *start_oid,\n> +\t\t\t\t   const char *start_name, int force,\n> +\t\t\t\t   int reflog, int quiet,\n> +\t\t\t\t   enum branch_track track, char **err_msg)\n> +{\n> +\tint ret = 0;\n> +\tstruct child_process child = CHILD_PROCESS_INIT;\n> +\tstruct strbuf child_err = STRBUF_INIT;\n> +\tchild.git_cmd = 1;\n> +\tchild.err = -1;\n> +\n> +\tprepare_other_repo_env(&child.env_array, r->gitdir);\n> +\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n> +\tif (force)\n> +\t\tstrvec_push(&child.args, \"--force\");\n> +\tif (quiet)\n> +\t\tstrvec_push(&child.args, \"--quiet\");\n> +\tif (reflog)\n> +\t\tstrvec_push(&child.args, \"--create-reflog\");\n> +\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n> +\t\tstrvec_push(&child.args, \"--track\");\n> +\n> +\tstrvec_pushl(&child.args, name, start_oid, start_name, NULL);\n> +\n> +\tif ((ret = start_command(&child)))\n> +\t\treturn ret;\n> +\tret = finish_command(&child);\n> +\tstrbuf_read(&child_err, child.err, 0);\n> +\t*err_msg = strbuf_detach(&child_err, NULL);\n> +\treturn ret;\n> +}\n> +\n> +void create_submodule_branches(struct repository *r, const char *name,\n> +\t\t\t       const char *start_name, int force, int reflog,\n> +\t\t\t       int quiet, enum branch_track track)\n> +{\n> +\tint i = 0;\n> +\tchar *branch_point = NULL;\n> +\tstruct repository *subrepos;\n> +\tstruct submodule *submodules;\n> +\tstruct object_id super_oid;\n> +\tstruct submodule_entry_list *submodule_entry_list;\n> +\tchar *err_msg = NULL;\n> +\n> +\tvalidate_branch_start(r, start_name, track, &super_oid, &branch_point);\n> +\n> +\tsubmodule_entry_list = submodules_of_tree(r, &super_oid);\n> +\tCALLOC_ARRAY(subrepos, submodule_entry_list->entry_nr);\n> +\tCALLOC_ARRAY(submodules, submodule_entry_list->entry_nr);\n> +\n> +\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n> +\t\tsubmodules[i] = *submodule_from_path(\n> +\t\t\tr, &super_oid,\n> +\t\t\tsubmodule_entry_list->name_entries[i].path);\n> +\n> +\t\tif (repo_submodule_init(\n> +\t\t\t    &subrepos[i], r,\n> +\t\t\t    submodule_entry_list->name_entries[i].path,\n> +\t\t\t    &super_oid)) {\n> +\t\t\tdie(_(\"submodule %s: unable to find submodule\"),\n> +\t\t\t    submodules[i].name);\n> +\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n> +\t\t\t\tadvise(_(\"You may try initializing the submodules using 'git checkout %s && git submodule update'\"),\n> +\t\t\t\t       start_name);\n\nApart from what Ævar pointed out about advise() being called after die(),\nI'm not sure this is the right advice, because if repo_submodule_init fails\nit means there is no .git/modules/<name> directory corresponding to the submodule's\nGit repository, i.e. the submodule was never cloned. So it's not guaranteed\nthat 'git checkout $start_name && git submodule update' would initialize (and clone) it,\nnot without '--init'.\n\n> +\t\t}\n> +\n> +\t\tif (submodule_validate_branchname(\n> +\t\t\t    &subrepos[i], name,\n> +\t\t\t    oid_to_hex(\n> +\t\t\t\t    &submodule_entry_list->name_entries[i].oid),\n> +\t\t\t    force, quiet, &err_msg))\n> +\t\t\tdie(_(\"submodule %s: could not create branch '%s'\\n\\t%s\"),\n> +\t\t\t    submodules[i].name, name, err_msg);\n\nminor nit abour wording: we did not try to create the branch here, we just checked if\ncreating is possible. So it *might* be confusing (maybe not). Maybe just \"can not\"\ninstead of \"could not\" ?\n\n> +\t}\n> +\n> +\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n> +\t\t      track);\n\nOK, here we create the superproject branch. This might not be expected when\njust reading the name of the function...\n\n> +\n> +\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n\nHere we loop over all submodules, so branches are created even in\ninactive submodules... this might not be wanted.\n\n> +\t\tprintf_ln(_(\"submodule %s: creating branch '%s'\"),\n> +\t\t\t  submodules[i].name, name);\n> +\t\tif (submodule_create_branch(\n> +\t\t\t    &subrepos[i], name,\n> +\t\t\t    oid_to_hex(\n> +\t\t\t\t    &submodule_entry_list->name_entries[i].oid),\n> +\t\t\t    branch_point, force, reflog, quiet, track,\n> +\t\t\t    &err_msg))\n> +\t\t\tdie(_(\"submodule %s: could not create branch '%s'\\n\\t%s\"),\n> +\t\t\t    submodules[i].name, name, err_msg);\n> +\n> +\t\trepo_clear(&subrepos[i]);\n> +\t}\n> +}\n> +\n>   void remove_merge_branch_state(struct repository *r)\n>   {\n>   \tunlink(git_path_merge_head(r));\n> diff --git a/branch.h b/branch.h\n> index d8e5ff4e28..1b4a635a2f 100644\n> --- a/branch.h\n> +++ b/branch.h\n> @@ -76,6 +76,12 @@ void create_branch(struct repository *r,\n>   \t\t   int force, int clobber_head_ok,\n>   \t\t   int reflog, int quiet, enum branch_track track);\n>   \n> +/*\n> + * Creates a new branch in repository and its submodules.\n> + */\n> +void create_submodule_branches(struct repository *r, const char *name,\n> +\t\t\t       const char *start_name, int force, int reflog,\n> +\t\t\t       int quiet, enum branch_track track);\n>   /*\n>    * Check if 'name' can be a valid name for a branch; die otherwise.\n>    * Return 1 if the named branch already exists; return 0 otherwise.\n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index 5d4b9c82b4..6a16bdb1a3 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -39,6 +39,8 @@ static const char * const builtin_branch_usage[] = {\n>   \n>   static const char *head;\n>   static struct object_id head_oid;\n> +static int recurse_submodules = 0;\n> +static int submodule_propagate_branches = 0;\n>   \n>   static int branch_use_color = -1;\n>   static char branch_colors[][COLOR_MAXLEN] = {\n> @@ -101,6 +103,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n>   \t\t\treturn config_error_nonbool(var);\n>   \t\treturn color_parse(value, branch_colors[slot]);\n>   \t}\n> +\tif (!strcmp(var, \"submodule.recurse\")) {\n> +\t\trecurse_submodules = git_config_bool(var, value);\n> +\t\treturn 0;\n> +\t}\n> +\tif (!strcasecmp(var, \"submodule.propagateBranches\")) {\n> +\t\tsubmodule_propagate_branches = git_config_bool(var, value);\n> +\t\treturn 0;\n> +\t}\n> +\n>   \treturn git_color_default_config(var, value, cb);\n>   }\n>   \n> @@ -621,7 +632,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>   \tint delete = 0, rename = 0, copy = 0, force = 0, list = 0, create = 0,\n>   \t    unset_upstream = 0, show_current = 0, edit_description = 0;\n>   \t/* possible options */\n> -\tint reflog = 0, quiet = 0, dry_run = 0, icase = 0;\n> +\tint reflog = 0, quiet = 0, dry_run = 0, icase = 0,\n> +\t    recurse_submodules_explicit = 0;\n>   \tconst char *new_upstream = NULL;\n>   \tenum branch_track track;\n>   \tstruct ref_filter filter;\n> @@ -670,6 +682,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>   \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n>   \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n>   \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n> +\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n>   \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n>   \t\tOPT__DRY_RUN(&dry_run, N_(\"show whether the branch would be created\")),\n>   \t\tOPT_END(),\n> @@ -713,9 +726,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>   \tif (create < 0)\n>   \t\tusage_with_options(builtin_branch_usage, options);\n>   \n> +\tif (recurse_submodules_explicit && submodule_propagate_branches &&\n> +\t    !create)\n> +\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n>   \tif (dry_run && !create)\n>   \t\tdie(_(\"--dry-run can only be used when creating branches\"));\n>   \n> +\trecurse_submodules =\n> +\t\t(recurse_submodules || recurse_submodules_explicit) &&\n> +\t\tsubmodule_propagate_branches;\n> +\n\nOK, so we get the new behaviour if either --recurse-submodules was used, or 'submodule.recurse' is true,\nand in both case we also need the new submodule.propagateBranches config set.\n\nWhy not adding 'branch.recurseSubmodules' instead, with a higher priority than submodule.recurse ?\nIs it because then it would be mildly confusing for 'git checkout / git switch' to also honor\na setting named 'branch.*' when they learn the new behaviour ? (I don't think this would be the\nfirst time that 'git foo' honors 'bar.*', so it might be worth mentioning).\n\nAlso, why do we quietly ignore '--recurse-submodules' if submodule.propagateBranches is unset ?\nWouldn't it be better to warn the user \"hey, if you want this new behaviour you need to\nset that config !\" ?\n\nI don't have a strong opinion about the fact that you need to set the config in the first\nplace, but I think it should be mentioned in the commit message why you chose to implement\nit that way (meaning, why do we need a config set, instead of adding the config but defaulting it\nto true, so that you get the new behaviour by default, but you still can disable it if you do not\nwant it)...\n\n>   \tif (filter.abbrev == -1)\n>   \t\tfilter.abbrev = DEFAULT_ABBREV;\n>   \tfilter.ignore_case = icase;\n> @@ -874,6 +894,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>   \t\t\tFREE_AND_NULL(unused_full_ref);\n>   \t\t\treturn 0;\n>   \t\t}\n> +\t\tif (recurse_submodules) {\n> +\t\t\tcreate_submodule_branches(the_repository, branch_name,\n> +\t\t\t\t\t\t  start_name, force, reflog,\n> +\t\t\t\t\t\t  quiet, track);\n> +\t\t\treturn 0;\n> +\t\t}\n>   \t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n>   \t\t\t      reflog, quiet, track);\n>   \t} else\n> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\n> index 6298cbdd4e..3ea1e8cc96 100644\n> --- a/builtin/submodule--helper.c\n> +++ b/builtin/submodule--helper.c\n> @@ -20,6 +20,7 @@\n>   #include \"diff.h\"\n>   #include \"object-store.h\"\n>   #include \"advice.h\"\n> +#include \"branch.h\"\n>   \n>   #define OPT_QUIET (1 << 0)\n>   #define OPT_CACHED (1 << 1)\n> @@ -2983,6 +2984,37 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n>   \treturn !!ret;\n>   }\n>   \n> +static int module_create_branch(int argc, const char **argv, const char *prefix)\n> +{\n> +\tenum branch_track track;\n> +\tint quiet = 0, force = 0, reflog = 0;\n> +\n> +\tstruct option options[] = {\n> +\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n> +\t\tOPT__FORCE(&force, N_(\"force creation\"), 0),\n> +\t\tOPT_BOOL(0, \"create-reflog\", &reflog,\n> +\t\t\t N_(\"create the branch's reflog\")),\n> +\t\tOPT_SET_INT('t', \"track\", &track,\n> +\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n> +\t\t\t    BRANCH_TRACK_EXPLICIT),\n> +\t\tOPT_END()\n> +\t};\n> +\tconst char *const usage[] = {\n> +\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] <name> <start_oid> <start_name>\"),\n> +\t\tNULL\n> +\t};\n> +\n> +\targc = parse_options(argc, argv, prefix, options, usage, 0);\n> +\n> +\tif (argc != 3)\n> +\t\tusage_with_options(usage, options);\n> +\n> +\tcreate_branch(the_repository, argv[0], argv[1], force, 0, reflog, quiet,\n> +\t\t      BRANCH_TRACK_NEVER);\n> +\tsetup_tracking(argv[0], argv[2], track, quiet, 0);\n> +\n> +\treturn 0;\n> +}\n>   struct add_data {\n>   \tconst char *prefix;\n>   \tconst char *branch;\n> @@ -3379,6 +3411,7 @@ static struct cmd_struct commands[] = {\n>   \t{\"config\", module_config, 0},\n>   \t{\"set-url\", module_set_url, 0},\n>   \t{\"set-branch\", module_set_branch, 0},\n> +\t{\"create-branch\", module_create_branch, 0},\n>   };\n>   \n>   int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\n> diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\n> new file mode 100755\n> index 0000000000..14ff066e91\n> --- /dev/null\n> +++ b/t/t3207-branch-submodule.sh\n> @@ -0,0 +1,249 @@\n> +#!/bin/sh\n> +\n> +test_description='git branch submodule tests'\n> +\n> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> +\n> +. ./test-lib.sh\n> +. \"$TEST_DIRECTORY\"/lib-rebase.sh\n> +\n> +test_expect_success 'setup superproject and submodule' '\n> +\tgit init super &&\n> +\ttest_commit foo &&\n> +\tgit init sub-upstream &&\n> +\ttest_commit -C sub-upstream foo &&\n> +\tgit -C super submodule add ../sub-upstream sub &&\n> +\tgit -C super commit -m \"add submodule\" &&\n> +\tgit -C super config submodule.propagateBranches true\n> +'\n> +\n> +cleanup_branches() {\n> +\tsuper_dir=\"$1\"\n> +\tshift\n> +\t(\n> +\t\tcd \"$super_dir\" &&\n> +\t\tgit checkout main &&\n> +\t\tfor branch_name in \"$@\"; do\n> +\t\t\tgit branch -D \"$branch_name\"\n> +\t\t\tgit submodule foreach \"(git checkout main && git branch -D $branch_name) || true\"\n> +\t\tdone\n> +\t)\n> +} >/dev/null 2>/dev/null\n> +\n> +# Test the argument parsing\n> +test_expect_success '--recurse-submodules should create branches' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch --recurse-submodules branch-a &&\n> +\t\tgit rev-parse --abbrev-ref branch-a &&\n> +\t\tgit -C sub rev-parse --abbrev-ref branch-a\n> +\t)\n> +'\n> +\n> +test_expect_success '--recurse-submodules should be ignored if submodule.propagateBranches is false' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit -c submodule.propagateBranches=false branch --recurse-submodules branch-a &&\n> +\t\tgit rev-parse branch-a &&\n> +\t\ttest_must_fail git -C sub rev-parse branch-a\n> +\t)\n> +'\n> +\n> +test_expect_success '--recurse-submodules should fail when not creating branches' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch --recurse-submodules branch-a &&\n> +\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n> +\t\t# Assert that the branches were not deleted\n> +\t\tgit rev-parse --abbrev-ref branch-a &&\n> +\t\tgit -C sub rev-parse --abbrev-ref branch-a\n> +\t)\n> +'\n> +\n> +test_expect_success 'should respect submodule.recurse when creating branches' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit -c submodule.recurse=true branch branch-a &&\n> +\t\tgit rev-parse --abbrev-ref branch-a &&\n> +\t\tgit -C sub rev-parse --abbrev-ref branch-a\n> +\t)\n> +'\n> +\n> +test_expect_success 'should ignore submodule.recurse when not creating branches' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch --recurse-submodules branch-a &&\n> +\t\tgit -c submodule.recurse=true branch -D branch-a &&\n> +\t\ttest_must_fail git rev-parse --abbrev-ref branch-a &&\n> +\t\tgit -C sub rev-parse --abbrev-ref branch-a\n> +\t)\n> +'\n> +\n> +# Test branch creation behavior\n> +test_expect_success 'should create branches based off commit id in superproject' '\n> +\ttest_when_finished \"cleanup_branches super branch-a branch-b\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch --recurse-submodules branch-a &&\n> +\t\tgit checkout --recurse-submodules branch-a &&\n> +\t\tgit -C sub rev-parse HEAD >expected &&\n> +\t\t# Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a\n> +\t\tgit -C sub checkout branch-a &&\n> +\t\ttest_commit -C sub bar &&\n> +\t\t# Create a new branch-b branch with start-point=branch-a\n> +\t\tgit branch --recurse-submodules branch-b branch-a &&\n> +\t\tgit rev-parse branch-b &&\n> +\t\tgit -C sub rev-parse branch-b >actual &&\n> +\t\t# Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a\n> +\t\ttest_cmp expected actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'should not create any branches if branch is not valid for all repos' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit -C sub branch branch-a &&\n> +\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n> +\t\ttest_must_fail git rev-parse branch-a &&\n> +\n> +\t\tcat >expected <<EOF &&\n> +fatal: submodule sub: could not create branch ${SQ}branch-a${SQ}\n> +\tfatal: A branch named ${SQ}branch-a${SQ} already exists.\n> +\n> +EOF\n> +\t\ttest_cmp expected actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'should create branches if branch exists and --force is given' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit -C sub rev-parse HEAD >expected &&\n> +\t\ttest_commit -C sub baz &&\n> +\t\tgit -C sub branch branch-a HEAD~1 &&\n> +\t\tgit branch --recurse-submodules --force branch-a &&\n> +\t\tgit rev-parse branch-a &&\n> +\t\t# assert that sub:branch-a was moved\n> +\t\tgit -C sub rev-parse branch-a >actual &&\n> +\t\ttest_cmp expected actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'should create branch when submodule is in .git/modules but not .gitmodules' '\n> +\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch branch-a &&\n> +\t\tgit checkout -b branch-b &&\n> +\t\tgit submodule add ../sub-upstream sub2 &&\n> +\t\t# branch-b now has a committed submodule not in branch-a\n> +\t\tgit commit -m \"add second submodule\" &&\n> +\t\tgit checkout branch-a &&\n> +\t\tgit branch --recurse-submodules branch-c branch-b &&\n> +\t\tgit rev-parse branch-c &&\n> +\t\tgit -C sub rev-parse branch-c &&\n> +\t\tgit checkout --recurse-submodules branch-c &&\n> +\t\tgit -C sub2 rev-parse branch-c\n> +\t)\n> +'\n> +\n> +test_expect_success 'should set up tracking of local branches with track=always' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n> +\t\tgit -C sub rev-parse main &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n> +\t)\n> +'\n> +\n> +test_expect_success 'should set up tracking of local branches with explicit track' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch --track --recurse-submodules branch-a main &&\n> +\t\tgit -C sub rev-parse main &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n> +\t)\n> +'\n> +\n> +test_expect_success 'should not set up unnecessary tracking of local branches' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch --recurse-submodules branch-a main &&\n> +\t\tgit -C sub rev-parse main &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n> +\t)\n\ndon't we have a \"config is empty\" test helper or something similar ?\n\n> +'\n> +\n> +test_expect_success 'setup remote-tracking tests' '\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch branch-a &&\n> +\t\tgit checkout -b branch-b &&\n> +\t\tgit submodule add ../sub-upstream sub2 &&\n> +\t\t# branch-b now has a committed submodule not in branch-a\n> +\t\tgit commit -m \"add second submodule\"\n> +\t) &&\n> +\t(\n> +\t\tcd sub-upstream &&\n> +\t\tgit branch branch-a\n> +\t) &&\n> +\tgit clone --branch main --recurse-submodules super super-clone &&\n> +\tgit -C super-clone config submodule.propagateBranches true\n> +'\n> +\n> +test_expect_success 'should not create branch when submodule is not in .git/modules' '\n> +\t# The cleanup needs to delete sub2:branch-b in particular because main does not have sub2\n> +\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n> +\t\tcleanup_branches super-clone branch-a branch-b\" &&\n> +\t(\n> +\t\tcd super-clone &&\n> +\t\t# This should succeed because super-clone has sub.\n> +\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n> +\t\t# This should fail because super-clone does not have sub2.\n> +\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n> +\t\tcat >expected <<-EOF &&\n> +\t\tfatal: submodule sub: unable to find submodule\n> +\t\tYou may reinitialize the submodules using ${SQ}git checkout origin/branch-b && git submodule update${SQ}\n> +\t\tEOF\n> +\t\ttest_must_fail git rev-parse branch-b &&\n> +\t\ttest_must_fail git -C sub rev-parse branch-b &&\n> +\t\t# User can fix themselves by initializing the submodule\n> +\t\tgit checkout origin/branch-b &&\n> +\t\tgit submodule update &&\n> +\t\tgit branch --recurse-submodules branch-b origin/branch-b\n> +\t)\n\nConsidering what has been pointed out above, I'm not sure why this test passes...\nUnless I'm missing something.\n\n> +'\n> +\n> +test_expect_success 'should set up tracking of remote-tracking branches' '\n> +\ttest_when_finished \"cleanup_branches super-clone branch-a\" &&\n> +\t(\n> +\t\tcd super-clone &&\n> +\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a\n> +\t)\n> +'\n> +\n> +test_expect_success 'should not fail when unable to set up tracking in submodule' '\n> +\ttest_when_finished \"cleanup_branches super-clone branch-b\" &&\n> +\t(\n> +\t\tcd super-clone &&\n> +\t\tgit branch --recurse-submodules branch-b origin/branch-b\n> +\t)\n> +'\n> +\n> +test_done\n> \n\nCheers,\n\nPhilippe.\n"},{"id":"442194","messageId":"kl6l35nmvekb.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20211123021223.3472472-1-jonathantanmy@google.com","subject":"Re: [PATCH 1/4] submodule-config: add submodules_of_tree() helper","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-23T19:48:20Z","receivedAt":"2021-11-23T19:48:24Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> I think that tree_entry() doesn't recurse into subtrees, but in any case we\n> should test this. (I looked at patch 4 and I think that the submodules are\n> always in the root tree.)\n\nI've tested this and indeed it doesn't work. I've attached my test case\nbelow.\n\n> This reminded me of a similar thing when fetching submodules recursively and we\n> needed the \"before\" and \"after\" of submodule gitlinks. You can look at the code\n> (collect_changed_submodules_cb() and the functions that use it in submodule.c)\n> but it may not be useful - in particular, that uses diff since we need to see\n> differences there, but we don't need that here.\n\nThanks for the hint. If that fails, I could also implement it via the\nhelper methods in submodule--helper.\n\n---- >8 ----\n\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nindex 14ff066e91..0e086f716d 100755\n--- a/t/t3207-branch-submodule.sh\n+++ b/t/t3207-branch-submodule.sh\n@@ -11,11 +11,15 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n test_expect_success 'setup superproject and submodule' '\n \tgit init super &&\n \ttest_commit foo &&\n+\tgit init sub-sub-upstream &&\n+\ttest_commit -C sub-sub-upstream foo &&\n \tgit init sub-upstream &&\n-\ttest_commit -C sub-upstream foo &&\n-\tgit -C super submodule add ../sub-upstream sub &&\n+\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n+\tgit -C sub-upstream commit -m \"add submodule\" &&\n+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n \tgit -C super commit -m \"add submodule\" &&\n-\tgit -C super config submodule.propagateBranches true\n+\tgit -C super config submodule.propagateBranches true &&\n+\tgit -C super/sub submodule update --init\n '\n \n cleanup_branches() {\n@@ -26,7 +30,7 @@ cleanup_branches() {\n \t\tgit checkout main &&\n \t\tfor branch_name in \"$@\"; do\n \t\t\tgit branch -D \"$branch_name\"\n-\t\t\tgit submodule foreach \"(git checkout main && git branch -D $branch_name) || true\"\n+\t\t\tgit submodule foreach \"cleanup_branches . $branch_name || true\"\n \t\tdone\n \t)\n } >/dev/null 2>/dev/null\n@@ -37,8 +41,9 @@ test_expect_success '--recurse-submodules should create branches' '\n \t(\n \t\tcd super &&\n \t\tgit branch --recurse-submodules branch-a &&\n-\t\tgit rev-parse --abbrev-ref branch-a &&\n-\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a &&\n+\t\tgit -C sub/sub-sub rev-parse branch-a\n \t)\n '\n \n"},{"id":"442205","messageId":"xmqqh7c27aod.fsf@gitster.g","threadId":"56962","inReplyTo":"20211122223252.19922-2-chooglen@google.com","subject":"Re: [PATCH 1/4] submodule-config: add submodules_of_tree() helper","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-23T22:46:10Z","receivedAt":"2021-11-23T22:46:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> +struct submodule_entry_list *\n> +submodules_of_tree(struct repository *r, const struct object_id *treeish_name)\n> +{\n> +\tstruct tree_desc tree;\n> +\tstruct name_entry entry;\n> +\tstruct submodule_entry_list *ret;\n> +\n> +\tCALLOC_ARRAY(ret, 1);\n> +\tfill_tree_descriptor(r, &tree, treeish_name);\n> +\twhile (tree_entry(&tree, &entry)) {\n> +\t\tif (!S_ISGITLINK(entry.mode))\n> +\t\t\tcontinue;\n> +\t\tALLOC_GROW(ret->name_entries, ret->entry_nr + 1, ret->entry_alloc);\n> +\t\tret->name_entries[ret->entry_nr++] = entry;\n> +\t}\n> +\treturn ret;\n> +}\n\nThis only looks at the root level of the tree, doesn't it?  Without\nany caller in the same step, it is impossible to tell if that is an\noutright bug, or merely an incomplete code that will gain recursion\nin a later step.\n\nIf it is the latter, I do not think that is a patch series\norganization that is very friendly to reviewers.\n\n\n"},{"id":"442206","messageId":"20211123231035.3607109-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20211122223252.19922-4-chooglen@google.com","subject":"Re: [PATCH 3/4] branch: add --dry-run option to branch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-11-23T23:10:35Z","receivedAt":"2021-11-23T23:10:41Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> When running \"git branch --recurse-submodules topic\"\n\nAt this point, this argument has not been introduced yet, so better to\njust say \"A future patch will introduce branch creation that recurses\ninto submodules, so...\"\n\n> +-n::\n> +--dry-run::\n> +\tCan only be used when creating a branch. If the branch creation\n> +\twould fail, show the relevant error message. If the branch\n> +\tcreation would succeed, show nothing.\n\nRight now we only plan to use this internally so it's not worth using a\nsingle character argument for this right now, I think. We can always add\nit later if we find it useful.\n\n> -\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n> -\t    list + edit_description + unset_upstream > 1)\n> +\tcreate = 1 - (!!delete + !!rename + !!copy + !!new_upstream +\n> +\t\t      !!show_current + !!list + !!edit_description +\n> +\t\t      !!unset_upstream);\n> +\tif (create < 0)\n>  \t\tusage_with_options(builtin_branch_usage, options);\n\nHmm...I think it would be clearer just to call it noncreate_options and\nprint usage if it is greater than 1. Then whenever you want to check if\nit's create, check `!noncreate_options` instead.\n\n> @@ -852,10 +862,20 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>  \t\tif (track == BRANCH_TRACK_OVERRIDE)\n>  \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n>  \n> -\t\tcreate_branch(the_repository,\n> -\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n> -\t\t\t      force, 0, reflog, quiet, track);\n> +\t\tif (dry_run) {\n> +\t\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\t\tchar *unused_full_ref;\n> +\t\t\tstruct object_id unused_oid;\n>  \n> +\t\t\tvalidate_new_branchname(branch_name, &buf, force);\n> +\t\t\tvalidate_branch_start(the_repository, start_name, track,\n> +\t\t\t\t\t      &unused_oid, &unused_full_ref);\n> +\t\t\tstrbuf_release(&buf);\n> +\t\t\tFREE_AND_NULL(unused_full_ref);\n> +\t\t\treturn 0;\n> +\t\t}\n> +\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n> +\t\t\t      reflog, quiet, track);\n>  \t} else\n>  \t\tusage_with_options(builtin_branch_usage, options);\n>  \n\nI don't think we should use separate code paths for the dry run and the\nregular run - could create_branch() take a dry_run parameter instead?\n(If there are too many boolean parameters, it might be time to convert\nsome or all to a struct.)\n\nThis suggestion would require a reworking of patch 2, which is why I\ndidn't comment there. But if we are not going to use this suggestion and\nare going to stick with patch 2, then my comment on it is that it seems\nto be doing too much: I ran \"git show --color-moved\" on it and saw that\nquite a few lines were newly introduced (not just moved around).\n"},{"id":"442212","messageId":"kl6lwnkytp3z.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"3ad3941c-de18-41bf-2e44-4238ae868d79@gmail.com","subject":"Re: [PATCH 4/4] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-23T23:43:28Z","receivedAt":"2021-11-23T23:43:38Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\nThanks for the thorough review! I really appreciate it, Philippe :)\n\nPhilippe Blain <levraiphilippeblain@gmail.com> writes:\n\n> Hi Glen,\n>\n> Le 2021-11-22 à 17:32, Glen Choo a écrit :\n>> Add a --recurse-submodules option when creating branches so that `git\n>> branch --recurse-submodules topic` will create the \"topic\" branch in the\n>> superproject and all submodules. Guard this (and future submodule\n>> branching) behavior behind a new configuration value\n>> 'submodule.propagateBranches'.\n>> \n>> Signed-off-by: Glen Choo <chooglen@google.com>\n>> ---\n>>   Documentation/config/advice.txt    |   3 +\n>>   Documentation/config/submodule.txt |   9 ++\n>\n> We would need to add the new flag to Documentation/git-branch.txt,\n> and also probably update the documentation of 'submodule.recurse'\n> in 'Documentation/config/submodule.txt'.\n\nYes, thanks for the catch.\n\n>> diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\n>> index ee454f8126..c318b849aa 100644\n>> --- a/Documentation/config/submodule.txt\n>> +++ b/Documentation/config/submodule.txt\n>> @@ -72,6 +72,15 @@ submodule.recurse::\n>>   \tFor these commands a workaround is to temporarily change the\n>>   \tconfiguration value by using `git -c submodule.recurse=0`.\n>>   \n>> +submodule.propagateBranches::\n>> +\t[EXPERIMENTAL] A boolean that enables branching support with\n>> +\tsubmodules. This allows certain commands to accept\n>> +\t`--recurse-submodules` (`git branch --recurse-submodules` will\n>> +\tcreate branches recursively), and certain commands that already\n>> +\taccept `--recurse-submodules` will now consider branches (`git\n>> +\tswitch --recurse-submodules` will switch to the correct branch\n>> +\tin all submodules).\n>\n> Looking at the rest of the patch, this just implements 'branch --recurse-submodules', right ?\n> i.e. 'git switch' and 'git checkout' are left alone for\n> now, so I think this addition to the doc should only mention 'git\n> branch'.\n\nThat sounds reasonable. I can move this description into the commit\nmessage instead.\n\n>> diff --git a/advice.h b/advice.h\n>> index 601265fd10..a7521d6087 100644\n>> --- a/advice.h\n>> +\n>> +\t\tif (repo_submodule_init(\n>> +\t\t\t    &subrepos[i], r,\n>> +\t\t\t    submodule_entry_list->name_entries[i].path,\n>> +\t\t\t    &super_oid)) {\n>> +\t\t\tdie(_(\"submodule %s: unable to find submodule\"),\n>> +\t\t\t    submodules[i].name);\n>> +\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n>> +\t\t\t\tadvise(_(\"You may try initializing the submodules using 'git checkout %s && git submodule update'\"),\n>> +\t\t\t\t       start_name);\n>\n> Apart from what Ævar pointed out about advise() being called after die(),\n> I'm not sure this is the right advice, because if repo_submodule_init fails\n> it means there is no .git/modules/<name> directory corresponding to the submodule's\n> Git repository, i.e. the submodule was never cloned. So it's not guaranteed\n> that 'git checkout $start_name && git submodule update' would initialize (and clone) it,\n> not without '--init'.\n\nAfter further testing, it seems that --init might be required for\nrecursive submodules, but as you note later on, it's not needed for the\ntest case I have created. Using --init is still good advice though, so I\nwill add that.\n\n>> +\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n>\n> Here we loop over all submodules, so branches are created even in\n> inactive submodules... this might not be wanted.\n\nYes, we should ignore inactive submodules. This is a bug.\n\n>> @@ -713,9 +726,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>>   \tif (create < 0)\n>>   \t\tusage_with_options(builtin_branch_usage, options);\n>>   \n>> +\tif (recurse_submodules_explicit && submodule_propagate_branches &&\n>> +\t    !create)\n>> +\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n>>   \tif (dry_run && !create)\n>>   \t\tdie(_(\"--dry-run can only be used when creating branches\"));\n>>   \n>> +\trecurse_submodules =\n>> +\t\t(recurse_submodules || recurse_submodules_explicit) &&\n>> +\t\tsubmodule_propagate_branches;\n>> +\n>\n> OK, so we get the new behaviour if either --recurse-submodules was used, or 'submodule.recurse' is true,\n> and in both case we also need the new submodule.propagateBranches config set.\n>\n> Why not adding 'branch.recurseSubmodules' instead, with a higher priority than submodule.recurse ?\n> Is it because then it would be mildly confusing for 'git checkout / git switch' to also honor\n> a setting named 'branch.*' when they learn the new behaviour ? (I don't think this would be the\n> first time that 'git foo' honors 'bar.*', so it might be worth mentioning).\n\nI am avoiding the prefix 'branch.' because that might suggest that the\nfunctionality is centered around the 'git branch' command. I chose the\n'submodule.' prefix because what we are feature flagging is an entirely\nredesigned UX for _submodules_ that uses branches; we also have work\nplanned for other commands like push/merge/rebase/reset.\n\n> Also, why do we quietly ignore '--recurse-submodules' if submodule.propagateBranches is unset ?\n> Wouldn't it be better to warn the user \"hey, if you want this new behaviour you need to\n> set that config !\" ?\n\nAh, yes this is an oversight on my part.\n\n> I don't have a strong opinion about the fact that you need to set the config in the first\n> place, but I think it should be mentioned in the commit message why you chose to implement\n> it that way (meaning, why do we need a config set, instead of adding the config but defaulting it\n> to true, so that you get the new behaviour by default, but you still can disable it if you do not\n> want it)...\n\nIt seems self-evident to me that experimental features should not be\nshipped to users by default.\n\n>> +test_expect_success 'should not set up unnecessary tracking of local branches' '\n>> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit branch --recurse-submodules branch-a main &&\n>> +\t\tgit -C sub rev-parse main &&\n>> +\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n>> +\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n>> +\t)\n>\n> don't we have a \"config is empty\" test helper or something similar ?\n\nHm, I couldn't find one, but there is test_cmp_config(). That's probably\nbetter than calling test() directly.\n\n>> +test_expect_success 'should not create branch when submodule is not in .git/modules' '\n>> +\t# The cleanup needs to delete sub2:branch-b in particular because main does not have sub2\n>> +\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n>> +\t\tcleanup_branches super-clone branch-a branch-b\" &&\n>> +\t(\n>> +\t\tcd super-clone &&\n>> +\t\t# This should succeed because super-clone has sub.\n>> +\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n>> +\t\t# This should fail because super-clone does not have sub2.\n>> +\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n>> +\t\tcat >expected <<-EOF &&\n>> +\t\tfatal: submodule sub: unable to find submodule\n>> +\t\tYou may reinitialize the submodules using ${SQ}git checkout origin/branch-b && git submodule update${SQ}\n>> +\t\tEOF\n>> +\t\ttest_must_fail git rev-parse branch-b &&\n>> +\t\ttest_must_fail git -C sub rev-parse branch-b &&\n>> +\t\t# User can fix themselves by initializing the submodule\n>> +\t\tgit checkout origin/branch-b &&\n>> +\t\tgit submodule update &&\n>> +\t\tgit branch --recurse-submodules branch-b origin/branch-b\n>> +\t)\n>\n> Considering what has been pointed out above, I'm not sure why this test passes...\n> Unless I'm missing something.\n\nAs I understand it, --init is used to set values in .git/config. My best\nguess is that 'git submodule update' doesn't use .git/config at all - it\nlooks for submodules in the index and .gitmodules and clones the\nsubmodules as expected.\n\nI still think that we should promote --init, but I still find this\nsituation very strange and inconsistent.\n"},{"id":"442218","messageId":"kl6lr1b6tlwq.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20211123231035.3607109-1-jonathantanmy@google.com","subject":"Re: [PATCH 3/4] branch: add --dry-run option to branch","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-24T00:52:37Z","receivedAt":"2021-11-24T00:52:45Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>> When running \"git branch --recurse-submodules topic\"\n>\n> At this point, this argument has not been introduced yet, so better to\n> just say \"A future patch will introduce branch creation that recurses\n> into submodules, so...\"\n>\n>> +-n::\n>> +--dry-run::\n>> +\tCan only be used when creating a branch. If the branch creation\n>> +\twould fail, show the relevant error message. If the branch\n>> +\tcreation would succeed, show nothing.\n>\n> Right now we only plan to use this internally so it's not worth using a\n> single character argument for this right now, I think. We can always add\n> it later if we find it useful.\n\nFor the reasons you specified, I didn't intend to add -n. However, -n is\nautomatically added by OPT__DRY_RUN, so I thought it was appropriate to\ndocument it.\n\n>> -\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n>> -\t    list + edit_description + unset_upstream > 1)\n>> +\tcreate = 1 - (!!delete + !!rename + !!copy + !!new_upstream +\n>> +\t\t      !!show_current + !!list + !!edit_description +\n>> +\t\t      !!unset_upstream);\n>> +\tif (create < 0)\n>>  \t\tusage_with_options(builtin_branch_usage, options);\n>\n> Hmm...I think it would be clearer just to call it noncreate_options and\n> print usage if it is greater than 1. Then whenever you want to check if\n> it's create, check `!noncreate_options` instead.\n\nSounds good.\n\n>> @@ -852,10 +862,20 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>>  \t\tif (track == BRANCH_TRACK_OVERRIDE)\n>>  \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n>>  \n>> -\t\tcreate_branch(the_repository,\n>> -\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n>> -\t\t\t      force, 0, reflog, quiet, track);\n>> +\t\tif (dry_run) {\n>> +\t\t\tstruct strbuf buf = STRBUF_INIT;\n>> +\t\t\tchar *unused_full_ref;\n>> +\t\t\tstruct object_id unused_oid;\n>>  \n>> +\t\t\tvalidate_new_branchname(branch_name, &buf, force);\n>> +\t\t\tvalidate_branch_start(the_repository, start_name, track,\n>> +\t\t\t\t\t      &unused_oid, &unused_full_ref);\n>> +\t\t\tstrbuf_release(&buf);\n>> +\t\t\tFREE_AND_NULL(unused_full_ref);\n>> +\t\t\treturn 0;\n>> +\t\t}\n>> +\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n>> +\t\t\t      reflog, quiet, track);\n>>  \t} else\n>>  \t\tusage_with_options(builtin_branch_usage, options);\n>>  \n>\n> I don't think we should use separate code paths for the dry run and the\n> regular run - could create_branch() take a dry_run parameter instead?\n> (If there are too many boolean parameters, it might be time to convert\n> some or all to a struct.)\n\nThat sounds reasonable, it would be good not to have duplicate code\npaths.\n\n> This suggestion would require a reworking of patch 2, which is why I\n> didn't comment there. But if we are not going to use this suggestion and\n> are going to stick with patch 2, then my comment on it is that it seems\n> to be doing too much: I ran \"git show --color-moved\" on it and saw that\n> quite a few lines were newly introduced (not just moved around).\n\nI will do the reworking, but the final result will probably look very\nsimilar to the one in patch 2. Does it look more acceptable with\n--color-moved-ws=ignore-all-space? Some text had to be reindented\n(because I removed one conditional), but I also replaced some functions\nwith repo_* versions.\n"},{"id":"442220","messageId":"20211124013153.3619998-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20211122223252.19922-5-chooglen@google.com","subject":"Re: [PATCH 4/4] branch: add --recurse-submodules option for branch creation","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-11-24T01:31:53Z","receivedAt":"2021-11-24T01:31:57Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> +submodule.propagateBranches::\n> +\t[EXPERIMENTAL] A boolean that enables branching support with\n> +\tsubmodules. This allows certain commands to accept\n> +\t`--recurse-submodules` (`git branch --recurse-submodules` will\n> +\tcreate branches recursively), and certain commands that already\n> +\taccept `--recurse-submodules` will now consider branches (`git\n> +\tswitch --recurse-submodules` will switch to the correct branch\n> +\tin all submodules).\n\nAfter some thought, I think that the way in this patch is the best way\nto do it, even if it's unfortunate that a natural-looking \"git branch\n--recurse-submodules\" wouldn't work without\n\"submodule.propagateBranches\" (or whatever we decide to call the\nvariable).\n\nRight now, as far as I know, \"--recurse-submodules\" in all Git commands\n(and equivalently, the configuration variable \"submodule.recurse\") does\nthe same thing but in submodules too. For example, \"fetch\" fetches in\nsubmodules, \"switch\" switches in submodules, and so on. It would be\nplausible for \"branch --recurse-submodules\" to also create a branch in\nsubmodules, but if a user just used it as-is, it would be surprising if\nsubsequent commands like \"switch --recurse-submodules\" or \"-c\nsubmodule.recurse=true switch\" didn't use the branches that were just\ncreated.\n\nSo OK, this makes sense.\n\n> +static int submodule_create_branch(struct repository *r, const char *name,\n> +\t\t\t\t   const char *start_oid,\n> +\t\t\t\t   const char *start_name, int force,\n> +\t\t\t\t   int reflog, int quiet,\n> +\t\t\t\t   enum branch_track track, char **err_msg)\n> +{\n> +\tint ret = 0;\n> +\tstruct child_process child = CHILD_PROCESS_INIT;\n> +\tstruct strbuf child_err = STRBUF_INIT;\n> +\tchild.git_cmd = 1;\n> +\tchild.err = -1;\n> +\n> +\tprepare_other_repo_env(&child.env_array, r->gitdir);\n> +\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n\nBefore this function is a function that calls \"git branch\" directly -\ncouldn't this function do the same? (And then you can make both of them\ninto one function.) The functionality should be exactly the same except\nthat one has \"--dry-run\" and the other doesn't.\n\n> @@ -874,6 +894,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>  \t\t\tFREE_AND_NULL(unused_full_ref);\n>  \t\t\treturn 0;\n>  \t\t}\n> +\t\tif (recurse_submodules) {\n> +\t\t\tcreate_submodule_branches(the_repository, branch_name,\n> +\t\t\t\t\t\t  start_name, force, reflog,\n> +\t\t\t\t\t\t  quiet, track);\n> +\t\t\treturn 0;\n> +\t\t}\n>  \t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n>  \t\t\t      reflog, quiet, track);\n>  \t} else\n\ncreate_submodule_branches() here is a bit misleading since it also\ncreates branches in the_repository. Might be better to write explicitly\ncheck in submodules -> create in main repository -> create in\nsubmodules. Or, if you want to combine all the submodule code in one\nfunction, (check in submodules -> create in submodules) -> create in\nmain repository.\n\n> +test_expect_success 'setup superproject and submodule' '\n> +\tgit init super &&\n> +\ttest_commit foo &&\n> +\tgit init sub-upstream &&\n> +\ttest_commit -C sub-upstream foo &&\n> +\tgit -C super submodule add ../sub-upstream sub &&\n> +\tgit -C super commit -m \"add submodule\" &&\n> +\tgit -C super config submodule.propagateBranches true\n> +'\n\nIf making each test independent is important (which seems like a good\ngoal to me, although I know that the Git tests are inconsistent on\nthat), we could make this into a bash function (with test_when_finished)\nthat gets called in every test. It doesn't violate the t/README request\nto put all test code inside test_expect_success assertions (since the\nfunction is still being run inside an assertion).\n\nIn the general case, it will make test code slower to run, but if you're\ngoing to have to cleanup branches, I think it's better to just recreate\nthe repo. In any case, for the general case, I can start a separate\nemail thread for this discussion.\n\n> +test_expect_success '--recurse-submodules should be ignored if submodule.propagateBranches is false' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit -c submodule.propagateBranches=false branch --recurse-submodules branch-a &&\n> +\t\tgit rev-parse branch-a &&\n> +\t\ttest_must_fail git -C sub rev-parse branch-a\n> +\t)\n> +'\n\nThis doesn't sound like the right behavior to me - I think it's fine if\nit was the config \"submodule.recurse\" instead of \"--recurse-submodules\",\nbut if the argument is given on CLI, it should be a fatal error.\n\n> +test_expect_success 'should create branch when submodule is in .git/modules but not .gitmodules' '\n> +\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch branch-a &&\n> +\t\tgit checkout -b branch-b &&\n> +\t\tgit submodule add ../sub-upstream sub2 &&\n> +\t\t# branch-b now has a committed submodule not in branch-a\n> +\t\tgit commit -m \"add second submodule\" &&\n> +\t\tgit checkout branch-a &&\n> +\t\tgit branch --recurse-submodules branch-c branch-b &&\n> +\t\tgit rev-parse branch-c &&\n> +\t\tgit -C sub rev-parse branch-c &&\n> +\t\tgit checkout --recurse-submodules branch-c &&\n> +\t\tgit -C sub2 rev-parse branch-c\n> +\t)\n> +'\n\nHmm...how is this submodule in .git/modules but not .gitmodules? It\nlooks like a normal submodule to me.\n\n> +test_expect_success 'should not create branch when submodule is not in .git/modules' '\n\nThe title of this test contradicts the title of the test that I quoted\npreviously.\n\n> +test_expect_success 'should not fail when unable to set up tracking in submodule' '\n> +\ttest_when_finished \"cleanup_branches super-clone branch-b\" &&\n> +\t(\n> +\t\tcd super-clone &&\n> +\t\tgit branch --recurse-submodules branch-b origin/branch-b\n> +\t)\n> +'\n\nIs there a warning printed that we can check?\n\nAlso, this patch set doesn't discuss the case in which the branch in a\nsubmodule already exists, but it points to the exact commit that we\nwant. What is the functionality in that case? I would say that the user\nshould be able to recursively create the branch in this case, but am\nopen to other opinions. In any case, that case should be tested.\n"},{"id":"442254","messageId":"kl6lmtltto1l.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20211124013153.3619998-1-jonathantanmy@google.com","subject":"Re: [PATCH 4/4] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-24T18:18:46Z","receivedAt":"2021-11-24T18:18:52Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\nThanks! The feedback is really useful.\n\nJonathan Tan <jonathantanmy@google.com> writes:\n\n>> +static int submodule_create_branch(struct repository *r, const char *name,\n>> +\t\t\t\t   const char *start_oid,\n>> +\t\t\t\t   const char *start_name, int force,\n>> +\t\t\t\t   int reflog, int quiet,\n>> +\t\t\t\t   enum branch_track track, char **err_msg)\n>> +{\n>> +\tint ret = 0;\n>> +\tstruct child_process child = CHILD_PROCESS_INIT;\n>> +\tstruct strbuf child_err = STRBUF_INIT;\n>> +\tchild.git_cmd = 1;\n>> +\tchild.err = -1;\n>> +\n>> +\tprepare_other_repo_env(&child.env_array, r->gitdir);\n>> +\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n>\n> Before this function is a function that calls \"git branch\" directly -\n> couldn't this function do the same? (And then you can make both of them\n> into one function.) The functionality should be exactly the same except\n> that one has \"--dry-run\" and the other doesn't.\n\nI see two somewhat valid interpretations, so I will address both.\n\nIf you are suggesting that I should call \"git branch\" instead of a new\n\"git submodule--helper create-branch\", that unfortunately does not work.\nBecause of how we've defined the semantics, we want the submodule to\nbranch off the commit in the superproject tree (which is a bare object\nid), but we want to set up tracking based off the ref that the user gave\n(evaluating it in the context of the submodule). This is why\nsubmodule--helper.c:module_create_branch() makes two calls like so:\n\n\tcreate_branch(the_repository, \"<branch name>\", \"<object id>\", force, 0, reflog, quiet,\n\t\t      BRANCH_TRACK_NEVER);\n\tsetup_tracking(\"<branch name>\", \"<tracking name>\", track, quiet, 0);\n\nOn the other hand, you might be suggesting that I should just add\n--dry-run to \"git submodule--helper create-branch\". That way, the\ndry-run form of the command is validating the non dry-run form (instead\nof using \"git branch --dry-run\" to validate \"git submodule--helper\ncreate-branch\"). That's a reasonable suggestion that avoids\nbikeshedding around \"git branch --dry-run\".\n\n>> @@ -874,6 +894,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>>  \t\t\tFREE_AND_NULL(unused_full_ref);\n>>  \t\t\treturn 0;\n>>  \t\t}\n>> +\t\tif (recurse_submodules) {\n>> +\t\t\tcreate_submodule_branches(the_repository, branch_name,\n>> +\t\t\t\t\t\t  start_name, force, reflog,\n>> +\t\t\t\t\t\t  quiet, track);\n>> +\t\t\treturn 0;\n>> +\t\t}\n>>  \t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n>>  \t\t\t      reflog, quiet, track);\n>>  \t} else\n>\n> create_submodule_branches() here is a bit misleading since it also\n> creates branches in the_repository. Might be better to write explicitly\n> check in submodules -> create in main repository -> create in\n> submodules. Or, if you want to combine all the submodule code in one\n> function, (check in submodules -> create in submodules) -> create in\n> main repository.\n\nPhilippe had a similar comment, I will rename it.\n\n>> +test_expect_success 'setup superproject and submodule' '\n>> +\tgit init super &&\n>> +\ttest_commit foo &&\n>> +\tgit init sub-upstream &&\n>> +\ttest_commit -C sub-upstream foo &&\n>> +\tgit -C super submodule add ../sub-upstream sub &&\n>> +\tgit -C super commit -m \"add submodule\" &&\n>> +\tgit -C super config submodule.propagateBranches true\n>> +'\n>\n> If making each test independent is important (which seems like a good\n> goal to me, although I know that the Git tests are inconsistent on\n> that), we could make this into a bash function (with test_when_finished)\n> that gets called in every test. It doesn't violate the t/README request\n> to put all test code inside test_expect_success assertions (since the\n> function is still being run inside an assertion).\n\nThat's an interesting idea and it's more likely to be correct than my\napproach. I think it lines up better with testing best practices.\nHowever...\n\n> In the general case, it will make test code slower to run, but if you're\n> going to have to cleanup branches, I think it's better to just recreate\n> the repo. In any case, for the general case, I can start a separate\n> email thread for this discussion.\n\nI'm concerned about the same thing and I suspect that recreating the\nrepo won't be kindly received by some reviewers, even if they might be\nable to stomach cleanup_branches(). I think your suggestion is a better\nlong-term direction, but I'd like to see discussion on the general case\nbefore changing the tests. A separate thread sounds good.\n\n>> +test_expect_success '--recurse-submodules should be ignored if submodule.propagateBranches is false' '\n>> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit -c submodule.propagateBranches=false branch --recurse-submodules branch-a &&\n>> +\t\tgit rev-parse branch-a &&\n>> +\t\ttest_must_fail git -C sub rev-parse branch-a\n>> +\t)\n>> +'\n>\n> This doesn't sound like the right behavior to me - I think it's fine if\n> it was the config \"submodule.recurse\" instead of \"--recurse-submodules\",\n> but if the argument is given on CLI, it should be a fatal error.\n\nPhilippe mentioned the same thing, which sounds right to me.\n\n>> +test_expect_success 'should create branch when submodule is in .git/modules but not .gitmodules' '\n>> +\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit branch branch-a &&\n>> +\t\tgit checkout -b branch-b &&\n>> +\t\tgit submodule add ../sub-upstream sub2 &&\n>> +\t\t# branch-b now has a committed submodule not in branch-a\n>> +\t\tgit commit -m \"add second submodule\" &&\n>> +\t\tgit checkout branch-a &&\n>> +\t\tgit branch --recurse-submodules branch-c branch-b &&\n>> +\t\tgit rev-parse branch-c &&\n>> +\t\tgit -C sub rev-parse branch-c &&\n>> +\t\tgit checkout --recurse-submodules branch-c &&\n>> +\t\tgit -C sub2 rev-parse branch-c\n>> +\t)\n>> +'\n>\n> Hmm...how is this submodule in .git/modules but not .gitmodules? It\n> looks like a normal submodule to me.\n\nThe test title is probably too terse - the submodule is not in the\nworking tree's .gitmodules, but it is in branch-b's .gitmodules. I'll\nreword the title.\n\n>> +test_expect_success 'should not create branch when submodule is not in .git/modules' '\n>\n> The title of this test contradicts the title of the test that I quoted\n> previously.\n\nI'm not sure how this is a contradiction, from before..\n\n  should create branch when submodule is in .git/modules but not\n  [the working tree's] .gitmodules\n\nmeaning \"we should create the branch if we can find the submodule in\n.git/modules\", i.e. the implication is: presence in .git/modules => can\ncreate branch.\n\nWhereas\n\n  should not create branch when submodule is not in .git/modules\n\nmeaning \"we should not create the branch if we cannot find the submodule\nin .git/modules\", i.e. the implication is: absence in .git/modules =>\ncannot create branch.\n\nTaken together, they assert that presence in .git/modules is a necessary\ncondition for the subodule branch to be created.\n\n>> +test_expect_success 'should not fail when unable to set up tracking in submodule' '\n>> +\ttest_when_finished \"cleanup_branches super-clone branch-b\" &&\n>> +\t(\n>> +\t\tcd super-clone &&\n>> +\t\tgit branch --recurse-submodules branch-b origin/branch-b\n>> +\t)\n>> +'\n>\n> Is there a warning printed that we can check?\n\n\"git branch\" does not warn if tracking is not set up when it is not\nexplicitly required, so this does not warn. However, I can imagine that\nif the superproject branch has tracking set up, a user might expect that\nall submodules would also have tracking set up, and thus a warning might\nbe useful. I don't think it will be _that_ useful for most users, but at\nleast some users would probably appreciate it.\n\nFor slightly unrelated reasons, I tried to get the tracking info of a\nnewly created branch and it is tedious. For this reason and the fact\nthat I'm not sure if the benefit is that great, I'm tempted *not* to add\nthe warning, but perhaps you feel more strongly than I do?\n\n> Also, this patch set doesn't discuss the case in which the branch in a\n> submodule already exists, but it points to the exact commit that we\n> want. What is the functionality in that case?\n\nThe behavior is identical to the general case where the branch already\nexists - branch validation (git branch --dry-run) fails.\n\n> I would say that the user should be able to recursively create the\n> branch in this case, but am open to other opinions.\n\nI'm inclined to disagree. To illustrate this in the real world, say a\nuser wants to create a 'new-feature' branch recursively, but there is\nalready a 'new-feature in a submodule. Here are two possible sets of\nevents that could lead to this situation:\n\n1) the 'new-feature' branch was intended for the same work as the new\n   branch but the user just happened to create the 'new-feature'\n   subomdule branch first\n2) the existing 'new-feature' branch doesn't actually contain the same\n   work (maybe it's an overloaded term, or maybe the user used a branch\n   to bookmark a commit before starting work on it)\n\nWhat would happen if we allowed the branch to be created? In case 1,\nthe user would be more than happy (because we read their mind! woohoo!)\nBut in case 2, the user might not realize that their bookmark is getting\nclobbered by the new recursive branch - they might try revisiting their\nbookmark only to realize that they've accidentally committed on top of\nit.\n\nI think the incidence of case 2 is far lower than case 1's, but I think\nit's reasonable to be defensive by default. In any case, we can change\nthe default after more user testing.\n\n> In any case, that case should be tested.\n\nTesting this case would make the intended behavior clearer, so I will\nadd this test.\n"},{"id":"442499","messageId":"20211129210140.937875-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"kl6lmtltto1l.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH 4/4] branch: add --recurse-submodules option for branch creation","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-11-29T21:01:40Z","receivedAt":"2021-11-29T22:11:48Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> > Before this function is a function that calls \"git branch\" directly -\n> > couldn't this function do the same? (And then you can make both of them\n> > into one function.) The functionality should be exactly the same except\n> > that one has \"--dry-run\" and the other doesn't.\n> \n> I see two somewhat valid interpretations, so I will address both.\n> \n> If you are suggesting that I should call \"git branch\" instead of a new\n> \"git submodule--helper create-branch\", that unfortunately does not work.\n> Because of how we've defined the semantics, we want the submodule to\n> branch off the commit in the superproject tree (which is a bare object\n> id), but we want to set up tracking based off the ref that the user gave\n> (evaluating it in the context of the submodule). This is why\n> submodule--helper.c:module_create_branch() makes two calls like so:\n> \n> \tcreate_branch(the_repository, \"<branch name>\", \"<object id>\", force, 0, reflog, quiet,\n> \t\t      BRANCH_TRACK_NEVER);\n> \tsetup_tracking(\"<branch name>\", \"<tracking name>\", track, quiet, 0);\n\nAh, I meant this interpretation. In that case, could we do it like this:\n\n - Add a dry_run parameter to submodule_create_branch(). It should stay\n   exactly the same (except passing dry_run), so that the same code path\n   is executed with and without dry_run. Delete\n   submodule_validate_branchname(). (This makes sense conceptually\n   because we're validating that we can later create the branches with\n   the exact same parameters.)\n\n - When actually creating the branches, call submodule_create_branch(),\n   then another command to set up tracking. I think it makes more sense\n   if this is done directly through a Git command. If you want to use\n   setup_tracking() through submodule--helper, I think that needs an\n   explanation as to why a Git command wouldn't work.\n\n> On the other hand, you might be suggesting that I should just add\n> --dry-run to \"git submodule--helper create-branch\". That way, the\n> dry-run form of the command is validating the non dry-run form (instead\n> of using \"git branch --dry-run\" to validate \"git submodule--helper\n> create-branch\"). That's a reasonable suggestion that avoids\n> bikeshedding around \"git branch --dry-run\".\n\nI didn't mean this interpretation, but this idea is reasonable if we\nneeded such functionality.\n\n> >> +test_expect_success 'should create branch when submodule is in .git/modules but not .gitmodules' '\n> >> +\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n> >> +\t(\n> >> +\t\tcd super &&\n> >> +\t\tgit branch branch-a &&\n> >> +\t\tgit checkout -b branch-b &&\n> >> +\t\tgit submodule add ../sub-upstream sub2 &&\n> >> +\t\t# branch-b now has a committed submodule not in branch-a\n> >> +\t\tgit commit -m \"add second submodule\" &&\n> >> +\t\tgit checkout branch-a &&\n> >> +\t\tgit branch --recurse-submodules branch-c branch-b &&\n> >> +\t\tgit rev-parse branch-c &&\n> >> +\t\tgit -C sub rev-parse branch-c &&\n> >> +\t\tgit checkout --recurse-submodules branch-c &&\n> >> +\t\tgit -C sub2 rev-parse branch-c\n> >> +\t)\n> >> +'\n> >\n> > Hmm...how is this submodule in .git/modules but not .gitmodules? It\n> > looks like a normal submodule to me.\n> \n> The test title is probably too terse - the submodule is not in the\n> working tree's .gitmodules, but it is in branch-b's .gitmodules. I'll\n> reword the title.\n\nAh, I see. I think just delete the \"is in .git/modules\" part, so\nsomething like \"should create branch when submodule is not in HEAD's\n.gitmodules\".\n\n> >> +test_expect_success 'should not create branch when submodule is not in .git/modules' '\n> >\n> > The title of this test contradicts the title of the test that I quoted\n> > previously.\n> \n> I'm not sure how this is a contradiction, from before..\n\n[snip]\n\nAh...yes you're right.\n\n> >> +test_expect_success 'should not fail when unable to set up tracking in submodule' '\n> >> +\ttest_when_finished \"cleanup_branches super-clone branch-b\" &&\n> >> +\t(\n> >> +\t\tcd super-clone &&\n> >> +\t\tgit branch --recurse-submodules branch-b origin/branch-b\n> >> +\t)\n> >> +'\n> >\n> > Is there a warning printed that we can check?\n> \n> \"git branch\" does not warn if tracking is not set up when it is not\n> explicitly required, so this does not warn. However, I can imagine that\n> if the superproject branch has tracking set up, a user might expect that\n> all submodules would also have tracking set up, and thus a warning might\n> be useful. I don't think it will be _that_ useful for most users, but at\n> least some users would probably appreciate it.\n> \n> For slightly unrelated reasons, I tried to get the tracking info of a\n> newly created branch and it is tedious. For this reason and the fact\n> that I'm not sure if the benefit is that great, I'm tempted *not* to add\n> the warning, but perhaps you feel more strongly than I do?\n\nIn that case, maybe explain why tracking cannot be set up and verify in\nthe test that no tracking was set up.\n\n> > Also, this patch set doesn't discuss the case in which the branch in a\n> > submodule already exists, but it points to the exact commit that we\n> > want. What is the functionality in that case?\n> \n> The behavior is identical to the general case where the branch already\n> exists - branch validation (git branch --dry-run) fails.\n> \n> > I would say that the user should be able to recursively create the\n> > branch in this case, but am open to other opinions.\n> \n> I'm inclined to disagree. To illustrate this in the real world, say a\n> user wants to create a 'new-feature' branch recursively, but there is\n> already a 'new-feature in a submodule. Here are two possible sets of\n> events that could lead to this situation:\n> \n> 1) the 'new-feature' branch was intended for the same work as the new\n>    branch but the user just happened to create the 'new-feature'\n>    subomdule branch first\n> 2) the existing 'new-feature' branch doesn't actually contain the same\n>    work (maybe it's an overloaded term, or maybe the user used a branch\n>    to bookmark a commit before starting work on it)\n> \n> What would happen if we allowed the branch to be created? In case 1,\n> the user would be more than happy (because we read their mind! woohoo!)\n> But in case 2, the user might not realize that their bookmark is getting\n> clobbered by the new recursive branch - they might try revisiting their\n> bookmark only to realize that they've accidentally committed on top of\n> it.\n> \n> I think the incidence of case 2 is far lower than case 1's, but I think\n> it's reasonable to be defensive by default. In any case, we can change\n> the default after more user testing.\n\nOK, this makes sense.\n"},{"id":"443202","messageId":"20211206215528.97050-1-chooglen@google.com","threadId":"56962","inReplyTo":"20211122223252.19922-1-chooglen@google.com","subject":"[PATCH v2 0/3] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-06T21:55:25Z","receivedAt":"2021-12-06T21:55:57Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Submodule branching RFC:\nhttps://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/\n\nOriginal Submodule UX RFC/Discussion:\nhttps://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n\nContributor Summit submodules Notes:\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/\n\nSubmodule UX overhaul updates:\nhttps://lore.kernel.org/git/?q=Submodule+UX+overhaul+update\n\nThis series implements branch --recurse-submodules as laid out in the\nSubmodule branching RFC (linked above). If there are concerns about the\nUX/behavior, I would appreciate feedback on the RFC thread as well :)\n\nThanks for the feedback, everyone. I really really appreciate it.\n\nThe biggest difference in V2 is that I took Jonathan's advice to remove\n\"git branch --dry-run\" in favor of adding \"--dry-run\" to \"git\nsubmodule--helper create-branch\" instead [1]. The benefit of having \"git\nbranch --dry-run\" is pretty small, and we'd have to explain to users why\n\"--dry-run\" doesn't work in more situations [2].\n\nUnfortunately patch 3 (formerly patch 4) is now bigger than I would\nprefer. This is due to the combined effect of removing \"--dry-run\" and\nsquashing the former patch 1. I'd appreciate any feedback on how I can\nstructure things differently :)\n\nChanges since v1:\n* Move the functionality of \"git branch --dry-run\" into \"git\n  submodule-helper create-branch --dry-run\"\n* Add more fields to the submodules_of_tree() struct to reduce the\n  number of allocations made by the caller [3]. Move this functionality\n  to patch 3 (formerly patch 4) and drop patch 1.\n* Make submodules_of_tree() ignore inactive submodules [4]\n* Structure the output of the submodules a bit better by adding prefixes\n  to the child process' output (instead of inconsistently indenting the\n  output).\n** I wasn't able to find a good way to interleave stdout/stderr\n   correctly, so a less-than-desirable workaround was to route the child\n   process output to stdout/stderr depending on the exit code.\n** Eventually, I would like to structure the output of submodules in a\n   report, as Ævar suggested [5]. But at this stage, I think that it's\n   better to spend time getting user feedback on the submodules\n   branching UX and it'll be easier to standardize the output when we've\n   implemented more of the UX :)\n\n[1] https://lore.kernel.org/git/20211129210140.937875-1-jonathantanmy@google.com\n[2] https://lore.kernel.org/git/211123.86zgpvup6m.gmgdl@evledraar.gmail.com\n[3] https://lore.kernel.org/git/211123.86r1b7uoil.gmgdl@evledraar.gmail.com\n[4] https://lore.kernel.org/git/3ad3941c-de18-41bf-2e44-4238ae868d79@gmail.com\n[5] https://lore.kernel.org/git/211123.86v90juovj.gmgdl@evledraar.gmail.com\n\nGlen Choo (3):\n  branch: move --set-upstream-to behavior to setup_tracking()\n  builtin/branch: clean up action-picking logic in cmd_branch()\n  branch: add --recurse-submodules option for branch creation\n\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |   8 +\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 322 +++++++++++++++++++++--------\n branch.h                           |  44 +++-\n builtin/branch.c                   |  66 ++++--\n builtin/checkout.c                 |   3 +-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  35 ++++\n submodule-config.h                 |  35 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3200-branch.sh                  |  17 ++\n t/t3207-branch-submodule.sh        | 284 +++++++++++++++++++++++++\n 15 files changed, 758 insertions(+), 113 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\nRange-diff against v1:\n1:  1551dd683f < -:  ---------- submodule-config: add submodules_of_tree() helper\n2:  a4984f6eef ! 1:  cc212dcd39 branch: refactor out branch validation from create_branch()\n    @@ Metadata\n     Author: Glen Choo <chooglen@google.com>\n     \n      ## Commit message ##\n    -    branch: refactor out branch validation from create_branch()\n    +    branch: move --set-upstream-to behavior to setup_tracking()\n     \n    -    In a subsequent commit, we would like to be able to validate whether or\n    -    not a branch name is valid before we create it (--dry-run). This is\n    -    useful for `git branch --recurse-submodules topic` because it allows Git\n    -    to determine if the branch 'topic' can be created in all submodules\n    -    without creating the branch 'topic'.\n    +    This refactor is motivated by a desire to add a \"dry_run\" parameter to\n    +    create_branch() that will validate whether or not a branch can be\n    +    created without actually creating it - this behavior be used in a\n    +    subsequent commit that adds `git branch --recurse-submodules topic`.\n     \n    -    A good starting point would be to refactor out the start point\n    -    validation and dwim logic in create_branch() in a\n    -    validate_branch_start() helper function. Once we do so, it becomes\n    -    clear that create_branch() is more complex than it needs to be -\n    -    create_branch() is also used to set tracking information when performing\n    -    `git branch --set-upstream-to`. This made more sense when\n    -    (the now unsupported) --set-upstream was first introduced in\n    -    4fc5006676 (Add branch --set-upstream, 2010-01-18), because\n    -    it would sometimes create a branch and sometimes update tracking\n    -    information without creating a branch.\n    +    Adding \"dry_run\" is not obvious because create_branch() is also used to\n    +    set tracking information without creating a branch, i.e. when using\n    +    --set-upstream-to. This appears to be a leftover from 4fc5006676 (Add\n    +    branch --set-upstream, 2010-01-18), when --set-upstream would sometimes\n    +    create a branch and sometimes update tracking information without\n    +    creating a branch. However, we no longer support --set-upstream, so it\n    +    makes more sense to set tracking information with another function, like\n    +    setup_tracking(), and use create_branch() only to create branches. When\n    +    this is done, it will be trivial to add \"dry_run\".\n     \n    -    Refactor out the branch validation and dwim logic from create_branch()\n    -    into validate_branch_start(), make it so that create_branch() always\n    -    tries to create a branch, and replace the now-incorrect create_branch()\n    -    call with setup_tracking(). Since there were none, add tests for\n    -    creating a branch with `--force`.\n    +    Do this refactor by moving the branch validation and dwim logic from\n    +    create_branch() into a new function, validate_branch_start(), and call\n    +    it from setup_tracking(). Now that setup_tracking() can perform dwim and\n    +    tracking setup without creating a branch, use it in `git branch\n    +    --set-upstream-to` and remove unnecessary behavior from create_branch().\n    +\n    +    Since there were none, add tests for creating a branch with `--force`.\n     \n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n    @@ branch.c: N_(\"\\n\"\n     -\t\t   const char *name, const char *start_name,\n     -\t\t   int force, int clobber_head_ok, int reflog,\n     -\t\t   int quiet, enum branch_track track)\n    ++/**\n    ++ * Validates whether a ref is a valid starting point for a branch, where:\n    ++ *\n    ++ *   - r is the repository to validate the branch for\n    ++ *\n    ++ *   - start_name is the ref that we would like to test. This is\n    ++ *     expanded with DWIM and assigned to real_ref.\n    ++ *\n    ++ *   - track is the tracking mode of the new branch. If tracking is\n    ++ *     explicitly requested, start_name must be a branch (because\n    ++ *     otherwise start_name cannot be tracked)\n    ++ *\n    ++ *   - oid is an out parameter containing the object_id of start_name\n    ++ *\n    ++ *   - real_ref is an out parameter containing the full, 'real' form of\n    ++ *     start_name e.g. refs/heads/main instead of main\n    ++ *\n    ++ */\n     +static void validate_branch_start(struct repository *r, const char *start_name,\n     +\t\t\t\t  enum branch_track track,\n    -+\t\t\t\t  struct object_id *oid, char **full_ref)\n    ++\t\t\t\t  struct object_id *oid, char **real_ref)\n      {\n      \tstruct commit *commit;\n     -\tstruct object_id oid;\n    @@ branch.c: void create_branch(struct repository *r,\n      \t}\n      \n     -\tswitch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {\n    -+\tswitch (repo_dwim_ref(r, start_name, strlen(start_name), oid, full_ref,\n    ++\tswitch (repo_dwim_ref(r, start_name, strlen(start_name), oid, real_ref,\n     +\t\t\t      0)) {\n      \tcase 0:\n      \t\t/* Not branching from any existing branch */\n    @@ branch.c: void create_branch(struct repository *r,\n      \t\t/* Unique completion -- good, only if it is a real branch */\n     -\t\tif (!starts_with(real_ref, \"refs/heads/\") &&\n     -\t\t    validate_remote_tracking_branch(real_ref)) {\n    -+\t\tif (!starts_with(*full_ref, \"refs/heads/\") &&\n    -+\t\t    validate_remote_tracking_branch(*full_ref)) {\n    ++\t\tif (!starts_with(*real_ref, \"refs/heads/\") &&\n    ++\t\t    validate_remote_tracking_branch(*real_ref)) {\n      \t\t\tif (explicit_tracking)\n      \t\t\t\tdie(_(upstream_not_branch), start_name);\n      \t\t\telse\n     -\t\t\t\tFREE_AND_NULL(real_ref);\n    -+\t\t\t\tFREE_AND_NULL(*full_ref);\n    ++\t\t\t\tFREE_AND_NULL(*real_ref);\n      \t\t}\n      \t\tbreak;\n      \tdefault:\n3:  cbcbc4f49e < -:  ---------- branch: add --dry-run option to branch\n-:  ---------- > 2:  320749cc82 builtin/branch: clean up action-picking logic in cmd_branch()\n4:  416a114fa9 ! 3:  c0441c6691 branch: add --recurse-submodules option for branch creation\n    @@ Metadata\n      ## Commit message ##\n         branch: add --recurse-submodules option for branch creation\n     \n    -    Teach cmd_branch to accept the --recurse-submodules option when creating\n    -    branches so that `git branch --recurse-submodules topic` will create the\n    -    \"topic\" branch in the superproject and all submodules. Guard this (and\n    -    future submodule branching) behavior behind a new configuration value\n    -    'submodule.propagateBranches'.\n    +    To improve the submodules UX, we would like to teach Git to handle\n    +    branches in submodules. Start this process by teaching `git branch` the\n    +    --recurse-submodules option so that `git branch --recurse-submodules\n    +    topic` will create the \"topic\" branch in the superproject and its\n    +    submodules.\n    +\n    +    Although this commit does not introduce breaking changes, it is\n    +    incompatible with existing --recurse-submodules semantics e.g. `git\n    +    checkout` does not recursively checkout the expected branches created by\n    +    `git branch` yet. To ensure that the correct set of semantics is used,\n    +    this commit introduces a new configuration value,\n    +    `submodule.propagateBranches`, which enables submodule branching when\n    +    true (defaults to false).\n    +\n    +    This commit includes changes that allow Git to work with submodules\n    +    that are in trees (and not just the index):\n    +\n    +    * add a submodules_of_tree() helper that gives the relevant\n    +      information of an in-tree submodule (e.g. path and oid) and\n    +      initializes the repository\n    +    * add is_tree_submodule_active() by adding a treeish_name parameter to\n    +      is_submodule_active()\n    +    * add the \"submoduleNotUpdated\" advice to advise users to update the\n    +      submodules in their trees\n    +\n    +    Other changes\n    +\n    +    * add a \"dry_run\" parameter to create_branch() in order to support\n    +      `git submodule--helper create-branch --dry-run`\n     \n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n    @@ Documentation/config/submodule.txt: submodule.recurse::\n      \tconfiguration value by using `git -c submodule.recurse=0`.\n      \n     +submodule.propagateBranches::\n    -+\t[EXPERIMENTAL] A boolean that enables branching support with\n    -+\tsubmodules. This allows certain commands to accept\n    -+\t`--recurse-submodules` (`git branch --recurse-submodules` will\n    -+\tcreate branches recursively), and certain commands that already\n    -+\taccept `--recurse-submodules` will now consider branches (`git\n    -+\tswitch --recurse-submodules` will switch to the correct branch\n    -+\tin all submodules).\n    ++\t[EXPERIMENTAL] A boolean that enables branching support when\n    ++\tusing `--recurse-submodules` or `submodule.recurse=true`.\n    ++\tEnabling this will allow certain commands to accept\n    ++\t`--recurse-submodules` and certain commands that already accept\n    ++\t`--recurse-submodules` will now consider branches.\n    ++\tDefaults to false.\n     +\n      submodule.fetchJobs::\n      \tSpecifies how many submodules are fetched/cloned at the same time.\n    @@ branch.c\n      \n      struct tracking {\n      \tstruct refspec_item spec;\n    +@@ branch.c: void setup_tracking(const char *new_ref, const char *orig_ref,\n    + \n    + void create_branch(struct repository *r, const char *name,\n    + \t\t   const char *start_name, int force, int clobber_head_ok,\n    +-\t\t   int reflog, int quiet, enum branch_track track)\n    ++\t\t   int reflog, int quiet, enum branch_track track, int dry_run)\n    + {\n    + \tstruct object_id oid;\n    + \tchar *real_ref;\n     @@ branch.c: void create_branch(struct repository *r, const char *name,\n    + \t}\n    + \n    + \tvalidate_branch_start(r, start_name, track, &oid, &real_ref);\n    ++\tif (dry_run)\n    ++\t\tgoto cleanup;\n    + \n    + \tif (reflog)\n    + \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n    +@@ branch.c: void create_branch(struct repository *r, const char *name,\n    + \tif (real_ref && track)\n    + \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet, 0);\n    + \n    ++cleanup:\n    + \tstrbuf_release(&ref);\n      \tfree(real_ref);\n      }\n      \n    -+static int submodule_validate_branchname(struct repository *r, const char *name,\n    -+\t\t\t\t\t const char *start_name, int force,\n    -+\t\t\t\t\t int quiet, char **err_msg)\n    -+{\n    -+\tint ret = 0;\n    -+\tstruct child_process child = CHILD_PROCESS_INIT;\n    -+\tstruct strbuf child_err = STRBUF_INIT;\n    -+\tchild.git_cmd = 1;\n    -+\tchild.err = -1;\n    -+\n    -+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n    -+\tstrvec_pushl(&child.args, \"branch\", \"--dry-run\", NULL);\n    -+\tif (force)\n    -+\t\tstrvec_push(&child.args, \"--force\");\n    -+\tif (quiet)\n    -+\t\tstrvec_push(&child.args, \"--quiet\");\n    -+\tstrvec_pushl(&child.args, name, start_name, NULL);\n    -+\n    -+\tif ((ret = start_command(&child)))\n    -+\t\treturn ret;\n    -+\tret = finish_command(&child);\n    -+\tstrbuf_read(&child_err, child.err, 0);\n    -+\t*err_msg = strbuf_detach(&child_err, NULL);\n    -+\treturn ret;\n    -+}\n    -+\n    -+static int submodule_create_branch(struct repository *r, const char *name,\n    -+\t\t\t\t   const char *start_oid,\n    ++static int submodule_create_branch(struct repository *r,\n    ++\t\t\t\t   const struct submodule *submodule,\n    ++\t\t\t\t   const char *name, const char *start_oid,\n     +\t\t\t\t   const char *start_name, int force,\n     +\t\t\t\t   int reflog, int quiet,\n    -+\t\t\t\t   enum branch_track track, char **err_msg)\n    ++\t\t\t\t   enum branch_track track, int dry_run)\n     +{\n     +\tint ret = 0;\n     +\tstruct child_process child = CHILD_PROCESS_INIT;\n     +\tstruct strbuf child_err = STRBUF_INIT;\n    ++\tstruct strbuf out_buf = STRBUF_INIT;\n    ++\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n     +\tchild.git_cmd = 1;\n     +\tchild.err = -1;\n    ++\tchild.stdout_to_stderr = 1;\n     +\n     +\tprepare_other_repo_env(&child.env_array, r->gitdir);\n     +\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n    ++\tif (dry_run)\n    ++\t\tstrvec_push(&child.args, \"--dry-run\");\n     +\tif (force)\n     +\t\tstrvec_push(&child.args, \"--force\");\n     +\tif (quiet)\n    @@ branch.c: void create_branch(struct repository *r, const char *name,\n     +\t\treturn ret;\n     +\tret = finish_command(&child);\n     +\tstrbuf_read(&child_err, child.err, 0);\n    -+\t*err_msg = strbuf_detach(&child_err, NULL);\n    ++\tstrbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);\n    ++\n    ++\tif (ret)\n    ++\t\tfprintf(stderr, \"%s\", out_buf.buf);\n    ++\telse\n    ++\t\tprintf(\"%s\", out_buf.buf);\n    ++\n    ++\tstrbuf_release(&child_err);\n    ++\tstrbuf_release(&out_buf);\n     +\treturn ret;\n     +}\n     +\n    -+void create_submodule_branches(struct repository *r, const char *name,\n    -+\t\t\t       const char *start_name, int force, int reflog,\n    -+\t\t\t       int quiet, enum branch_track track)\n    ++void create_branches_recursively(struct repository *r, const char *name,\n    ++\t\t\t\t const char *start_name,\n    ++\t\t\t\t const char *tracking_name, int force,\n    ++\t\t\t\t int reflog, int quiet, enum branch_track track,\n    ++\t\t\t\t int dry_run)\n     +{\n     +\tint i = 0;\n     +\tchar *branch_point = NULL;\n    -+\tstruct repository *subrepos;\n    -+\tstruct submodule *submodules;\n     +\tstruct object_id super_oid;\n    -+\tstruct submodule_entry_list *submodule_entry_list;\n    -+\tchar *err_msg = NULL;\n    -+\n    -+\tvalidate_branch_start(r, start_name, track, &super_oid, &branch_point);\n    -+\n    -+\tsubmodule_entry_list = submodules_of_tree(r, &super_oid);\n    -+\tCALLOC_ARRAY(subrepos, submodule_entry_list->entry_nr);\n    -+\tCALLOC_ARRAY(submodules, submodule_entry_list->entry_nr);\n    -+\n    -+\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n    -+\t\tsubmodules[i] = *submodule_from_path(\n    -+\t\t\tr, &super_oid,\n    -+\t\t\tsubmodule_entry_list->name_entries[i].path);\n    -+\n    -+\t\tif (repo_submodule_init(\n    -+\t\t\t    &subrepos[i], r,\n    -+\t\t\t    submodule_entry_list->name_entries[i].path,\n    -+\t\t\t    &super_oid)) {\n    -+\t\t\tdie(_(\"submodule %s: unable to find submodule\"),\n    -+\t\t\t    submodules[i].name);\n    ++\tstruct submodule_entry_list submodule_entry_list;\n    ++\n    ++\t/* Perform dwim on start_name to get super_oid and branch_point. */\n    ++\tvalidate_branch_start(r, start_name, BRANCH_TRACK_NEVER, &super_oid,\n    ++\t\t\t      &branch_point);\n    ++\n    ++\t/*\n    ++\t * If we were not given an explicit name to track, then assume we are at\n    ++\t * the top level and, just like the non-recursive case, the tracking\n    ++\t * name is the branch point.\n    ++\t */\n    ++\tif (!tracking_name)\n    ++\t\ttracking_name = branch_point;\n    ++\n    ++\tsubmodules_of_tree(r, &super_oid, &submodule_entry_list);\n    ++\t/*\n    ++\t * Before creating any branches, first check that the branch can\n    ++\t * be created in every submodule.\n    ++\t */\n    ++\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n    ++\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n     +\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n    -+\t\t\t\tadvise(_(\"You may try initializing the submodules using 'git checkout %s && git submodule update'\"),\n    ++\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n     +\t\t\t\t       start_name);\n    ++\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n    ++\t\t\t    submodule_entry_list.entries[i].submodule->name);\n     +\t\t}\n     +\n    -+\t\tif (submodule_validate_branchname(\n    -+\t\t\t    &subrepos[i], name,\n    -+\t\t\t    oid_to_hex(\n    -+\t\t\t\t    &submodule_entry_list->name_entries[i].oid),\n    -+\t\t\t    force, quiet, &err_msg))\n    -+\t\t\tdie(_(\"submodule %s: could not create branch '%s'\\n\\t%s\"),\n    -+\t\t\t    submodules[i].name, name, err_msg);\n    ++\t\tif (submodule_create_branch(\n    ++\t\t\t    submodule_entry_list.entries[i].repo,\n    ++\t\t\t    submodule_entry_list.entries[i].submodule, name,\n    ++\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n    ++\t\t\t\t\t\t.name_entry->oid),\n    ++\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n    ++\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n    ++\t\t\t    submodule_entry_list.entries[i].submodule->name,\n    ++\t\t\t    name);\n     +\t}\n     +\n     +\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n    -+\t\t      track);\n    -+\n    -+\tfor (i = 0; i < submodule_entry_list->entry_nr; i++) {\n    -+\t\tprintf_ln(_(\"submodule %s: creating branch '%s'\"),\n    -+\t\t\t  submodules[i].name, name);\n    ++\t\t      BRANCH_TRACK_NEVER, dry_run);\n    ++\tif (dry_run)\n    ++\t\treturn;\n    ++\t/*\n    ++\t * NEEDSWORK If tracking was set up in the superproject but not the\n    ++\t * submodule, users might expect \"git branch --recurse-submodules\" to\n    ++\t * fail or give a warning, but this is not yet implemented because it is\n    ++\t * tedious to determine whether or not tracking was set up in the\n    ++\t * superproject.\n    ++\t */\n    ++\tsetup_tracking(name, tracking_name, track, quiet, 0);\n    ++\n    ++\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n     +\t\tif (submodule_create_branch(\n    -+\t\t\t    &subrepos[i], name,\n    -+\t\t\t    oid_to_hex(\n    -+\t\t\t\t    &submodule_entry_list->name_entries[i].oid),\n    -+\t\t\t    branch_point, force, reflog, quiet, track,\n    -+\t\t\t    &err_msg))\n    -+\t\t\tdie(_(\"submodule %s: could not create branch '%s'\\n\\t%s\"),\n    -+\t\t\t    submodules[i].name, name, err_msg);\n    -+\n    -+\t\trepo_clear(&subrepos[i]);\n    ++\t\t\t    submodule_entry_list.entries[i].repo,\n    ++\t\t\t    submodule_entry_list.entries[i].submodule, name,\n    ++\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n    ++\t\t\t\t\t\t.name_entry->oid),\n    ++\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n    ++\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n    ++\t\t\t    submodule_entry_list.entries[i].submodule->name,\n    ++\t\t\t    name);\n    ++\t\trepo_clear(submodule_entry_list.entries[i].repo);\n     +\t}\n     +}\n     +\n    @@ branch.c: void create_branch(struct repository *r, const char *name,\n      \tunlink(git_path_merge_head(r));\n     \n      ## branch.h ##\n    -@@ branch.h: void create_branch(struct repository *r,\n    - \t\t   int force, int clobber_head_ok,\n    - \t\t   int reflog, int quiet, enum branch_track track);\n    +@@ branch.h: void setup_tracking(const char *new_ref, const char *orig_ref,\n    +  *   - track causes the new branch to be configured to merge the remote branch\n    +  *     that start_name is a tracking branch for (if any).\n    +  *\n    ++ *   - dry_run causes the branch to be validated but not created.\n    ++ *\n    +  */\n    +-void create_branch(struct repository *r,\n    +-\t\t   const char *name, const char *start_name,\n    +-\t\t   int force, int clobber_head_ok,\n    +-\t\t   int reflog, int quiet, enum branch_track track);\n    ++void create_branch(struct repository *r, const char *name,\n    ++\t\t   const char *start_name, int force, int clobber_head_ok,\n    ++\t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n      \n     +/*\n    -+ * Creates a new branch in repository and its submodules.\n    ++ * Creates a new branch in repository and its submodules (and its\n    ++ * submodules, recursively). Besides these exceptions, the parameters\n    ++ * function identically to create_branch():\n    ++ *\n    ++ * - start_name is the name of the ref, in repository r, that the new\n    ++ *   branch should start from. In submodules, branches will start from\n    ++ *   the respective gitlink commit ids in start_name's tree.\n    ++ *\n    ++ * - tracking_name is the name used of the ref that will be used to set\n    ++ *   up tracking, e.g. origin/main. This is propagated to submodules so\n    ++ *   that tracking information will appear as if the branch branched off\n    ++ *   tracking_name instead of start_name (which is a plain commit id for\n    ++ *   submodules). If omitted, start_name is used for tracking (just like\n    ++ *   create_branch()).\n    ++ *\n     + */\n    -+void create_submodule_branches(struct repository *r, const char *name,\n    -+\t\t\t       const char *start_name, int force, int reflog,\n    -+\t\t\t       int quiet, enum branch_track track);\n    ++void create_branches_recursively(struct repository *r, const char *name,\n    ++\t\t\t\t const char *start_name,\n    ++\t\t\t\t const char *tracking_name, int force,\n    ++\t\t\t\t int reflog, int quiet, enum branch_track track,\n    ++\t\t\t\t int dry_run);\n      /*\n       * Check if 'name' can be a valid name for a branch; die otherwise.\n       * Return 1 if the named branch already exists; return 0 otherwise.\n    @@ builtin/branch.c: static int git_branch_config(const char *var, const char *valu\n      }\n      \n     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)\n    - \tint delete = 0, rename = 0, copy = 0, force = 0, list = 0, create = 0,\n      \t    unset_upstream = 0, show_current = 0, edit_description = 0;\n    + \tint noncreate_actions = 0;\n      \t/* possible options */\n    --\tint reflog = 0, quiet = 0, dry_run = 0, icase = 0;\n    -+\tint reflog = 0, quiet = 0, dry_run = 0, icase = 0,\n    -+\t    recurse_submodules_explicit = 0;\n    +-\tint reflog = 0, quiet = 0, icase = 0;\n    ++\tint reflog = 0, quiet = 0, icase = 0, recurse_submodules_explicit = 0;\n      \tconst char *new_upstream = NULL;\n      \tenum branch_track track;\n      \tstruct ref_filter filter;\n    @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix\n      \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n     +\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n      \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n    - \t\tOPT__DRY_RUN(&dry_run, N_(\"show whether the branch would be created\")),\n      \t\tOPT_END(),\n    + \t};\n     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)\n    - \tif (create < 0)\n    + \tif (noncreate_actions > 1)\n      \t\tusage_with_options(builtin_branch_usage, options);\n      \n    -+\tif (recurse_submodules_explicit && submodule_propagate_branches &&\n    -+\t    !create)\n    -+\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n    - \tif (dry_run && !create)\n    - \t\tdie(_(\"--dry-run can only be used when creating branches\"));\n    - \n    ++\tif (recurse_submodules_explicit) {\n    ++\t\tif (!submodule_propagate_branches)\n    ++\t\t\tdie(_(\"branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\"));\n    ++\t\tif (noncreate_actions)\n    ++\t\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n    ++\t}\n    ++\n     +\trecurse_submodules =\n     +\t\t(recurse_submodules || recurse_submodules_explicit) &&\n     +\t\tsubmodule_propagate_branches;\n    @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix\n      \t\tfilter.abbrev = DEFAULT_ABBREV;\n      \tfilter.ignore_case = icase;\n     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)\n    - \t\t\tFREE_AND_NULL(unused_full_ref);\n    - \t\t\treturn 0;\n    - \t\t}\n    + \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n    + \t\tstrbuf_release(&buf);\n    + \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n    ++\t\tconst char *branch_name = argv[0];\n    ++\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n    ++\n    + \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n    + \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n    + \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n    +@@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)\n    + \t\tif (track == BRANCH_TRACK_OVERRIDE)\n    + \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n    + \n    +-\t\tcreate_branch(the_repository,\n    +-\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n    +-\t\t\t      force, 0, reflog, quiet, track);\n    +-\n     +\t\tif (recurse_submodules) {\n    -+\t\t\tcreate_submodule_branches(the_repository, branch_name,\n    -+\t\t\t\t\t\t  start_name, force, reflog,\n    -+\t\t\t\t\t\t  quiet, track);\n    ++\t\t\tcreate_branches_recursively(the_repository, branch_name,\n    ++\t\t\t\t\t\t    start_name, NULL, force,\n    ++\t\t\t\t\t\t    reflog, quiet, track, 0);\n     +\t\t\treturn 0;\n     +\t\t}\n    - \t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n    - \t\t\t      reflog, quiet, track);\n    ++\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n    ++\t\t\t      reflog, quiet, track, 0);\n      \t} else\n    + \t\tusage_with_options(builtin_branch_usage, options);\n    + \n    +\n    + ## builtin/checkout.c ##\n    +@@ builtin/checkout.c: static void update_refs_for_switch(const struct checkout_opts *opts,\n    + \t\t\t\t      opts->new_branch_force ? 1 : 0,\n    + \t\t\t\t      opts->new_branch_log,\n    + \t\t\t\t      opts->quiet,\n    +-\t\t\t\t      opts->track);\n    ++\t\t\t\t      opts->track,\n    ++\t\t\t\t      0);\n    + \t\tnew_branch_info->name = opts->new_branch;\n    + \t\tsetup_branch_path(new_branch_info);\n    + \t}\n     \n      ## builtin/submodule--helper.c ##\n     @@\n    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *\n     +static int module_create_branch(int argc, const char **argv, const char *prefix)\n     +{\n     +\tenum branch_track track;\n    -+\tint quiet = 0, force = 0, reflog = 0;\n    ++\tint quiet = 0, force = 0, reflog = 0, dry_run = 0;\n     +\n     +\tstruct option options[] = {\n     +\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *\n     +\t\tOPT_SET_INT('t', \"track\", &track,\n     +\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n     +\t\t\t    BRANCH_TRACK_EXPLICIT),\n    ++\t\tOPT__DRY_RUN(&dry_run,\n    ++\t\t\t     N_(\"show whether the branch would be created\")),\n     +\t\tOPT_END()\n     +\t};\n     +\tconst char *const usage[] = {\n    -+\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] <name> <start_oid> <start_name>\"),\n    ++\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>\"),\n     +\t\tNULL\n     +\t};\n     +\n    ++\tgit_config(git_default_config, NULL);\n    ++\ttrack = git_branch_track;\n     +\targc = parse_options(argc, argv, prefix, options, usage, 0);\n     +\n     +\tif (argc != 3)\n     +\t\tusage_with_options(usage, options);\n     +\n    -+\tcreate_branch(the_repository, argv[0], argv[1], force, 0, reflog, quiet,\n    -+\t\t      BRANCH_TRACK_NEVER);\n    -+\tsetup_tracking(argv[0], argv[2], track, quiet, 0);\n    ++\tif (!quiet && !dry_run)\n    ++\t\tprintf_ln(_(\"creating branch '%s'\"), argv[0]);\n     +\n    ++\tcreate_branches_recursively(the_repository, argv[0], argv[1], argv[2],\n    ++\t\t\t\t    force, reflog, quiet, track, dry_run);\n     +\treturn 0;\n     +}\n      struct add_data {\n    @@ builtin/submodule--helper.c: static struct cmd_struct commands[] = {\n      \n      int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\n     \n    + ## submodule-config.c ##\n    +@@\n    + #include \"strbuf.h\"\n    + #include \"object-store.h\"\n    + #include \"parse-options.h\"\n    ++#include \"tree-walk.h\"\n    + \n    + /*\n    +  * submodule cache lookup structure\n    +@@ submodule-config.c: const struct submodule *submodule_from_path(struct repository *r,\n    + \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n    + }\n    + \n    ++void submodules_of_tree(struct repository *r,\n    ++\t\t\tconst struct object_id *treeish_name,\n    ++\t\t\tstruct submodule_entry_list *out)\n    ++{\n    ++\tstruct tree_desc tree;\n    ++\tstruct submodule_tree_entry *st_entry;\n    ++\tstruct name_entry *name_entry;\n    ++\n    ++\tname_entry = xmalloc(sizeof(*name_entry));\n    ++\n    ++\tCALLOC_ARRAY(out->entries, 0);\n    ++\tout->entry_nr = 0;\n    ++\tout->entry_alloc = 0;\n    ++\n    ++\tfill_tree_descriptor(r, &tree, treeish_name);\n    ++\twhile (tree_entry(&tree, name_entry)) {\n    ++\t\tif (!S_ISGITLINK(name_entry->mode) || !is_tree_submodule_active(r, treeish_name, name_entry->path)) {\n    ++\t\t\tcontinue;\n    ++\t\t}\n    ++\n    ++\t\tst_entry = xmalloc(sizeof(*st_entry));\n    ++\t\tst_entry->name_entry = name_entry;\n    ++\t\tst_entry->submodule =\n    ++\t\t\tsubmodule_from_path(r, treeish_name, name_entry->path);\n    ++\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n    ++\t\tif (repo_submodule_init(st_entry->repo, r, name_entry->path,\n    ++\t\t\t\t\ttreeish_name))\n    ++\t\t\tFREE_AND_NULL(st_entry->repo);\n    ++\n    ++\t\tALLOC_GROW(out->entries, out->entry_nr + 1, out->entry_alloc);\n    ++\t\tout->entries[out->entry_nr++] = *st_entry;\n    ++\t}\n    ++}\n    ++\n    + void submodule_free(struct repository *r)\n    + {\n    + \tif (r->submodule_cache)\n    +\n    + ## submodule-config.h ##\n    +@@\n    + #include \"hashmap.h\"\n    + #include \"submodule.h\"\n    + #include \"strbuf.h\"\n    ++#include \"tree-walk.h\"\n    + \n    + /**\n    +  * The submodule config cache API allows to read submodule\n    +@@ submodule-config.h: int check_submodule_name(const char *name);\n    + void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n    + void update_clone_config_from_gitmodules(int *max_jobs);\n    + \n    ++/*\n    ++ * Submodule entry that contains relevant information about a\n    ++ * submodule in a tree.\n    ++ */\n    ++struct submodule_tree_entry {\n    ++\t/* The submodule's tree entry. */\n    ++\tstruct name_entry *name_entry;\n    ++\t/*\n    ++\t * A struct repository corresponding to the submodule. May be\n    ++\t * NULL if the submodule has not been updated.\n    ++\t */\n    ++\tstruct repository *repo;\n    ++\t/*\n    ++\t * A struct submodule containing the submodule config in the\n    ++\t * tree's .gitmodules.\n    ++\t */\n    ++\tconst struct submodule *submodule;\n    ++};\n    ++\n    ++struct submodule_entry_list {\n    ++\tstruct submodule_tree_entry *entries;\n    ++\tint entry_nr;\n    ++\tint entry_alloc;\n    ++};\n    ++\n    ++/**\n    ++ * Given a treeish, return all submodules in the tree. This only reads\n    ++ * one level of the tree, so it will not return nested submodules;\n    ++ * callers that require nested submodules are expected to handle the\n    ++ * recursion themselves.\n    ++ */\n    ++void submodules_of_tree(struct repository *r,\n    ++\t\t\tconst struct object_id *treeish_name,\n    ++\t\t\tstruct submodule_entry_list *ret);\n    + #endif /* SUBMODULE_CONFIG_H */\n    +\n    + ## submodule.c ##\n    +@@ submodule.c: int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n    +  * ie, the config looks like: \"[submodule] active\\n\".\n    +  * Since that is an invalid pathspec, we should inform the user.\n    +  */\n    +-int is_submodule_active(struct repository *repo, const char *path)\n    ++int is_tree_submodule_active(struct repository *repo,\n    ++\t\t\t     const struct object_id *treeish_name,\n    ++\t\t\t     const char *path)\n    + {\n    + \tint ret = 0;\n    + \tchar *key = NULL;\n    +@@ submodule.c: int is_submodule_active(struct repository *repo, const char *path)\n    + \tconst struct string_list *sl;\n    + \tconst struct submodule *module;\n    + \n    +-\tmodule = submodule_from_path(repo, null_oid(), path);\n    ++\tmodule = submodule_from_path(repo, treeish_name, path);\n    + \n    + \t/* early return if there isn't a path->module mapping */\n    + \tif (!module)\n    +@@ submodule.c: int is_submodule_active(struct repository *repo, const char *path)\n    + \treturn ret;\n    + }\n    + \n    ++int is_submodule_active(struct repository *repo, const char *path)\n    ++{\n    ++\treturn is_tree_submodule_active(repo, null_oid(), path);\n    ++}\n    ++\n    + int is_submodule_populated_gently(const char *path, int *return_error_code)\n    + {\n    + \tint ret = 0;\n    +\n    + ## submodule.h ##\n    +@@ submodule.h: int git_default_submodule_config(const char *var, const char *value, void *cb);\n    + struct option;\n    + int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n    + \t\t\t\t\t\t     const char *arg, int unset);\n    ++int is_tree_submodule_active(struct repository *repo,\n    ++\t\t\t     const struct object_id *treeish_name,\n    ++\t\t\t     const char *path);\n    + int is_submodule_active(struct repository *repo, const char *path);\n    + /*\n    +  * Determine if a submodule has been populated at a given 'path' by checking if\n    +\n      ## t/t3207-branch-submodule.sh (new) ##\n     @@\n     +#!/bin/sh\n    @@ t/t3207-branch-submodule.sh (new)\n     +test_expect_success 'setup superproject and submodule' '\n     +\tgit init super &&\n     +\ttest_commit foo &&\n    ++\tgit init sub-sub-upstream &&\n    ++\ttest_commit -C sub-sub-upstream foo &&\n     +\tgit init sub-upstream &&\n    -+\ttest_commit -C sub-upstream foo &&\n    -+\tgit -C super submodule add ../sub-upstream sub &&\n    ++\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n    ++\tgit -C sub-upstream commit -m \"add submodule\" &&\n    ++\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n     +\tgit -C super commit -m \"add submodule\" &&\n    -+\tgit -C super config submodule.propagateBranches true\n    ++\tgit -C super config submodule.propagateBranches true &&\n    ++\tgit -C super/sub submodule update --init\n     +'\n     +\n    -+cleanup_branches() {\n    ++CLEANUP_SCRIPT_PATH=\"$TRASH_DIRECTORY/cleanup_branches.sh\"\n    ++\n    ++cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n    ++\t#!/bin/sh\n    ++\n     +\tsuper_dir=\"$1\"\n     +\tshift\n     +\t(\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tgit checkout main &&\n     +\t\tfor branch_name in \"$@\"; do\n     +\t\t\tgit branch -D \"$branch_name\"\n    -+\t\t\tgit submodule foreach \"(git checkout main && git branch -D $branch_name) || true\"\n    ++\t\t\tgit submodule foreach \"$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true\"\n     +\t\tdone\n     +\t)\n    ++EOF\n    ++chmod +x \"$CLEANUP_SCRIPT_PATH\"\n    ++\n    ++cleanup_branches() {\n    ++\tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n     +} >/dev/null 2>/dev/null\n     +\n     +# Test the argument parsing\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a &&\n    -+\t\tgit rev-parse --abbrev-ref branch-a &&\n    -+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n    ++\t\tgit rev-parse branch-a &&\n    ++\t\tgit -C sub rev-parse branch-a &&\n    ++\t\tgit -C sub/sub-sub rev-parse branch-a\n     +\t)\n     +'\n     +\n    -+test_expect_success '--recurse-submodules should be ignored if submodule.propagateBranches is false' '\n    ++test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '\n     +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n     +\t(\n     +\t\tcd super &&\n    -+\t\tgit -c submodule.propagateBranches=false branch --recurse-submodules branch-a &&\n    -+\t\tgit rev-parse branch-a &&\n    -+\t\ttest_must_fail git -C sub rev-parse branch-a\n    ++\t\techo \"fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\" >expected &&\n    ++\t\ttest_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&\n    ++\t\ttest_cmp expected actual\n     +\t)\n     +'\n     +\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n     +\t\ttest_must_fail git rev-parse branch-a &&\n     +\n    -+\t\tcat >expected <<EOF &&\n    -+fatal: submodule sub: could not create branch ${SQ}branch-a${SQ}\n    -+\tfatal: A branch named ${SQ}branch-a${SQ} already exists.\n    -+\n    -+EOF\n    ++\t\tcat >expected <<-EOF &&\n    ++\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n    ++\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n    ++\t\tEOF\n     +\t\ttest_cmp expected actual\n     +\t)\n     +'\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t)\n     +'\n     +\n    -+test_expect_success 'should create branch when submodule is in .git/modules but not .gitmodules' '\n    ++test_expect_success 'should create branch when submodule is not in HEAD .gitmodules' '\n     +\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch branch-a &&\n     +\t\tgit checkout -b branch-b &&\n     +\t\tgit submodule add ../sub-upstream sub2 &&\n    ++\t\tgit -C sub2 submodule update --init &&\n     +\t\t# branch-b now has a committed submodule not in branch-a\n     +\t\tgit commit -m \"add second submodule\" &&\n     +\t\tgit checkout branch-a &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t)\n     +'\n     +\n    ++test_expect_success 'should not create branches in inactive submodules' '\n    ++\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_config -C super submodule.sub.active false &&\n    ++\t(\n    ++\t\tcd super &&\n    ++\t\tgit branch --recurse-submodules branch-a &&\n    ++\t\tgit rev-parse branch-a &&\n    ++\t\ttest_must_fail git -C sub branch-a\n    ++\t)\n    ++'\n    ++\n     +test_expect_success 'setup remote-tracking tests' '\n     +\t(\n     +\t\tcd super &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\t# branch-b now has a committed submodule not in branch-a\n     +\t\tgit commit -m \"add second submodule\"\n     +\t) &&\n    -+\t(\n    -+\t\tcd sub-upstream &&\n    -+\t\tgit branch branch-a\n    -+\t) &&\n     +\tgit clone --branch main --recurse-submodules super super-clone &&\n     +\tgit -C super-clone config submodule.propagateBranches true\n     +'\n     +\n     +test_expect_success 'should not create branch when submodule is not in .git/modules' '\n    -+\t# The cleanup needs to delete sub2:branch-b in particular because main does not have sub2\n    ++\t# The cleanup needs to delete sub2 separately because main does not have sub2\n     +\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n    ++\t\tgit -C super-clone/sub2/sub-sub branch -D branch-b && \\\n     +\t\tcleanup_branches super-clone branch-a branch-b\" &&\n     +\t(\n     +\t\tcd super-clone &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\t# This should fail because super-clone does not have sub2.\n     +\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n     +\t\tcat >expected <<-EOF &&\n    -+\t\tfatal: submodule sub: unable to find submodule\n    -+\t\tYou may reinitialize the submodules using ${SQ}git checkout origin/branch-b && git submodule update${SQ}\n    ++\t\thint: You may try updating the submodules using ${SQ}git checkout origin/branch-b && git submodule update --init${SQ}\n    ++\t\tfatal: submodule ${SQ}sub2${SQ}: unable to find submodule\n     +\t\tEOF\n    ++\t\ttest_cmp expected actual &&\n     +\t\ttest_must_fail git rev-parse branch-b &&\n     +\t\ttest_must_fail git -C sub rev-parse branch-b &&\n     +\t\t# User can fix themselves by initializing the submodule\n     +\t\tgit checkout origin/branch-b &&\n    -+\t\tgit submodule update &&\n    ++\t\tgit submodule update --init --recursive &&\n     +\t\tgit branch --recurse-submodules branch-b origin/branch-b\n     +\t)\n     +'\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t(\n     +\t\tcd super-clone &&\n     +\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n    ++\t\ttest \"$(git config branch.branch-a.remote)\" = origin &&\n    ++\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n    ++\t\t# \"origin/branch-a\" does not exist for \"sub\", but it matches the refspec\n    ++\t\t# so tracking should be set up\n     +\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a\n    ++\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a &&\n    ++\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.remote)\" = origin &&\n    ++\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.merge)\" = refs/heads/branch-a\n     +\t)\n     +'\n     +\n     +test_expect_success 'should not fail when unable to set up tracking in submodule' '\n    -+\ttest_when_finished \"cleanup_branches super-clone branch-b\" &&\n    ++\ttest_when_finished \"cleanup_branches super-clone branch-a && \\\n    ++\t\tgit -C super-clone remote rename ex-origin origin\" &&\n     +\t(\n     +\t\tcd super-clone &&\n    -+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n    ++\t\tgit remote rename origin ex-origin &&\n    ++\t\tgit branch --recurse-submodules branch-a ex-origin/branch-a &&\n    ++\t\ttest \"$(git config branch.branch-a.remote)\" = ex-origin &&\n    ++\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n    ++\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n    ++\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n     +\t)\n     +'\n     +\n-- \n2.33.GIT\n\n"},{"id":"443203","messageId":"20211206215528.97050-2-chooglen@google.com","threadId":"56962","inReplyTo":"20211206215528.97050-1-chooglen@google.com","subject":"[PATCH v2 1/3] branch: move --set-upstream-to behavior to setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-06T21:55:26Z","receivedAt":"2021-12-06T21:56:05Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"This refactor is motivated by a desire to add a \"dry_run\" parameter to\ncreate_branch() that will validate whether or not a branch can be\ncreated without actually creating it - this behavior be used in a\nsubsequent commit that adds `git branch --recurse-submodules topic`.\n\nAdding \"dry_run\" is not obvious because create_branch() is also used to\nset tracking information without creating a branch, i.e. when using\n--set-upstream-to. This appears to be a leftover from 4fc5006676 (Add\nbranch --set-upstream, 2010-01-18), when --set-upstream would sometimes\ncreate a branch and sometimes update tracking information without\ncreating a branch. However, we no longer support --set-upstream, so it\nmakes more sense to set tracking information with another function, like\nsetup_tracking(), and use create_branch() only to create branches. When\nthis is done, it will be trivial to add \"dry_run\".\n\nDo this refactor by moving the branch validation and dwim logic from\ncreate_branch() into a new function, validate_branch_start(), and call\nit from setup_tracking(). Now that setup_tracking() can perform dwim and\ntracking setup without creating a branch, use it in `git branch\n--set-upstream-to` and remove unnecessary behavior from create_branch().\n\nSince there were none, add tests for creating a branch with `--force`.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\nAs Jonathan noted in v1, the diff is quite large. I could shrink this\nby forward-declaring setup_tracking() so that the function definitions\nare in the same order; let me know if that would be preferred.\n\n branch.c          | 195 ++++++++++++++++++++++++++--------------------\n branch.h          |  13 +++-\n builtin/branch.c  |   7 +-\n t/t3200-branch.sh |  17 ++++\n 4 files changed, 139 insertions(+), 93 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 07a46430b3..a635a60f8b 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -126,43 +126,6 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \treturn -1;\n }\n \n-/*\n- * This is called when new_ref is branched off of orig_ref, and tries\n- * to infer the settings for branch.<new_ref>.{remote,merge} from the\n- * config.\n- */\n-static void setup_tracking(const char *new_ref, const char *orig_ref,\n-\t\t\t   enum branch_track track, int quiet)\n-{\n-\tstruct tracking tracking;\n-\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n-\n-\tmemset(&tracking, 0, sizeof(tracking));\n-\ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n-\t\treturn;\n-\n-\tif (!tracking.matches)\n-\t\tswitch (track) {\n-\t\tcase BRANCH_TRACK_ALWAYS:\n-\t\tcase BRANCH_TRACK_EXPLICIT:\n-\t\tcase BRANCH_TRACK_OVERRIDE:\n-\t\t\tbreak;\n-\t\tdefault:\n-\t\t\treturn;\n-\t\t}\n-\n-\tif (tracking.matches > 1)\n-\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n-\t\t    orig_ref);\n-\n-\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n-\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n-\t\texit(-1);\n-\n-\tfree(tracking.src);\n-}\n-\n int read_branch_desc(struct strbuf *buf, const char *branch_name)\n {\n \tchar *v = NULL;\n@@ -243,33 +206,35 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+/**\n+ * Validates whether a ref is a valid starting point for a branch, where:\n+ *\n+ *   - r is the repository to validate the branch for\n+ *\n+ *   - start_name is the ref that we would like to test. This is\n+ *     expanded with DWIM and assigned to real_ref.\n+ *\n+ *   - track is the tracking mode of the new branch. If tracking is\n+ *     explicitly requested, start_name must be a branch (because\n+ *     otherwise start_name cannot be tracked)\n+ *\n+ *   - oid is an out parameter containing the object_id of start_name\n+ *\n+ *   - real_ref is an out parameter containing the full, 'real' form of\n+ *     start_name e.g. refs/heads/main instead of main\n+ *\n+ */\n+static void validate_branch_start(struct repository *r, const char *start_name,\n+\t\t\t\t  enum branch_track track,\n+\t\t\t\t  struct object_id *oid, char **real_ref)\n {\n \tstruct commit *commit;\n-\tstruct object_id oid;\n-\tchar *real_ref;\n-\tstruct strbuf ref = STRBUF_INIT;\n-\tint forcing = 0;\n-\tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n \n \tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n \t\texplicit_tracking = 1;\n \n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n-\t}\n-\n-\treal_ref = NULL;\n-\tif (get_oid_mb(start_name, &oid)) {\n+\tif (repo_get_oid_mb(r, start_name, oid)) {\n \t\tif (explicit_tracking) {\n \t\t\tif (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {\n \t\t\t\terror(_(upstream_missing), start_name);\n@@ -281,7 +246,8 @@ void create_branch(struct repository *r,\n \t\tdie(_(\"Not a valid object name: '%s'.\"), start_name);\n \t}\n \n-\tswitch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {\n+\tswitch (repo_dwim_ref(r, start_name, strlen(start_name), oid, real_ref,\n+\t\t\t      0)) {\n \tcase 0:\n \t\t/* Not branching from any existing branch */\n \t\tif (explicit_tracking)\n@@ -289,12 +255,12 @@ void create_branch(struct repository *r,\n \t\tbreak;\n \tcase 1:\n \t\t/* Unique completion -- good, only if it is a real branch */\n-\t\tif (!starts_with(real_ref, \"refs/heads/\") &&\n-\t\t    validate_remote_tracking_branch(real_ref)) {\n+\t\tif (!starts_with(*real_ref, \"refs/heads/\") &&\n+\t\t    validate_remote_tracking_branch(*real_ref)) {\n \t\t\tif (explicit_tracking)\n \t\t\t\tdie(_(upstream_not_branch), start_name);\n \t\t\telse\n-\t\t\t\tFREE_AND_NULL(real_ref);\n+\t\t\t\tFREE_AND_NULL(*real_ref);\n \t\t}\n \t\tbreak;\n \tdefault:\n@@ -302,37 +268,96 @@ void create_branch(struct repository *r,\n \t\tbreak;\n \t}\n \n-\tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n+\tif ((commit = lookup_commit_reference(r, oid)) == NULL)\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n-\toidcpy(&oid, &commit->object.oid);\n+\toidcpy(oid, &commit->object.oid);\n+}\n+\n+void setup_tracking(const char *new_ref, const char *orig_ref,\n+\t\t\t   enum branch_track track, int quiet, int expand_orig)\n+{\n+\tstruct tracking tracking;\n+\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n+\tchar *full_orig_ref;\n+\tstruct object_id unused_oid;\n+\n+\tmemset(&tracking, 0, sizeof(tracking));\n+\tif (expand_orig)\n+\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n+\telse\n+\t\tfull_orig_ref = xstrdup(orig_ref);\n+\n+\ttracking.spec.dst = full_orig_ref;\n+\tif (for_each_remote(find_tracked_branch, &tracking))\n+\t\tgoto cleanup;\n+\n+\tif (!tracking.matches)\n+\t\tswitch (track) {\n+\t\tcase BRANCH_TRACK_ALWAYS:\n+\t\tcase BRANCH_TRACK_EXPLICIT:\n+\t\tcase BRANCH_TRACK_OVERRIDE:\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\tif (tracking.matches > 1)\n+\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n+\t\t    full_orig_ref);\n+\n+\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n+\t\t\t      tracking.src ? tracking.src : full_orig_ref) < 0)\n+\t\texit(-1);\n+\n+cleanup:\n+\tfree(tracking.src);\n+\tfree(full_orig_ref);\n+}\n+\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track)\n+{\n+\tstruct object_id oid;\n+\tchar *real_ref;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tint forcing = 0;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *msg;\n+\n+\tif (clobber_head_ok && !force)\n+\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n+\n+\tif (clobber_head_ok ?\n+\t\t\t  validate_branchname(name, &ref) :\n+\t\t\t  validate_new_branchname(name, &ref, force)) {\n+\t\tforcing = 1;\n+\t}\n+\n+\tvalidate_branch_start(r, start_name, track, &oid, &real_ref);\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n-\tif (!dont_change_ref) {\n-\t\tstruct ref_transaction *transaction;\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\t\tchar *msg;\n-\n-\t\tif (forcing)\n-\t\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n-\t\telse\n-\t\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n-\n-\t\ttransaction = ref_transaction_begin(&err);\n-\t\tif (!transaction ||\n-\t\t    ref_transaction_update(transaction, ref.buf,\n-\t\t\t\t\t   &oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t   0, msg, &err) ||\n-\t\t    ref_transaction_commit(transaction, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\t\tref_transaction_free(transaction);\n-\t\tstrbuf_release(&err);\n-\t\tfree(msg);\n-\t}\n+\tif (forcing)\n+\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n+\telse\n+\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n+\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t\tref_transaction_update(transaction, ref.buf,\n+\t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n+\t\t\t\t\t0, msg, &err) ||\n+\t\tref_transaction_commit(transaction, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tfree(msg);\n \n \tif (real_ref && track)\n-\t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n+\t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet, 0);\n \n \tstrbuf_release(&ref);\n \tfree(real_ref);\ndiff --git a/branch.h b/branch.h\nindex df0be61506..75cefcdcbd 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -17,6 +17,15 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+/*\n+ * This sets the branch.<new_ref>.{remote,merge} config settings so that\n+ * branch 'new_ref' tracks 'orig_ref'. This is called when branches are\n+ * created, or when branch configs are updated (e.g. with\n+ * git branch --set-upstream-to).\n+ */\n+void setup_tracking(const char *new_ref, const char *orig_ref,\n+\t\t    enum branch_track track, int quiet, int expand_orig);\n+\n /*\n  * Creates a new branch, where:\n  *\n@@ -29,8 +38,8 @@ extern enum branch_track git_branch_track;\n  *\n  *   - force enables overwriting an existing (non-head) branch\n  *\n- *   - clobber_head_ok allows the currently checked out (hence existing)\n- *     branch to be overwritten; without 'force', it has no effect.\n+ *   - clobber_head_ok, when enabled with 'force', allows the currently\n+ *     checked out (head) branch to be overwritten\n  *\n  *   - reflog creates a reflog for the branch\n  *\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 81b5c111cb..19f2845e7a 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -821,12 +821,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (!ref_exists(branch->refname))\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \n-\t\t/*\n-\t\t * create_branch takes care of setting up the tracking\n-\t\t * info and making sure new_upstream is correct\n-\t\t */\n-\t\tcreate_branch(the_repository, branch->name, new_upstream,\n-\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n+\t\tsetup_tracking(branch->name, new_upstream, BRANCH_TRACK_OVERRIDE, quiet, 1);\n \t} else if (unset_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n \t\tstruct strbuf buf = STRBUF_INIT;\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 8c5c1ccf33..f97cf495ab 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n \tgit branch abc && test_path_is_file .git/refs/heads/abc\n '\n \n+test_expect_success 'git branch abc should fail when abc exists' '\n+\ttest_must_fail git branch abc\n+'\n+\n+test_expect_success 'git branch --force abc should fail when abc is checked out' '\n+\ttest_when_finished git switch main &&\n+\tgit switch abc &&\n+\ttest_must_fail git branch --force abc HEAD~1\n+'\n+\n+test_expect_success 'git branch --force abc should succeed when abc exists' '\n+\tgit rev-parse HEAD~1 >expect &&\n+\tgit branch --force abc HEAD~1 &&\n+\tgit rev-parse abc >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n '\n-- \n2.33.GIT\n\n"},{"id":"443204","messageId":"20211206215528.97050-3-chooglen@google.com","threadId":"56962","inReplyTo":"20211206215528.97050-1-chooglen@google.com","subject":"[PATCH v2 2/3] builtin/branch: clean up action-picking logic in cmd_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-06T21:55:27Z","receivedAt":"2021-12-06T21:56:06Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a variable, noncreate_actions, to cmd_branch() that will tell us\nwhether or not cmd_branch() will default to creating a branch (instead\nof performing another action). Besides making the function more\nexplicit, this allows us to validate options that can only be used when\ncreating a branch. Such an option does not exist yet, but one will be\nintroduced in a subsequent commit.\n\nIncidentally, fix an incorrect usage string that combined the 'list'\nusage of git branch (-l) with the 'create' usage; this string has been\nincorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\nparse_options., 2007-10-07).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 21 ++++++++++++---------\n 1 file changed, 12 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 19f2845e7a..83b8016b38 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -27,7 +27,7 @@\n \n static const char * const builtin_branch_usage[] = {\n \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n-\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n@@ -616,14 +616,15 @@ static int edit_branch_description(const char *branch_name)\n \n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n-\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n-\tint show_current = 0;\n-\tint reflog = 0, edit_description = 0;\n-\tint quiet = 0, unset_upstream = 0;\n+\t/* possible actions */\n+\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0,\n+\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n+\tint noncreate_actions = 0;\n+\t/* possible options */\n+\tint reflog = 0, quiet = 0, icase = 0;\n \tconst char *new_upstream = NULL;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n-\tint icase = 0;\n \tstatic struct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n@@ -706,8 +707,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)\n \t\tlist = 1;\n \n-\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n-\t    list + edit_description + unset_upstream > 1)\n+\tnoncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +\n+\t\t\t    !!show_current + !!list + !!edit_description +\n+\t\t\t    !!unset_upstream;\n+\tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (filter.abbrev == -1)\n@@ -845,7 +848,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&buf, \"branch.%s.merge\", branch->name);\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n-\t} else if (argc > 0 && argc <= 2) {\n+\t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n-- \n2.33.GIT\n\n"},{"id":"443205","messageId":"20211206215528.97050-4-chooglen@google.com","threadId":"56962","inReplyTo":"20211206215528.97050-1-chooglen@google.com","subject":"[PATCH v2 3/3] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-06T21:55:28Z","receivedAt":"2021-12-06T21:56:29Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"To improve the submodules UX, we would like to teach Git to handle\nbranches in submodules. Start this process by teaching `git branch` the\n--recurse-submodules option so that `git branch --recurse-submodules\ntopic` will create the \"topic\" branch in the superproject and its\nsubmodules.\n\nAlthough this commit does not introduce breaking changes, it is\nincompatible with existing --recurse-submodules semantics e.g. `git\ncheckout` does not recursively checkout the expected branches created by\n`git branch` yet. To ensure that the correct set of semantics is used,\nthis commit introduces a new configuration value,\n`submodule.propagateBranches`, which enables submodule branching when\ntrue (defaults to false).\n\nThis commit includes changes that allow Git to work with submodules\nthat are in trees (and not just the index):\n\n* add a submodules_of_tree() helper that gives the relevant\n  information of an in-tree submodule (e.g. path and oid) and\n  initializes the repository\n* add is_tree_submodule_active() by adding a treeish_name parameter to\n  is_submodule_active()\n* add the \"submoduleNotUpdated\" advice to advise users to update the\n  submodules in their trees\n\nOther changes\n\n* add a \"dry_run\" parameter to create_branch() in order to support\n  `git submodule--helper create-branch --dry-run`\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |   8 +\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 129 ++++++++++++-\n branch.h                           |  31 +++-\n builtin/branch.c                   |  40 +++-\n builtin/checkout.c                 |   3 +-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  35 ++++\n submodule-config.h                 |  35 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3207-branch-submodule.sh        | 284 +++++++++++++++++++++++++++++\n 14 files changed, 609 insertions(+), 13 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex 063eec2511..e52262dc69 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -116,6 +116,9 @@ advice.*::\n \tsubmoduleAlternateErrorStrategyDie::\n \t\tAdvice shown when a submodule.alternateErrorStrategy option\n \t\tconfigured to \"die\" causes a fatal error.\n+\tsubmodulesNotUpdated::\n+\t\tAdvice shown when a user runs a submodule command that fails\n+\t\tbecause `git submodule update` was not run.\n \taddIgnoredFile::\n \t\tAdvice shown if a user attempts to add an ignored file to\n \t\tthe index.\ndiff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\nindex ee454f8126..52b35964c0 100644\n--- a/Documentation/config/submodule.txt\n+++ b/Documentation/config/submodule.txt\n@@ -72,6 +72,14 @@ submodule.recurse::\n \tFor these commands a workaround is to temporarily change the\n \tconfiguration value by using `git -c submodule.recurse=0`.\n \n+submodule.propagateBranches::\n+\t[EXPERIMENTAL] A boolean that enables branching support when\n+\tusing `--recurse-submodules` or `submodule.recurse=true`.\n+\tEnabling this will allow certain commands to accept\n+\t`--recurse-submodules` and certain commands that already accept\n+\t`--recurse-submodules` will now consider branches.\n+\tDefaults to false.\n+\n submodule.fetchJobs::\n \tSpecifies how many submodules are fetched/cloned at the same time.\n \tA positive integer allows up to that number of submodules fetched\ndiff --git a/advice.c b/advice.c\nindex 1dfc91d176..e00d30254c 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -70,6 +70,7 @@ static struct {\n \t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n \t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n \t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n+\t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n };\ndiff --git a/advice.h b/advice.h\nindex 601265fd10..a7521d6087 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -44,6 +44,7 @@ struct string_list;\n \tADVICE_STATUS_HINTS,\n \tADVICE_STATUS_U_OPTION,\n \tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_SUBMODULES_NOT_UPDATED,\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\ndiff --git a/branch.c b/branch.c\nindex a635a60f8b..55b44f2417 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -8,6 +8,8 @@\n #include \"sequencer.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n+#include \"submodule-config.h\"\n+#include \"run-command.h\"\n \n struct tracking {\n \tstruct refspec_item spec;\n@@ -316,7 +318,7 @@ void setup_tracking(const char *new_ref, const char *orig_ref,\n \n void create_branch(struct repository *r, const char *name,\n \t\t   const char *start_name, int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track)\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run)\n {\n \tstruct object_id oid;\n \tchar *real_ref;\n@@ -336,6 +338,8 @@ void create_branch(struct repository *r, const char *name,\n \t}\n \n \tvalidate_branch_start(r, start_name, track, &oid, &real_ref);\n+\tif (dry_run)\n+\t\tgoto cleanup;\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -359,10 +363,133 @@ void create_branch(struct repository *r, const char *name,\n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet, 0);\n \n+cleanup:\n \tstrbuf_release(&ref);\n \tfree(real_ref);\n }\n \n+static int submodule_create_branch(struct repository *r,\n+\t\t\t\t   const struct submodule *submodule,\n+\t\t\t\t   const char *name, const char *start_oid,\n+\t\t\t\t   const char *start_name, int force,\n+\t\t\t\t   int reflog, int quiet,\n+\t\t\t\t   enum branch_track track, int dry_run)\n+{\n+\tint ret = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tstruct strbuf child_err = STRBUF_INIT;\n+\tstruct strbuf out_buf = STRBUF_INIT;\n+\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n+\tchild.git_cmd = 1;\n+\tchild.err = -1;\n+\tchild.stdout_to_stderr = 1;\n+\n+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n+\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n+\tif (dry_run)\n+\t\tstrvec_push(&child.args, \"--dry-run\");\n+\tif (force)\n+\t\tstrvec_push(&child.args, \"--force\");\n+\tif (quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\tif (reflog)\n+\t\tstrvec_push(&child.args, \"--create-reflog\");\n+\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n+\t\tstrvec_push(&child.args, \"--track\");\n+\n+\tstrvec_pushl(&child.args, name, start_oid, start_name, NULL);\n+\n+\tif ((ret = start_command(&child)))\n+\t\treturn ret;\n+\tret = finish_command(&child);\n+\tstrbuf_read(&child_err, child.err, 0);\n+\tstrbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);\n+\n+\tif (ret)\n+\t\tfprintf(stderr, \"%s\", out_buf.buf);\n+\telse\n+\t\tprintf(\"%s\", out_buf.buf);\n+\n+\tstrbuf_release(&child_err);\n+\tstrbuf_release(&out_buf);\n+\treturn ret;\n+}\n+\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run)\n+{\n+\tint i = 0;\n+\tchar *branch_point = NULL;\n+\tstruct object_id super_oid;\n+\tstruct submodule_entry_list submodule_entry_list;\n+\n+\t/* Perform dwim on start_name to get super_oid and branch_point. */\n+\tvalidate_branch_start(r, start_name, BRANCH_TRACK_NEVER, &super_oid,\n+\t\t\t      &branch_point);\n+\n+\t/*\n+\t * If we were not given an explicit name to track, then assume we are at\n+\t * the top level and, just like the non-recursive case, the tracking\n+\t * name is the branch point.\n+\t */\n+\tif (!tracking_name)\n+\t\ttracking_name = branch_point;\n+\n+\tsubmodules_of_tree(r, &super_oid, &submodule_entry_list);\n+\t/*\n+\t * Before creating any branches, first check that the branch can\n+\t * be created in every submodule.\n+\t */\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n+\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n+\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n+\t\t\t\t       start_name);\n+\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name);\n+\t\t}\n+\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t}\n+\n+\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n+\t\t      BRANCH_TRACK_NEVER, dry_run);\n+\tif (dry_run)\n+\t\treturn;\n+\t/*\n+\t * NEEDSWORK If tracking was set up in the superproject but not the\n+\t * submodule, users might expect \"git branch --recurse-submodules\" to\n+\t * fail or give a warning, but this is not yet implemented because it is\n+\t * tedious to determine whether or not tracking was set up in the\n+\t * superproject.\n+\t */\n+\tsetup_tracking(name, tracking_name, track, quiet, 0);\n+\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t\trepo_clear(submodule_entry_list.entries[i].repo);\n+\t}\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 75cefcdcbd..19ebc9253a 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -48,12 +48,35 @@ void setup_tracking(const char *new_ref, const char *orig_ref,\n  *   - track causes the new branch to be configured to merge the remote branch\n  *     that start_name is a tracking branch for (if any).\n  *\n+ *   - dry_run causes the branch to be validated but not created.\n+ *\n  */\n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track);\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n \n+/*\n+ * Creates a new branch in repository and its submodules (and its\n+ * submodules, recursively). Besides these exceptions, the parameters\n+ * function identically to create_branch():\n+ *\n+ * - start_name is the name of the ref, in repository r, that the new\n+ *   branch should start from. In submodules, branches will start from\n+ *   the respective gitlink commit ids in start_name's tree.\n+ *\n+ * - tracking_name is the name used of the ref that will be used to set\n+ *   up tracking, e.g. origin/main. This is propagated to submodules so\n+ *   that tracking information will appear as if the branch branched off\n+ *   tracking_name instead of start_name (which is a plain commit id for\n+ *   submodules). If omitted, start_name is used for tracking (just like\n+ *   create_branch()).\n+ *\n+ */\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run);\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 83b8016b38..bd3a7e566a 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -38,6 +38,8 @@ static const char * const builtin_branch_usage[] = {\n \n static const char *head;\n static struct object_id head_oid;\n+static int recurse_submodules = 0;\n+static int submodule_propagate_branches = 0;\n \n static int branch_use_color = -1;\n static char branch_colors[][COLOR_MAXLEN] = {\n@@ -99,6 +101,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\t\treturn config_error_nonbool(var);\n \t\treturn color_parse(value, branch_colors[slot]);\n \t}\n+\tif (!strcmp(var, \"submodule.recurse\")) {\n+\t\trecurse_submodules = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcasecmp(var, \"submodule.propagateBranches\")) {\n+\t\tsubmodule_propagate_branches = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_color_default_config(var, value, cb);\n }\n \n@@ -621,7 +632,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    unset_upstream = 0, show_current = 0, edit_description = 0;\n \tint noncreate_actions = 0;\n \t/* possible options */\n-\tint reflog = 0, quiet = 0, icase = 0;\n+\tint reflog = 0, quiet = 0, icase = 0, recurse_submodules_explicit = 0;\n \tconst char *new_upstream = NULL;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n@@ -671,6 +682,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n+\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT_END(),\n \t};\n@@ -713,6 +725,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n+\tif (recurse_submodules_explicit) {\n+\t\tif (!submodule_propagate_branches)\n+\t\t\tdie(_(\"branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\"));\n+\t\tif (noncreate_actions)\n+\t\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n+\t}\n+\n+\trecurse_submodules =\n+\t\t(recurse_submodules || recurse_submodules_explicit) &&\n+\t\tsubmodule_propagate_branches;\n+\n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n \tfilter.ignore_case = icase;\n@@ -849,6 +872,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n+\t\tconst char *branch_name = argv[0];\n+\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n+\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n@@ -856,10 +882,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (track == BRANCH_TRACK_OVERRIDE)\n \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n \n-\t\tcreate_branch(the_repository,\n-\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track);\n-\n+\t\tif (recurse_submodules) {\n+\t\t\tcreate_branches_recursively(the_repository, branch_name,\n+\t\t\t\t\t\t    start_name, NULL, force,\n+\t\t\t\t\t\t    reflog, quiet, track, 0);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n+\t\t\t      reflog, quiet, track, 0);\n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex cbf73b8c9f..c927a175a3 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -893,7 +893,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t      opts->new_branch_force ? 1 : 0,\n \t\t\t\t      opts->new_branch_log,\n \t\t\t\t      opts->quiet,\n-\t\t\t\t      opts->track);\n+\t\t\t\t      opts->track,\n+\t\t\t\t      0);\n \t\tnew_branch_info->name = opts->new_branch;\n \t\tsetup_branch_path(new_branch_info);\n \t}\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex e630f0c730..44b6283c08 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -20,6 +20,7 @@\n #include \"diff.h\"\n #include \"object-store.h\"\n #include \"advice.h\"\n+#include \"branch.h\"\n \n #define OPT_QUIET (1 << 0)\n #define OPT_CACHED (1 << 1)\n@@ -2983,6 +2984,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n \treturn !!ret;\n }\n \n+static int module_create_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tenum branch_track track;\n+\tint quiet = 0, force = 0, reflog = 0, dry_run = 0;\n+\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n+\t\tOPT__FORCE(&force, N_(\"force creation\"), 0),\n+\t\tOPT_BOOL(0, \"create-reflog\", &reflog,\n+\t\t\t N_(\"create the branch's reflog\")),\n+\t\tOPT_SET_INT('t', \"track\", &track,\n+\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\t    BRANCH_TRACK_EXPLICIT),\n+\t\tOPT__DRY_RUN(&dry_run,\n+\t\t\t     N_(\"show whether the branch would be created\")),\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>\"),\n+\t\tNULL\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\ttrack = git_branch_track;\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\n+\tif (argc != 3)\n+\t\tusage_with_options(usage, options);\n+\n+\tif (!quiet && !dry_run)\n+\t\tprintf_ln(_(\"creating branch '%s'\"), argv[0]);\n+\n+\tcreate_branches_recursively(the_repository, argv[0], argv[1], argv[2],\n+\t\t\t\t    force, reflog, quiet, track, dry_run);\n+\treturn 0;\n+}\n struct add_data {\n \tconst char *prefix;\n \tconst char *branch;\n@@ -3389,6 +3426,7 @@ static struct cmd_struct commands[] = {\n \t{\"config\", module_config, 0},\n \t{\"set-url\", module_set_url, 0},\n \t{\"set-branch\", module_set_branch, 0},\n+\t{\"create-branch\", module_create_branch, 0},\n };\n \n int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f95344028b..f246359d63 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -7,6 +7,7 @@\n #include \"strbuf.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n+#include \"tree-walk.h\"\n \n /*\n  * submodule cache lookup structure\n@@ -726,6 +727,40 @@ const struct submodule *submodule_from_path(struct repository *r,\n \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *out)\n+{\n+\tstruct tree_desc tree;\n+\tstruct submodule_tree_entry *st_entry;\n+\tstruct name_entry *name_entry;\n+\n+\tname_entry = xmalloc(sizeof(*name_entry));\n+\n+\tCALLOC_ARRAY(out->entries, 0);\n+\tout->entry_nr = 0;\n+\tout->entry_alloc = 0;\n+\n+\tfill_tree_descriptor(r, &tree, treeish_name);\n+\twhile (tree_entry(&tree, name_entry)) {\n+\t\tif (!S_ISGITLINK(name_entry->mode) || !is_tree_submodule_active(r, treeish_name, name_entry->path)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tst_entry = xmalloc(sizeof(*st_entry));\n+\t\tst_entry->name_entry = name_entry;\n+\t\tst_entry->submodule =\n+\t\t\tsubmodule_from_path(r, treeish_name, name_entry->path);\n+\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n+\t\tif (repo_submodule_init(st_entry->repo, r, name_entry->path,\n+\t\t\t\t\ttreeish_name))\n+\t\t\tFREE_AND_NULL(st_entry->repo);\n+\n+\t\tALLOC_GROW(out->entries, out->entry_nr + 1, out->entry_alloc);\n+\t\tout->entries[out->entry_nr++] = *st_entry;\n+\t}\n+}\n+\n void submodule_free(struct repository *r)\n {\n \tif (r->submodule_cache)\ndiff --git a/submodule-config.h b/submodule-config.h\nindex 65875b94ea..56c12af03f 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"submodule.h\"\n #include \"strbuf.h\"\n+#include \"tree-walk.h\"\n \n /**\n  * The submodule config cache API allows to read submodule\n@@ -101,4 +102,38 @@ int check_submodule_name(const char *name);\n void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n void update_clone_config_from_gitmodules(int *max_jobs);\n \n+/*\n+ * Submodule entry that contains relevant information about a\n+ * submodule in a tree.\n+ */\n+struct submodule_tree_entry {\n+\t/* The submodule's tree entry. */\n+\tstruct name_entry *name_entry;\n+\t/*\n+\t * A struct repository corresponding to the submodule. May be\n+\t * NULL if the submodule has not been updated.\n+\t */\n+\tstruct repository *repo;\n+\t/*\n+\t * A struct submodule containing the submodule config in the\n+\t * tree's .gitmodules.\n+\t */\n+\tconst struct submodule *submodule;\n+};\n+\n+struct submodule_entry_list {\n+\tstruct submodule_tree_entry *entries;\n+\tint entry_nr;\n+\tint entry_alloc;\n+};\n+\n+/**\n+ * Given a treeish, return all submodules in the tree. This only reads\n+ * one level of the tree, so it will not return nested submodules;\n+ * callers that require nested submodules are expected to handle the\n+ * recursion themselves.\n+ */\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *ret);\n #endif /* SUBMODULE_CONFIG_H */\ndiff --git a/submodule.c b/submodule.c\nindex c689070524..5ace18a7d9 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -267,7 +267,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n  * ie, the config looks like: \"[submodule] active\\n\".\n  * Since that is an invalid pathspec, we should inform the user.\n  */\n-int is_submodule_active(struct repository *repo, const char *path)\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path)\n {\n \tint ret = 0;\n \tchar *key = NULL;\n@@ -275,7 +277,7 @@ int is_submodule_active(struct repository *repo, const char *path)\n \tconst struct string_list *sl;\n \tconst struct submodule *module;\n \n-\tmodule = submodule_from_path(repo, null_oid(), path);\n+\tmodule = submodule_from_path(repo, treeish_name, path);\n \n \t/* early return if there isn't a path->module mapping */\n \tif (!module)\n@@ -317,6 +319,11 @@ int is_submodule_active(struct repository *repo, const char *path)\n \treturn ret;\n }\n \n+int is_submodule_active(struct repository *repo, const char *path)\n+{\n+\treturn is_tree_submodule_active(repo, null_oid(), path);\n+}\n+\n int is_submodule_populated_gently(const char *path, int *return_error_code)\n {\n \tint ret = 0;\ndiff --git a/submodule.h b/submodule.h\nindex 6bd2c99fd9..784ceffc0e 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb);\n struct option;\n int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n \t\t\t\t\t\t     const char *arg, int unset);\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path);\n int is_submodule_active(struct repository *repo, const char *path);\n /*\n  * Determine if a submodule has been populated at a given 'path' by checking if\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nnew file mode 100755\nindex 0000000000..2dd0e2b01f\n--- /dev/null\n+++ b/t/t3207-branch-submodule.sh\n@@ -0,0 +1,284 @@\n+#!/bin/sh\n+\n+test_description='git branch submodule tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup superproject and submodule' '\n+\tgit init super &&\n+\ttest_commit foo &&\n+\tgit init sub-sub-upstream &&\n+\ttest_commit -C sub-sub-upstream foo &&\n+\tgit init sub-upstream &&\n+\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n+\tgit -C sub-upstream commit -m \"add submodule\" &&\n+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n+\tgit -C super commit -m \"add submodule\" &&\n+\tgit -C super config submodule.propagateBranches true &&\n+\tgit -C super/sub submodule update --init\n+'\n+\n+CLEANUP_SCRIPT_PATH=\"$TRASH_DIRECTORY/cleanup_branches.sh\"\n+\n+cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n+\t#!/bin/sh\n+\n+\tsuper_dir=\"$1\"\n+\tshift\n+\t(\n+\t\tcd \"$super_dir\" &&\n+\t\tgit checkout main &&\n+\t\tfor branch_name in \"$@\"; do\n+\t\t\tgit branch -D \"$branch_name\"\n+\t\t\tgit submodule foreach \"$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true\"\n+\t\tdone\n+\t)\n+EOF\n+chmod +x \"$CLEANUP_SCRIPT_PATH\"\n+\n+cleanup_branches() {\n+\tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n+} >/dev/null 2>/dev/null\n+\n+# Test the argument parsing\n+test_expect_success '--recurse-submodules should create branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a &&\n+\t\tgit -C sub/sub-sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\techo \"fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\" >expected &&\n+\t\ttest_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should fail when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n+\t\t# Assert that the branches were not deleted\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should respect submodule.recurse when creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c submodule.recurse=true branch branch-a &&\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should ignore submodule.recurse when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit -c submodule.recurse=true branch -D branch-a &&\n+\t\ttest_must_fail git rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+# Test branch creation behavior\n+test_expect_success 'should create branches based off commit id in superproject' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit checkout --recurse-submodules branch-a &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\t# Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a\n+\t\tgit -C sub checkout branch-a &&\n+\t\ttest_commit -C sub bar &&\n+\t\t# Create a new branch-b branch with start-point=branch-a\n+\t\tgit branch --recurse-submodules branch-b branch-a &&\n+\t\tgit rev-parse branch-b &&\n+\t\tgit -C sub rev-parse branch-b >actual &&\n+\t\t# Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should not create any branches if branch is not valid for all repos' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub branch branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_must_fail git rev-parse branch-a &&\n+\n+\t\tcat >expected <<-EOF &&\n+\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n+\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branches if branch exists and --force is given' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\ttest_commit -C sub baz &&\n+\t\tgit -C sub branch branch-a HEAD~1 &&\n+\t\tgit branch --recurse-submodules --force branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\t# assert that sub:branch-a was moved\n+\t\tgit -C sub rev-parse branch-a >actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branch when submodule is not in HEAD .gitmodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\tgit -C sub2 submodule update --init &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\" &&\n+\t\tgit checkout branch-a &&\n+\t\tgit branch --recurse-submodules branch-c branch-b &&\n+\t\tgit rev-parse branch-c &&\n+\t\tgit -C sub rev-parse branch-c &&\n+\t\tgit checkout --recurse-submodules branch-c &&\n+\t\tgit -C sub2 rev-parse branch-c\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with track=always' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with explicit track' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --track --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should not set up unnecessary tracking of local branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_expect_success 'should not create branches in inactive submodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\ttest_config -C super submodule.sub.active false &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\ttest_must_fail git -C sub branch-a\n+\t)\n+'\n+\n+test_expect_success 'setup remote-tracking tests' '\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\"\n+\t) &&\n+\tgit clone --branch main --recurse-submodules super super-clone &&\n+\tgit -C super-clone config submodule.propagateBranches true\n+'\n+\n+test_expect_success 'should not create branch when submodule is not in .git/modules' '\n+\t# The cleanup needs to delete sub2 separately because main does not have sub2\n+\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n+\t\tgit -C super-clone/sub2/sub-sub branch -D branch-b && \\\n+\t\tcleanup_branches super-clone branch-a branch-b\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\t# This should succeed because super-clone has sub.\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\t# This should fail because super-clone does not have sub2.\n+\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n+\t\tcat >expected <<-EOF &&\n+\t\thint: You may try updating the submodules using ${SQ}git checkout origin/branch-b && git submodule update --init${SQ}\n+\t\tfatal: submodule ${SQ}sub2${SQ}: unable to find submodule\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\t\ttest_must_fail git rev-parse branch-b &&\n+\t\ttest_must_fail git -C sub rev-parse branch-b &&\n+\t\t# User can fix themselves by initializing the submodule\n+\t\tgit checkout origin/branch-b &&\n+\t\tgit submodule update --init --recursive &&\n+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of remote-tracking branches' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\t# \"origin/branch-a\" does not exist for \"sub\", but it matches the refspec\n+\t\t# so tracking should be set up\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.merge)\" = refs/heads/branch-a\n+\t)\n+'\n+\n+test_expect_success 'should not fail when unable to set up tracking in submodule' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a && \\\n+\t\tgit -C super-clone remote rename ex-origin origin\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit remote rename origin ex-origin &&\n+\t\tgit branch --recurse-submodules branch-a ex-origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = ex-origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_done\n-- \n2.33.GIT\n\n"},{"id":"443211","messageId":"xmqqbl1tcptq.fsf@gitster.g","threadId":"56962","inReplyTo":"20211206215528.97050-2-chooglen@google.com","subject":"Re: [PATCH v2 1/3] branch: move --set-upstream-to behavior to setup_tracking()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-06T22:48:33Z","receivedAt":"2021-12-06T22:48:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> As Jonathan noted in v1, the diff is quite large. I could shrink this\n> by forward-declaring setup_tracking() so that the function definitions\n> are in the same order; let me know if that would be preferred.\n\nIf you are not making any changes to setup_tracking() and just\nmoving, then the patch size inflated by the move is OK.\n\nIf you are moving and changing at the same time, well, that would\nmake it harder to read what is going on in the patch, so you want to\nfind a way to avoid it.  Splitting it the pure move into a separate\npatch or use of forward-declaration may be good ways to do so.\n\n\n\n\n>  branch.c          | 195 ++++++++++++++++++++++++++--------------------\n>  branch.h          |  13 +++-\n>  builtin/branch.c  |   7 +-\n>  t/t3200-branch.sh |  17 ++++\n>  4 files changed, 139 insertions(+), 93 deletions(-)\n>\n> diff --git a/branch.c b/branch.c\n> index 07a46430b3..a635a60f8b 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -126,43 +126,6 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \treturn -1;\n>  }\n>  \n> -/*\n> - * This is called when new_ref is branched off of orig_ref, and tries\n> - * to infer the settings for branch.<new_ref>.{remote,merge} from the\n> - * config.\n> - */\n> -static void setup_tracking(const char *new_ref, const char *orig_ref,\n> -\t\t\t   enum branch_track track, int quiet)\n> -{\n> -\tstruct tracking tracking;\n> -\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n> -\n> -\tmemset(&tracking, 0, sizeof(tracking));\n> -\ttracking.spec.dst = (char *)orig_ref;\n> -\tif (for_each_remote(find_tracked_branch, &tracking))\n> -\t\treturn;\n> -\n> -\tif (!tracking.matches)\n> -\t\tswitch (track) {\n> -\t\tcase BRANCH_TRACK_ALWAYS:\n> -\t\tcase BRANCH_TRACK_EXPLICIT:\n> -\t\tcase BRANCH_TRACK_OVERRIDE:\n> -\t\t\tbreak;\n> -\t\tdefault:\n> -\t\t\treturn;\n> -\t\t}\n> -\n> -\tif (tracking.matches > 1)\n> -\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n> -\t\t    orig_ref);\n> -\n> -\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n> -\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n> -\t\texit(-1);\n> -\n> -\tfree(tracking.src);\n> -}\n> -\n>  int read_branch_desc(struct strbuf *buf, const char *branch_name)\n>  {\n>  \tchar *v = NULL;\n> @@ -243,33 +206,35 @@ N_(\"\\n\"\n>  \"will track its remote counterpart, you may want to use\\n\"\n>  \"\\\"git push -u\\\" to set the upstream config as you push.\");\n>  \n> -void create_branch(struct repository *r,\n> -\t\t   const char *name, const char *start_name,\n> -\t\t   int force, int clobber_head_ok, int reflog,\n> -\t\t   int quiet, enum branch_track track)\n> +/**\n> + * Validates whether a ref is a valid starting point for a branch, where:\n> + *\n> + *   - r is the repository to validate the branch for\n> + *\n> + *   - start_name is the ref that we would like to test. This is\n> + *     expanded with DWIM and assigned to real_ref.\n> + *\n> + *   - track is the tracking mode of the new branch. If tracking is\n> + *     explicitly requested, start_name must be a branch (because\n> + *     otherwise start_name cannot be tracked)\n> + *\n> + *   - oid is an out parameter containing the object_id of start_name\n> + *\n> + *   - real_ref is an out parameter containing the full, 'real' form of\n> + *     start_name e.g. refs/heads/main instead of main\n> + *\n> + */\n\nGood description that will help reviewers and future developers.\nVery much appreciated.\n\n> +static void validate_branch_start(struct repository *r, const char *start_name,\n> +\t\t\t\t  enum branch_track track,\n> +\t\t\t\t  struct object_id *oid, char **real_ref)\n>  {\n>  \tstruct commit *commit;\n> -\tstruct object_id oid;\n> -\tchar *real_ref;\n> -\tstruct strbuf ref = STRBUF_INIT;\n> -\tint forcing = 0;\n> -\tint dont_change_ref = 0;\n>  \tint explicit_tracking = 0;\n>  \n>  \tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n>  \t\texplicit_tracking = 1;\n>  \n> -\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n> -\t    ? validate_branchname(name, &ref)\n> -\t    : validate_new_branchname(name, &ref, force)) {\n> -\t\tif (!force)\n> -\t\t\tdont_change_ref = 1;\n> -\t\telse\n> -\t\t\tforcing = 1;\n> -\t}\n> -\n> -\treal_ref = NULL;\n> -\tif (get_oid_mb(start_name, &oid)) {\n> +\tif (repo_get_oid_mb(r, start_name, oid)) {\n>  \t\tif (explicit_tracking) {\n>  \t\t\tif (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {\n>  \t\t\t\terror(_(upstream_missing), start_name);\n\nThe post context continues with:\n\n\t\t\t\tadvise(_(upstream_advice));\n\t\t\t\texit(1);\n\t\t\t}\n\t\t\tdie(_(upstream_missing), start_name);\n\nThis is not a problem with this patch, and it should not be fixed as\npart of this series, but since I noticed it, I'll mention it as a\nleftover low-hanging fruit to be fixed after the dust settles.  The\nexit(1) looks wrong.  We should exit with 128 just like die() does.\nIssuing of an advice message should not affect the exit code.\n\n> +void setup_tracking(const char *new_ref, const char *orig_ref,\n> +\t\t\t   enum branch_track track, int quiet, int expand_orig)\n\nIt is unclear what expand_orig option is supposed to do and how it\nwould help the caller.  Perhaps a comment before the function is in\norder (the comment in branch.h before the declaration of this\nfunction does not make it clear, either).\n\n> +{\n> +\tstruct tracking tracking;\n> +\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n> +\tchar *full_orig_ref;\n> +\tstruct object_id unused_oid;\n> +\n> +\tmemset(&tracking, 0, sizeof(tracking));\n> +\tif (expand_orig)\n> +\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n\nSo, the idea is, because we are setting up a new_ref to \"track\"\norig_ref, we may be better off pretending that we are \"creating a\nnew branch from the orig_ref and tracking it\", so that orig_ref that\nis not something we can track will be caught with the same logic?\n\nThis will cause full_orig_ref to start with \"refs/heads/\" or\n\"refs/remotes/\" if 'track' is something that requires tracking.\n\n> +\telse\n> +\t\tfull_orig_ref = xstrdup(orig_ref);\n\nEven though the variable claims to be FULL orig_ref, when this side\nof if/else is taken, nobody guarantees that full_orig_ref is in fact\na full ref, or merely the name of the branch, no?  Would that cause\nproblems later?\n\n> +\ttracking.spec.dst = full_orig_ref;\n> +\tif (for_each_remote(find_tracked_branch, &tracking))\n> +\t\tgoto cleanup;\n> +\n> +\tif (!tracking.matches)\n> +\t\tswitch (track) {\n> +\t\tcase BRANCH_TRACK_ALWAYS:\n> +\t\tcase BRANCH_TRACK_EXPLICIT:\n> +\t\tcase BRANCH_TRACK_OVERRIDE:\n> +\t\t\tbreak;\n> +\t\tdefault:\n> +\t\t\tgoto cleanup;\n> +\t\t}\n> +\n> +\tif (tracking.matches > 1)\n> +\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n> +\t\t    full_orig_ref);\n\nWhat's the next step for the user to take, after seeing this message?\nDo we have the necessary info readily available to help them at this\npoint in tracking.* structure (e.g. \"it could be following X or Y and\nwe cannot decide between the two for you\"), or have we discarded the\ninformation already?\n\nIf tracking.matches == 0, because we broke out of the switch() for\nsome values of track, we will make this install_branch_config()\nusing members of the tracking structure, which is a bit unnerving.\n\n> +\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n> +\t\t\t      tracking.src ? tracking.src : full_orig_ref) < 0)\n\nBut tracking.src==NULL is substituted with full_orig_ref, so as long\nas the value in that variable is sensible, we would probably be ok\non the 4th parameter.  I am not sure who set tracking.remote or if\nit is always set to a sensible value.  Especially when tracking.matches\nis zero.\n\n> +\t\texit(-1);\n\nDon't exit with a negative value.\n\n> +cleanup:\n> +\tfree(tracking.src);\n> +\tfree(full_orig_ref);\n> +}\n\nI'll stop here for now.\n\nThanks.\n\n"},{"id":"443213","messageId":"xmqq1r2pcnyw.fsf@gitster.g","threadId":"56962","inReplyTo":"20211206215528.97050-2-chooglen@google.com","subject":"Re: [PATCH v2 1/3] branch: move --set-upstream-to behavior to setup_tracking()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-06T23:28:39Z","receivedAt":"2021-12-06T23:28:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> +void setup_tracking(const char *new_ref, const char *orig_ref,\n> +\t\t\t   enum branch_track track, int quiet, int expand_orig)\n> +{\n> +\tstruct tracking tracking;\n> +\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n> +\tchar *full_orig_ref;\n> +\tstruct object_id unused_oid;\n> +\n> +\tmemset(&tracking, 0, sizeof(tracking));\n> +\tif (expand_orig)\n> +\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n> +\telse\n> +\t\tfull_orig_ref = xstrdup(orig_ref);\n> +\n> +\ttracking.spec.dst = full_orig_ref;\n> +\tif (for_each_remote(find_tracked_branch, &tracking))\n> +\t\tgoto cleanup;\n> +\n> +\tif (!tracking.matches)\n> +\t\tswitch (track) {\n> +\t\tcase BRANCH_TRACK_ALWAYS:\n> +\t\tcase BRANCH_TRACK_EXPLICIT:\n> +\t\tcase BRANCH_TRACK_OVERRIDE:\n> +\t\t\tbreak;\n\nThis heavily conflicts with what another topic \"inherit tracking\ninfo from the other branch\" wants to do to this function.  What's \nthe status of that topic, by the way?  Should we block this one\nwaiting for the other, or the other way around?\n\nThanks.\n\n\n\n"},{"id":"443486","messageId":"kl6l8rwvaury.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"xmqq1r2pcnyw.fsf@gitster.g","subject":"Re: [PATCH v2 1/3] branch: move --set-upstream-to behavior to setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-08T17:09:05Z","receivedAt":"2021-12-08T17:09:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> +void setup_tracking(const char *new_ref, const char *orig_ref,\n>> +\t\t\t   enum branch_track track, int quiet, int expand_orig)\n>> +{\n>> +\tstruct tracking tracking;\n>> +\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n>> +\tchar *full_orig_ref;\n>> +\tstruct object_id unused_oid;\n>> +\n>> +\tmemset(&tracking, 0, sizeof(tracking));\n>> +\tif (expand_orig)\n>> +\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n>> +\telse\n>> +\t\tfull_orig_ref = xstrdup(orig_ref);\n>> +\n>> +\ttracking.spec.dst = full_orig_ref;\n>> +\tif (for_each_remote(find_tracked_branch, &tracking))\n>> +\t\tgoto cleanup;\n>> +\n>> +\tif (!tracking.matches)\n>> +\t\tswitch (track) {\n>> +\t\tcase BRANCH_TRACK_ALWAYS:\n>> +\t\tcase BRANCH_TRACK_EXPLICIT:\n>> +\t\tcase BRANCH_TRACK_OVERRIDE:\n>> +\t\t\tbreak;\n>\n> This heavily conflicts with what another topic \"inherit tracking\n> info from the other branch\" wants to do to this function.  What's \n> the status of that topic, by the way?  Should we block this one\n> waiting for the other, or the other way around?\n>\n> Thanks.\n\nAs mentioned in [1], I plan to rebase this series on top of the \"inherit\ntracking info from the other branch\" series.\n\nI'll send a re-roll soon, thanks!\n\n[1] https://lore.kernel.org/git/kl6lbl1rauw3.fsf@chooglen-macbookpro.roam.corp.google.com/\n"},{"id":"443495","messageId":"kl6l5yrzaq5z.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"xmqqbl1tcptq.fsf@gitster.g","subject":"Re: [PATCH v2 1/3] branch: move --set-upstream-to behavior to setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-08T18:48:40Z","receivedAt":"2021-12-08T18:48:44Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> As Jonathan noted in v1, the diff is quite large. I could shrink this\n>> by forward-declaring setup_tracking() so that the function definitions\n>> are in the same order; let me know if that would be preferred.\n>\n> If you are not making any changes to setup_tracking() and just\n> moving, then the patch size inflated by the move is OK.\n>\n> If you are moving and changing at the same time, well, that would\n> make it harder to read what is going on in the patch, so you want to\n> find a way to avoid it.  Splitting it the pure move into a separate\n> patch or use of forward-declaration may be good ways to do so.\n\nThanks for the advice! I'll use one of these approaches.\n\n> The post context continues with:\n>\n> \t\t\t\tadvise(_(upstream_advice));\n> \t\t\t\texit(1);\n> \t\t\t}\n> \t\t\tdie(_(upstream_missing), start_name);\n>\n> This is not a problem with this patch, and it should not be fixed as\n> part of this series, but since I noticed it, I'll mention it as a\n> leftover low-hanging fruit to be fixed after the dust settles.  The\n> exit(1) looks wrong.  We should exit with 128 just like die() does.\n> Issuing of an advice message should not affect the exit code.\n\nI'll include an optional cleanup patch to address this and the exit(-1).\n\n>> +void setup_tracking(const char *new_ref, const char *orig_ref,\n>> +\t\t\t   enum branch_track track, int quiet, int expand_orig)\n>\n> It is unclear what expand_orig option is supposed to do and how it\n> would help the caller.  Perhaps a comment before the function is in\n> order (the comment in branch.h before the declaration of this\n> function does not make it clear, either).\n\nAh, I will add extra clarification.\n\nThis boolean parameter controls whether or not we should validate + DWIM\norig_ref. This is a performance optimization to avoid expanding an\norig_ref that has already been expanded (e.g. because create_branch()\nhas already expanded it).\n\nBut, as you point out later on, this may not be a good idea.\n\n>> +{\n>> +\tstruct tracking tracking;\n>> +\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n>> +\tchar *full_orig_ref;\n>> +\tstruct object_id unused_oid;\n>> +\n>> +\tmemset(&tracking, 0, sizeof(tracking));\n>> +\tif (expand_orig)\n>> +\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n>\n> So, the idea is, because we are setting up a new_ref to \"track\"\n> orig_ref, we may be better off pretending that we are \"creating a\n> new branch from the orig_ref and tracking it\", so that orig_ref that\n> is not something we can track will be caught with the same logic?\n>\n> This will cause full_orig_ref to start with \"refs/heads/\" or\n> \"refs/remotes/\" if 'track' is something that requires tracking.\n\nYes.\n\n>> +\telse\n>> +\t\tfull_orig_ref = xstrdup(orig_ref);\n>\n> Even though the variable claims to be FULL orig_ref, when this side\n> of if/else is taken, nobody guarantees that full_orig_ref is in fact\n> a full ref, or merely the name of the branch, no?  Would that cause\n> problems later?\n\nYes, the assumption is that the caller has already done the work of\nmaking sure that orig_ref has been validated and expanded into its full\nform. As mentioned earlier, this is purely a performance optimzation,\nbut it is not a very safe one because it requires the caller to pick the\ncorrect value for expand_orig.\n\nI chose this approach because I anticipate that the only callers of\nsetup_tracking() will be create_branch() (which always wants expand_orig\n= 0) and cmd_branch() (which always wants expand_orig = 1), so the right\ncourse of action is clear for now. I don't think setup_tracking() will\nbe useful to anyone else, but expand_orig is a potential sharp edge for\nnew callers.\n\nMy next-preferred option would be to remove \"expand_orig\" and always\ncall validate_branch_start(). We might waste a few cycles sometimes, but\nthe function becomes impossible to misuse.\n\n>> +\ttracking.spec.dst = full_orig_ref;\n>> +\tif (for_each_remote(find_tracked_branch, &tracking))\n>> +\t\tgoto cleanup;\n>> +\n>> +\tif (!tracking.matches)\n>> +\t\tswitch (track) {\n>> +\t\tcase BRANCH_TRACK_ALWAYS:\n>> +\t\tcase BRANCH_TRACK_EXPLICIT:\n>> +\t\tcase BRANCH_TRACK_OVERRIDE:\n>> +\t\t\tbreak;\n>> +\t\tdefault:\n>> +\t\t\tgoto cleanup;\n>> +\t\t}\n>> +\n>> +\tif (tracking.matches > 1)\n>> +\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n>> +\t\t    full_orig_ref);\n>\n> What's the next step for the user to take, after seeing this message?\n> Do we have the necessary info readily available to help them at this\n> point in tracking.* structure (e.g. \"it could be following X or Y and\n> we cannot decide between the two for you\"), or have we discarded the\n> information already?\n\nThis information is discarded in expand_ref(). From the function\nsignature:\n\n  int expand_ref(struct repository *repo, const char *str, int len,\n\t       struct object_id *oid, char **ref)\n\nThe return value is the number of matched refs and \"ref\" is an out\nparameter containing the first matched ref.\n\n> If tracking.matches == 0, because we broke out of the switch() for\n> some values of track, we will make this install_branch_config()\n> using members of the tracking structure, which is a bit unnerving.\n>\n>> +\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n>> +\t\t\t      tracking.src ? tracking.src : full_orig_ref) < 0)\n>\n> But tracking.src==NULL is substituted with full_orig_ref, so as long\n> as the value in that variable is sensible, we would probably be ok\n> on the 4th parameter.\n\nWhen tracking.matches is zero, the assumption is that\ninstall_branch_config() should set up tracking based off a local branch.\nBy default, we assume that users only want tracking for remote-tracking\nbranches, so only track local branches if we are confident that the user\nwants this, aka track = BRANCH_TRACK_{EXPLICIT,ALWAYS,OVERRIDE} (and\nsoon, BRANCH_TRACK_INHERIT).\n\n> I am not sure who set tracking.remote or if it is always set to a\n> sensible value. Especially when tracking.matches is zero.\n\ntracking.matches == 0 should imply that tracking.remote == NULL, which\ngives us the expected behavior of tracking a local branch in\ninstall_branch_config().\n\nThis is also a bit too implicit for my tastes, but I don't think this is\nthe time for such a refactor :)\n"},{"id":"443661","messageId":"20211209184928.71413-1-chooglen@google.com","threadId":"56962","inReplyTo":"20211206215528.97050-1-chooglen@google.com","subject":"[PATCH v3 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T18:49:23Z","receivedAt":"2021-12-09T18:49:58Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Submodule branching RFC:\nhttps://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/\n\nOriginal Submodule UX RFC/Discussion:\nhttps://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n\nContributor Summit submodules Notes:\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/\n\nSubmodule UX overhaul updates:\nhttps://lore.kernel.org/git/?q=Submodule+UX+overhaul+update\n\nThis series implements branch --recurse-submodules as laid out in the\nSubmodule branching RFC (linked above). If there are concerns about the\nUX/behavior, I would appreciate feedback on the RFC thread as well :)\n\nThis series is based off js/branch-track-inherit.\n\nThis version is functionally identical to v2. I've only addressed\nfeedback around code organization, i.e. the the merge conflict with\njs/branch-track-inherit and making patch 1 easier to review. Thus, some\ndiscussions on [1] are still unaddressed.\n\nPatch 5 is an optional cleanup of the questionable exit codes that Junio\nfound [1]. I wasn't able to figure out the intent of the original\nauthors, so it is mostly a best-guess at the right exit code. It doesn't\ncause any tests to fail, but this wasn't covered by tests to begin with.\n\nChanges since v2\n* Rebase onto js/branch-track-inherit. This series should continue to be\n  the case going forward.\n* Patch 1 has a smaller diff because the introduction of\n  validate_branch_start() no longer changes the function order thanks to a\n  forward declaration. This artificial forward declaration is removed in a\n  patch 2 (which can just be squashed into patch 1).\n* Optional cleanup: fix questionable exit codes in patch 5.\n\nChanges since v1:\n* Move the functionality of \"git branch --dry-run\" into \"git submodule-helper create-branch --dry-run\"\n* Add more fields to the submodules_of_tree() struct to reduce the\n  number of allocations made by the caller [2]. Move this functionality\n  to patch 3 (formerly patch 4) and drop patch 1.\n* Make submodules_of_tree() ignore inactive submodules [3]\n* Structure the output of the submodules a bit better by adding prefixes\n  to the child process' output (instead of inconsistently indenting the\n  output).\n** I wasn't able to find a good way to interleave stdout/stderr\n   correctly, so a less-than-desirable workaround was to route the child\n   process output to stdout/stderr depending on the exit code.\n** Eventually, I would like to structure the output of submodules in a\n   report, as Ævar suggested [4]. But at this stage, I think that it's\n   better to spend time getting user feedback on the submodules\n   branching UX and it'll be easier to standardize the output when we've\n   implemented more of the UX :)\n\n[1] https://lore.kernel.org/git/xmqqbl1tcptq.fsf@gitster.g\n[2] https://lore.kernel.org/git/211123.86r1b7uoil.gmgdl@evledraar.gmail.com\n[3] https://lore.kernel.org/git/3ad3941c-de18-41bf-2e44-4238ae868d79@gmail.com\n[4] https://lore.kernel.org/git/211123.86v90juovj.gmgdl@evledraar.gmail.com\n\nGlen Choo (5):\n  branch: move --set-upstream-to behavior to setup_tracking()\n  branch: remove forward declaration of validate_branch_start()\n  builtin/branch: clean up action-picking logic in cmd_branch()\n  branch: add --recurse-submodules option for branch creation\n  branch.c: replace questionable exit() codes\n\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |   8 +\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 336 +++++++++++++++++++++--------\n branch.h                           |  44 +++-\n builtin/branch.c                   |  66 ++++--\n builtin/checkout.c                 |   3 +-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  35 +++\n submodule-config.h                 |  35 +++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3200-branch.sh                  |  17 ++\n t/t3207-branch-submodule.sh        | 284 ++++++++++++++++++++++++\n 15 files changed, 765 insertions(+), 120 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\nRange-diff against v2:\n1:  cc212dcd39 ! 1:  8241c0b51a branch: move --set-upstream-to behavior to setup_tracking()\n    @@ Commit message\n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n      ## branch.c ##\n    -@@ branch.c: int install_branch_config(int flag, const char *local, const char *origin, const\n    - \treturn -1;\n    +@@ branch.c: static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n    + \treturn 0;\n      }\n      \n     -/*\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n     - */\n     -static void setup_tracking(const char *new_ref, const char *orig_ref,\n     -\t\t\t   enum branch_track track, int quiet)\n    --{\n    --\tstruct tracking tracking;\n    --\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n    --\n    --\tmemset(&tracking, 0, sizeof(tracking));\n    ++static void validate_branch_start(struct repository *r, const char *start_name,\n    ++\t\t\t\t  enum branch_track track,\n    ++\t\t\t\t  struct object_id *oid, char **real_ref);\n    ++\n    ++void setup_tracking(const char *new_ref, const char *orig_ref,\n    ++\t\t\t   enum branch_track track, int quiet, int expand_orig)\n    + {\n    + \tstruct tracking tracking;\n    + \tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n    + \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n    ++\tchar *full_orig_ref;\n    ++\tstruct object_id unused_oid;\n    + \n    + \tmemset(&tracking, 0, sizeof(tracking));\n     -\ttracking.spec.dst = (char *)orig_ref;\n    --\tif (for_each_remote(find_tracked_branch, &tracking))\n    --\t\treturn;\n    --\n    --\tif (!tracking.matches)\n    --\t\tswitch (track) {\n    --\t\tcase BRANCH_TRACK_ALWAYS:\n    --\t\tcase BRANCH_TRACK_EXPLICIT:\n    --\t\tcase BRANCH_TRACK_OVERRIDE:\n    --\t\t\tbreak;\n    --\t\tdefault:\n    ++\tif (expand_orig)\n    ++\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n    ++\telse\n    ++\t\tfull_orig_ref = xstrdup(orig_ref);\n    ++\n    ++\ttracking.spec.dst = full_orig_ref;\n    + \ttracking.srcs = &tracking_srcs;\n    + \tif (track != BRANCH_TRACK_INHERIT)\n    + \t\tfor_each_remote(find_tracked_branch, &tracking);\n    +@@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n    + \t\tcase BRANCH_TRACK_OVERRIDE:\n    + \t\t\tbreak;\n    + \t\tdefault:\n     -\t\t\treturn;\n    --\t\t}\n    --\n    --\tif (tracking.matches > 1)\n    --\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n    ++\t\t\tgoto cleanup;\n    + \t\t}\n    + \n    + \tif (tracking.matches > 1)\n    + \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n     -\t\t    orig_ref);\n    --\n    --\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n    --\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n    --\t\texit(-1);\n    --\n    --\tfree(tracking.src);\n    --}\n    --\n    ++\t\t    full_orig_ref);\n    + \n    + \tif (tracking.srcs->nr < 1)\n    +-\t\tstring_list_append(tracking.srcs, orig_ref);\n    ++\t\tstring_list_append(tracking.srcs, full_orig_ref);\n    + \tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n    + \t\t\t      tracking.srcs) < 0)\n    + \t\texit(-1);\n    + \n    ++cleanup:\n    + \tstring_list_clear(tracking.srcs, 0);\n    ++\tfree(full_orig_ref);\n    + }\n    + \n      int read_branch_desc(struct strbuf *buf, const char *branch_name)\n    - {\n    - \tchar *v = NULL;\n     @@ branch.c: N_(\"\\n\"\n      \"will track its remote counterpart, you may want to use\\n\"\n      \"\\\"git push -u\\\" to set the upstream config as you push.\");\n    @@ branch.c: void create_branch(struct repository *r,\n     +\toidcpy(oid, &commit->object.oid);\n     +}\n     +\n    -+void setup_tracking(const char *new_ref, const char *orig_ref,\n    -+\t\t\t   enum branch_track track, int quiet, int expand_orig)\n    -+{\n    -+\tstruct tracking tracking;\n    -+\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n    -+\tchar *full_orig_ref;\n    -+\tstruct object_id unused_oid;\n    -+\n    -+\tmemset(&tracking, 0, sizeof(tracking));\n    -+\tif (expand_orig)\n    -+\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n    -+\telse\n    -+\t\tfull_orig_ref = xstrdup(orig_ref);\n    -+\n    -+\ttracking.spec.dst = full_orig_ref;\n    -+\tif (for_each_remote(find_tracked_branch, &tracking))\n    -+\t\tgoto cleanup;\n    -+\n    -+\tif (!tracking.matches)\n    -+\t\tswitch (track) {\n    -+\t\tcase BRANCH_TRACK_ALWAYS:\n    -+\t\tcase BRANCH_TRACK_EXPLICIT:\n    -+\t\tcase BRANCH_TRACK_OVERRIDE:\n    -+\t\t\tbreak;\n    -+\t\tdefault:\n    -+\t\t\tgoto cleanup;\n    -+\t\t}\n    -+\n    -+\tif (tracking.matches > 1)\n    -+\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n    -+\t\t    full_orig_ref);\n    -+\n    -+\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n    -+\t\t\t      tracking.src ? tracking.src : full_orig_ref) < 0)\n    -+\t\texit(-1);\n    -+\n    -+cleanup:\n    -+\tfree(tracking.src);\n    -+\tfree(full_orig_ref);\n    -+}\n    -+\n     +void create_branch(struct repository *r, const char *name,\n     +\t\t   const char *start_name, int force, int clobber_head_ok,\n     +\t\t   int reflog, int quiet, enum branch_track track)\n-:  ---------- > 2:  b74bcbaade branch: remove forward declaration of validate_branch_start()\n2:  320749cc82 = 3:  235173efc9 builtin/branch: clean up action-picking logic in cmd_branch()\n3:  c0441c6691 = 4:  3dabb8e2fa branch: add --recurse-submodules option for branch creation\n-:  ---------- > 5:  70fb03f882 branch.c: replace questionable exit() codes\n-- \n2.33.GIT\n\n"},{"id":"443662","messageId":"20211209184928.71413-2-chooglen@google.com","threadId":"56962","inReplyTo":"20211209184928.71413-1-chooglen@google.com","subject":"[PATCH v3 1/5] branch: move --set-upstream-to behavior to setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T18:49:24Z","receivedAt":"2021-12-09T18:50:00Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"This refactor is motivated by a desire to add a \"dry_run\" parameter to\ncreate_branch() that will validate whether or not a branch can be\ncreated without actually creating it - this behavior be used in a\nsubsequent commit that adds `git branch --recurse-submodules topic`.\n\nAdding \"dry_run\" is not obvious because create_branch() is also used to\nset tracking information without creating a branch, i.e. when using\n--set-upstream-to. This appears to be a leftover from 4fc5006676 (Add\nbranch --set-upstream, 2010-01-18), when --set-upstream would sometimes\ncreate a branch and sometimes update tracking information without\ncreating a branch. However, we no longer support --set-upstream, so it\nmakes more sense to set tracking information with another function, like\nsetup_tracking(), and use create_branch() only to create branches. When\nthis is done, it will be trivial to add \"dry_run\".\n\nDo this refactor by moving the branch validation and dwim logic from\ncreate_branch() into a new function, validate_branch_start(), and call\nit from setup_tracking(). Now that setup_tracking() can perform dwim and\ntracking setup without creating a branch, use it in `git branch\n--set-upstream-to` and remove unnecessary behavior from create_branch().\n\nSince there were none, add tests for creating a branch with `--force`.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c          | 147 +++++++++++++++++++++++++++-------------------\n branch.h          |  13 +++-\n builtin/branch.c  |   7 +--\n t/t3200-branch.sh |  17 ++++++\n 4 files changed, 117 insertions(+), 67 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 29e835d367..9429936734 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -209,20 +209,26 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n \treturn 0;\n }\n \n-/*\n- * This is called when new_ref is branched off of orig_ref, and tries\n- * to infer the settings for branch.<new_ref>.{remote,merge} from the\n- * config.\n- */\n-static void setup_tracking(const char *new_ref, const char *orig_ref,\n-\t\t\t   enum branch_track track, int quiet)\n+static void validate_branch_start(struct repository *r, const char *start_name,\n+\t\t\t\t  enum branch_track track,\n+\t\t\t\t  struct object_id *oid, char **real_ref);\n+\n+void setup_tracking(const char *new_ref, const char *orig_ref,\n+\t\t\t   enum branch_track track, int quiet, int expand_orig)\n {\n \tstruct tracking tracking;\n \tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n+\tchar *full_orig_ref;\n+\tstruct object_id unused_oid;\n \n \tmemset(&tracking, 0, sizeof(tracking));\n-\ttracking.spec.dst = (char *)orig_ref;\n+\tif (expand_orig)\n+\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n+\telse\n+\t\tfull_orig_ref = xstrdup(orig_ref);\n+\n+\ttracking.spec.dst = full_orig_ref;\n \ttracking.srcs = &tracking_srcs;\n \tif (track != BRANCH_TRACK_INHERIT)\n \t\tfor_each_remote(find_tracked_branch, &tracking);\n@@ -236,20 +242,22 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tcase BRANCH_TRACK_OVERRIDE:\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\treturn;\n+\t\t\tgoto cleanup;\n \t\t}\n \n \tif (tracking.matches > 1)\n \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n-\t\t    orig_ref);\n+\t\t    full_orig_ref);\n \n \tif (tracking.srcs->nr < 1)\n-\t\tstring_list_append(tracking.srcs, orig_ref);\n+\t\tstring_list_append(tracking.srcs, full_orig_ref);\n \tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n \t\t\t      tracking.srcs) < 0)\n \t\texit(-1);\n \n+cleanup:\n \tstring_list_clear(tracking.srcs, 0);\n+\tfree(full_orig_ref);\n }\n \n int read_branch_desc(struct strbuf *buf, const char *branch_name)\n@@ -332,33 +340,35 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+/**\n+ * Validates whether a ref is a valid starting point for a branch, where:\n+ *\n+ *   - r is the repository to validate the branch for\n+ *\n+ *   - start_name is the ref that we would like to test. This is\n+ *     expanded with DWIM and assigned to real_ref.\n+ *\n+ *   - track is the tracking mode of the new branch. If tracking is\n+ *     explicitly requested, start_name must be a branch (because\n+ *     otherwise start_name cannot be tracked)\n+ *\n+ *   - oid is an out parameter containing the object_id of start_name\n+ *\n+ *   - real_ref is an out parameter containing the full, 'real' form of\n+ *     start_name e.g. refs/heads/main instead of main\n+ *\n+ */\n+static void validate_branch_start(struct repository *r, const char *start_name,\n+\t\t\t\t  enum branch_track track,\n+\t\t\t\t  struct object_id *oid, char **real_ref)\n {\n \tstruct commit *commit;\n-\tstruct object_id oid;\n-\tchar *real_ref;\n-\tstruct strbuf ref = STRBUF_INIT;\n-\tint forcing = 0;\n-\tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n \n \tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n \t\texplicit_tracking = 1;\n \n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n-\t}\n-\n-\treal_ref = NULL;\n-\tif (get_oid_mb(start_name, &oid)) {\n+\tif (repo_get_oid_mb(r, start_name, oid)) {\n \t\tif (explicit_tracking) {\n \t\t\tif (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {\n \t\t\t\terror(_(upstream_missing), start_name);\n@@ -370,7 +380,8 @@ void create_branch(struct repository *r,\n \t\tdie(_(\"Not a valid object name: '%s'.\"), start_name);\n \t}\n \n-\tswitch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {\n+\tswitch (repo_dwim_ref(r, start_name, strlen(start_name), oid, real_ref,\n+\t\t\t      0)) {\n \tcase 0:\n \t\t/* Not branching from any existing branch */\n \t\tif (explicit_tracking)\n@@ -378,12 +389,12 @@ void create_branch(struct repository *r,\n \t\tbreak;\n \tcase 1:\n \t\t/* Unique completion -- good, only if it is a real branch */\n-\t\tif (!starts_with(real_ref, \"refs/heads/\") &&\n-\t\t    validate_remote_tracking_branch(real_ref)) {\n+\t\tif (!starts_with(*real_ref, \"refs/heads/\") &&\n+\t\t    validate_remote_tracking_branch(*real_ref)) {\n \t\t\tif (explicit_tracking)\n \t\t\t\tdie(_(upstream_not_branch), start_name);\n \t\t\telse\n-\t\t\t\tFREE_AND_NULL(real_ref);\n+\t\t\t\tFREE_AND_NULL(*real_ref);\n \t\t}\n \t\tbreak;\n \tdefault:\n@@ -391,37 +402,55 @@ void create_branch(struct repository *r,\n \t\tbreak;\n \t}\n \n-\tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n+\tif ((commit = lookup_commit_reference(r, oid)) == NULL)\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n-\toidcpy(&oid, &commit->object.oid);\n+\toidcpy(oid, &commit->object.oid);\n+}\n+\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track)\n+{\n+\tstruct object_id oid;\n+\tchar *real_ref;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tint forcing = 0;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *msg;\n+\n+\tif (clobber_head_ok && !force)\n+\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n+\n+\tif (clobber_head_ok ?\n+\t\t\t  validate_branchname(name, &ref) :\n+\t\t\t  validate_new_branchname(name, &ref, force)) {\n+\t\tforcing = 1;\n+\t}\n+\n+\tvalidate_branch_start(r, start_name, track, &oid, &real_ref);\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n-\tif (!dont_change_ref) {\n-\t\tstruct ref_transaction *transaction;\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\t\tchar *msg;\n-\n-\t\tif (forcing)\n-\t\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n-\t\telse\n-\t\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n-\n-\t\ttransaction = ref_transaction_begin(&err);\n-\t\tif (!transaction ||\n-\t\t    ref_transaction_update(transaction, ref.buf,\n-\t\t\t\t\t   &oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t   0, msg, &err) ||\n-\t\t    ref_transaction_commit(transaction, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\t\tref_transaction_free(transaction);\n-\t\tstrbuf_release(&err);\n-\t\tfree(msg);\n-\t}\n+\tif (forcing)\n+\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n+\telse\n+\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n+\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t\tref_transaction_update(transaction, ref.buf,\n+\t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n+\t\t\t\t\t0, msg, &err) ||\n+\t\tref_transaction_commit(transaction, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tfree(msg);\n \n \tif (real_ref && track)\n-\t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n+\t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet, 0);\n \n \tstrbuf_release(&ref);\n \tfree(real_ref);\ndiff --git a/branch.h b/branch.h\nindex 6484bda8a2..46951c446b 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -18,6 +18,15 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+/*\n+ * This sets the branch.<new_ref>.{remote,merge} config settings so that\n+ * branch 'new_ref' tracks 'orig_ref'. This is called when branches are\n+ * created, or when branch configs are updated (e.g. with\n+ * git branch --set-upstream-to).\n+ */\n+void setup_tracking(const char *new_ref, const char *orig_ref,\n+\t\t    enum branch_track track, int quiet, int expand_orig);\n+\n /*\n  * Creates a new branch, where:\n  *\n@@ -30,8 +39,8 @@ extern enum branch_track git_branch_track;\n  *\n  *   - force enables overwriting an existing (non-head) branch\n  *\n- *   - clobber_head_ok allows the currently checked out (hence existing)\n- *     branch to be overwritten; without 'force', it has no effect.\n+ *   - clobber_head_ok, when enabled with 'force', allows the currently\n+ *     checked out (head) branch to be overwritten\n  *\n  *   - reflog creates a reflog for the branch\n  *\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 81a29edb4a..e19aab5356 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -823,12 +823,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (!ref_exists(branch->refname))\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \n-\t\t/*\n-\t\t * create_branch takes care of setting up the tracking\n-\t\t * info and making sure new_upstream is correct\n-\t\t */\n-\t\tcreate_branch(the_repository, branch->name, new_upstream,\n-\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n+\t\tsetup_tracking(branch->name, new_upstream, BRANCH_TRACK_OVERRIDE, quiet, 1);\n \t} else if (unset_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n \t\tstruct strbuf buf = STRBUF_INIT;\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 267a624671..18e285a876 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n \tgit branch abc && test_path_is_file .git/refs/heads/abc\n '\n \n+test_expect_success 'git branch abc should fail when abc exists' '\n+\ttest_must_fail git branch abc\n+'\n+\n+test_expect_success 'git branch --force abc should fail when abc is checked out' '\n+\ttest_when_finished git switch main &&\n+\tgit switch abc &&\n+\ttest_must_fail git branch --force abc HEAD~1\n+'\n+\n+test_expect_success 'git branch --force abc should succeed when abc exists' '\n+\tgit rev-parse HEAD~1 >expect &&\n+\tgit branch --force abc HEAD~1 &&\n+\tgit rev-parse abc >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n '\n-- \n2.33.GIT\n\n"},{"id":"443663","messageId":"20211209184928.71413-3-chooglen@google.com","threadId":"56962","inReplyTo":"20211209184928.71413-1-chooglen@google.com","subject":"[PATCH v3 2/5] branch: remove forward declaration of validate_branch_start()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T18:49:25Z","receivedAt":"2021-12-09T18:50:03Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"In the previous commit, validate_branch_start() was forward declared in\norder to preserve the function order and minimize the diff. Since the\nforward declaration is no longer needed, remove it by moving\nsetup_tracking() to the appropriate position.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\nThis patch is logically part of the previous patch because it just\ncleans up the artificial forward declaration that exists only to shrink\nthe diff for reviewers.\n\nAs such, if/when this series is merged, I would prefer for this patch to\nbe squashed with the previous one.\n\n branch.c | 98 +++++++++++++++++++++++++++-----------------------------\n 1 file changed, 47 insertions(+), 51 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 9429936734..6b9d64cdf9 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -209,57 +209,6 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n \treturn 0;\n }\n \n-static void validate_branch_start(struct repository *r, const char *start_name,\n-\t\t\t\t  enum branch_track track,\n-\t\t\t\t  struct object_id *oid, char **real_ref);\n-\n-void setup_tracking(const char *new_ref, const char *orig_ref,\n-\t\t\t   enum branch_track track, int quiet, int expand_orig)\n-{\n-\tstruct tracking tracking;\n-\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n-\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n-\tchar *full_orig_ref;\n-\tstruct object_id unused_oid;\n-\n-\tmemset(&tracking, 0, sizeof(tracking));\n-\tif (expand_orig)\n-\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n-\telse\n-\t\tfull_orig_ref = xstrdup(orig_ref);\n-\n-\ttracking.spec.dst = full_orig_ref;\n-\ttracking.srcs = &tracking_srcs;\n-\tif (track != BRANCH_TRACK_INHERIT)\n-\t\tfor_each_remote(find_tracked_branch, &tracking);\n-\telse if (inherit_tracking(&tracking, orig_ref))\n-\t\treturn;\n-\n-\tif (!tracking.matches)\n-\t\tswitch (track) {\n-\t\tcase BRANCH_TRACK_ALWAYS:\n-\t\tcase BRANCH_TRACK_EXPLICIT:\n-\t\tcase BRANCH_TRACK_OVERRIDE:\n-\t\t\tbreak;\n-\t\tdefault:\n-\t\t\tgoto cleanup;\n-\t\t}\n-\n-\tif (tracking.matches > 1)\n-\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n-\t\t    full_orig_ref);\n-\n-\tif (tracking.srcs->nr < 1)\n-\t\tstring_list_append(tracking.srcs, full_orig_ref);\n-\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n-\t\t\t      tracking.srcs) < 0)\n-\t\texit(-1);\n-\n-cleanup:\n-\tstring_list_clear(tracking.srcs, 0);\n-\tfree(full_orig_ref);\n-}\n-\n int read_branch_desc(struct strbuf *buf, const char *branch_name)\n {\n \tchar *v = NULL;\n@@ -407,6 +356,53 @@ static void validate_branch_start(struct repository *r, const char *start_name,\n \toidcpy(oid, &commit->object.oid);\n }\n \n+void setup_tracking(const char *new_ref, const char *orig_ref,\n+\t\t\t   enum branch_track track, int quiet, int expand_orig)\n+{\n+\tstruct tracking tracking;\n+\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n+\tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n+\tchar *full_orig_ref;\n+\tstruct object_id unused_oid;\n+\n+\tmemset(&tracking, 0, sizeof(tracking));\n+\tif (expand_orig)\n+\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n+\telse\n+\t\tfull_orig_ref = xstrdup(orig_ref);\n+\n+\ttracking.spec.dst = full_orig_ref;\n+\ttracking.srcs = &tracking_srcs;\n+\tif (track != BRANCH_TRACK_INHERIT)\n+\t\tfor_each_remote(find_tracked_branch, &tracking);\n+\telse if (inherit_tracking(&tracking, orig_ref))\n+\t\treturn;\n+\n+\tif (!tracking.matches)\n+\t\tswitch (track) {\n+\t\tcase BRANCH_TRACK_ALWAYS:\n+\t\tcase BRANCH_TRACK_EXPLICIT:\n+\t\tcase BRANCH_TRACK_OVERRIDE:\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tgoto cleanup;\n+\t\t}\n+\n+\tif (tracking.matches > 1)\n+\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n+\t\t    full_orig_ref);\n+\n+\tif (tracking.srcs->nr < 1)\n+\t\tstring_list_append(tracking.srcs, full_orig_ref);\n+\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n+\t\t\t      tracking.srcs) < 0)\n+\t\texit(-1);\n+\n+cleanup:\n+\tstring_list_clear(tracking.srcs, 0);\n+\tfree(full_orig_ref);\n+}\n+\n void create_branch(struct repository *r, const char *name,\n \t\t   const char *start_name, int force, int clobber_head_ok,\n \t\t   int reflog, int quiet, enum branch_track track)\n-- \n2.33.GIT\n\n"},{"id":"443664","messageId":"20211209184928.71413-4-chooglen@google.com","threadId":"56962","inReplyTo":"20211209184928.71413-1-chooglen@google.com","subject":"[PATCH v3 3/5] builtin/branch: clean up action-picking logic in cmd_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T18:49:26Z","receivedAt":"2021-12-09T18:50:04Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a variable to cmd_branch() that will tell us whether or not\ncmd_branch() will default to creating a branch (instead of performing\nanother action). Besides making the function more explicit, this allows\nus to validate options that can only be used when creating a branch.\nSuch an option does not exist yet, but one will be introduced in a\nsubsequent commit.\n\nIncidentally, fix an incorrect usage string that combined the 'list'\nusage of git branch (-l) with the 'create' usage; this string has been\nincorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\nparse_options., 2007-10-07).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 21 ++++++++++++---------\n 1 file changed, 12 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e19aab5356..14aff33a50 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -27,7 +27,7 @@\n \n static const char * const builtin_branch_usage[] = {\n \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n-\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n@@ -616,14 +616,15 @@ static int edit_branch_description(const char *branch_name)\n \n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n-\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n-\tint show_current = 0;\n-\tint reflog = 0, edit_description = 0;\n-\tint quiet = 0, unset_upstream = 0;\n+\t/* possible actions */\n+\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0,\n+\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n+\tint noncreate_actions = 0;\n+\t/* possible options */\n+\tint reflog = 0, quiet = 0, icase = 0;\n \tconst char *new_upstream = NULL;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n-\tint icase = 0;\n \tstatic struct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n@@ -708,8 +709,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)\n \t\tlist = 1;\n \n-\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n-\t    list + edit_description + unset_upstream > 1)\n+\tnoncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +\n+\t\t\t    !!show_current + !!list + !!edit_description +\n+\t\t\t    !!unset_upstream;\n+\tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (filter.abbrev == -1)\n@@ -847,7 +850,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&buf, \"branch.%s.merge\", branch->name);\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n-\t} else if (argc > 0 && argc <= 2) {\n+\t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n-- \n2.33.GIT\n\n"},{"id":"443665","messageId":"20211209184928.71413-5-chooglen@google.com","threadId":"56962","inReplyTo":"20211209184928.71413-1-chooglen@google.com","subject":"[PATCH v3 4/5] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T18:49:27Z","receivedAt":"2021-12-09T18:50:06Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"To improve the submodules UX, we would like to teach Git to handle\nbranches in submodules. Start this process by teaching `git branch` the\n--recurse-submodules option so that `git branch --recurse-submodules\ntopic` will create the \"topic\" branch in the superproject and its\nsubmodules.\n\nAlthough this commit does not introduce breaking changes, it is\nincompatible with existing --recurse-submodules semantics e.g. `git\ncheckout` does not recursively checkout the expected branches created by\n`git branch` yet. To ensure that the correct set of semantics is used,\nthis commit introduces a new configuration value,\n`submodule.propagateBranches`, which enables submodule branching when\ntrue (defaults to false).\n\nThis commit includes changes that allow Git to work with submodules\nthat are in trees (and not just the index):\n\n* add a submodules_of_tree() helper that gives the relevant\n  information of an in-tree submodule (e.g. path and oid) and\n  initializes the repository\n* add is_tree_submodule_active() by adding a treeish_name parameter to\n  is_submodule_active()\n* add the \"submoduleNotUpdated\" advice to advise users to update the\n  submodules in their trees\n\nOther changes\n\n* add a \"dry_run\" parameter to create_branch() in order to support\n  `git submodule--helper create-branch --dry-run`\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |   8 +\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 129 ++++++++++++-\n branch.h                           |  31 +++-\n builtin/branch.c                   |  40 +++-\n builtin/checkout.c                 |   3 +-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  35 ++++\n submodule-config.h                 |  35 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3207-branch-submodule.sh        | 284 +++++++++++++++++++++++++++++\n 14 files changed, 609 insertions(+), 13 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex 063eec2511..e52262dc69 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -116,6 +116,9 @@ advice.*::\n \tsubmoduleAlternateErrorStrategyDie::\n \t\tAdvice shown when a submodule.alternateErrorStrategy option\n \t\tconfigured to \"die\" causes a fatal error.\n+\tsubmodulesNotUpdated::\n+\t\tAdvice shown when a user runs a submodule command that fails\n+\t\tbecause `git submodule update` was not run.\n \taddIgnoredFile::\n \t\tAdvice shown if a user attempts to add an ignored file to\n \t\tthe index.\ndiff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\nindex ee454f8126..52b35964c0 100644\n--- a/Documentation/config/submodule.txt\n+++ b/Documentation/config/submodule.txt\n@@ -72,6 +72,14 @@ submodule.recurse::\n \tFor these commands a workaround is to temporarily change the\n \tconfiguration value by using `git -c submodule.recurse=0`.\n \n+submodule.propagateBranches::\n+\t[EXPERIMENTAL] A boolean that enables branching support when\n+\tusing `--recurse-submodules` or `submodule.recurse=true`.\n+\tEnabling this will allow certain commands to accept\n+\t`--recurse-submodules` and certain commands that already accept\n+\t`--recurse-submodules` will now consider branches.\n+\tDefaults to false.\n+\n submodule.fetchJobs::\n \tSpecifies how many submodules are fetched/cloned at the same time.\n \tA positive integer allows up to that number of submodules fetched\ndiff --git a/advice.c b/advice.c\nindex 1dfc91d176..e00d30254c 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -70,6 +70,7 @@ static struct {\n \t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n \t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n \t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n+\t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n };\ndiff --git a/advice.h b/advice.h\nindex 601265fd10..a7521d6087 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -44,6 +44,7 @@ struct string_list;\n \tADVICE_STATUS_HINTS,\n \tADVICE_STATUS_U_OPTION,\n \tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_SUBMODULES_NOT_UPDATED,\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\ndiff --git a/branch.c b/branch.c\nindex 6b9d64cdf9..305154de0b 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -8,6 +8,8 @@\n #include \"sequencer.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n+#include \"submodule-config.h\"\n+#include \"run-command.h\"\n \n struct tracking {\n \tstruct refspec_item spec;\n@@ -405,7 +407,7 @@ void setup_tracking(const char *new_ref, const char *orig_ref,\n \n void create_branch(struct repository *r, const char *name,\n \t\t   const char *start_name, int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track)\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run)\n {\n \tstruct object_id oid;\n \tchar *real_ref;\n@@ -425,6 +427,8 @@ void create_branch(struct repository *r, const char *name,\n \t}\n \n \tvalidate_branch_start(r, start_name, track, &oid, &real_ref);\n+\tif (dry_run)\n+\t\tgoto cleanup;\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -448,10 +452,133 @@ void create_branch(struct repository *r, const char *name,\n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet, 0);\n \n+cleanup:\n \tstrbuf_release(&ref);\n \tfree(real_ref);\n }\n \n+static int submodule_create_branch(struct repository *r,\n+\t\t\t\t   const struct submodule *submodule,\n+\t\t\t\t   const char *name, const char *start_oid,\n+\t\t\t\t   const char *start_name, int force,\n+\t\t\t\t   int reflog, int quiet,\n+\t\t\t\t   enum branch_track track, int dry_run)\n+{\n+\tint ret = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tstruct strbuf child_err = STRBUF_INIT;\n+\tstruct strbuf out_buf = STRBUF_INIT;\n+\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n+\tchild.git_cmd = 1;\n+\tchild.err = -1;\n+\tchild.stdout_to_stderr = 1;\n+\n+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n+\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n+\tif (dry_run)\n+\t\tstrvec_push(&child.args, \"--dry-run\");\n+\tif (force)\n+\t\tstrvec_push(&child.args, \"--force\");\n+\tif (quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\tif (reflog)\n+\t\tstrvec_push(&child.args, \"--create-reflog\");\n+\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n+\t\tstrvec_push(&child.args, \"--track\");\n+\n+\tstrvec_pushl(&child.args, name, start_oid, start_name, NULL);\n+\n+\tif ((ret = start_command(&child)))\n+\t\treturn ret;\n+\tret = finish_command(&child);\n+\tstrbuf_read(&child_err, child.err, 0);\n+\tstrbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);\n+\n+\tif (ret)\n+\t\tfprintf(stderr, \"%s\", out_buf.buf);\n+\telse\n+\t\tprintf(\"%s\", out_buf.buf);\n+\n+\tstrbuf_release(&child_err);\n+\tstrbuf_release(&out_buf);\n+\treturn ret;\n+}\n+\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run)\n+{\n+\tint i = 0;\n+\tchar *branch_point = NULL;\n+\tstruct object_id super_oid;\n+\tstruct submodule_entry_list submodule_entry_list;\n+\n+\t/* Perform dwim on start_name to get super_oid and branch_point. */\n+\tvalidate_branch_start(r, start_name, BRANCH_TRACK_NEVER, &super_oid,\n+\t\t\t      &branch_point);\n+\n+\t/*\n+\t * If we were not given an explicit name to track, then assume we are at\n+\t * the top level and, just like the non-recursive case, the tracking\n+\t * name is the branch point.\n+\t */\n+\tif (!tracking_name)\n+\t\ttracking_name = branch_point;\n+\n+\tsubmodules_of_tree(r, &super_oid, &submodule_entry_list);\n+\t/*\n+\t * Before creating any branches, first check that the branch can\n+\t * be created in every submodule.\n+\t */\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n+\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n+\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n+\t\t\t\t       start_name);\n+\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name);\n+\t\t}\n+\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t}\n+\n+\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n+\t\t      BRANCH_TRACK_NEVER, dry_run);\n+\tif (dry_run)\n+\t\treturn;\n+\t/*\n+\t * NEEDSWORK If tracking was set up in the superproject but not the\n+\t * submodule, users might expect \"git branch --recurse-submodules\" to\n+\t * fail or give a warning, but this is not yet implemented because it is\n+\t * tedious to determine whether or not tracking was set up in the\n+\t * superproject.\n+\t */\n+\tsetup_tracking(name, tracking_name, track, quiet, 0);\n+\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t\trepo_clear(submodule_entry_list.entries[i].repo);\n+\t}\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 46951c446b..8e28a9507f 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -49,12 +49,35 @@ void setup_tracking(const char *new_ref, const char *orig_ref,\n  *   - track causes the new branch to be configured to merge the remote branch\n  *     that start_name is a tracking branch for (if any).\n  *\n+ *   - dry_run causes the branch to be validated but not created.\n+ *\n  */\n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track);\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n \n+/*\n+ * Creates a new branch in repository and its submodules (and its\n+ * submodules, recursively). Besides these exceptions, the parameters\n+ * function identically to create_branch():\n+ *\n+ * - start_name is the name of the ref, in repository r, that the new\n+ *   branch should start from. In submodules, branches will start from\n+ *   the respective gitlink commit ids in start_name's tree.\n+ *\n+ * - tracking_name is the name used of the ref that will be used to set\n+ *   up tracking, e.g. origin/main. This is propagated to submodules so\n+ *   that tracking information will appear as if the branch branched off\n+ *   tracking_name instead of start_name (which is a plain commit id for\n+ *   submodules). If omitted, start_name is used for tracking (just like\n+ *   create_branch()).\n+ *\n+ */\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run);\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 14aff33a50..9adbf70aa7 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -38,6 +38,8 @@ static const char * const builtin_branch_usage[] = {\n \n static const char *head;\n static struct object_id head_oid;\n+static int recurse_submodules = 0;\n+static int submodule_propagate_branches = 0;\n \n static int branch_use_color = -1;\n static char branch_colors[][COLOR_MAXLEN] = {\n@@ -99,6 +101,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\t\treturn config_error_nonbool(var);\n \t\treturn color_parse(value, branch_colors[slot]);\n \t}\n+\tif (!strcmp(var, \"submodule.recurse\")) {\n+\t\trecurse_submodules = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcasecmp(var, \"submodule.propagateBranches\")) {\n+\t\tsubmodule_propagate_branches = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_color_default_config(var, value, cb);\n }\n \n@@ -621,7 +632,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    unset_upstream = 0, show_current = 0, edit_description = 0;\n \tint noncreate_actions = 0;\n \t/* possible options */\n-\tint reflog = 0, quiet = 0, icase = 0;\n+\tint reflog = 0, quiet = 0, icase = 0, recurse_submodules_explicit = 0;\n \tconst char *new_upstream = NULL;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n@@ -673,6 +684,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n+\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT_END(),\n \t};\n@@ -715,6 +727,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n+\tif (recurse_submodules_explicit) {\n+\t\tif (!submodule_propagate_branches)\n+\t\t\tdie(_(\"branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\"));\n+\t\tif (noncreate_actions)\n+\t\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n+\t}\n+\n+\trecurse_submodules =\n+\t\t(recurse_submodules || recurse_submodules_explicit) &&\n+\t\tsubmodule_propagate_branches;\n+\n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n \tfilter.ignore_case = icase;\n@@ -851,6 +874,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n+\t\tconst char *branch_name = argv[0];\n+\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n+\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n@@ -858,10 +884,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (track == BRANCH_TRACK_OVERRIDE)\n \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n \n-\t\tcreate_branch(the_repository,\n-\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track);\n-\n+\t\tif (recurse_submodules) {\n+\t\t\tcreate_branches_recursively(the_repository, branch_name,\n+\t\t\t\t\t\t    start_name, NULL, force,\n+\t\t\t\t\t\t    reflog, quiet, track, 0);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n+\t\t\t      reflog, quiet, track, 0);\n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 8d511aa6b7..fb4323dfca 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -893,7 +893,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t      opts->new_branch_force ? 1 : 0,\n \t\t\t\t      opts->new_branch_log,\n \t\t\t\t      opts->quiet,\n-\t\t\t\t      opts->track);\n+\t\t\t\t      opts->track,\n+\t\t\t\t      0);\n \t\tnew_branch_info->name = opts->new_branch;\n \t\tsetup_branch_path(new_branch_info);\n \t}\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex e630f0c730..44b6283c08 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -20,6 +20,7 @@\n #include \"diff.h\"\n #include \"object-store.h\"\n #include \"advice.h\"\n+#include \"branch.h\"\n \n #define OPT_QUIET (1 << 0)\n #define OPT_CACHED (1 << 1)\n@@ -2983,6 +2984,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n \treturn !!ret;\n }\n \n+static int module_create_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tenum branch_track track;\n+\tint quiet = 0, force = 0, reflog = 0, dry_run = 0;\n+\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n+\t\tOPT__FORCE(&force, N_(\"force creation\"), 0),\n+\t\tOPT_BOOL(0, \"create-reflog\", &reflog,\n+\t\t\t N_(\"create the branch's reflog\")),\n+\t\tOPT_SET_INT('t', \"track\", &track,\n+\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\t    BRANCH_TRACK_EXPLICIT),\n+\t\tOPT__DRY_RUN(&dry_run,\n+\t\t\t     N_(\"show whether the branch would be created\")),\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>\"),\n+\t\tNULL\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\ttrack = git_branch_track;\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\n+\tif (argc != 3)\n+\t\tusage_with_options(usage, options);\n+\n+\tif (!quiet && !dry_run)\n+\t\tprintf_ln(_(\"creating branch '%s'\"), argv[0]);\n+\n+\tcreate_branches_recursively(the_repository, argv[0], argv[1], argv[2],\n+\t\t\t\t    force, reflog, quiet, track, dry_run);\n+\treturn 0;\n+}\n struct add_data {\n \tconst char *prefix;\n \tconst char *branch;\n@@ -3389,6 +3426,7 @@ static struct cmd_struct commands[] = {\n \t{\"config\", module_config, 0},\n \t{\"set-url\", module_set_url, 0},\n \t{\"set-branch\", module_set_branch, 0},\n+\t{\"create-branch\", module_create_branch, 0},\n };\n \n int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f95344028b..f246359d63 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -7,6 +7,7 @@\n #include \"strbuf.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n+#include \"tree-walk.h\"\n \n /*\n  * submodule cache lookup structure\n@@ -726,6 +727,40 @@ const struct submodule *submodule_from_path(struct repository *r,\n \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *out)\n+{\n+\tstruct tree_desc tree;\n+\tstruct submodule_tree_entry *st_entry;\n+\tstruct name_entry *name_entry;\n+\n+\tname_entry = xmalloc(sizeof(*name_entry));\n+\n+\tCALLOC_ARRAY(out->entries, 0);\n+\tout->entry_nr = 0;\n+\tout->entry_alloc = 0;\n+\n+\tfill_tree_descriptor(r, &tree, treeish_name);\n+\twhile (tree_entry(&tree, name_entry)) {\n+\t\tif (!S_ISGITLINK(name_entry->mode) || !is_tree_submodule_active(r, treeish_name, name_entry->path)) {\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tst_entry = xmalloc(sizeof(*st_entry));\n+\t\tst_entry->name_entry = name_entry;\n+\t\tst_entry->submodule =\n+\t\t\tsubmodule_from_path(r, treeish_name, name_entry->path);\n+\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n+\t\tif (repo_submodule_init(st_entry->repo, r, name_entry->path,\n+\t\t\t\t\ttreeish_name))\n+\t\t\tFREE_AND_NULL(st_entry->repo);\n+\n+\t\tALLOC_GROW(out->entries, out->entry_nr + 1, out->entry_alloc);\n+\t\tout->entries[out->entry_nr++] = *st_entry;\n+\t}\n+}\n+\n void submodule_free(struct repository *r)\n {\n \tif (r->submodule_cache)\ndiff --git a/submodule-config.h b/submodule-config.h\nindex 65875b94ea..56c12af03f 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"submodule.h\"\n #include \"strbuf.h\"\n+#include \"tree-walk.h\"\n \n /**\n  * The submodule config cache API allows to read submodule\n@@ -101,4 +102,38 @@ int check_submodule_name(const char *name);\n void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n void update_clone_config_from_gitmodules(int *max_jobs);\n \n+/*\n+ * Submodule entry that contains relevant information about a\n+ * submodule in a tree.\n+ */\n+struct submodule_tree_entry {\n+\t/* The submodule's tree entry. */\n+\tstruct name_entry *name_entry;\n+\t/*\n+\t * A struct repository corresponding to the submodule. May be\n+\t * NULL if the submodule has not been updated.\n+\t */\n+\tstruct repository *repo;\n+\t/*\n+\t * A struct submodule containing the submodule config in the\n+\t * tree's .gitmodules.\n+\t */\n+\tconst struct submodule *submodule;\n+};\n+\n+struct submodule_entry_list {\n+\tstruct submodule_tree_entry *entries;\n+\tint entry_nr;\n+\tint entry_alloc;\n+};\n+\n+/**\n+ * Given a treeish, return all submodules in the tree. This only reads\n+ * one level of the tree, so it will not return nested submodules;\n+ * callers that require nested submodules are expected to handle the\n+ * recursion themselves.\n+ */\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *ret);\n #endif /* SUBMODULE_CONFIG_H */\ndiff --git a/submodule.c b/submodule.c\nindex c689070524..5ace18a7d9 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -267,7 +267,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n  * ie, the config looks like: \"[submodule] active\\n\".\n  * Since that is an invalid pathspec, we should inform the user.\n  */\n-int is_submodule_active(struct repository *repo, const char *path)\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path)\n {\n \tint ret = 0;\n \tchar *key = NULL;\n@@ -275,7 +277,7 @@ int is_submodule_active(struct repository *repo, const char *path)\n \tconst struct string_list *sl;\n \tconst struct submodule *module;\n \n-\tmodule = submodule_from_path(repo, null_oid(), path);\n+\tmodule = submodule_from_path(repo, treeish_name, path);\n \n \t/* early return if there isn't a path->module mapping */\n \tif (!module)\n@@ -317,6 +319,11 @@ int is_submodule_active(struct repository *repo, const char *path)\n \treturn ret;\n }\n \n+int is_submodule_active(struct repository *repo, const char *path)\n+{\n+\treturn is_tree_submodule_active(repo, null_oid(), path);\n+}\n+\n int is_submodule_populated_gently(const char *path, int *return_error_code)\n {\n \tint ret = 0;\ndiff --git a/submodule.h b/submodule.h\nindex 6bd2c99fd9..784ceffc0e 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb);\n struct option;\n int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n \t\t\t\t\t\t     const char *arg, int unset);\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path);\n int is_submodule_active(struct repository *repo, const char *path);\n /*\n  * Determine if a submodule has been populated at a given 'path' by checking if\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nnew file mode 100755\nindex 0000000000..2dd0e2b01f\n--- /dev/null\n+++ b/t/t3207-branch-submodule.sh\n@@ -0,0 +1,284 @@\n+#!/bin/sh\n+\n+test_description='git branch submodule tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup superproject and submodule' '\n+\tgit init super &&\n+\ttest_commit foo &&\n+\tgit init sub-sub-upstream &&\n+\ttest_commit -C sub-sub-upstream foo &&\n+\tgit init sub-upstream &&\n+\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n+\tgit -C sub-upstream commit -m \"add submodule\" &&\n+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n+\tgit -C super commit -m \"add submodule\" &&\n+\tgit -C super config submodule.propagateBranches true &&\n+\tgit -C super/sub submodule update --init\n+'\n+\n+CLEANUP_SCRIPT_PATH=\"$TRASH_DIRECTORY/cleanup_branches.sh\"\n+\n+cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n+\t#!/bin/sh\n+\n+\tsuper_dir=\"$1\"\n+\tshift\n+\t(\n+\t\tcd \"$super_dir\" &&\n+\t\tgit checkout main &&\n+\t\tfor branch_name in \"$@\"; do\n+\t\t\tgit branch -D \"$branch_name\"\n+\t\t\tgit submodule foreach \"$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true\"\n+\t\tdone\n+\t)\n+EOF\n+chmod +x \"$CLEANUP_SCRIPT_PATH\"\n+\n+cleanup_branches() {\n+\tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n+} >/dev/null 2>/dev/null\n+\n+# Test the argument parsing\n+test_expect_success '--recurse-submodules should create branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a &&\n+\t\tgit -C sub/sub-sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\techo \"fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\" >expected &&\n+\t\ttest_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should fail when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n+\t\t# Assert that the branches were not deleted\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should respect submodule.recurse when creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c submodule.recurse=true branch branch-a &&\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should ignore submodule.recurse when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit -c submodule.recurse=true branch -D branch-a &&\n+\t\ttest_must_fail git rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+# Test branch creation behavior\n+test_expect_success 'should create branches based off commit id in superproject' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit checkout --recurse-submodules branch-a &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\t# Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a\n+\t\tgit -C sub checkout branch-a &&\n+\t\ttest_commit -C sub bar &&\n+\t\t# Create a new branch-b branch with start-point=branch-a\n+\t\tgit branch --recurse-submodules branch-b branch-a &&\n+\t\tgit rev-parse branch-b &&\n+\t\tgit -C sub rev-parse branch-b >actual &&\n+\t\t# Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should not create any branches if branch is not valid for all repos' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub branch branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_must_fail git rev-parse branch-a &&\n+\n+\t\tcat >expected <<-EOF &&\n+\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n+\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branches if branch exists and --force is given' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\ttest_commit -C sub baz &&\n+\t\tgit -C sub branch branch-a HEAD~1 &&\n+\t\tgit branch --recurse-submodules --force branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\t# assert that sub:branch-a was moved\n+\t\tgit -C sub rev-parse branch-a >actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branch when submodule is not in HEAD .gitmodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\tgit -C sub2 submodule update --init &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\" &&\n+\t\tgit checkout branch-a &&\n+\t\tgit branch --recurse-submodules branch-c branch-b &&\n+\t\tgit rev-parse branch-c &&\n+\t\tgit -C sub rev-parse branch-c &&\n+\t\tgit checkout --recurse-submodules branch-c &&\n+\t\tgit -C sub2 rev-parse branch-c\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with track=always' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with explicit track' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --track --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should not set up unnecessary tracking of local branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_expect_success 'should not create branches in inactive submodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\ttest_config -C super submodule.sub.active false &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\ttest_must_fail git -C sub branch-a\n+\t)\n+'\n+\n+test_expect_success 'setup remote-tracking tests' '\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\"\n+\t) &&\n+\tgit clone --branch main --recurse-submodules super super-clone &&\n+\tgit -C super-clone config submodule.propagateBranches true\n+'\n+\n+test_expect_success 'should not create branch when submodule is not in .git/modules' '\n+\t# The cleanup needs to delete sub2 separately because main does not have sub2\n+\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n+\t\tgit -C super-clone/sub2/sub-sub branch -D branch-b && \\\n+\t\tcleanup_branches super-clone branch-a branch-b\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\t# This should succeed because super-clone has sub.\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\t# This should fail because super-clone does not have sub2.\n+\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n+\t\tcat >expected <<-EOF &&\n+\t\thint: You may try updating the submodules using ${SQ}git checkout origin/branch-b && git submodule update --init${SQ}\n+\t\tfatal: submodule ${SQ}sub2${SQ}: unable to find submodule\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\t\ttest_must_fail git rev-parse branch-b &&\n+\t\ttest_must_fail git -C sub rev-parse branch-b &&\n+\t\t# User can fix themselves by initializing the submodule\n+\t\tgit checkout origin/branch-b &&\n+\t\tgit submodule update --init --recursive &&\n+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of remote-tracking branches' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\t# \"origin/branch-a\" does not exist for \"sub\", but it matches the refspec\n+\t\t# so tracking should be set up\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.merge)\" = refs/heads/branch-a\n+\t)\n+'\n+\n+test_expect_success 'should not fail when unable to set up tracking in submodule' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a && \\\n+\t\tgit -C super-clone remote rename ex-origin origin\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit remote rename origin ex-origin &&\n+\t\tgit branch --recurse-submodules branch-a ex-origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = ex-origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_done\n-- \n2.33.GIT\n\n"},{"id":"443666","messageId":"20211209184928.71413-6-chooglen@google.com","threadId":"56962","inReplyTo":"20211209184928.71413-1-chooglen@google.com","subject":"[PATCH v3 5/5] branch.c: replace questionable exit() codes","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T18:49:28Z","receivedAt":"2021-12-09T18:50:12Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Replace exit() calls in branch.c that have questionable exit codes:\n\n* in setup_tracking(), exit(-1) was introduced in 27852b2c53 (branch:\n  report errors in tracking branch setup, 2016-02-22). This may have\n  been a mechanical typo because the same commit changes the return type\n  of setup_tracking() from int to void.\n\n* in validate_branch_start(), the exit code changes depending on whether\n  or not advice is enabled. This behavior was not discussed\n  upstream (see caa2036b3b (branch: give advice when tracking\n  start-point is missing, 2013-04-02)).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\nI don't know what the 'correct' exit codes should be, only that Junio\nmakes a good case that the existing exit codes are wrong. My best,\nnon-prescriptive, choice is 128, to be consistent with the surrounding\ncode and Documentation/technical/api-error-handling.txt.\n\n branch.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 305154de0b..ad70ddd120 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -324,7 +324,7 @@ static void validate_branch_start(struct repository *r, const char *start_name,\n \t\t\tif (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {\n \t\t\t\terror(_(upstream_missing), start_name);\n \t\t\t\tadvise(_(upstream_advice));\n-\t\t\t\texit(1);\n+\t\t\t\texit(128);\n \t\t\t}\n \t\t\tdie(_(upstream_missing), start_name);\n \t\t}\n@@ -398,7 +398,7 @@ void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tstring_list_append(tracking.srcs, full_orig_ref);\n \tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n \t\t\t      tracking.srcs) < 0)\n-\t\texit(-1);\n+\t\texit(128);\n \n cleanup:\n \tstring_list_clear(tracking.srcs, 0);\n-- \n2.33.GIT\n\n"},{"id":"443698","messageId":"20211209211951.498358-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20211209184928.71413-2-chooglen@google.com","subject":"Re: [PATCH v3 1/5] branch: move --set-upstream-to behavior to setup_tracking()","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-12-09T21:19:51Z","receivedAt":"2021-12-09T21:19:57Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> This refactor is motivated by a desire to add a \"dry_run\" parameter to\n> create_branch() that will validate whether or not a branch can be\n> created without actually creating it - this behavior be used in a\n> subsequent commit that adds `git branch --recurse-submodules topic`.\n\nWhen I said that the patch was doing too much [1], I meant that for a\npatch that is supposed to be about refactoring, there were many lines\nthat didn't seem to be a straightforward move. I've attached a version\nof the patch that I expected - notice that a reviewer can see that the\nlines are straightforwardly moved.\n\n[1] https://lore.kernel.org/git/20211123231035.3607109-1-jonathantanmy@google.com/\n\n> +void setup_tracking(const char *new_ref, const char *orig_ref,\n> +\t\t\t   enum branch_track track, int quiet, int expand_orig)\n>  {\n>  \tstruct tracking tracking;\n>  \tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n>  \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n> +\tchar *full_orig_ref;\n> +\tstruct object_id unused_oid;\n>  \n>  \tmemset(&tracking, 0, sizeof(tracking));\n> -\ttracking.spec.dst = (char *)orig_ref;\n> +\tif (expand_orig)\n> +\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n> +\telse\n> +\t\tfull_orig_ref = xstrdup(orig_ref);\n\nHaving two meanings for a parameter (and which meaning used depending on\nanother parameter) is quite confusing - I think it's better to call\nanother function to expand the ref if necessary. See my patch below for\nan example.\n\n> diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\n> index 267a624671..18e285a876 100755\n> --- a/t/t3200-branch.sh\n> +++ b/t/t3200-branch.sh\n> @@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n>  \tgit branch abc && test_path_is_file .git/refs/heads/abc\n>  '\n>  \n> +test_expect_success 'git branch abc should fail when abc exists' '\n> +\ttest_must_fail git branch abc\n> +'\n> +\n> +test_expect_success 'git branch --force abc should fail when abc is checked out' '\n> +\ttest_when_finished git switch main &&\n> +\tgit switch abc &&\n> +\ttest_must_fail git branch --force abc HEAD~1\n> +'\n> +\n> +test_expect_success 'git branch --force abc should succeed when abc exists' '\n> +\tgit rev-parse HEAD~1 >expect &&\n> +\tgit branch --force abc HEAD~1 &&\n> +\tgit rev-parse abc >actual &&\n> +\ttest_cmp expect actual\n> +'\n\nThis seems like an unrelated test for a refactoring that doesn't seem to\ntouch \"force\" (in builtin/branch.c, the line removed hardcodes force to\n0).\n\nBelow is my diff, as mentioned.\n\n---\ndiff --git a/branch.c b/branch.c\nindex 07a46430b3..a6803e9900 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -131,8 +131,8 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n  * config.\n  */\n-static void setup_tracking(const char *new_ref, const char *orig_ref,\n-\t\t\t   enum branch_track track, int quiet)\n+void setup_tracking(const char *new_ref, const char *orig_ref,\n+\t\t    enum branch_track track, int quiet)\n {\n \tstruct tracking tracking;\n \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n@@ -243,30 +243,12 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+void TODO_name(struct repository *r, const char *start_name, int explicit_tracking,\n+\t       char **out_real_ref, struct commit **out_commit)\n {\n \tstruct commit *commit;\n \tstruct object_id oid;\n \tchar *real_ref;\n-\tstruct strbuf ref = STRBUF_INIT;\n-\tint forcing = 0;\n-\tint dont_change_ref = 0;\n-\tint explicit_tracking = 0;\n-\n-\tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n-\t\texplicit_tracking = 1;\n-\n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n-\t}\n \n \treal_ref = NULL;\n \tif (get_oid_mb(start_name, &oid)) {\n@@ -304,6 +286,38 @@ void create_branch(struct repository *r,\n \n \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n+\tif (out_real_ref)\n+\t\t*out_real_ref = real_ref;\n+\tif (out_commit)\n+\t\t*out_commit = commit;\n+}\n+\n+void create_branch(struct repository *r,\n+\t\t   const char *name, const char *start_name,\n+\t\t   int force, int clobber_head_ok, int reflog,\n+\t\t   int quiet, enum branch_track track)\n+{\n+\tstruct commit *commit;\n+\tstruct object_id oid;\n+\tchar *real_ref = NULL;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tint forcing = 0;\n+\tint dont_change_ref = 0;\n+\tint explicit_tracking = 0;\n+\n+\tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n+\t\texplicit_tracking = 1;\n+\n+\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n+\t    ? validate_branchname(name, &ref)\n+\t    : validate_new_branchname(name, &ref, force)) {\n+\t\tif (!force)\n+\t\t\tdont_change_ref = 1;\n+\t\telse\n+\t\t\tforcing = 1;\n+\t}\n+\n+\tTODO_name(r, start_name, explicit_tracking, &real_ref, &commit);\n \toidcpy(&oid, &commit->object.oid);\n \n \tif (reflog)\ndiff --git a/branch.h b/branch.h\nindex df0be61506..577483012b 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -1,6 +1,7 @@\n #ifndef BRANCH_H\n #define BRANCH_H\n \n+struct commit;\n struct repository;\n struct strbuf;\n \n@@ -17,6 +18,11 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+void setup_tracking(const char *new_ref, const char *orig_ref,\n+\t\t    enum branch_track track, int quiet);\n+void TODO_name(struct repository *r, const char *start_name, int explicit_tracking,\n+\t       char **out_real_ref, struct commit **out_commit);\n+\n /*\n  * Creates a new branch, where:\n  *\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 81b5c111cb..78f5c1d17e 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -806,6 +806,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"too many arguments for a rename operation\"));\n \t} else if (new_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n+\t\tchar *real_ref;\n \n \t\tif (argc > 1)\n \t\t\tdie(_(\"too many arguments to set new upstream\"));\n@@ -821,12 +822,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (!ref_exists(branch->refname))\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \n-\t\t/*\n-\t\t * create_branch takes care of setting up the tracking\n-\t\t * info and making sure new_upstream is correct\n-\t\t */\n-\t\tcreate_branch(the_repository, branch->name, new_upstream,\n-\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n+\t\tTODO_name(the_repository, new_upstream, 1, &real_ref, NULL);\n+\t\tsetup_tracking(branch->name, real_ref, BRANCH_TRACK_OVERRIDE, quiet);\n+\t\tfree(real_ref);\n \t} else if (unset_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n \t\tstruct strbuf buf = STRBUF_INIT;\n"},{"id":"443699","messageId":"20211209212322.499217-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20211209184928.71413-4-chooglen@google.com","subject":"Re: [PATCH v3 3/5] builtin/branch: clean up action-picking logic in cmd_branch()","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-12-09T21:23:22Z","receivedAt":"2021-12-09T21:23:26Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> Incidentally, fix an incorrect usage string that combined the 'list'\n> usage of git branch (-l) with the 'create' usage; this string has been\n> incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\n> parse_options., 2007-10-07).\n\nI think that we implement such incidental fixes only when we're touching\nthe relevant lines, but this change looks correct.\n\n> -\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n> -\tint show_current = 0;\n> -\tint reflog = 0, edit_description = 0;\n> -\tint quiet = 0, unset_upstream = 0;\n> +\t/* possible actions */\n> +\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0,\n> +\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n> +\tint noncreate_actions = 0;\n> +\t/* possible options */\n> +\tint reflog = 0, quiet = 0, icase = 0;\n\n[snip]\n\n> -\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n> -\t    list + edit_description + unset_upstream > 1)\n> +\tnoncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +\n> +\t\t\t    !!show_current + !!list + !!edit_description +\n> +\t\t\t    !!unset_upstream;\n> +\tif (noncreate_actions > 1)\n>  \t\tusage_with_options(builtin_branch_usage, options);\n\nOverall this change looks good, although if you're going to rearrange\nthe variable declarations (e.g. the positions of show_current,\nedit_description, and unset_upstream have moved), you might as well make\nthem consistent with the noncreate_actions statement, I guess. Also\nmaybe move new_upstream closer.\n"},{"id":"443701","messageId":"kl6lk0gda1b6.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20211209212322.499217-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 3/5] builtin/branch: clean up action-picking logic in cmd_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T21:57:49Z","receivedAt":"2021-12-09T21:57:53Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> Incidentally, fix an incorrect usage string that combined the 'list'\n>> usage of git branch (-l) with the 'create' usage; this string has been\n>> incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\n>> parse_options., 2007-10-07).\n>\n> I think that we implement such incidental fixes only when we're touching\n> the relevant lines, but this change looks correct.\n\nThat's fair. This fix is such low-hanging fruit that I don't think it\ndeserves its patch, but if others agree, I'll separate it.\n\n>\n>> -\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n>> -\tint show_current = 0;\n>> -\tint reflog = 0, edit_description = 0;\n>> -\tint quiet = 0, unset_upstream = 0;\n>> +\t/* possible actions */\n>> +\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0,\n>> +\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n>> +\tint noncreate_actions = 0;\n>> +\t/* possible options */\n>> +\tint reflog = 0, quiet = 0, icase = 0;\n>\n> [snip]\n>\n>> -\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n>> -\t    list + edit_description + unset_upstream > 1)\n>> +\tnoncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +\n>> +\t\t\t    !!show_current + !!list + !!edit_description +\n>> +\t\t\t    !!unset_upstream;\n>> +\tif (noncreate_actions > 1)\n>>  \t\tusage_with_options(builtin_branch_usage, options);\n>\n> Overall this change looks good, although if you're going to rearrange\n> the variable declarations (e.g. the positions of show_current,\n> edit_description, and unset_upstream have moved), you might as well make\n> them consistent with the noncreate_actions statement, I guess. Also\n> maybe move new_upstream closer.\n\nYeah this is obviously inconsistent, thanks for the catch.\n\n* force isn't an action\n* new_upstream is an action\n* for QoL, all of the actions should be listed in the same order at both\n  sites\n"},{"id":"443702","messageId":"20211209215912.506354-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20211209184928.71413-1-chooglen@google.com","subject":"Re: [PATCH v3 0/5] implement branch --recurse-submodules","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-12-09T21:59:12Z","receivedAt":"2021-12-09T21:59:17Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> This version is functionally identical to v2. I've only addressed\n> feedback around code organization, i.e. the the merge conflict with\n> js/branch-track-inherit and making patch 1 easier to review. Thus, some\n> discussions on [1] are still unaddressed.\n\nI do notice that some of my comments on \"add --recurse-submodules option\nfor branch creation\" are still unaddressed so I'll hold off review until\nthey are.\n"},{"id":"443706","messageId":"kl6lh7bha0fp.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20211209211951.498358-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 1/5] branch: move --set-upstream-to behavior to setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T22:16:42Z","receivedAt":"2021-12-09T22:16:47Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> This refactor is motivated by a desire to add a \"dry_run\" parameter to\n>> create_branch() that will validate whether or not a branch can be\n>> created without actually creating it - this behavior be used in a\n>> subsequent commit that adds `git branch --recurse-submodules topic`.\n>\n> When I said that the patch was doing too much [1], I meant that for a\n> patch that is supposed to be about refactoring, there were many lines\n> that didn't seem to be a straightforward move. I've attached a version\n> of the patch that I expected - notice that a reviewer can see that the\n> lines are straightforwardly moved.\n>\n> [1] https://lore.kernel.org/git/20211123231035.3607109-1-jonathantanmy@google.com/\n\nThanks, the patch gave me a lot of think about.\n\nComparing yours and mine side-by-side makes it obvious that what I\nthought was a 'simple' refactor is not so obvious to readers. I think I\ncan incorporate most of your patch.\n\n>> +void setup_tracking(const char *new_ref, const char *orig_ref,\n>> +\t\t\t   enum branch_track track, int quiet, int expand_orig)\n>>  {\n>>  \tstruct tracking tracking;\n>>  \tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n>>  \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n>> +\tchar *full_orig_ref;\n>> +\tstruct object_id unused_oid;\n>>  \n>>  \tmemset(&tracking, 0, sizeof(tracking));\n>> -\ttracking.spec.dst = (char *)orig_ref;\n>> +\tif (expand_orig)\n>> +\t\tvalidate_branch_start(the_repository, orig_ref, track, &unused_oid, &full_orig_ref);\n>> +\telse\n>> +\t\tfull_orig_ref = xstrdup(orig_ref);\n>\n> Having two meanings for a parameter (and which meaning used depending on\n> another parameter) is quite confusing - I think it's better to call\n> another function to expand the ref if necessary. See my patch below for\n> an example.\n\nSince you and Junio have both commented on this inconsistency, I'll drop\nthe extra parameter.\n\nI wonder if you've considered another alternative [1], which is to\n_always_ expand the ref, instead of _never_ expanding the ref. Always\nexpanding the ref wastes cycles, but it avoids creating an implicit\ncontract between setup_tracking() and\nvalidate_branch_start_or_TODO_name().\n\n>> diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\n>> index 267a624671..18e285a876 100755\n>> --- a/t/t3200-branch.sh\n>> +++ b/t/t3200-branch.sh\n>> @@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n>>  \tgit branch abc && test_path_is_file .git/refs/heads/abc\n>>  '\n>>  \n>> +test_expect_success 'git branch abc should fail when abc exists' '\n>> +\ttest_must_fail git branch abc\n>> +'\n>> +\n>> +test_expect_success 'git branch --force abc should fail when abc is checked out' '\n>> +\ttest_when_finished git switch main &&\n>> +\tgit switch abc &&\n>> +\ttest_must_fail git branch --force abc HEAD~1\n>> +'\n>> +\n>> +test_expect_success 'git branch --force abc should succeed when abc exists' '\n>> +\tgit rev-parse HEAD~1 >expect &&\n>> +\tgit branch --force abc HEAD~1 &&\n>> +\tgit rev-parse abc >actual &&\n>> +\ttest_cmp expect actual\n>> +'\n>\n> This seems like an unrelated test for a refactoring that doesn't seem to\n> touch \"force\" (in builtin/branch.c, the line removed hardcodes force to\n> 0).\n\nI added this test because I was trying to simplify create_branch() by\nmoving around/deleting forcing and clobber_head_ok. It turns out that I\nhad actually broken the behavior of --force, but the test suite didn't\ncatch it.\n\nI think this test is a step in the right direction, but it probably\nfalls into the category of 'incidental fix'. I am not sure where it\nbelongs if not here.\n\n> Below is my diff, as mentioned.\n>\n> ---\n> diff --git a/branch.c b/branch.c\n> index 07a46430b3..a6803e9900 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -131,8 +131,8 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>   * to infer the settings for branch.<new_ref>.{remote,merge} from the\n>   * config.\n>   */\n> -static void setup_tracking(const char *new_ref, const char *orig_ref,\n> -\t\t\t   enum branch_track track, int quiet)\n> +void setup_tracking(const char *new_ref, const char *orig_ref,\n> +\t\t    enum branch_track track, int quiet)\n>  {\n>  \tstruct tracking tracking;\n>  \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n> @@ -243,30 +243,12 @@ N_(\"\\n\"\n>  \"will track its remote counterpart, you may want to use\\n\"\n>  \"\\\"git push -u\\\" to set the upstream config as you push.\");\n>  \n> -void create_branch(struct repository *r,\n> -\t\t   const char *name, const char *start_name,\n> -\t\t   int force, int clobber_head_ok, int reflog,\n> -\t\t   int quiet, enum branch_track track)\n> +void TODO_name(struct repository *r, const char *start_name, int explicit_tracking,\n> +\t       char **out_real_ref, struct commit **out_commit)\n>  {\n>  \tstruct commit *commit;\n>  \tstruct object_id oid;\n>  \tchar *real_ref;\n> -\tstruct strbuf ref = STRBUF_INIT;\n> -\tint forcing = 0;\n> -\tint dont_change_ref = 0;\n> -\tint explicit_tracking = 0;\n> -\n> -\tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n> -\t\texplicit_tracking = 1;\n> -\n> -\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n> -\t    ? validate_branchname(name, &ref)\n> -\t    : validate_new_branchname(name, &ref, force)) {\n> -\t\tif (!force)\n> -\t\t\tdont_change_ref = 1;\n> -\t\telse\n> -\t\t\tforcing = 1;\n> -\t}\n>  \n>  \treal_ref = NULL;\n>  \tif (get_oid_mb(start_name, &oid)) {\n> @@ -304,6 +286,38 @@ void create_branch(struct repository *r,\n>  \n>  \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n>  \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n> +\tif (out_real_ref)\n> +\t\t*out_real_ref = real_ref;\n> +\tif (out_commit)\n> +\t\t*out_commit = commit;\n> +}\n\nUsing a separate out_real_ref makes a lot more sense than reusing\nreal_ref. I'll borrow this :)\n\n> +\n> +void create_branch(struct repository *r,\n> +\t\t   const char *name, const char *start_name,\n> +\t\t   int force, int clobber_head_ok, int reflog,\n> +\t\t   int quiet, enum branch_track track)\n> +{\n> +\tstruct commit *commit;\n> +\tstruct object_id oid;\n> +\tchar *real_ref = NULL;\n> +\tstruct strbuf ref = STRBUF_INIT;\n> +\tint forcing = 0;\n> +\tint dont_change_ref = 0;\n> +\tint explicit_tracking = 0;\n> +\n> +\tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n> +\t\texplicit_tracking = 1;\n> +\n> +\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n> +\t    ? validate_branchname(name, &ref)\n> +\t    : validate_new_branchname(name, &ref, force)) {\n> +\t\tif (!force)\n> +\t\t\tdont_change_ref = 1;\n> +\t\telse\n> +\t\t\tforcing = 1;\n> +\t}\n\nThis patch just moves things around without performing internal\nsimplification (like removing dont_change_ref). The simplification would\nprobably be done in a later patch.\n\nMakes sense, I see how this is easier to review.\n\n> @@ -821,12 +822,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>  \t\tif (!ref_exists(branch->refname))\n>  \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n>  \n> -\t\t/*\n> -\t\t * create_branch takes care of setting up the tracking\n> -\t\t * info and making sure new_upstream is correct\n> -\t\t */\n> -\t\tcreate_branch(the_repository, branch->name, new_upstream,\n> -\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n> +\t\tTODO_name(the_repository, new_upstream, 1, &real_ref, NULL);\n> +\t\tsetup_tracking(branch->name, real_ref, BRANCH_TRACK_OVERRIDE, quiet);\n> +\t\tfree(real_ref);\n>  \t} else if (unset_upstream) {\n>  \t\tstruct branch *branch = branch_get(argv[0]);\n>  \t\tstruct strbuf buf = STRBUF_INIT;\n\nSee my previous comment and [1] about avoiding the implicit contract of\nTODO_name() + setup_tracking(), but of course, this is still more\nconsistent than what what I proposed.\n\n[1] https://lore.kernel.org/git/kl6l5yrzaq5z.fsf@chooglen-macbookpro.roam.corp.google.com\n\nThanks!\n"},{"id":"443707","messageId":"kl6lee6la07a.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20211209215912.506354-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T22:21:45Z","receivedAt":"2021-12-09T22:21:48Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>> This version is functionally identical to v2. I've only addressed\n>> feedback around code organization, i.e. the the merge conflict with\n>> js/branch-track-inherit and making patch 1 easier to review. Thus, some\n>> discussions on [1] are still unaddressed.\n>\n> I do notice that some of my comments on \"add --recurse-submodules option\n> for branch creation\" are still unaddressed so I'll hold off review until\n> they are.\n\nAre you referring to your comments on v1 [1]? If so, I believe I had\naddressed them all in v2 (and v3 is mostly a reorganization of v2).\n\nLet me know what you think is unaddressed :)\n\n[1] https://lore.kernel.org/git/20211124013153.3619998-1-jonathantanmy@google.com\n"},{"id":"443724","messageId":"211210.86ee6ldwlc.gmgdl@evledraar.gmail.com","threadId":"56962","inReplyTo":"20211209184928.71413-6-chooglen@google.com","subject":"Re: [PATCH v3 5/5] branch.c: replace questionable exit() codes","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-10T02:21:58Z","receivedAt":"2021-12-10T02:26:13Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Dec 09 2021, Glen Choo wrote:\n\n> Replace exit() calls in branch.c that have questionable exit codes:\n>\n> * in setup_tracking(), exit(-1) was introduced in 27852b2c53 (branch:\n>   report errors in tracking branch setup, 2016-02-22). This may have\n>   been a mechanical typo because the same commit changes the return type\n>   of setup_tracking() from int to void.\n>\n> * in validate_branch_start(), the exit code changes depending on whether\n>   or not advice is enabled. This behavior was not discussed\n>   upstream (see caa2036b3b (branch: give advice when tracking\n>   start-point is missing, 2013-04-02)).\n>\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n> I don't know what the 'correct' exit codes should be, only that Junio\n> makes a good case that the existing exit codes are wrong. My best,\n> non-prescriptive, choice is 128, to be consistent with the surrounding\n> code and Documentation/technical/api-error-handling.txt.\n>\n>  branch.c | 4 ++--\n>  1 file changed, 2 insertions(+), 2 deletions(-)\n>\n> diff --git a/branch.c b/branch.c\n> index 305154de0b..ad70ddd120 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -324,7 +324,7 @@ static void validate_branch_start(struct repository *r, const char *start_name,\n>  \t\t\tif (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {\n>  \t\t\t\terror(_(upstream_missing), start_name);\n>  \t\t\t\tadvise(_(upstream_advice));\n> -\t\t\t\texit(1);\n> +\t\t\t\texit(128);\n>  \t\t\t}\n>  \t\t\tdie(_(upstream_missing), start_name);\n>  \t\t}\n> @@ -398,7 +398,7 @@ void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\tstring_list_append(tracking.srcs, full_orig_ref);\n>  \tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n>  \t\t\t      tracking.srcs) < 0)\n> -\t\texit(-1);\n> +\t\texit(128);\n>  \n>  cleanup:\n>  \tstring_list_clear(tracking.srcs, 0);\n\nJunio noted in <xmqqbl1tcptq.fsf@gitster.g>:\n    \n    This is not a problem with this patch, and it should not be fixed as\n    part of this series, but since I noticed it, I'll mention it as a\n    leftover low-hanging fruit to be fixed after the dust settles.  The\n    exit(1) looks wrong.  We should exit with 128 just like die() does.\n    Issuing of an advice message should not affect the exit code.\n\nI think it's good to fix these inconsistencies, but also that we\nshouldn't be doing it as part of this series, or does it conflict in\nsome way that's hard to untangle?\n\nFWIW the former hunk is a perfect candidate for the new die_message()\nfunction[1]. I.e. we should be doing:\n\n    int code = die_message(_(upsream_missing), start_name);\n    if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n        advise(_(upstream_advice));\n    exit(code);\n\nThat we print an \"error\" when giving the advice but \"fatal\" when not is\nreally UX wart, and also that the exit code differs.\n\nThe latter should really be \"exit(1)\", not 128. We should reserve that\nfor die(). FWIW I had some local hacks to detect all these cases of exit\n-1 via the test suite, they're almost all cases where we want to exit\nwith 1, but just conflated an error() return value with a return from\nmain() (or exit).\n\n1. https://lore.kernel.org/git/cover-v2-0.6-00000000000-20211207T182419Z-avarab@gmail.com/#t\n"},{"id":"443827","messageId":"kl6lwnkc8ifl.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"211210.86ee6ldwlc.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 5/5] branch.c: replace questionable exit() codes","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-10T17:43:10Z","receivedAt":"2021-12-10T17:43:13Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Thu, Dec 09 2021, Glen Choo wrote:\n>\n>> Replace exit() calls in branch.c that have questionable exit codes:\n>>\n>> * in setup_tracking(), exit(-1) was introduced in 27852b2c53 (branch:\n>>   report errors in tracking branch setup, 2016-02-22). This may have\n>>   been a mechanical typo because the same commit changes the return type\n>>   of setup_tracking() from int to void.\n>>\n>> * in validate_branch_start(), the exit code changes depending on whether\n>>   or not advice is enabled. This behavior was not discussed\n>>   upstream (see caa2036b3b (branch: give advice when tracking\n>>   start-point is missing, 2013-04-02)).\n>>\n>> Signed-off-by: Glen Choo <chooglen@google.com>\n>> ---\n>> I don't know what the 'correct' exit codes should be, only that Junio\n>> makes a good case that the existing exit codes are wrong. My best,\n>> non-prescriptive, choice is 128, to be consistent with the surrounding\n>> code and Documentation/technical/api-error-handling.txt.\n>>\n>>  branch.c | 4 ++--\n>>  1 file changed, 2 insertions(+), 2 deletions(-)\n>>\n>> diff --git a/branch.c b/branch.c\n>> index 305154de0b..ad70ddd120 100644\n>> --- a/branch.c\n>> +++ b/branch.c\n>> @@ -324,7 +324,7 @@ static void validate_branch_start(struct repository *r, const char *start_name,\n>>  \t\t\tif (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {\n>>  \t\t\t\terror(_(upstream_missing), start_name);\n>>  \t\t\t\tadvise(_(upstream_advice));\n>> -\t\t\t\texit(1);\n>> +\t\t\t\texit(128);\n>>  \t\t\t}\n>>  \t\t\tdie(_(upstream_missing), start_name);\n>>  \t\t}\n>> @@ -398,7 +398,7 @@ void setup_tracking(const char *new_ref, const char *orig_ref,\n>>  \t\tstring_list_append(tracking.srcs, full_orig_ref);\n>>  \tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n>>  \t\t\t      tracking.srcs) < 0)\n>> -\t\texit(-1);\n>> +\t\texit(128);\n>>  \n>>  cleanup:\n>>  \tstring_list_clear(tracking.srcs, 0);\n>\n> Junio noted in <xmqqbl1tcptq.fsf@gitster.g>:\n>     \n>     This is not a problem with this patch, and it should not be fixed as\n>     part of this series, but since I noticed it, I'll mention it as a\n>     leftover low-hanging fruit to be fixed after the dust settles.  The\n>     exit(1) looks wrong.  We should exit with 128 just like die() does.\n>     Issuing of an advice message should not affect the exit code.\n>\n> I think it's good to fix these inconsistencies, but also that we\n> shouldn't be doing it as part of this series, or does it conflict in\n> some way that's hard to untangle?\n\nThere isn't any conflict. Probably a leftover habit from previous\nprojects, but I thought that this would be right time to clean up. Looks\nlike we think it'll be better to clean this up in a separate series, so\nI'll do that instead.\n\n> FWIW the former hunk is a perfect candidate for the new die_message()\n> function[1]. I.e. we should be doing:\n>\n>     int code = die_message(_(upsream_missing), start_name);\n>     if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n>         advise(_(upstream_advice));\n>     exit(code);\n>\n> That we print an \"error\" when giving the advice but \"fatal\" when not is\n> really UX wart, and also that the exit code differs.\n\nAh, thanks!\n\n> The latter should really be \"exit(1)\", not 128. We should reserve that\n> for die().\n\nThanks, this is exactly what I was looking for guidance on.\nDocumentation/technical/api-error-handling.txt is silent on what exit\ncode to use when a command does 90% of what the caller wants (so it's\nnot really an application error) but fails on the 10% that the user\ndoesn't care so much about - in this case, creating a branch but failing\nto setup tracking.\n\n> FWIW I had some local hacks to detect all these cases of exit -1 via\n> the test suite, they're almost all cases where we want to exit with 1,\n> but just conflated an error() return value with a return from main()\n> (or exit).\n\nYes, this sounds like what happened here.\n"},{"id":"443891","messageId":"a54c6015-6afb-b25f-d2d2-392bf77e93f0@gmail.com","threadId":"56962","inReplyTo":"20211209184928.71413-5-chooglen@google.com","subject":"Re: [PATCH v3 4/5] branch: add --recurse-submodules option for branch creation","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2021-12-11T18:08:11Z","receivedAt":"2021-12-11T18:08:20Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Glen,\n\nLe 2021-12-09 à 13:49, Glen Choo a écrit :\n> To improve the submodules UX, we would like to teach Git to handle\n> branches in submodules. Start this process by teaching `git branch` the\n> --recurse-submodules option so that `git branch --recurse-submodules\n> topic` will create the \"topic\" branch in the superproject and its\n> submodules.\n> \n> Although this commit does not introduce breaking changes, it is\n> incompatible with existing --recurse-submodules semantics e.g. `git\n> checkout` does not recursively checkout the expected branches created by\n> `git branch` yet. To ensure that the correct set of semantics is used,\n> this commit introduces a new configuration value,\n> `submodule.propagateBranches`, which enables submodule branching when\n> true (defaults to false).\n> \n> This commit includes changes that allow Git to work with submodules\n> that are in trees (and not just the index):\n> \n> * add a submodules_of_tree() helper that gives the relevant\n>    information of an in-tree submodule (e.g. path and oid) and\n>    initializes the repository\n> * add is_tree_submodule_active() by adding a treeish_name parameter to\n>    is_submodule_active()\n> * add the \"submoduleNotUpdated\" advice to advise users to update the\n>    submodules in their trees\n> \n> Other changes\n> \n> * add a \"dry_run\" parameter to create_branch() in order to support\n>    `git submodule--helper create-branch --dry-run`\n> \n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>   Documentation/config/advice.txt    |   3 +\n>   Documentation/config/submodule.txt |   8 +\n\nSame comment as I remarked in v1 [1]:\n\nWe would need to add the new flag to Documentation/git-branch.txt,\nand also probably update the documentation of 'submodule.recurse'\nin 'Documentation/config/submodule.txt'.\n\n[1] https://lore.kernel.org/git/3ad3941c-de18-41bf-2e44-4238ae868d79@gmail.com/\n\n>   advice.c                           |   1 +\n>   advice.h                           |   1 +\n>   branch.c                           | 129 ++++++++++++-\n>   branch.h                           |  31 +++-\n>   builtin/branch.c                   |  40 +++-\n>   builtin/checkout.c                 |   3 +-\n>   builtin/submodule--helper.c        |  38 ++++\n>   submodule-config.c                 |  35 ++++\n>   submodule-config.h                 |  35 ++++\n>   submodule.c                        |  11 +-\n>   submodule.h                        |   3 +\n>   t/t3207-branch-submodule.sh        | 284 +++++++++++++++++++++++++++++\n>   14 files changed, 609 insertions(+), 13 deletions(-)\n>   create mode 100755 t/t3207-branch-submodule.sh\n> \n> diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\n> index 063eec2511..e52262dc69 100644\n> --- a/Documentation/config/advice.txt\n> +++ b/Documentation/config/advice.txt\n> @@ -116,6 +116,9 @@ advice.*::\n>   \tsubmoduleAlternateErrorStrategyDie::\n>   \t\tAdvice shown when a submodule.alternateErrorStrategy option\n>   \t\tconfigured to \"die\" causes a fatal error.\n> +\tsubmodulesNotUpdated::\n> +\t\tAdvice shown when a user runs a submodule command that fails\n> +\t\tbecause `git submodule update` was not run.\n\nI see you added '--init' in the actual message below, but maybe it would be more accurate\nto also add it here ?\n\n>   \taddIgnoredFile::\n>   \t\tAdvice shown if a user attempts to add an ignored file to\n>   \t\tthe index.\n> diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\n> index ee454f8126..52b35964c0 100644\n> --- a/Documentation/config/submodule.txt\n> +++ b/Documentation/config/submodule.txt\n> @@ -72,6 +72,14 @@ submodule.recurse::\n>   \tFor these commands a workaround is to temporarily change the\n>   \tconfiguration value by using `git -c submodule.recurse=0`.\n>   \n> +submodule.propagateBranches::\n> +\t[EXPERIMENTAL] A boolean that enables branching support when\n> +\tusing `--recurse-submodules` or `submodule.recurse=true`.\n> +\tEnabling this will allow certain commands to accept\n> +\t`--recurse-submodules` and certain commands that already accept\n> +\t`--recurse-submodules` will now consider branches.\n> +\tDefaults to false.\n> +\n\nThanks, that updated description seems simpler and more accurate with what\nthis series is doing.\n\n>   submodule.fetchJobs::\n>   \tSpecifies how many submodules are fetched/cloned at the same time.\n>   \tA positive integer allows up to that number of submodules fetched\n\n> diff --git a/branch.c b/branch.c\n> index 6b9d64cdf9..305154de0b 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -8,6 +8,8 @@\n\n\n> +void create_branches_recursively(struct repository *r, const char *name,\n> +\t\t\t\t const char *start_name,\n> +\t\t\t\t const char *tracking_name, int force,\n> +\t\t\t\t int reflog, int quiet, enum branch_track track,\n> +\t\t\t\t int dry_run)\n> +{\n> +\tint i = 0;\n> +\tchar *branch_point = NULL;\n> +\tstruct object_id super_oid;\n> +\tstruct submodule_entry_list submodule_entry_list;\n> +\n> +\t/* Perform dwim on start_name to get super_oid and branch_point. */\n> +\tvalidate_branch_start(r, start_name, BRANCH_TRACK_NEVER, &super_oid,\n> +\t\t\t      &branch_point);\n> +\n> +\t/*\n> +\t * If we were not given an explicit name to track, then assume we are at\n> +\t * the top level and, just like the non-recursive case, the tracking\n> +\t * name is the branch point.\n> +\t */\n> +\tif (!tracking_name)\n> +\t\ttracking_name = branch_point;\n> +\n> +\tsubmodules_of_tree(r, &super_oid, &submodule_entry_list);\n> +\t/*\n> +\t * Before creating any branches, first check that the branch can\n> +\t * be created in every submodule.\n> +\t */\n> +\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n> +\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n> +\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n> +\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n> +\t\t\t\t       start_name);\n> +\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n\nsmall nit, maybe write: \"unable to find submodule repository\" ?\n\n> +\t\t\t    submodule_entry_list.entries[i].submodule->name);\n> +\t\t}\n> +\n> +\t\tif (submodule_create_branch(\n> +\t\t\t    submodule_entry_list.entries[i].repo,\n> +\t\t\t    submodule_entry_list.entries[i].submodule, name,\n> +\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n> +\t\t\t\t\t\t.name_entry->oid),\n> +\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n\nHere, we do not actually use the dry_run argument, since we *always* want to\ndo a dry run for the submodules...\n\n> +\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n> +\t\t\t    submodule_entry_list.entries[i].submodule->name,\n> +\t\t\t    name);\n> +\t}\n> +\n> +\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n> +\t\t      BRANCH_TRACK_NEVER, dry_run);\n\n... whereas for the superproject branch, we use the given dry_run argument...\n\n> +\tif (dry_run)\n> +\t\treturn;\n> +\t/*\n> +\t * NEEDSWORK If tracking was set up in the superproject but not the\n> +\t * submodule, users might expect \"git branch --recurse-submodules\" to\n> +\t * fail or give a warning, but this is not yet implemented because it is\n> +\t * tedious to determine whether or not tracking was set up in the\n> +\t * superproject.\n> +\t */\n> +\tsetup_tracking(name, tracking_name, track, quiet, 0);\n> +\n> +\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n> +\t\tif (submodule_create_branch(\n> +\t\t\t    submodule_entry_list.entries[i].repo,\n> +\t\t\t    submodule_entry_list.entries[i].submodule, name,\n> +\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n> +\t\t\t\t\t\t.name_entry->oid),\n> +\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n\n... and if !dry_run, then we do create the branches in the submodules.\n\nThat is a little bit hard to follow if you are not careful. Maybe it's just me,\nbut as I was first reading it I wondered why '0' and '1' were hard-coded as the dry-run\narguments to submodule_create_branch... It would maybe be clearer to use a named\nvariable ?\n\n\n> @@ -851,6 +874,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>   \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n>   \t\tstrbuf_release(&buf);\n>   \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n> +\t\tconst char *branch_name = argv[0];\n> +\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n> +\n>   \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n>   \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n>   \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n> @@ -858,10 +884,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>   \t\tif (track == BRANCH_TRACK_OVERRIDE)\n>   \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n>   \n> -\t\tcreate_branch(the_repository,\n> -\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n> -\t\t\t      force, 0, reflog, quiet, track);\n> -\n> +\t\tif (recurse_submodules) {\n> +\t\t\tcreate_branches_recursively(the_repository, branch_name,\n> +\t\t\t\t\t\t    start_name, NULL, force,\n> +\t\t\t\t\t\t    reflog, quiet, track, 0);\n\nHere again, maybe it would be clearer to use a named variable instead of hard-coding '0' ?\n\n> +\t\t\treturn 0;\n> +\t\t}\n> +\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n> +\t\t\t      reflog, quiet, track, 0);\n\nSame here.\n\n>   \t} else\n>   \t\tusage_with_options(builtin_branch_usage, options);\n>   \n\n> diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\n> new file mode 100755\n> index 0000000000..2dd0e2b01f\n> --- /dev/null\n> +++ b/t/t3207-branch-submodule.sh\n\nThe tests look pretty thourough.\n\n\n> +\n> +test_expect_success 'should create branch when submodule is not in HEAD .gitmodules' '\n\nmicro-nit: maybe write: HEAD:.gitmodules as this is revision synatx.\n\n\nCheers,\n\nPhilippe.\n"},{"id":"443967","messageId":"xmqq5yrsg9n2.fsf@gitster.g","threadId":"56962","inReplyTo":"211210.86ee6ldwlc.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 5/5] branch.c: replace questionable exit() codes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-13T09:02:41Z","receivedAt":"2021-12-13T09:02:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> The latter should really be \"exit(1)\", not 128. We should reserve that\n> for die().\n\nIs it because install_branch_config_multiple_remotes() gives enough\ninformation to the user that the caller exits without its own\nmessage?  In other words, are messages given by the callee to the\nusers are morally equivalent to what the caller would call die()\nwith, if the callee were silent?  If so, 128 is perfectly fine.  If\nnot, 1 or anything positive that is not 128 may be more appropriate.\n\nEither case, -1 is a definite no-no.\n"},{"id":"443970","messageId":"211213.8635mwamah.gmgdl@evledraar.gmail.com","threadId":"56962","inReplyTo":"xmqq5yrsg9n2.fsf@gitster.g","subject":"Re: [PATCH v3 5/5] branch.c: replace questionable exit() codes","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-13T09:19:16Z","receivedAt":"2021-12-13T09:26:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Dec 13 2021, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> The latter should really be \"exit(1)\", not 128. We should reserve that\n>> for die().\n>\n> Is it because install_branch_config_multiple_remotes() gives enough\n> information to the user that the caller exits without its own\n> message?  In other words, are messages given by the callee to the\n> users are morally equivalent to what the caller would call die()\n> with, if the callee were silent?  If so, 128 is perfectly fine.  If\n> not, 1 or anything positive that is not 128 may be more appropriate.\n\nWe don't really document this outside of this tidbit:\n    \n    Documentation/technical/api-error-handling.txt-- `die` is for fatal application errors.  It prints a message to\n    Documentation/technical/api-error-handling.txt:  the user and exits with status 128.\n    Documentation/technical/api-error-handling.txt-\n    Documentation/technical/api-error-handling.txt-- `usage` is for errors in command line usage.  After printing its\n    Documentation/technical/api-error-handling.txt-  message, it exits with status 129.  (See also `usage_with_options`\n    Documentation/technical/api-error-handling.txt-  in the link:api-parse-options.html[parse-options API].)\n\nBut while that doesn't say that you can *only* use 128 for die, and I\nwouldn't consider the existing code that calls exit(128) in dire need of\na change, most of the builtins simply return with 1 for generic errors,\nand reserve 128 for die()..\n\nSo for any new code it makes sense to follow that convention.\n\n> Either case, -1 is a definite no-no.\n\nI've got a local WIP patch to fix those that I can polish up, if you're\ninterested. It's the result of adding the below & running the test suite\nagainst it:\n\ndiff --git a/git.c b/git.c\nindex 60c2784be45..d6bdb3571df 100644\n--- a/git.c\n+++ b/git.c\n@@ -419,6 +419,7 @@ static int handle_alias(int *argcp, const char ***argv)\n static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n {\n \tint status, help;\n+\tint posix_status;\n \tstruct stat st;\n \tconst char *prefix;\n \n@@ -459,6 +460,9 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \n \tvalidate_cache_entries(the_repository->index);\n \tstatus = p->fn(argc, argv, prefix);\n+\tposix_status = status & 0xFF;\n+\tif (status != posix_status)\n+\t\tBUG(\"got status %d which will be cast to %d, returning error() perhaps?\", status, posix_status);\n \tvalidate_cache_entries(the_repository->index);\n \n \tif (status)\n"},{"id":"444020","messageId":"xmqqzgp4b922.fsf@gitster.g","threadId":"56962","inReplyTo":"211213.8635mwamah.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 5/5] branch.c: replace questionable exit() codes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-13T19:26:29Z","receivedAt":"2021-12-13T19:26:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Mon, Dec 13 2021, Junio C Hamano wrote:\n>\n>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>>\n>>> The latter should really be \"exit(1)\", not 128. We should reserve that\n>>> for die().\n>>\n>> Is it because install_branch_config_multiple_remotes() gives enough\n>> information to the user that the caller exits without its own\n>> message?  In other words, are messages given by the callee to the\n>> users are morally equivalent to what the caller would call die()\n>> with, if the callee were silent?  If so, 128 is perfectly fine.  If\n>> not, 1 or anything positive that is not 128 may be more appropriate.\n>\n> We don't really document this outside of this tidbit:\n>     \n>     Documentation/technical/api-error-handling.txt-- `die` is for fatal application errors.  It prints a message to\n>     Documentation/technical/api-error-handling.txt:  the user and exits with status 128.\n>     Documentation/technical/api-error-handling.txt-\n>     Documentation/technical/api-error-handling.txt-- `usage` is for errors in command line usage.  After printing its\n>     Documentation/technical/api-error-handling.txt-  message, it exits with status 129.  (See also `usage_with_options`\n>     Documentation/technical/api-error-handling.txt-  in the link:api-parse-options.html[parse-options API].)\n>\n> But while that doesn't say that you can *only* use 128 for die, and I\n> wouldn't consider the existing code that calls exit(128) in dire need of\n> a change, most of the builtins simply return with 1 for generic errors,\n> and reserve 128 for die()..\n>\n> So for any new code it makes sense to follow that convention.\n\nOnly when they are not calling die() for some technical reasons,\nthough.  IOW, if you would have called die() if you could, that is a\ngood indication that you would want to consistently use 128.\n\nCapturing return value from die_message(), giving more message and\nthen dying with the 128 squarely falls into that pattern.  I am not\nsure if the install_branch_config_multiple_remotes() case falls into\nit, though.\n\nAnd more importantly in the context of this topic, I am not\nconvinced install_branch_config_multiple_remotes() helper function\nitself is a good idea to begin with.  It is to handle a case where\nbranch.$name.remote is set multiple times right?\n\nThis is because I do not think I saw anybody defined the right\nsemantics during the discussion (or written in the documentation)\nand explained why being able to do so makes sense in the first\nplace, and it is not known if it makes sense to copy such a\nconfiguration blindly to a new branch.  If it punts without doing\nanything with a warning(), or calls a die(), it would be a more\nsensible first step for this topic.  Users with real need for such a\nconfiguration will then come to us with real use case, and what they\nneed may turn out to be something different from a blind copying of\nthe original.\n\nThanks.\n\n\n\n"},{"id":"444051","messageId":"20211213232008.771414-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"kl6lee6la07a.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v3 0/5] implement branch --recurse-submodules","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-12-13T23:20:08Z","receivedAt":"2021-12-13T23:20:12Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> Jonathan Tan <jonathantanmy@google.com> writes:\n> \n> > Glen Choo <chooglen@google.com> writes:\n> >> This version is functionally identical to v2. I've only addressed\n> >> feedback around code organization, i.e. the the merge conflict with\n> >> js/branch-track-inherit and making patch 1 easier to review. Thus, some\n> >> discussions on [1] are still unaddressed.\n> >\n> > I do notice that some of my comments on \"add --recurse-submodules option\n> > for branch creation\" are still unaddressed so I'll hold off review until\n> > they are.\n> \n> Are you referring to your comments on v1 [1]? If so, I believe I had\n> addressed them all in v2 (and v3 is mostly a reorganization of v2).\n> \n> Let me know what you think is unaddressed :)\n> \n> [1] https://lore.kernel.org/git/20211124013153.3619998-1-jonathantanmy@google.com\n\nAh...I just saw that you are creating branches through a remote helper\n[1] and are still using tree_entry() non-recursively [2] (to be\nspecific, I think we need a test where the submodule is at\n$ROOT/foo/bar, not just $ROOT/foo), and saw your cover letter that said\nthat some comments were unaddressed, and jumped to a conclusion. Looking\nback, I think that these are my only unaddressed comments.\n\n[1] I said \"If you want to use setup_tracking() through\nsubmodule--helper, I think that needs an explanation as to why a Git\ncommand wouldn't work.\" in\nhttps://lore.kernel.org/git/20211129210140.937875-1-jonathantanmy@google.com/\n\n[2] Discussed in\nhttps://lore.kernel.org/git/20211123021223.3472472-1-jonathantanmy@google.com/\n"},{"id":"444116","messageId":"kl6lr1af81n7.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20211213232008.771414-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-14T18:47:08Z","receivedAt":"2021-12-14T18:47:13Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ah, I should make my cover letter more explicit then. I believe some of\nthe comments are addressed in the body of the patches, but that's not\nthe most reviewer-friendly way to do it.\n\nI'll address the comments out-of-order because it makes a bit more sense\nthis way.\n\nAll the patch snippets come from\nhttps://lore.kernel.org/git/20211209184928.71413-5-chooglen@google.com\n\nJonathan Tan <jonathantanmy@google.com> writes:\n\n> I think we need a test where the submodule is at\n> $ROOT/foo/bar, not just $ROOT/foo\n\nThis scenario is tested by changing how the test repo is set up:\n\n  +test_expect_success 'setup superproject and submodule' '\n  +\tgit init super &&\n  +\ttest_commit foo &&\n  +\tgit init sub-sub-upstream &&\n  +\ttest_commit -C sub-sub-upstream foo &&\n  +\tgit init sub-upstream &&\n  +\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n  +\tgit -C sub-upstream commit -m \"add submodule\" &&\n  +\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n  +\tgit -C super commit -m \"add submodule\" &&\n  +\tgit -C super config submodule.propagateBranches true &&\n  +\tgit -C super/sub submodule update --init\n  +'\n\nso the tests are now all written against a superproject with a 2-level\nsubmodule.\n\n> I just saw that you [...] are still using tree_entry() non-recursively\n> [2]\n\n> [2] Discussed in https://lore.kernel.org/git/20211123021223.3472472-1-jonathantanmy@google.com/\n\nYes, but this limitation is overcome by having\ncreate_branches_recursively() call itself. I documented this caveat on\nsubmodules_of_tree(), which is the function that calls tree_entry().\n\n  +/**\n  + * Given a treeish, return all submodules in the tree. This only reads\n  + * one level of the tree, so it will not return nested submodules;\n  + * callers that require nested submodules are expected to handle the\n  + * recursion themselves.\n  + */\n  +void submodules_of_tree(struct repository *r,\n  +\t\t\tconst struct object_id *treeish_name,\n  +\t\t\tstruct submodule_entry_list *ret);\n\n> [1] I said \"If you want to use setup_tracking() through\n> submodule--helper, I think that needs an explanation as to why a Git\n> command wouldn't work.\" in\n> https://lore.kernel.org/git/20211129210140.937875-1-jonathantanmy@google.com/\n\nNow that create_branches_recursively() calls itself, I moved\nsetup_tracking() into create_branches_recursively() instead of having a\nsubmodule helper that only calls create_branch() and setup_tracking().\nAs a result, I think this comment isn't as relevant as before.\n\nThat said, it might still be unobvious that create_branch() does not do\nthe right thing without setup_tracking(), i.e.\n\n  # Why doesn't create_branch() just do the right thing?\n  +\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n  +\t\t      BRANCH_TRACK_NEVER, dry_run);\n  +\tsetup_tracking(name, tracking_name, track, quiet, 0);\n\nI was hoping that readers who were unclear would find the comment on\ncreate_branches_recursively() adequate:\n\n  + * - tracking_name is the name used of the ref that will be used to set\n  + *   up tracking, e.g. origin/main. This is propagated to submodules so\n  + *   that tracking information will appear as if the branch branched off\n  + *   tracking_name instead of start_name (which is a plain commit id for\n  + *   submodules). If omitted, start_name is used for tracking (just like\n  + *   create_branch()).\n\n"},{"id":"444120","messageId":"kl6l7dc7uey8.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"a54c6015-6afb-b25f-d2d2-392bf77e93f0@gmail.com","subject":"Re: [PATCH v3 4/5] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-14T20:08:47Z","receivedAt":"2021-12-14T20:08:54Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Philippe Blain <levraiphilippeblain@gmail.com> writes:\n\n> Hi Glen,\n>\n> Le 2021-12-09 à 13:49, Glen Choo a écrit :\n>>   Documentation/config/advice.txt    |   3 +\n>>   Documentation/config/submodule.txt |   8 +\n>\n> Same comment as I remarked in v1 [1]:\n>\n> We would need to add the new flag to Documentation/git-branch.txt,\n> and also probably update the documentation of 'submodule.recurse'\n> in 'Documentation/config/submodule.txt'.\n>\n> [1] https://lore.kernel.org/git/3ad3941c-de18-41bf-2e44-4238ae868d79@gmail.com/\n\nAh, I missed Documentation/git-branch.txt. Thanks.\n\nI avoided updating 'submodule.recurse' because it seemed a bit redundant\nwhen 'submodule.propagateBranches' is in the next paragraph. But I can\nimagine that this helps users with tunnel vision (like me...), so I'll\ndo the update.\n\n>> diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\n>> index 063eec2511..e52262dc69 100644\n>> --- a/Documentation/config/advice.txt\n>> +++ b/Documentation/config/advice.txt\n>> @@ -116,6 +116,9 @@ advice.*::\n>>   \tsubmoduleAlternateErrorStrategyDie::\n>>   \t\tAdvice shown when a submodule.alternateErrorStrategy option\n>>   \t\tconfigured to \"die\" causes a fatal error.\n>> +\tsubmodulesNotUpdated::\n>> +\t\tAdvice shown when a user runs a submodule command that fails\n>> +\t\tbecause `git submodule update` was not run.\n>\n> I see you added '--init' in the actual message below, but maybe it would be more accurate\n> to also add it here ?\n\nSounds good.\n\n>> +\t/*\n>> +\t * Before creating any branches, first check that the branch can\n>> +\t * be created in every submodule.\n>> +\t */\n>> +\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n>> +\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n>> +\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n>> +\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n>> +\t\t\t\t       start_name);\n>> +\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n>\n> small nit, maybe write: \"unable to find submodule repository\" ?\n\nI don't think that adding the word 'repository' makes the problem\nclearer to the user. It might even be misleading e.g. a hypotethical\nuser might think \"Unable to find the repository? The remote repository\nis giving a 404 Not Found?\".\n\n>> +\t\t\t    submodule_entry_list.entries[i].submodule->name);\n>> +\t\t}\n>> +\n>> +\t\tif (submodule_create_branch(\n>> +\t\t\t    submodule_entry_list.entries[i].repo,\n>> +\t\t\t    submodule_entry_list.entries[i].submodule, name,\n>> +\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n>> +\t\t\t\t\t\t.name_entry->oid),\n>> +\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n>\n> Here, we do not actually use the dry_run argument, since we *always* want to\n> do a dry run for the submodules...\n>\n>> +\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n>> +\t\t\t    submodule_entry_list.entries[i].submodule->name,\n>> +\t\t\t    name);\n>> +\t}\n>> +\n>> +\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n>> +\t\t      BRANCH_TRACK_NEVER, dry_run);\n>\n> ... whereas for the superproject branch, we use the given dry_run argument...\n>\n>> +\tif (dry_run)\n>> +\t\treturn;\n>> +\t/*\n>> +\t * NEEDSWORK If tracking was set up in the superproject but not the\n>> +\t * submodule, users might expect \"git branch --recurse-submodules\" to\n>> +\t * fail or give a warning, but this is not yet implemented because it is\n>> +\t * tedious to determine whether or not tracking was set up in the\n>> +\t * superproject.\n>> +\t */\n>> +\tsetup_tracking(name, tracking_name, track, quiet, 0);\n>> +\n>> +\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n>> +\t\tif (submodule_create_branch(\n>> +\t\t\t    submodule_entry_list.entries[i].repo,\n>> +\t\t\t    submodule_entry_list.entries[i].submodule, name,\n>> +\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n>> +\t\t\t\t\t\t.name_entry->oid),\n>> +\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n>\n> ... and if !dry_run, then we do create the branches in the submodules.\n>\n> That is a little bit hard to follow if you are not careful. Maybe it's just me,\n> but as I was first reading it I wondered why '0' and '1' were hard-coded as the dry-run\n> arguments to submodule_create_branch... It would maybe be clearer to use a named\n> variable ?\n\nThis is valid feedback, but I'm not sure how to make this clearer\n(besides adding comments). In every case, we really do want the\nhard-coded value, i.e. we'd always want dry_run = 1 (validation) at the\nbeginning, and always dry_run = 0 (actually create branches) at the end.\n\nAs such, a named variable like the following:\n\n  int dry_run_off = 0;\n  setup_tracking(name, tracking_name, track, quiet, dry_run_off);\n\nseems rather artificial. Perhaps you had something like an enum in mind,\nlike:\n\n  enum dry_run {\n    DRY_RUN_OFF,\n    DRY_RUN_ON\n  };\n  setup_tracking(name, tracking_name, track, quiet, DRY_RUN_OFF);\n\nwhich is better, but still feels artificial for a single function with\nan boolean parameter.\n\nPassing literal 0s and 1s don't seem to contradict\nDocumentation/CodingGuidelines or the style of code I have touched, e.g.\n\n>> -\t\tcreate_branch(the_repository,\n>> -\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n>> -\t\t\t      force, 0, reflog, quiet, track);\n\nhard-codes 0 to clobber_head_ok.\n\nPerhaps you had something else in mind?\n\n>> @@ -851,6 +874,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>>   \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n>>   \t\tstrbuf_release(&buf);\n>>   \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n>> +\t\tconst char *branch_name = argv[0];\n>> +\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n>> +\n>>   \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n>>   \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n>>   \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n>> @@ -858,10 +884,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>>   \t\tif (track == BRANCH_TRACK_OVERRIDE)\n>>   \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n>>   \n>> -\t\tcreate_branch(the_repository,\n>> -\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n>> -\t\t\t      force, 0, reflog, quiet, track);\n>> -\n>> +\t\tif (recurse_submodules) {\n>> +\t\t\tcreate_branches_recursively(the_repository, branch_name,\n>> +\t\t\t\t\t\t    start_name, NULL, force,\n>> +\t\t\t\t\t\t    reflog, quiet, track, 0);\n>\n> Here again, maybe it would be clearer to use a named variable instead of hard-coding '0' ?\n>\n>> +\t\t\treturn 0;\n>> +\t\t}\n>> +\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n>> +\t\t\t      reflog, quiet, track, 0);\n>\n> Same here.\n\nI agree that the previous example of create_branches_recursively() is\nhard to follow, but for cmd_branch() I think there is very little reason\nto name a variable for dry_run when cmd_branch() only uses 0.\n\n>>   \t} else\n>>   \t\tusage_with_options(builtin_branch_usage, options);\n>>   \n>\n>> diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\n>> new file mode 100755\n>> index 0000000000..2dd0e2b01f\n>> --- /dev/null\n>> +++ b/t/t3207-branch-submodule.sh\n>\n> The tests look pretty thourough.\n\nThanks!\n\n>> +\n>> +test_expect_success 'should create branch when submodule is not in HEAD .gitmodules' '\n>\n> micro-nit: maybe write: HEAD:.gitmodules as this is revision synatx.\n\nSounds good.\n"},{"id":"444185","messageId":"20211216003213.99135-1-chooglen@google.com","threadId":"56962","inReplyTo":"20211209184928.71413-1-chooglen@google.com","subject":"[PATCH v4 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T00:32:08Z","receivedAt":"2021-12-16T00:32:28Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Submodule branching RFC:\nhttps://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/\n\nOriginal Submodule UX RFC/Discussion:\nhttps://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n\nContributor Summit submodules Notes:\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/\n\nSubmodule UX overhaul updates:\nhttps://lore.kernel.org/git/?q=Submodule+UX+overhaul+update\n\nThis series implements branch --recurse-submodules as laid out in the\nSubmodule branching RFC (linked above). If there are concerns about the\nUX/behavior, I would appreciate feedback on the RFC thread as well :)\n\nThis series is based off js/branch-track-inherit.\n\nFuture work:\n* `git branch -d --recurse-submodules` so that users can clean up\n  extraneous branches.\n* `git [checkout | switch] --recurse-submodules` +\n  submodule.propagateBranches so that users can actually checkout the\n  branches.\n* After [1], it seems clear that --recurse-submodules parsing could\n  really benefit from some standardization. It's not obvious which\n  RECURSE_SUBMODULE_* enums are applicable to which commands, and there\n  is no way to distinguish between an explicit --recurse-submodules from\n  argv vs submodule.recurse from the config.\n\n  I chose not to use them in this series because their usage is already\n  inconsistent (grep.c doesn't use them either), and it would be _more_\n  confusing to use the enum (handling RECURSE_SUBMODULES_DEFAULT = 1 is\n  trickier than boolean 0 and 1).\n\n  At this point, I think it would be too noisy to introduce the enum,\n  but this would be a nice cleanup to do later.\n* As documented in branch.c, we create branches using a child process\n  only because install_branch_config() does not support submodules.\n  It should be possible to remove the child process once we make the\n  appropriate changes to config.c. I attempted this in [2] but chose to\n  punt it because it was too time-consuming at the time.\n\nChanges since v3:\n* Split up the old patch 1. Patch 1 had a big diff because it used to\n  move lines, remove dead code and introduce repo_* functions (thanks\n  Jonathan!)\n** repo_* functions have been dropped; they added noise and are not\n   necessary for correctness.\n* Use a new, harder-to-misuse function in --set-upstream-to,\n  dwim_and_setup_tracking(). Now, setup_tracking() never does DWIM and\n  dwim_and_setup_tracking() always does DWIM.\n* Move create_branch() dry_run to its own patch.\n* Fix an oversight where submodules in subtrees were ignored. This was\n  because submodules_of_tree() and tree_entry() didn't recurse into\n  subtrees. Test this accordingly (thanks Jonathan!).\n* cmd_branch() possible actions are more consistently ordered.\n* Documentation fixes (thanks Philippe!).\n* Additional comments and explanation.\n* Drop patch 5 (optional cleanup).\n* Rebase onto js/branch-track-inherit v6.\n\nChanges since v2:\n* Rebase onto js/branch-track-inherit. This series should continue to be\n  the case going forward.\n* Patch 1 has a smaller diff because the introduction of\n  validate_branch_start() no longer changes the function order thanks to a\n  forward declaration. This artificial forward declaration is removed in a\n  patch 2 (which can just be squashed into patch 1).\n* Optional cleanup: fix questionable exit codes in patch 5.\n\nChanges since v1:\n* Move the functionality of \"git branch --dry-run\" into \"git submodule-helper create-branch --dry-run\"\n* Add more fields to the submodules_of_tree() struct to reduce the\n  number of allocations made by the caller. Move this functionality\n  to patch 3 (formerly patch 4) and drop patch 1.\n* Make submodules_of_tree() ignore inactive submodules\n* Structure the output of the submodules a bit better by adding prefixes\n  to the child process' output (instead of inconsistently indenting the\n  output).\n** I wasn't able to find a good way to interleave stdout/stderr\n   correctly, so a less-than-desirable workaround was to route the child\n   process output to stdout/stderr depending on the exit code.\n** Eventually, I would like to structure the output of submodules in a\n   report, as Ævar suggested. But at this stage, I think that it's\n   better to spend time getting user feedback on the submodules\n   branching UX and it'll be easier to standardize the output when we've\n   implemented more of the UX :)\n\n[1] https://lore.kernel.org/git/kl6lbl1p9zjf.fsf@chooglen-macbookpro.roam.corp.google.com/\n[2] https://lore.kernel.org/git/kl6lv90ytd4v.fsf@chooglen-macbookpro.roam.corp.google.com/\n\nGlen Choo (5):\n  branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n  branch: make create_branch() always create a branch\n  branch: add a dry_run parameter to create_branch()\n  builtin/branch: clean up action-picking logic in cmd_branch()\n  branch: add --recurse-submodules option for branch creation\n\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  24 ++-\n Documentation/git-branch.txt       |  11 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 257 ++++++++++++++++++++-----\n branch.h                           |  57 +++++-\n builtin/branch.c                   |  70 +++++--\n builtin/checkout.c                 |   3 +-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3200-branch.sh                  |  17 ++\n t/t3207-branch-submodule.sh        | 291 +++++++++++++++++++++++++++++\n 16 files changed, 805 insertions(+), 76 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\nRange-diff against v3:\n1:  8241c0b51a < -:  ---------- branch: move --set-upstream-to behavior to setup_tracking()\n2:  b74bcbaade < -:  ---------- branch: remove forward declaration of validate_branch_start()\n-:  ---------- > 1:  dfdbbaaca5 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n-:  ---------- > 2:  e22a177cb7 branch: make create_branch() always create a branch\n-:  ---------- > 3:  8a895aa401 branch: add a dry_run parameter to create_branch()\n3:  235173efc9 ! 4:  971c53ec85 builtin/branch: clean up action-picking logic in cmd_branch()\n    @@ Commit message\n         Such an option does not exist yet, but one will be introduced in a\n         subsequent commit.\n     \n    -    Incidentally, fix an incorrect usage string that combined the 'list'\n    -    usage of git branch (-l) with the 'create' usage; this string has been\n    -    incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\n    -    parse_options., 2007-10-07).\n    -\n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n      ## builtin/branch.c ##\n    -@@\n    - \n    - static const char * const builtin_branch_usage[] = {\n    - \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n    --\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n    -+\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n    - \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n    - \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n    - \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n     @@ builtin/branch.c: static int edit_branch_description(const char *branch_name)\n      \n      int cmd_branch(int argc, const char **argv, const char *prefix)\n    @@ builtin/branch.c: static int edit_branch_description(const char *branch_name)\n     -\tint reflog = 0, edit_description = 0;\n     -\tint quiet = 0, unset_upstream = 0;\n     +\t/* possible actions */\n    -+\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0,\n    ++\tint delete = 0, rename = 0, copy = 0, list = 0,\n     +\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n    + \tconst char *new_upstream = NULL;\n     +\tint noncreate_actions = 0;\n     +\t/* possible options */\n    -+\tint reflog = 0, quiet = 0, icase = 0;\n    - \tconst char *new_upstream = NULL;\n    ++\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n      \tenum branch_track track;\n      \tstruct ref_filter filter;\n     -\tint icase = 0;\n4:  3dabb8e2fa ! 5:  cd88f3ad92 branch: add --recurse-submodules option for branch creation\n    @@ Commit message\n         * add the \"submoduleNotUpdated\" advice to advise users to update the\n           submodules in their trees\n     \n    -    Other changes\n    -\n    -    * add a \"dry_run\" parameter to create_branch() in order to support\n    -      `git submodule--helper create-branch --dry-run`\n    +    Incidentally, fix an incorrect usage string that combined the 'list'\n    +    usage of git branch (-l) with the 'create' usage; this string has been\n    +    incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\n    +    parse_options., 2007-10-07).\n     \n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n    @@ Documentation/config/advice.txt: advice.*::\n      \t\tconfigured to \"die\" causes a fatal error.\n     +\tsubmodulesNotUpdated::\n     +\t\tAdvice shown when a user runs a submodule command that fails\n    -+\t\tbecause `git submodule update` was not run.\n    ++\t\tbecause `git submodule update --init` was not run.\n      \taddIgnoredFile::\n      \t\tAdvice shown if a user attempts to add an ignored file to\n      \t\tthe index.\n     \n      ## Documentation/config/submodule.txt ##\n    +@@ Documentation/config/submodule.txt: submodule.active::\n    + \n    + submodule.recurse::\n    + \tA boolean indicating if commands should enable the `--recurse-submodules`\n    +-\toption by default.\n    +-\tApplies to all commands that support this option\n    +-\t(`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,\n    +-\t`restore` and `switch`) except `clone` and `ls-files`.\n    +-\tDefaults to false.\n    ++\toption by default. Defaults to false.\n    ++\t+\n    + \tWhen set to true, it can be deactivated via the\n    + \t`--no-recurse-submodules` option. Note that some Git commands\n    + \tlacking this option may call some of the above commands affected by\n     @@ Documentation/config/submodule.txt: submodule.recurse::\n    + \t`git fetch` but does not have a `--no-recurse-submodules` option.\n      \tFor these commands a workaround is to temporarily change the\n      \tconfiguration value by using `git -c submodule.recurse=0`.\n    - \n    ++\t+\n    ++\tThe following list shows the commands that accept\n    ++\t`--recurse-submodules` and whether they are supported by this\n    ++\tsetting.\n    ++\t* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n    ++\t`reset`, `restore` and `switch` are always supported.\n    ++\t* `clone` and `ls-files` are not supported.\n    ++\t* `branch` is supported only if `submodule.propagateBranches` is\n    ++\tenabled\n    ++\n     +submodule.propagateBranches::\n     +\t[EXPERIMENTAL] A boolean that enables branching support when\n     +\tusing `--recurse-submodules` or `submodule.recurse=true`.\n    @@ Documentation/config/submodule.txt: submodule.recurse::\n     +\t`--recurse-submodules` and certain commands that already accept\n     +\t`--recurse-submodules` will now consider branches.\n     +\tDefaults to false.\n    -+\n    + \n      submodule.fetchJobs::\n      \tSpecifies how many submodules are fetched/cloned at the same time.\n    - \tA positive integer allows up to that number of submodules fetched\n    +\n    + ## Documentation/git-branch.txt ##\n    +@@ Documentation/git-branch.txt: SYNOPSIS\n    + \t[--points-at <object>] [--format=<format>]\n    + \t[(-r | --remotes) | (-a | --all)]\n    + \t[--list] [<pattern>...]\n    +-'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n    ++'git branch' [--track [direct|inherit] | --no-track] [-f]\n    ++\t[--recurse-submodules] <branchname> [<start-point>]\n    + 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n    + 'git branch' --unset-upstream [<branchname>]\n    + 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n    +@@ Documentation/git-branch.txt: how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n    + \tDo not set up \"upstream\" configuration, even if the\n    + \tbranch.autoSetupMerge configuration variable is set.\n    + \n    ++--recurse-submodules::\n    ++\tTHIS OPTION IS EXPERIMENTAL! Causes the current command to\n    ++\trecurse into submodules if `submodule.propagateBranches` is\n    ++\tenabled. See `submodule.propagateBranches` in\n    ++\tlinkgit:git-config[1].\n    ++\t+\n    ++\tCurrently, only branch creation is supported.\n    ++\n    + --set-upstream::\n    + \tAs this option had confusing syntax, it is no longer supported.\n    + \tPlease use `--track` or `--set-upstream-to` instead.\n     \n      ## advice.c ##\n     @@ advice.c: static struct {\n    @@ branch.c\n      \n      struct tracking {\n      \tstruct refspec_item spec;\n    -@@ branch.c: void setup_tracking(const char *new_ref, const char *orig_ref,\n    - \n    - void create_branch(struct repository *r, const char *name,\n    - \t\t   const char *start_name, int force, int clobber_head_ok,\n    --\t\t   int reflog, int quiet, enum branch_track track)\n    -+\t\t   int reflog, int quiet, enum branch_track track, int dry_run)\n    - {\n    - \tstruct object_id oid;\n    - \tchar *real_ref;\n    -@@ branch.c: void create_branch(struct repository *r, const char *name,\n    - \t}\n    - \n    - \tvalidate_branch_start(r, start_name, track, &oid, &real_ref);\n    -+\tif (dry_run)\n    -+\t\tgoto cleanup;\n    - \n    - \tif (reflog)\n    - \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n    -@@ branch.c: void create_branch(struct repository *r, const char *name,\n    - \tif (real_ref && track)\n    - \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet, 0);\n    - \n    -+cleanup:\n    - \tstrbuf_release(&ref);\n    - \tfree(real_ref);\n    +@@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n    + \tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n      }\n      \n    ++/**\n    ++ * Creates a branch in a submodule by calling\n    ++ * create_branches_recursively() in a child process. The child process\n    ++ * is necessary because install_branch_config() (and its variants) do\n    ++ * not support writing configs to submodules.\n    ++ */\n     +static int submodule_create_branch(struct repository *r,\n     +\t\t\t\t   const struct submodule *submodule,\n     +\t\t\t\t   const char *name, const char *start_oid,\n    @@ branch.c: void create_branch(struct repository *r, const char *name,\n     +\tstruct submodule_entry_list submodule_entry_list;\n     +\n     +\t/* Perform dwim on start_name to get super_oid and branch_point. */\n    -+\tvalidate_branch_start(r, start_name, BRANCH_TRACK_NEVER, &super_oid,\n    -+\t\t\t      &branch_point);\n    ++\tdwim_branch_start(r, start_name, BRANCH_TRACK_NEVER, &branch_point,\n    ++\t\t\t  &super_oid);\n     +\n     +\t/*\n     +\t * If we were not given an explicit name to track, then assume we are at\n    @@ branch.c: void create_branch(struct repository *r, const char *name,\n     +\t * tedious to determine whether or not tracking was set up in the\n     +\t * superproject.\n     +\t */\n    -+\tsetup_tracking(name, tracking_name, track, quiet, 0);\n    ++\tsetup_tracking(name, tracking_name, track, quiet);\n     +\n     +\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n     +\t\tif (submodule_create_branch(\n    @@ branch.c: void create_branch(struct repository *r, const char *name,\n      \tunlink(git_path_merge_head(r));\n     \n      ## branch.h ##\n    -@@ branch.h: void setup_tracking(const char *new_ref, const char *orig_ref,\n    -  *   - track causes the new branch to be configured to merge the remote branch\n    -  *     that start_name is a tracking branch for (if any).\n    -  *\n    -+ *   - dry_run causes the branch to be validated but not created.\n    -+ *\n    -  */\n    --void create_branch(struct repository *r,\n    --\t\t   const char *name, const char *start_name,\n    --\t\t   int force, int clobber_head_ok,\n    --\t\t   int reflog, int quiet, enum branch_track track);\n    -+void create_branch(struct repository *r, const char *name,\n    -+\t\t   const char *start_name, int force, int clobber_head_ok,\n    -+\t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n    +@@ branch.h: void create_branch(struct repository *r, const char *name,\n    + \t\t   const char *start_name, int force, int clobber_head_ok,\n    + \t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n      \n     +/*\n     + * Creates a new branch in repository and its submodules (and its\n    @@ branch.h: void setup_tracking(const char *new_ref, const char *orig_ref,\n       * Return 1 if the named branch already exists; return 0 otherwise.\n     \n      ## builtin/branch.c ##\n    +@@\n    + \n    + static const char * const builtin_branch_usage[] = {\n    + \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n    +-\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n    ++\tN_(\"git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]\"),\n    ++\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n    + \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n    + \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n    + \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n     @@ builtin/branch.c: static const char * const builtin_branch_usage[] = {\n      \n      static const char *head;\n    @@ builtin/branch.c: static int git_branch_config(const char *var, const char *valu\n      }\n      \n     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)\n    - \t    unset_upstream = 0, show_current = 0, edit_description = 0;\n    + \tconst char *new_upstream = NULL;\n      \tint noncreate_actions = 0;\n      \t/* possible options */\n    --\tint reflog = 0, quiet = 0, icase = 0;\n    -+\tint reflog = 0, quiet = 0, icase = 0, recurse_submodules_explicit = 0;\n    - \tconst char *new_upstream = NULL;\n    +-\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n    ++\tint reflog = 0, quiet = 0, icase = 0, force = 0,\n    ++\t    recurse_submodules_explicit = 0;\n      \tenum branch_track track;\n      \tstruct ref_filter filter;\n    + \tstatic struct ref_sorting *sorting;\n     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)\n      \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n      \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n    @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix\n      \n     -\t\tcreate_branch(the_repository,\n     -\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n    --\t\t\t      force, 0, reflog, quiet, track);\n    +-\t\t\t      force, 0, reflog, quiet, track, 0);\n     -\n     +\t\tif (recurse_submodules) {\n     +\t\t\tcreate_branches_recursively(the_repository, branch_name,\n    @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix\n      \t\tusage_with_options(builtin_branch_usage, options);\n      \n     \n    - ## builtin/checkout.c ##\n    -@@ builtin/checkout.c: static void update_refs_for_switch(const struct checkout_opts *opts,\n    - \t\t\t\t      opts->new_branch_force ? 1 : 0,\n    - \t\t\t\t      opts->new_branch_log,\n    - \t\t\t\t      opts->quiet,\n    --\t\t\t\t      opts->track);\n    -+\t\t\t\t      opts->track,\n    -+\t\t\t\t      0);\n    - \t\tnew_branch_info->name = opts->new_branch;\n    - \t\tsetup_branch_path(new_branch_info);\n    - \t}\n    -\n      ## builtin/submodule--helper.c ##\n     @@\n      #include \"diff.h\"\n    @@ submodule-config.c: const struct submodule *submodule_from_path(struct repositor\n      \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n      }\n      \n    -+void submodules_of_tree(struct repository *r,\n    -+\t\t\tconst struct object_id *treeish_name,\n    -+\t\t\tstruct submodule_entry_list *out)\n    ++/**\n    ++ * Used internally by submodules_of_tree(). Recurses into 'treeish_name'\n    ++ * and appends submodule entries to 'out'. The submodule_cache expects\n    ++ * a root-level treeish_name and paths, so keep track of these values\n    ++ * with 'root_tree' and 'prefix'.\n    ++ */\n    ++static void traverse_tree_submodules(struct repository *r,\n    ++\t\t\t\t     const struct object_id *root_tree,\n    ++\t\t\t\t     char *prefix,\n    ++\t\t\t\t     const struct object_id *treeish_name,\n    ++\t\t\t\t     struct submodule_entry_list *out)\n     +{\n     +\tstruct tree_desc tree;\n     +\tstruct submodule_tree_entry *st_entry;\n     +\tstruct name_entry *name_entry;\n    ++\tchar *tree_path = NULL;\n     +\n     +\tname_entry = xmalloc(sizeof(*name_entry));\n     +\n    ++\tfill_tree_descriptor(r, &tree, treeish_name);\n    ++\twhile (tree_entry(&tree, name_entry)) {\n    ++\t\tif (prefix)\n    ++\t\t\ttree_path =\n    ++\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n    ++\t\telse\n    ++\t\t\ttree_path = xstrdup(name_entry->path);\n    ++\n    ++\t\tif (S_ISGITLINK(name_entry->mode) &&\n    ++\t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n    ++\t\t\tst_entry = xmalloc(sizeof(*st_entry));\n    ++\t\t\tst_entry->name_entry = name_entry;\n    ++\t\t\tst_entry->submodule =\n    ++\t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n    ++\t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n    ++\t\t\tif (repo_submodule_init(st_entry->repo, r, tree_path,\n    ++\t\t\t\t\t\troot_tree))\n    ++\t\t\t\tFREE_AND_NULL(st_entry->repo);\n    ++\n    ++\t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n    ++\t\t\t\t   out->entry_alloc);\n    ++\t\t\tout->entries[out->entry_nr++] = *st_entry;\n    ++\t\t} else if (S_ISDIR(name_entry->mode))\n    ++\t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n    ++\t\t\t\t\t\t &name_entry->oid, out);\n    ++\t\tfree(tree_path);\n    ++\t}\n    ++}\n    ++\n    ++void submodules_of_tree(struct repository *r,\n    ++\t\t\tconst struct object_id *treeish_name,\n    ++\t\t\tstruct submodule_entry_list *out)\n    ++{\n     +\tCALLOC_ARRAY(out->entries, 0);\n     +\tout->entry_nr = 0;\n     +\tout->entry_alloc = 0;\n     +\n    -+\tfill_tree_descriptor(r, &tree, treeish_name);\n    -+\twhile (tree_entry(&tree, name_entry)) {\n    -+\t\tif (!S_ISGITLINK(name_entry->mode) || !is_tree_submodule_active(r, treeish_name, name_entry->path)) {\n    -+\t\t\tcontinue;\n    -+\t\t}\n    -+\n    -+\t\tst_entry = xmalloc(sizeof(*st_entry));\n    -+\t\tst_entry->name_entry = name_entry;\n    -+\t\tst_entry->submodule =\n    -+\t\t\tsubmodule_from_path(r, treeish_name, name_entry->path);\n    -+\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n    -+\t\tif (repo_submodule_init(st_entry->repo, r, name_entry->path,\n    -+\t\t\t\t\ttreeish_name))\n    -+\t\t\tFREE_AND_NULL(st_entry->repo);\n    -+\n    -+\t\tALLOC_GROW(out->entries, out->entry_nr + 1, out->entry_alloc);\n    -+\t\tout->entries[out->entry_nr++] = *st_entry;\n    -+\t}\n    ++\ttraverse_tree_submodules(r, treeish_name, NULL, treeish_name, out);\n     +}\n     +\n      void submodule_free(struct repository *r)\n    @@ submodule-config.h: int check_submodule_name(const char *name);\n     +};\n     +\n     +/**\n    -+ * Given a treeish, return all submodules in the tree. This only reads\n    -+ * one level of the tree, so it will not return nested submodules;\n    -+ * callers that require nested submodules are expected to handle the\n    -+ * recursion themselves.\n    ++ * Given a treeish, return all submodules in the tree and its subtrees,\n    ++ * but excluding nested submodules. Callers that require nested\n    ++ * submodules are expected to recurse into the submodules themselves.\n     + */\n     +void submodules_of_tree(struct repository *r,\n     +\t\t\tconst struct object_id *treeish_name,\n    @@ t/t3207-branch-submodule.sh (new)\n     +\tgit init sub-sub-upstream &&\n     +\ttest_commit -C sub-sub-upstream foo &&\n     +\tgit init sub-upstream &&\n    ++\t# Submodule in a submodule\n     +\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n     +\tgit -C sub-upstream commit -m \"add submodule\" &&\n    ++\t# Regular submodule\n     +\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n    ++\t# Submodule in a subdirectory\n    ++\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" second/sub &&\n     +\tgit -C super commit -m \"add submodule\" &&\n     +\tgit -C super config submodule.propagateBranches true &&\n     +\tgit -C super/sub submodule update --init\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tgit branch --recurse-submodules branch-a &&\n     +\t\tgit rev-parse branch-a &&\n     +\t\tgit -C sub rev-parse branch-a &&\n    -+\t\tgit -C sub/sub-sub rev-parse branch-a\n    ++\t\tgit -C sub/sub-sub rev-parse branch-a &&\n    ++\t\tgit -C second/sub rev-parse branch-a\n     +\t)\n     +'\n     +\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t)\n     +'\n     +\n    -+test_expect_success 'should create branch when submodule is not in HEAD .gitmodules' '\n    ++test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '\n     +\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n     +\t(\n     +\t\tcd super &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tgit branch --recurse-submodules branch-c branch-b &&\n     +\t\tgit rev-parse branch-c &&\n     +\t\tgit -C sub rev-parse branch-c &&\n    ++\t\tgit -C second/sub rev-parse branch-c &&\n     +\t\tgit checkout --recurse-submodules branch-c &&\n    -+\t\tgit -C sub2 rev-parse branch-c\n    ++\t\tgit -C sub2 rev-parse branch-c &&\n    ++\t\tgit -C sub2/sub-sub rev-parse branch-c\n     +\t)\n     +'\n     +\n5:  70fb03f882 < -:  ---------- branch.c: replace questionable exit() codes\n-- \n2.33.GIT\n\n"},{"id":"444186","messageId":"20211216003213.99135-2-chooglen@google.com","threadId":"56962","inReplyTo":"20211216003213.99135-1-chooglen@google.com","subject":"[PATCH v4 1/5] branch: move --set-upstream-to behavior to dwim_and_setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T00:32:09Z","receivedAt":"2021-12-16T00:32:30Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"This refactor is motivated by a desire to add a \"dry_run\" parameter to\ncreate_branch() that will validate whether or not a branch can be\ncreated without actually creating it - this behavior will be used in a\nsubsequent commit that adds `git branch --recurse-submodules topic`.\n\nAdding \"dry_run\" is not obvious because create_branch() is also used to\nset tracking information without creating a branch, i.e. when using\n--set-upstream-to. This appears to be a leftover from 4fc5006676 (Add\nbranch --set-upstream, 2010-01-18), when --set-upstream would sometimes\ncreate a branch and sometimes update tracking information without\ncreating a branch. However, we no longer support --set-upstream, so it\nmakes more sense to set tracking information with another function and\nuse create_branch() only to create branches. In a later commit, we will\nremove the now-unnecessary logic from create_branch() so that \"dry_run\"\nbecomes trivial to implement.\n\nIntroduce dwim_and_setup_tracking(), which replaces create_branch()\nin `git branch --set-upstream-to`. Ensure correctness by moving the DWIM\nand branch validation logic from create_branch() into a helper function,\ndwim_branch_start(), so that the logic is shared by both functions.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c         | 87 ++++++++++++++++++++++++++++++++++++------------\n branch.h         | 22 ++++++++++++\n builtin/branch.c |  9 ++---\n 3 files changed, 91 insertions(+), 27 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 6e7e2af5cb..fc3304e51d 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -212,9 +212,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n }\n \n /*\n- * This is called when new_ref is branched off of orig_ref, and tries\n- * to infer the settings for branch.<new_ref>.{remote,merge} from the\n- * config.\n+ * Used internally to set the branch.<new_ref>.{remote,merge} config\n+ * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike\n+ * dwim_and_setup_tracking(), this does not do DWIM, i.e. \"origin/main\"\n+ * will not be expanded to \"refs/remotes/origin/main\", so it is not safe\n+ * for 'orig_ref' to be raw user input.\n  */\n static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t   enum branch_track track, int quiet)\n@@ -239,7 +241,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tcase BRANCH_TRACK_INHERIT:\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\treturn;\n+\t\t\tgoto cleanup;\n \t\t}\n \n \tif (tracking.matches > 1)\n@@ -252,6 +254,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t      tracking.srcs) < 0)\n \t\texit(-1);\n \n+cleanup:\n \tstring_list_clear(tracking.srcs, 0);\n }\n \n@@ -335,31 +338,37 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+/**\n+ * DWIMs a user-provided ref to determine the starting point for a\n+ * branch and validates it, where:\n+ *\n+ *   - r is the repository to validate the branch for\n+ *\n+ *   - start_name is the ref that we would like to test. This is\n+ *     expanded with DWIM and assigned to out_real_ref.\n+ *\n+ *   - track is the tracking mode of the new branch. If tracking is\n+ *     explicitly requested, start_name must be a branch (because\n+ *     otherwise start_name cannot be tracked)\n+ *\n+ *   - out_oid is an out parameter containing the object_id of start_name\n+ *\n+ *   - out_real_ref is an out parameter containing the full, 'real' form\n+ *     of start_name e.g. refs/heads/main instead of main\n+ *\n+ */\n+static void dwim_branch_start(struct repository *r, const char *start_name,\n+\t\t\t   enum branch_track track, char **out_real_ref,\n+\t\t\t   struct object_id *out_oid)\n {\n \tstruct commit *commit;\n \tstruct object_id oid;\n \tchar *real_ref;\n-\tstruct strbuf ref = STRBUF_INIT;\n-\tint forcing = 0;\n-\tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n \n \tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n \t\texplicit_tracking = 1;\n \n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n-\t}\n-\n \treal_ref = NULL;\n \tif (get_oid_mb(start_name, &oid)) {\n \t\tif (explicit_tracking) {\n@@ -396,7 +405,34 @@ void create_branch(struct repository *r,\n \n \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n-\toidcpy(&oid, &commit->object.oid);\n+\tif (out_real_ref)\n+\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n+\tif (out_oid)\n+\t\toidcpy(out_oid, &commit->object.oid);\n+\n+\tFREE_AND_NULL(real_ref);\n+}\n+\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track)\n+{\n+\tstruct object_id oid;\n+\tchar *real_ref;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tint forcing = 0;\n+\tint dont_change_ref = 0;\n+\n+\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n+\t    ? validate_branchname(name, &ref)\n+\t    : validate_new_branchname(name, &ref, force)) {\n+\t\tif (!force)\n+\t\t\tdont_change_ref = 1;\n+\t\telse\n+\t\t\tforcing = 1;\n+\t}\n+\n+\tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -430,6 +466,15 @@ void create_branch(struct repository *r,\n \tfree(real_ref);\n }\n \n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet)\n+{\n+\tchar *real_orig_ref;\n+\tdwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL);\n+\tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 815dcd40c0..ab2315c611 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -18,6 +18,28 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+/**\n+ * Sets branch.<new_ref>.{remote,merge} config settings such that\n+ * new_ref tracks orig_ref according to the specified tracking mode.\n+ *\n+ *   - new_ref is the name of the branch that we are setting tracking\n+ *     for.\n+ *\n+ *   - orig_ref is the name of the ref that is 'upstream' of new_ref.\n+ *     orig_ref will be expanded with DWIM so that the config settings\n+ *     are in the correct format e.g. \"refs/remotes/origin/main\" instead\n+ *     of \"origin/main\".\n+ *\n+ *   - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes\n+ *     new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT\n+ *     causes new_ref to track whatever orig_ref tracks.\n+ *\n+ *   - quiet suppresses tracking information.\n+ */\n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet);\n+\n /*\n  * Creates a new branch, where:\n  *\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 81a29edb4a..16a7e80df5 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -823,12 +823,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (!ref_exists(branch->refname))\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \n-\t\t/*\n-\t\t * create_branch takes care of setting up the tracking\n-\t\t * info and making sure new_upstream is correct\n-\t\t */\n-\t\tcreate_branch(the_repository, branch->name, new_upstream,\n-\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n+\t\tdwim_and_setup_tracking(the_repository, branch->name,\n+\t\t\t\t\tnew_upstream, BRANCH_TRACK_OVERRIDE,\n+\t\t\t\t\tquiet);\n \t} else if (unset_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n \t\tstruct strbuf buf = STRBUF_INIT;\n-- \n2.33.GIT\n\n"},{"id":"444187","messageId":"20211216003213.99135-3-chooglen@google.com","threadId":"56962","inReplyTo":"20211216003213.99135-1-chooglen@google.com","subject":"[PATCH v4 2/5] branch: make create_branch() always create a branch","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T00:32:10Z","receivedAt":"2021-12-16T00:32:32Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"create_branch() was formerly used to set tracking without creating a\nbranch. Since the previous commit replaces this use case with\nsetup_tracking(), we can simplify create_branch() so that it always\ncreates a branch.\n\nDo this simplification, in particular:\n\n* remove the special handling of BRANCH_TRACK_OVERRIDE because it is no\n  longer used\n* assert that clobber_head_ok can only be provided with force\n* check that we're handling clobber_head_ok and force correctly by\n  introducing tests for `git branch --force`\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c          | 55 +++++++++++++++++++++--------------------------\n branch.h          |  4 ++--\n t/t3200-branch.sh | 17 +++++++++++++++\n 3 files changed, 44 insertions(+), 32 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex fc3304e51d..f8944d3117 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -421,15 +421,17 @@ void create_branch(struct repository *r, const char *name,\n \tchar *real_ref;\n \tstruct strbuf ref = STRBUF_INIT;\n \tint forcing = 0;\n-\tint dont_change_ref = 0;\n-\n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *msg;\n+\n+\tif (clobber_head_ok && !force)\n+\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n+\n+\tif (clobber_head_ok ?\n+\t\t\t  validate_branchname(name, &ref) :\n+\t\t\t  validate_new_branchname(name, &ref, force)) {\n+\t\tforcing = 1;\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n@@ -437,27 +439,20 @@ void create_branch(struct repository *r, const char *name,\n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n-\tif (!dont_change_ref) {\n-\t\tstruct ref_transaction *transaction;\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\t\tchar *msg;\n-\n-\t\tif (forcing)\n-\t\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n-\t\telse\n-\t\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n-\n-\t\ttransaction = ref_transaction_begin(&err);\n-\t\tif (!transaction ||\n-\t\t    ref_transaction_update(transaction, ref.buf,\n-\t\t\t\t\t   &oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t   0, msg, &err) ||\n-\t\t    ref_transaction_commit(transaction, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\t\tref_transaction_free(transaction);\n-\t\tstrbuf_release(&err);\n-\t\tfree(msg);\n-\t}\n+\tif (forcing)\n+\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n+\telse\n+\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t\tref_transaction_update(transaction, ref.buf,\n+\t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n+\t\t\t\t\t0, msg, &err) ||\n+\t\tref_transaction_commit(transaction, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tfree(msg);\n \n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\ndiff --git a/branch.h b/branch.h\nindex ab2315c611..cf3a4d3ff3 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -52,8 +52,8 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *\n  *   - force enables overwriting an existing (non-head) branch\n  *\n- *   - clobber_head_ok allows the currently checked out (hence existing)\n- *     branch to be overwritten; without 'force', it has no effect.\n+ *   - clobber_head_ok, when enabled with 'force', allows the currently\n+ *     checked out (head) branch to be overwritten\n  *\n  *   - reflog creates a reflog for the branch\n  *\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 09ab132377..71a72efcb2 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n \tgit branch abc && test_path_is_file .git/refs/heads/abc\n '\n \n+test_expect_success 'git branch abc should fail when abc exists' '\n+\ttest_must_fail git branch abc\n+'\n+\n+test_expect_success 'git branch --force abc should fail when abc is checked out' '\n+\ttest_when_finished git switch main &&\n+\tgit switch abc &&\n+\ttest_must_fail git branch --force abc HEAD~1\n+'\n+\n+test_expect_success 'git branch --force abc should succeed when abc exists' '\n+\tgit rev-parse HEAD~1 >expect &&\n+\tgit branch --force abc HEAD~1 &&\n+\tgit rev-parse abc >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n '\n-- \n2.33.GIT\n\n"},{"id":"444188","messageId":"20211216003213.99135-4-chooglen@google.com","threadId":"56962","inReplyTo":"20211216003213.99135-1-chooglen@google.com","subject":"[PATCH v4 3/5] branch: add a dry_run parameter to create_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T00:32:11Z","receivedAt":"2021-12-16T00:32:37Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a dry_run parameter to create_branch() such that dry_run = 1 will\nvalidate a new branch without trying to create it. This will be used in\n`git branch --recurse-submodules` to ensure that the new branch can be\ncreated in all submodules.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c           | 5 ++++-\n branch.h           | 9 +++++----\n builtin/branch.c   | 2 +-\n builtin/checkout.c | 3 ++-\n 4 files changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex f8944d3117..bcaac0ac5e 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -415,7 +415,7 @@ static void dwim_branch_start(struct repository *r, const char *start_name,\n \n void create_branch(struct repository *r, const char *name,\n \t\t   const char *start_name, int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track)\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run)\n {\n \tstruct object_id oid;\n \tchar *real_ref;\n@@ -435,6 +435,8 @@ void create_branch(struct repository *r, const char *name,\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n+\tif (dry_run)\n+\t\tgoto cleanup;\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -457,6 +459,7 @@ void create_branch(struct repository *r, const char *name,\n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n \n+cleanup:\n \tstrbuf_release(&ref);\n \tfree(real_ref);\n }\ndiff --git a/branch.h b/branch.h\nindex cf3a4d3ff3..8009266343 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -62,11 +62,12 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *   - track causes the new branch to be configured to merge the remote branch\n  *     that start_name is a tracking branch for (if any).\n  *\n+ *   - dry_run causes the branch to be validated but not created.\n+ *\n  */\n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track);\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n \n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 16a7e80df5..9b300cf42f 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -859,7 +859,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \t\tcreate_branch(the_repository,\n \t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track);\n+\t\t\t      force, 0, reflog, quiet, track, 0);\n \n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 8d511aa6b7..fb4323dfca 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -893,7 +893,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t      opts->new_branch_force ? 1 : 0,\n \t\t\t\t      opts->new_branch_log,\n \t\t\t\t      opts->quiet,\n-\t\t\t\t      opts->track);\n+\t\t\t\t      opts->track,\n+\t\t\t\t      0);\n \t\tnew_branch_info->name = opts->new_branch;\n \t\tsetup_branch_path(new_branch_info);\n \t}\n-- \n2.33.GIT\n\n"},{"id":"444189","messageId":"20211216003213.99135-5-chooglen@google.com","threadId":"56962","inReplyTo":"20211216003213.99135-1-chooglen@google.com","subject":"[PATCH v4 4/5] builtin/branch: clean up action-picking logic in cmd_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T00:32:12Z","receivedAt":"2021-12-16T00:32:38Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a variable to cmd_branch() that will tell us whether or not\ncmd_branch() will default to creating a branch (instead of performing\nanother action). Besides making the function more explicit, this allows\nus to validate options that can only be used when creating a branch.\nSuch an option does not exist yet, but one will be introduced in a\nsubsequent commit.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 19 +++++++++++--------\n 1 file changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 9b300cf42f..3b010c1d2c 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -616,14 +616,15 @@ static int edit_branch_description(const char *branch_name)\n \n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n-\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n-\tint show_current = 0;\n-\tint reflog = 0, edit_description = 0;\n-\tint quiet = 0, unset_upstream = 0;\n+\t/* possible actions */\n+\tint delete = 0, rename = 0, copy = 0, list = 0,\n+\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n \tconst char *new_upstream = NULL;\n+\tint noncreate_actions = 0;\n+\t/* possible options */\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n-\tint icase = 0;\n \tstatic struct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n@@ -708,8 +709,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)\n \t\tlist = 1;\n \n-\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n-\t    list + edit_description + unset_upstream > 1)\n+\tnoncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +\n+\t\t\t    !!show_current + !!list + !!edit_description +\n+\t\t\t    !!unset_upstream;\n+\tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (filter.abbrev == -1)\n@@ -849,7 +852,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&buf, \"branch.%s.merge\", branch->name);\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n-\t} else if (argc > 0 && argc <= 2) {\n+\t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n-- \n2.33.GIT\n\n"},{"id":"444190","messageId":"20211216003213.99135-6-chooglen@google.com","threadId":"56962","inReplyTo":"20211216003213.99135-1-chooglen@google.com","subject":"[PATCH v4 5/5] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T00:32:13Z","receivedAt":"2021-12-16T00:32:40Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"To improve the submodules UX, we would like to teach Git to handle\nbranches in submodules. Start this process by teaching `git branch` the\n--recurse-submodules option so that `git branch --recurse-submodules\ntopic` will create the \"topic\" branch in the superproject and its\nsubmodules.\n\nAlthough this commit does not introduce breaking changes, it is\nincompatible with existing --recurse-submodules semantics e.g. `git\ncheckout` does not recursively checkout the expected branches created by\n`git branch` yet. To ensure that the correct set of semantics is used,\nthis commit introduces a new configuration value,\n`submodule.propagateBranches`, which enables submodule branching when\ntrue (defaults to false).\n\nThis commit includes changes that allow Git to work with submodules\nthat are in trees (and not just the index):\n\n* add a submodules_of_tree() helper that gives the relevant\n  information of an in-tree submodule (e.g. path and oid) and\n  initializes the repository\n* add is_tree_submodule_active() by adding a treeish_name parameter to\n  is_submodule_active()\n* add the \"submoduleNotUpdated\" advice to advise users to update the\n  submodules in their trees\n\nIncidentally, fix an incorrect usage string that combined the 'list'\nusage of git branch (-l) with the 'create' usage; this string has been\nincorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\nparse_options., 2007-10-07).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  24 ++-\n Documentation/git-branch.txt       |  11 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 130 +++++++++++++\n branch.h                           |  22 +++\n builtin/branch.c                   |  44 ++++-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3207-branch-submodule.sh        | 291 +++++++++++++++++++++++++++++\n 14 files changed, 659 insertions(+), 14 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex 063eec2511..adee26fbbb 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -116,6 +116,9 @@ advice.*::\n \tsubmoduleAlternateErrorStrategyDie::\n \t\tAdvice shown when a submodule.alternateErrorStrategy option\n \t\tconfigured to \"die\" causes a fatal error.\n+\tsubmodulesNotUpdated::\n+\t\tAdvice shown when a user runs a submodule command that fails\n+\t\tbecause `git submodule update --init` was not run.\n \taddIgnoredFile::\n \t\tAdvice shown if a user attempts to add an ignored file to\n \t\tthe index.\ndiff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\nindex ee454f8126..0aa5946364 100644\n--- a/Documentation/config/submodule.txt\n+++ b/Documentation/config/submodule.txt\n@@ -59,11 +59,8 @@ submodule.active::\n \n submodule.recurse::\n \tA boolean indicating if commands should enable the `--recurse-submodules`\n-\toption by default.\n-\tApplies to all commands that support this option\n-\t(`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,\n-\t`restore` and `switch`) except `clone` and `ls-files`.\n-\tDefaults to false.\n+\toption by default. Defaults to false.\n+\t+\n \tWhen set to true, it can be deactivated via the\n \t`--no-recurse-submodules` option. Note that some Git commands\n \tlacking this option may call some of the above commands affected by\n@@ -71,6 +68,23 @@ submodule.recurse::\n \t`git fetch` but does not have a `--no-recurse-submodules` option.\n \tFor these commands a workaround is to temporarily change the\n \tconfiguration value by using `git -c submodule.recurse=0`.\n+\t+\n+\tThe following list shows the commands that accept\n+\t`--recurse-submodules` and whether they are supported by this\n+\tsetting.\n+\t* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n+\t`reset`, `restore` and `switch` are always supported.\n+\t* `clone` and `ls-files` are not supported.\n+\t* `branch` is supported only if `submodule.propagateBranches` is\n+\tenabled\n+\n+submodule.propagateBranches::\n+\t[EXPERIMENTAL] A boolean that enables branching support when\n+\tusing `--recurse-submodules` or `submodule.recurse=true`.\n+\tEnabling this will allow certain commands to accept\n+\t`--recurse-submodules` and certain commands that already accept\n+\t`--recurse-submodules` will now consider branches.\n+\tDefaults to false.\n \n submodule.fetchJobs::\n \tSpecifies how many submodules are fetched/cloned at the same time.\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 2d52ae396b..435dc2b20e 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,8 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f]\n+\t[--recurse-submodules] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -235,6 +236,14 @@ how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n \tDo not set up \"upstream\" configuration, even if the\n \tbranch.autoSetupMerge configuration variable is set.\n \n+--recurse-submodules::\n+\tTHIS OPTION IS EXPERIMENTAL! Causes the current command to\n+\trecurse into submodules if `submodule.propagateBranches` is\n+\tenabled. See `submodule.propagateBranches` in\n+\tlinkgit:git-config[1].\n+\t+\n+\tCurrently, only branch creation is supported.\n+\n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\n \tPlease use `--track` or `--set-upstream-to` instead.\ndiff --git a/advice.c b/advice.c\nindex 1dfc91d176..e00d30254c 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -70,6 +70,7 @@ static struct {\n \t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n \t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n \t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n+\t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n };\ndiff --git a/advice.h b/advice.h\nindex 601265fd10..a7521d6087 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -44,6 +44,7 @@ struct string_list;\n \tADVICE_STATUS_HINTS,\n \tADVICE_STATUS_U_OPTION,\n \tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_SUBMODULES_NOT_UPDATED,\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\ndiff --git a/branch.c b/branch.c\nindex bcaac0ac5e..90609c2ea9 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -8,6 +8,8 @@\n #include \"sequencer.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n+#include \"submodule-config.h\"\n+#include \"run-command.h\"\n \n struct tracking {\n \tstruct refspec_item spec;\n@@ -473,6 +475,134 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n \tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n }\n \n+/**\n+ * Creates a branch in a submodule by calling\n+ * create_branches_recursively() in a child process. The child process\n+ * is necessary because install_branch_config() (and its variants) do\n+ * not support writing configs to submodules.\n+ */\n+static int submodule_create_branch(struct repository *r,\n+\t\t\t\t   const struct submodule *submodule,\n+\t\t\t\t   const char *name, const char *start_oid,\n+\t\t\t\t   const char *start_name, int force,\n+\t\t\t\t   int reflog, int quiet,\n+\t\t\t\t   enum branch_track track, int dry_run)\n+{\n+\tint ret = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tstruct strbuf child_err = STRBUF_INIT;\n+\tstruct strbuf out_buf = STRBUF_INIT;\n+\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n+\tchild.git_cmd = 1;\n+\tchild.err = -1;\n+\tchild.stdout_to_stderr = 1;\n+\n+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n+\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n+\tif (dry_run)\n+\t\tstrvec_push(&child.args, \"--dry-run\");\n+\tif (force)\n+\t\tstrvec_push(&child.args, \"--force\");\n+\tif (quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\tif (reflog)\n+\t\tstrvec_push(&child.args, \"--create-reflog\");\n+\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n+\t\tstrvec_push(&child.args, \"--track\");\n+\n+\tstrvec_pushl(&child.args, name, start_oid, start_name, NULL);\n+\n+\tif ((ret = start_command(&child)))\n+\t\treturn ret;\n+\tret = finish_command(&child);\n+\tstrbuf_read(&child_err, child.err, 0);\n+\tstrbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);\n+\n+\tif (ret)\n+\t\tfprintf(stderr, \"%s\", out_buf.buf);\n+\telse\n+\t\tprintf(\"%s\", out_buf.buf);\n+\n+\tstrbuf_release(&child_err);\n+\tstrbuf_release(&out_buf);\n+\treturn ret;\n+}\n+\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run)\n+{\n+\tint i = 0;\n+\tchar *branch_point = NULL;\n+\tstruct object_id super_oid;\n+\tstruct submodule_entry_list submodule_entry_list;\n+\n+\t/* Perform dwim on start_name to get super_oid and branch_point. */\n+\tdwim_branch_start(r, start_name, BRANCH_TRACK_NEVER, &branch_point,\n+\t\t\t  &super_oid);\n+\n+\t/*\n+\t * If we were not given an explicit name to track, then assume we are at\n+\t * the top level and, just like the non-recursive case, the tracking\n+\t * name is the branch point.\n+\t */\n+\tif (!tracking_name)\n+\t\ttracking_name = branch_point;\n+\n+\tsubmodules_of_tree(r, &super_oid, &submodule_entry_list);\n+\t/*\n+\t * Before creating any branches, first check that the branch can\n+\t * be created in every submodule.\n+\t */\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n+\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n+\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n+\t\t\t\t       start_name);\n+\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name);\n+\t\t}\n+\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t}\n+\n+\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n+\t\t      BRANCH_TRACK_NEVER, dry_run);\n+\tif (dry_run)\n+\t\treturn;\n+\t/*\n+\t * NEEDSWORK If tracking was set up in the superproject but not the\n+\t * submodule, users might expect \"git branch --recurse-submodules\" to\n+\t * fail or give a warning, but this is not yet implemented because it is\n+\t * tedious to determine whether or not tracking was set up in the\n+\t * superproject.\n+\t */\n+\tsetup_tracking(name, tracking_name, track, quiet);\n+\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t\trepo_clear(submodule_entry_list.entries[i].repo);\n+\t}\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 8009266343..63448e4278 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -69,6 +69,28 @@ void create_branch(struct repository *r, const char *name,\n \t\t   const char *start_name, int force, int clobber_head_ok,\n \t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n \n+/*\n+ * Creates a new branch in repository and its submodules (and its\n+ * submodules, recursively). Besides these exceptions, the parameters\n+ * function identically to create_branch():\n+ *\n+ * - start_name is the name of the ref, in repository r, that the new\n+ *   branch should start from. In submodules, branches will start from\n+ *   the respective gitlink commit ids in start_name's tree.\n+ *\n+ * - tracking_name is the name used of the ref that will be used to set\n+ *   up tracking, e.g. origin/main. This is propagated to submodules so\n+ *   that tracking information will appear as if the branch branched off\n+ *   tracking_name instead of start_name (which is a plain commit id for\n+ *   submodules). If omitted, start_name is used for tracking (just like\n+ *   create_branch()).\n+ *\n+ */\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run);\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 3b010c1d2c..c06de58873 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -27,7 +27,8 @@\n \n static const char * const builtin_branch_usage[] = {\n \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n-\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n@@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = {\n \n static const char *head;\n static struct object_id head_oid;\n+static int recurse_submodules = 0;\n+static int submodule_propagate_branches = 0;\n \n static int branch_use_color = -1;\n static char branch_colors[][COLOR_MAXLEN] = {\n@@ -99,6 +102,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\t\treturn config_error_nonbool(var);\n \t\treturn color_parse(value, branch_colors[slot]);\n \t}\n+\tif (!strcmp(var, \"submodule.recurse\")) {\n+\t\trecurse_submodules = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcasecmp(var, \"submodule.propagateBranches\")) {\n+\t\tsubmodule_propagate_branches = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_color_default_config(var, value, cb);\n }\n \n@@ -622,7 +634,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tconst char *new_upstream = NULL;\n \tint noncreate_actions = 0;\n \t/* possible options */\n-\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0,\n+\t    recurse_submodules_explicit = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n \tstatic struct ref_sorting *sorting;\n@@ -673,6 +686,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n+\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT_END(),\n \t};\n@@ -715,6 +729,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n+\tif (recurse_submodules_explicit) {\n+\t\tif (!submodule_propagate_branches)\n+\t\t\tdie(_(\"branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\"));\n+\t\tif (noncreate_actions)\n+\t\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n+\t}\n+\n+\trecurse_submodules =\n+\t\t(recurse_submodules || recurse_submodules_explicit) &&\n+\t\tsubmodule_propagate_branches;\n+\n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n \tfilter.ignore_case = icase;\n@@ -853,6 +878,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n+\t\tconst char *branch_name = argv[0];\n+\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n+\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n@@ -860,10 +888,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (track == BRANCH_TRACK_OVERRIDE)\n \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n \n-\t\tcreate_branch(the_repository,\n-\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track, 0);\n-\n+\t\tif (recurse_submodules) {\n+\t\t\tcreate_branches_recursively(the_repository, branch_name,\n+\t\t\t\t\t\t    start_name, NULL, force,\n+\t\t\t\t\t\t    reflog, quiet, track, 0);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n+\t\t\t      reflog, quiet, track, 0);\n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex e630f0c730..44b6283c08 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -20,6 +20,7 @@\n #include \"diff.h\"\n #include \"object-store.h\"\n #include \"advice.h\"\n+#include \"branch.h\"\n \n #define OPT_QUIET (1 << 0)\n #define OPT_CACHED (1 << 1)\n@@ -2983,6 +2984,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n \treturn !!ret;\n }\n \n+static int module_create_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tenum branch_track track;\n+\tint quiet = 0, force = 0, reflog = 0, dry_run = 0;\n+\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n+\t\tOPT__FORCE(&force, N_(\"force creation\"), 0),\n+\t\tOPT_BOOL(0, \"create-reflog\", &reflog,\n+\t\t\t N_(\"create the branch's reflog\")),\n+\t\tOPT_SET_INT('t', \"track\", &track,\n+\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\t    BRANCH_TRACK_EXPLICIT),\n+\t\tOPT__DRY_RUN(&dry_run,\n+\t\t\t     N_(\"show whether the branch would be created\")),\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>\"),\n+\t\tNULL\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\ttrack = git_branch_track;\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\n+\tif (argc != 3)\n+\t\tusage_with_options(usage, options);\n+\n+\tif (!quiet && !dry_run)\n+\t\tprintf_ln(_(\"creating branch '%s'\"), argv[0]);\n+\n+\tcreate_branches_recursively(the_repository, argv[0], argv[1], argv[2],\n+\t\t\t\t    force, reflog, quiet, track, dry_run);\n+\treturn 0;\n+}\n struct add_data {\n \tconst char *prefix;\n \tconst char *branch;\n@@ -3389,6 +3426,7 @@ static struct cmd_struct commands[] = {\n \t{\"config\", module_config, 0},\n \t{\"set-url\", module_set_url, 0},\n \t{\"set-branch\", module_set_branch, 0},\n+\t{\"create-branch\", module_create_branch, 0},\n };\n \n int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f95344028b..24b8d1a700 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -7,6 +7,7 @@\n #include \"strbuf.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n+#include \"tree-walk.h\"\n \n /*\n  * submodule cache lookup structure\n@@ -726,6 +727,65 @@ const struct submodule *submodule_from_path(struct repository *r,\n \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n+/**\n+ * Used internally by submodules_of_tree(). Recurses into 'treeish_name'\n+ * and appends submodule entries to 'out'. The submodule_cache expects\n+ * a root-level treeish_name and paths, so keep track of these values\n+ * with 'root_tree' and 'prefix'.\n+ */\n+static void traverse_tree_submodules(struct repository *r,\n+\t\t\t\t     const struct object_id *root_tree,\n+\t\t\t\t     char *prefix,\n+\t\t\t\t     const struct object_id *treeish_name,\n+\t\t\t\t     struct submodule_entry_list *out)\n+{\n+\tstruct tree_desc tree;\n+\tstruct submodule_tree_entry *st_entry;\n+\tstruct name_entry *name_entry;\n+\tchar *tree_path = NULL;\n+\n+\tname_entry = xmalloc(sizeof(*name_entry));\n+\n+\tfill_tree_descriptor(r, &tree, treeish_name);\n+\twhile (tree_entry(&tree, name_entry)) {\n+\t\tif (prefix)\n+\t\t\ttree_path =\n+\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n+\t\telse\n+\t\t\ttree_path = xstrdup(name_entry->path);\n+\n+\t\tif (S_ISGITLINK(name_entry->mode) &&\n+\t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n+\t\t\tst_entry = xmalloc(sizeof(*st_entry));\n+\t\t\tst_entry->name_entry = name_entry;\n+\t\t\tst_entry->submodule =\n+\t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n+\t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n+\t\t\tif (repo_submodule_init(st_entry->repo, r, tree_path,\n+\t\t\t\t\t\troot_tree))\n+\t\t\t\tFREE_AND_NULL(st_entry->repo);\n+\n+\t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n+\t\t\t\t   out->entry_alloc);\n+\t\t\tout->entries[out->entry_nr++] = *st_entry;\n+\t\t} else if (S_ISDIR(name_entry->mode))\n+\t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n+\t\t\t\t\t\t &name_entry->oid, out);\n+\t\tfree(tree_path);\n+\t}\n+}\n+\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *out)\n+{\n+\tCALLOC_ARRAY(out->entries, 0);\n+\tout->entry_nr = 0;\n+\tout->entry_alloc = 0;\n+\n+\ttraverse_tree_submodules(r, treeish_name, NULL, treeish_name, out);\n+}\n+\n void submodule_free(struct repository *r)\n {\n \tif (r->submodule_cache)\ndiff --git a/submodule-config.h b/submodule-config.h\nindex 65875b94ea..fa229a8b97 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"submodule.h\"\n #include \"strbuf.h\"\n+#include \"tree-walk.h\"\n \n /**\n  * The submodule config cache API allows to read submodule\n@@ -101,4 +102,37 @@ int check_submodule_name(const char *name);\n void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n void update_clone_config_from_gitmodules(int *max_jobs);\n \n+/*\n+ * Submodule entry that contains relevant information about a\n+ * submodule in a tree.\n+ */\n+struct submodule_tree_entry {\n+\t/* The submodule's tree entry. */\n+\tstruct name_entry *name_entry;\n+\t/*\n+\t * A struct repository corresponding to the submodule. May be\n+\t * NULL if the submodule has not been updated.\n+\t */\n+\tstruct repository *repo;\n+\t/*\n+\t * A struct submodule containing the submodule config in the\n+\t * tree's .gitmodules.\n+\t */\n+\tconst struct submodule *submodule;\n+};\n+\n+struct submodule_entry_list {\n+\tstruct submodule_tree_entry *entries;\n+\tint entry_nr;\n+\tint entry_alloc;\n+};\n+\n+/**\n+ * Given a treeish, return all submodules in the tree and its subtrees,\n+ * but excluding nested submodules. Callers that require nested\n+ * submodules are expected to recurse into the submodules themselves.\n+ */\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *ret);\n #endif /* SUBMODULE_CONFIG_H */\ndiff --git a/submodule.c b/submodule.c\nindex c689070524..5ace18a7d9 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -267,7 +267,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n  * ie, the config looks like: \"[submodule] active\\n\".\n  * Since that is an invalid pathspec, we should inform the user.\n  */\n-int is_submodule_active(struct repository *repo, const char *path)\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path)\n {\n \tint ret = 0;\n \tchar *key = NULL;\n@@ -275,7 +277,7 @@ int is_submodule_active(struct repository *repo, const char *path)\n \tconst struct string_list *sl;\n \tconst struct submodule *module;\n \n-\tmodule = submodule_from_path(repo, null_oid(), path);\n+\tmodule = submodule_from_path(repo, treeish_name, path);\n \n \t/* early return if there isn't a path->module mapping */\n \tif (!module)\n@@ -317,6 +319,11 @@ int is_submodule_active(struct repository *repo, const char *path)\n \treturn ret;\n }\n \n+int is_submodule_active(struct repository *repo, const char *path)\n+{\n+\treturn is_tree_submodule_active(repo, null_oid(), path);\n+}\n+\n int is_submodule_populated_gently(const char *path, int *return_error_code)\n {\n \tint ret = 0;\ndiff --git a/submodule.h b/submodule.h\nindex 6bd2c99fd9..784ceffc0e 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb);\n struct option;\n int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n \t\t\t\t\t\t     const char *arg, int unset);\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path);\n int is_submodule_active(struct repository *repo, const char *path);\n /*\n  * Determine if a submodule has been populated at a given 'path' by checking if\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nnew file mode 100755\nindex 0000000000..a2dfb5ad7f\n--- /dev/null\n+++ b/t/t3207-branch-submodule.sh\n@@ -0,0 +1,291 @@\n+#!/bin/sh\n+\n+test_description='git branch submodule tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup superproject and submodule' '\n+\tgit init super &&\n+\ttest_commit foo &&\n+\tgit init sub-sub-upstream &&\n+\ttest_commit -C sub-sub-upstream foo &&\n+\tgit init sub-upstream &&\n+\t# Submodule in a submodule\n+\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n+\tgit -C sub-upstream commit -m \"add submodule\" &&\n+\t# Regular submodule\n+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n+\t# Submodule in a subdirectory\n+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" second/sub &&\n+\tgit -C super commit -m \"add submodule\" &&\n+\tgit -C super config submodule.propagateBranches true &&\n+\tgit -C super/sub submodule update --init\n+'\n+\n+CLEANUP_SCRIPT_PATH=\"$TRASH_DIRECTORY/cleanup_branches.sh\"\n+\n+cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n+\t#!/bin/sh\n+\n+\tsuper_dir=\"$1\"\n+\tshift\n+\t(\n+\t\tcd \"$super_dir\" &&\n+\t\tgit checkout main &&\n+\t\tfor branch_name in \"$@\"; do\n+\t\t\tgit branch -D \"$branch_name\"\n+\t\t\tgit submodule foreach \"$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true\"\n+\t\tdone\n+\t)\n+EOF\n+chmod +x \"$CLEANUP_SCRIPT_PATH\"\n+\n+cleanup_branches() {\n+\tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n+} >/dev/null 2>/dev/null\n+\n+# Test the argument parsing\n+test_expect_success '--recurse-submodules should create branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a &&\n+\t\tgit -C sub/sub-sub rev-parse branch-a &&\n+\t\tgit -C second/sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\techo \"fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\" >expected &&\n+\t\ttest_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should fail when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n+\t\t# Assert that the branches were not deleted\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should respect submodule.recurse when creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c submodule.recurse=true branch branch-a &&\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should ignore submodule.recurse when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit -c submodule.recurse=true branch -D branch-a &&\n+\t\ttest_must_fail git rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+# Test branch creation behavior\n+test_expect_success 'should create branches based off commit id in superproject' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit checkout --recurse-submodules branch-a &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\t# Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a\n+\t\tgit -C sub checkout branch-a &&\n+\t\ttest_commit -C sub bar &&\n+\t\t# Create a new branch-b branch with start-point=branch-a\n+\t\tgit branch --recurse-submodules branch-b branch-a &&\n+\t\tgit rev-parse branch-b &&\n+\t\tgit -C sub rev-parse branch-b >actual &&\n+\t\t# Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should not create any branches if branch is not valid for all repos' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub branch branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_must_fail git rev-parse branch-a &&\n+\n+\t\tcat >expected <<-EOF &&\n+\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n+\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branches if branch exists and --force is given' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\ttest_commit -C sub baz &&\n+\t\tgit -C sub branch branch-a HEAD~1 &&\n+\t\tgit branch --recurse-submodules --force branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\t# assert that sub:branch-a was moved\n+\t\tgit -C sub rev-parse branch-a >actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\tgit -C sub2 submodule update --init &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\" &&\n+\t\tgit checkout branch-a &&\n+\t\tgit branch --recurse-submodules branch-c branch-b &&\n+\t\tgit rev-parse branch-c &&\n+\t\tgit -C sub rev-parse branch-c &&\n+\t\tgit -C second/sub rev-parse branch-c &&\n+\t\tgit checkout --recurse-submodules branch-c &&\n+\t\tgit -C sub2 rev-parse branch-c &&\n+\t\tgit -C sub2/sub-sub rev-parse branch-c\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with track=always' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with explicit track' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --track --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should not set up unnecessary tracking of local branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_expect_success 'should not create branches in inactive submodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\ttest_config -C super submodule.sub.active false &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\ttest_must_fail git -C sub branch-a\n+\t)\n+'\n+\n+test_expect_success 'setup remote-tracking tests' '\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\"\n+\t) &&\n+\tgit clone --branch main --recurse-submodules super super-clone &&\n+\tgit -C super-clone config submodule.propagateBranches true\n+'\n+\n+test_expect_success 'should not create branch when submodule is not in .git/modules' '\n+\t# The cleanup needs to delete sub2 separately because main does not have sub2\n+\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n+\t\tgit -C super-clone/sub2/sub-sub branch -D branch-b && \\\n+\t\tcleanup_branches super-clone branch-a branch-b\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\t# This should succeed because super-clone has sub.\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\t# This should fail because super-clone does not have sub2.\n+\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n+\t\tcat >expected <<-EOF &&\n+\t\thint: You may try updating the submodules using ${SQ}git checkout origin/branch-b && git submodule update --init${SQ}\n+\t\tfatal: submodule ${SQ}sub2${SQ}: unable to find submodule\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\t\ttest_must_fail git rev-parse branch-b &&\n+\t\ttest_must_fail git -C sub rev-parse branch-b &&\n+\t\t# User can fix themselves by initializing the submodule\n+\t\tgit checkout origin/branch-b &&\n+\t\tgit submodule update --init --recursive &&\n+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of remote-tracking branches' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\t# \"origin/branch-a\" does not exist for \"sub\", but it matches the refspec\n+\t\t# so tracking should be set up\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.merge)\" = refs/heads/branch-a\n+\t)\n+'\n+\n+test_expect_success 'should not fail when unable to set up tracking in submodule' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a && \\\n+\t\tgit -C super-clone remote rename ex-origin origin\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit remote rename origin ex-origin &&\n+\t\tgit branch --recurse-submodules branch-a ex-origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = ex-origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_done\n-- \n2.33.GIT\n\n"},{"id":"444276","messageId":"20211216233324.65126-1-chooglen@google.com","threadId":"56962","inReplyTo":"20211216003213.99135-1-chooglen@google.com","subject":"[PATCH v5 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T23:33:19Z","receivedAt":"2021-12-16T23:33:30Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Submodule branching RFC:\nhttps://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/\n\nOriginal Submodule UX RFC/Discussion:\nhttps://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n\nContributor Summit submodules Notes:\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/\n\nSubmodule UX overhaul updates:\nhttps://lore.kernel.org/git/?q=Submodule+UX+overhaul+update\n\nThis series implements branch --recurse-submodules as laid out in the\nSubmodule branching RFC (linked above). If there are concerns about the\nUX/behavior, I would appreciate feedback on the RFC thread as well :)\n\nThis series is based off js/branch-track-inherit.\n\nFuture work:\n* `git branch -d --recurse-submodules` so that users can clean up\n  extraneous branches.\n* `git [checkout | switch] --recurse-submodules` +\n  submodule.propagateBranches so that users can actually checkout the\n  branches.\n* After [1], it seems clear that --recurse-submodules parsing could\n  really benefit from some standardization. It's not obvious which\n  RECURSE_SUBMODULE_* enums are applicable to which commands, and there\n  is no way to distinguish between an explicit --recurse-submodules from\n  argv vs submodule.recurse from the config.\n\n  I chose not to use them in this series because their usage is already\n  inconsistent (grep.c doesn't use them either), and it would be _more_\n  confusing to use the enum (handling RECURSE_SUBMODULES_DEFAULT = 1 is\n  trickier than boolean 0 and 1).\n\n  At this point, I think it would be too noisy to introduce the enum,\n  but this would be a nice cleanup to do later.\n* As documented in branch.c, we create branches using a child process\n  only because install_branch_config() does not support submodules.\n  It should be possible to remove the child process once we make the\n  appropriate changes to config.c. I attempted this in [2] but chose to\n  punt it because it was too time-consuming at the time.\n\nChanges since v4:\n* Rebase correctly onto 'gitster/seen^{/^Merge branch .js/branch-track-inherit.}'\n  (see base-commit) as suggested in [3] (thanks Junio!)\n* These patches were also verified on top of 'next'.\n\nChanges since v3:\n* Split up the old patch 1. Patch 1 had a big diff because it used to\n  move lines, remove dead code and introduce repo_* functions (thanks\n  Jonathan!)\n** repo_* functions have been dropped; they added noise and are not\n   necessary for correctness.\n* Use a new, harder-to-misuse function in --set-upstream-to,\n  dwim_and_setup_tracking(). Now, setup_tracking() never does DWIM and\n  dwim_and_setup_tracking() always does DWIM.\n* Move create_branch() dry_run to its own patch.\n* Fix an oversight where submodules in subtrees were ignored. This was\n  because submodules_of_tree() and tree_entry() didn't recurse into\n  subtrees. Test this accordingly (thanks Jonathan!).\n* cmd_branch() possible actions are more consistently ordered.\n* Documentation fixes (thanks Philippe!).\n* Additional comments and explanation.\n* Drop patch 5 (optional cleanup).\n* Rebase onto js/branch-track-inherit v6.\n\nChanges since v2:\n* Rebase onto js/branch-track-inherit. This series should continue to be\n  the case going forward.\n* Patch 1 has a smaller diff because the introduction of\n  validate_branch_start() no longer changes the function order thanks to a\n  forward declaration. This artificial forward declaration is removed in a\n  patch 2 (which can just be squashed into patch 1).\n* Optional cleanup: fix questionable exit codes in patch 5.\n\nChanges since v1:\n* Move the functionality of \"git branch --dry-run\" into \"git submodule-helper create-branch --dry-run\"\n* Add more fields to the submodules_of_tree() struct to reduce the\n  number of allocations made by the caller. Move this functionality\n  to patch 3 (formerly patch 4) and drop patch 1.\n* Make submodules_of_tree() ignore inactive submodules\n* Structure the output of the submodules a bit better by adding prefixes\n  to the child process' output (instead of inconsistently indenting the\n  output).\n** I wasn't able to find a good way to interleave stdout/stderr\n   correctly, so a less-than-desirable workaround was to route the child\n   process output to stdout/stderr depending on the exit code.\n** Eventually, I would like to structure the output of submodules in a\n   report, as Ævar suggested. But at this stage, I think that it's\n   better to spend time getting user feedback on the submodules\n   branching UX and it'll be easier to standardize the output when we've\n   implemented more of the UX :)\n\n[1] https://lore.kernel.org/git/kl6lbl1p9zjf.fsf@chooglen-macbookpro.roam.corp.google.com/\n[2] https://lore.kernel.org/git/kl6lv90ytd4v.fsf@chooglen-macbookpro.roam.corp.google.com/\n[3] https://lore.kernel.org/git/xmqqlf0lz6os.fsf@gitster.g \n\nGlen Choo (5):\n  branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n  branch: make create_branch() always create a branch\n  branch: add a dry_run parameter to create_branch()\n  builtin/branch: clean up action-picking logic in cmd_branch()\n  branch: add --recurse-submodules option for branch creation\n\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  24 ++-\n Documentation/git-branch.txt       |  11 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 257 ++++++++++++++++++++-----\n branch.h                           |  57 +++++-\n builtin/branch.c                   |  70 +++++--\n builtin/checkout.c                 |   3 +-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3200-branch.sh                  |  17 ++\n t/t3207-branch-submodule.sh        | 291 +++++++++++++++++++++++++++++\n 16 files changed, 805 insertions(+), 76 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\nRange-diff against v4:\n1:  751e8ae566 < -:  ---------- branch: accept multiple upstream branches for tracking\n2:  5d1ebe1495 < -:  ---------- branch: add flags and config to inherit tracking\n3:  0080a1fb35 < -:  ---------- config: require lowercase for branch.autosetupmerge\n4:  dfdbbaaca5 ! 1:  a9d1108b3e branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n    @@ Commit message\n     \n         This refactor is motivated by a desire to add a \"dry_run\" parameter to\n         create_branch() that will validate whether or not a branch can be\n    -    created without actually creating it - this behavior be used in a\n    +    created without actually creating it - this behavior will be used in a\n         subsequent commit that adds `git branch --recurse-submodules topic`.\n     \n         Adding \"dry_run\" is not obvious because create_branch() is also used to\n    @@ branch.c: N_(\"\\n\"\n     @@ branch.c: void create_branch(struct repository *r,\n      \n      \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n    - \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n    + \t\tdie(_(\"not a valid branch point: '%s'\"), start_name);\n     -\toidcpy(&oid, &commit->object.oid);\n     +\tif (out_real_ref)\n     +\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n5:  e22a177cb7 ! 2:  c543c1412a branch: make create_branch() always create a branch\n    @@ Commit message\n     \n         create_branch() was formerly used to set tracking without creating a\n         branch. Since the previous commit replaces this use case with\n    -    setup_tracking(), we can simplify create_branch() so that it always\n    -    creates a branch.\n    +    dwim_and_setup_tracking(), we can simplify create_branch() so that it\n    +    always creates a branch.\n     \n         Do this simplification, in particular:\n     \n6:  8a895aa401 ! 3:  dddd434d7a branch: add a dry_run parameter to create_branch()\n    @@ builtin/checkout.c: static void update_refs_for_switch(const struct checkout_opt\n     -\t\t\t\t      opts->track);\n     +\t\t\t\t      opts->track,\n     +\t\t\t\t      0);\n    - \t\tnew_branch_info->name = opts->new_branch;\n    - \t\tsetup_branch_path(new_branch_info);\n    - \t}\n    + \t\tfree(new_branch_info->name);\n    + \t\tfree(new_branch_info->refname);\n    + \t\tnew_branch_info->name = xstrdup(opts->new_branch);\n7:  971c53ec85 = 4:  41cca3bd52 builtin/branch: clean up action-picking logic in cmd_branch()\n8:  cd88f3ad92 ! 5:  540eeab183 branch: add --recurse-submodules option for branch creation\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\ttest_must_fail git rev-parse branch-a &&\n     +\n     +\t\tcat >expected <<-EOF &&\n    -+\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n    ++\t\tsubmodule ${SQ}sub${SQ}: fatal: a branch named ${SQ}branch-a${SQ} already exists\n     +\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n     +\t\tEOF\n     +\t\ttest_cmp expected actual\n\nbase-commit: a1eb3ee8288c96c95d18fef027fc276b5cb3b17a\n-- \n2.33.GIT\n\n"},{"id":"444277","messageId":"20211216233324.65126-2-chooglen@google.com","threadId":"56962","inReplyTo":"20211216233324.65126-1-chooglen@google.com","subject":"[PATCH v5 1/5] branch: move --set-upstream-to behavior to dwim_and_setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T23:33:20Z","receivedAt":"2021-12-16T23:33:35Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"This refactor is motivated by a desire to add a \"dry_run\" parameter to\ncreate_branch() that will validate whether or not a branch can be\ncreated without actually creating it - this behavior will be used in a\nsubsequent commit that adds `git branch --recurse-submodules topic`.\n\nAdding \"dry_run\" is not obvious because create_branch() is also used to\nset tracking information without creating a branch, i.e. when using\n--set-upstream-to. This appears to be a leftover from 4fc5006676 (Add\nbranch --set-upstream, 2010-01-18), when --set-upstream would sometimes\ncreate a branch and sometimes update tracking information without\ncreating a branch. However, we no longer support --set-upstream, so it\nmakes more sense to set tracking information with another function and\nuse create_branch() only to create branches. In a later commit, we will\nremove the now-unnecessary logic from create_branch() so that \"dry_run\"\nbecomes trivial to implement.\n\nIntroduce dwim_and_setup_tracking(), which replaces create_branch()\nin `git branch --set-upstream-to`. Ensure correctness by moving the DWIM\nand branch validation logic from create_branch() into a helper function,\ndwim_branch_start(), so that the logic is shared by both functions.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c         | 87 ++++++++++++++++++++++++++++++++++++------------\n branch.h         | 22 ++++++++++++\n builtin/branch.c |  9 ++---\n 3 files changed, 91 insertions(+), 27 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex eed8665b76..54e989f3c8 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -212,9 +212,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n }\n \n /*\n- * This is called when new_ref is branched off of orig_ref, and tries\n- * to infer the settings for branch.<new_ref>.{remote,merge} from the\n- * config.\n+ * Used internally to set the branch.<new_ref>.{remote,merge} config\n+ * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike\n+ * dwim_and_setup_tracking(), this does not do DWIM, i.e. \"origin/main\"\n+ * will not be expanded to \"refs/remotes/origin/main\", so it is not safe\n+ * for 'orig_ref' to be raw user input.\n  */\n static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t   enum branch_track track, int quiet)\n@@ -239,7 +241,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tcase BRANCH_TRACK_INHERIT:\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\treturn;\n+\t\t\tgoto cleanup;\n \t\t}\n \n \tif (tracking.matches > 1)\n@@ -252,6 +254,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t      tracking.srcs) < 0)\n \t\texit(-1);\n \n+cleanup:\n \tstring_list_clear(tracking.srcs, 0);\n }\n \n@@ -340,31 +343,37 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+/**\n+ * DWIMs a user-provided ref to determine the starting point for a\n+ * branch and validates it, where:\n+ *\n+ *   - r is the repository to validate the branch for\n+ *\n+ *   - start_name is the ref that we would like to test. This is\n+ *     expanded with DWIM and assigned to out_real_ref.\n+ *\n+ *   - track is the tracking mode of the new branch. If tracking is\n+ *     explicitly requested, start_name must be a branch (because\n+ *     otherwise start_name cannot be tracked)\n+ *\n+ *   - out_oid is an out parameter containing the object_id of start_name\n+ *\n+ *   - out_real_ref is an out parameter containing the full, 'real' form\n+ *     of start_name e.g. refs/heads/main instead of main\n+ *\n+ */\n+static void dwim_branch_start(struct repository *r, const char *start_name,\n+\t\t\t   enum branch_track track, char **out_real_ref,\n+\t\t\t   struct object_id *out_oid)\n {\n \tstruct commit *commit;\n \tstruct object_id oid;\n \tchar *real_ref;\n-\tstruct strbuf ref = STRBUF_INIT;\n-\tint forcing = 0;\n-\tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n \n \tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n \t\texplicit_tracking = 1;\n \n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n-\t}\n-\n \treal_ref = NULL;\n \tif (get_oid_mb(start_name, &oid)) {\n \t\tif (explicit_tracking) {\n@@ -401,7 +410,34 @@ void create_branch(struct repository *r,\n \n \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n \t\tdie(_(\"not a valid branch point: '%s'\"), start_name);\n-\toidcpy(&oid, &commit->object.oid);\n+\tif (out_real_ref)\n+\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n+\tif (out_oid)\n+\t\toidcpy(out_oid, &commit->object.oid);\n+\n+\tFREE_AND_NULL(real_ref);\n+}\n+\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track)\n+{\n+\tstruct object_id oid;\n+\tchar *real_ref;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tint forcing = 0;\n+\tint dont_change_ref = 0;\n+\n+\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n+\t    ? validate_branchname(name, &ref)\n+\t    : validate_new_branchname(name, &ref, force)) {\n+\t\tif (!force)\n+\t\t\tdont_change_ref = 1;\n+\t\telse\n+\t\t\tforcing = 1;\n+\t}\n+\n+\tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -435,6 +471,15 @@ void create_branch(struct repository *r,\n \tfree(real_ref);\n }\n \n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet)\n+{\n+\tchar *real_orig_ref;\n+\tdwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL);\n+\tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 815dcd40c0..ab2315c611 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -18,6 +18,28 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+/**\n+ * Sets branch.<new_ref>.{remote,merge} config settings such that\n+ * new_ref tracks orig_ref according to the specified tracking mode.\n+ *\n+ *   - new_ref is the name of the branch that we are setting tracking\n+ *     for.\n+ *\n+ *   - orig_ref is the name of the ref that is 'upstream' of new_ref.\n+ *     orig_ref will be expanded with DWIM so that the config settings\n+ *     are in the correct format e.g. \"refs/remotes/origin/main\" instead\n+ *     of \"origin/main\".\n+ *\n+ *   - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes\n+ *     new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT\n+ *     causes new_ref to track whatever orig_ref tracks.\n+ *\n+ *   - quiet suppresses tracking information.\n+ */\n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet);\n+\n /*\n  * Creates a new branch, where:\n  *\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e99bdae897..4a9e0f5727 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -828,12 +828,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (!ref_exists(branch->refname))\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \n-\t\t/*\n-\t\t * create_branch takes care of setting up the tracking\n-\t\t * info and making sure new_upstream is correct\n-\t\t */\n-\t\tcreate_branch(the_repository, branch->name, new_upstream,\n-\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n+\t\tdwim_and_setup_tracking(the_repository, branch->name,\n+\t\t\t\t\tnew_upstream, BRANCH_TRACK_OVERRIDE,\n+\t\t\t\t\tquiet);\n \t} else if (unset_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n \t\tstruct strbuf buf = STRBUF_INIT;\n-- \n2.33.GIT\n\n"},{"id":"444278","messageId":"20211216233324.65126-3-chooglen@google.com","threadId":"56962","inReplyTo":"20211216233324.65126-1-chooglen@google.com","subject":"[PATCH v5 2/5] branch: make create_branch() always create a branch","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T23:33:21Z","receivedAt":"2021-12-16T23:33:36Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"create_branch() was formerly used to set tracking without creating a\nbranch. Since the previous commit replaces this use case with\ndwim_and_setup_tracking(), we can simplify create_branch() so that it\nalways creates a branch.\n\nDo this simplification, in particular:\n\n* remove the special handling of BRANCH_TRACK_OVERRIDE because it is no\n  longer used\n* assert that clobber_head_ok can only be provided with force\n* check that we're handling clobber_head_ok and force correctly by\n  introducing tests for `git branch --force`\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c          | 55 +++++++++++++++++++++--------------------------\n branch.h          |  4 ++--\n t/t3200-branch.sh | 17 +++++++++++++++\n 3 files changed, 44 insertions(+), 32 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 54e989f3c8..6f474a5505 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -426,15 +426,17 @@ void create_branch(struct repository *r, const char *name,\n \tchar *real_ref;\n \tstruct strbuf ref = STRBUF_INIT;\n \tint forcing = 0;\n-\tint dont_change_ref = 0;\n-\n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *msg;\n+\n+\tif (clobber_head_ok && !force)\n+\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n+\n+\tif (clobber_head_ok ?\n+\t\t\t  validate_branchname(name, &ref) :\n+\t\t\t  validate_new_branchname(name, &ref, force)) {\n+\t\tforcing = 1;\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n@@ -442,27 +444,20 @@ void create_branch(struct repository *r, const char *name,\n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n-\tif (!dont_change_ref) {\n-\t\tstruct ref_transaction *transaction;\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\t\tchar *msg;\n-\n-\t\tif (forcing)\n-\t\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n-\t\telse\n-\t\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n-\n-\t\ttransaction = ref_transaction_begin(&err);\n-\t\tif (!transaction ||\n-\t\t    ref_transaction_update(transaction, ref.buf,\n-\t\t\t\t\t   &oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t   0, msg, &err) ||\n-\t\t    ref_transaction_commit(transaction, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\t\tref_transaction_free(transaction);\n-\t\tstrbuf_release(&err);\n-\t\tfree(msg);\n-\t}\n+\tif (forcing)\n+\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n+\telse\n+\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t\tref_transaction_update(transaction, ref.buf,\n+\t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n+\t\t\t\t\t0, msg, &err) ||\n+\t\tref_transaction_commit(transaction, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tfree(msg);\n \n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\ndiff --git a/branch.h b/branch.h\nindex ab2315c611..cf3a4d3ff3 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -52,8 +52,8 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *\n  *   - force enables overwriting an existing (non-head) branch\n  *\n- *   - clobber_head_ok allows the currently checked out (hence existing)\n- *     branch to be overwritten; without 'force', it has no effect.\n+ *   - clobber_head_ok, when enabled with 'force', allows the currently\n+ *     checked out (head) branch to be overwritten\n  *\n  *   - reflog creates a reflog for the branch\n  *\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 1bc3795847..7a0ff75ba8 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n \tgit branch abc && test_path_is_file .git/refs/heads/abc\n '\n \n+test_expect_success 'git branch abc should fail when abc exists' '\n+\ttest_must_fail git branch abc\n+'\n+\n+test_expect_success 'git branch --force abc should fail when abc is checked out' '\n+\ttest_when_finished git switch main &&\n+\tgit switch abc &&\n+\ttest_must_fail git branch --force abc HEAD~1\n+'\n+\n+test_expect_success 'git branch --force abc should succeed when abc exists' '\n+\tgit rev-parse HEAD~1 >expect &&\n+\tgit branch --force abc HEAD~1 &&\n+\tgit rev-parse abc >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n '\n-- \n2.33.GIT\n\n"},{"id":"444279","messageId":"20211216233324.65126-4-chooglen@google.com","threadId":"56962","inReplyTo":"20211216233324.65126-1-chooglen@google.com","subject":"[PATCH v5 3/5] branch: add a dry_run parameter to create_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T23:33:22Z","receivedAt":"2021-12-16T23:33:36Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a dry_run parameter to create_branch() such that dry_run = 1 will\nvalidate a new branch without trying to create it. This will be used in\n`git branch --recurse-submodules` to ensure that the new branch can be\ncreated in all submodules.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c           | 5 ++++-\n branch.h           | 9 +++++----\n builtin/branch.c   | 2 +-\n builtin/checkout.c | 3 ++-\n 4 files changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 6f474a5505..047d9958fc 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -420,7 +420,7 @@ static void dwim_branch_start(struct repository *r, const char *start_name,\n \n void create_branch(struct repository *r, const char *name,\n \t\t   const char *start_name, int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track)\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run)\n {\n \tstruct object_id oid;\n \tchar *real_ref;\n@@ -440,6 +440,8 @@ void create_branch(struct repository *r, const char *name,\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n+\tif (dry_run)\n+\t\tgoto cleanup;\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -462,6 +464,7 @@ void create_branch(struct repository *r, const char *name,\n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n \n+cleanup:\n \tstrbuf_release(&ref);\n \tfree(real_ref);\n }\ndiff --git a/branch.h b/branch.h\nindex cf3a4d3ff3..8009266343 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -62,11 +62,12 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *   - track causes the new branch to be configured to merge the remote branch\n  *     that start_name is a tracking branch for (if any).\n  *\n+ *   - dry_run causes the branch to be validated but not created.\n+ *\n  */\n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track);\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n \n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 4a9e0f5727..d785ebdba0 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -864,7 +864,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \t\tcreate_branch(the_repository,\n \t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track);\n+\t\t\t      force, 0, reflog, quiet, track, 0);\n \n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex a1ba3e2b56..da183724d3 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -905,7 +905,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t      opts->new_branch_force ? 1 : 0,\n \t\t\t\t      opts->new_branch_log,\n \t\t\t\t      opts->quiet,\n-\t\t\t\t      opts->track);\n+\t\t\t\t      opts->track,\n+\t\t\t\t      0);\n \t\tfree(new_branch_info->name);\n \t\tfree(new_branch_info->refname);\n \t\tnew_branch_info->name = xstrdup(opts->new_branch);\n-- \n2.33.GIT\n\n"},{"id":"444280","messageId":"20211216233324.65126-5-chooglen@google.com","threadId":"56962","inReplyTo":"20211216233324.65126-1-chooglen@google.com","subject":"[PATCH v5 4/5] builtin/branch: clean up action-picking logic in cmd_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T23:33:23Z","receivedAt":"2021-12-16T23:33:40Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a variable to cmd_branch() that will tell us whether or not\ncmd_branch() will default to creating a branch (instead of performing\nanother action). Besides making the function more explicit, this allows\nus to validate options that can only be used when creating a branch.\nSuch an option does not exist yet, but one will be introduced in a\nsubsequent commit.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 19 +++++++++++--------\n 1 file changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex d785ebdba0..6c0069bf00 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -621,14 +621,15 @@ static int edit_branch_description(const char *branch_name)\n \n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n-\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n-\tint show_current = 0;\n-\tint reflog = 0, edit_description = 0;\n-\tint quiet = 0, unset_upstream = 0;\n+\t/* possible actions */\n+\tint delete = 0, rename = 0, copy = 0, list = 0,\n+\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n \tconst char *new_upstream = NULL;\n+\tint noncreate_actions = 0;\n+\t/* possible options */\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n-\tint icase = 0;\n \tstatic struct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n@@ -713,8 +714,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)\n \t\tlist = 1;\n \n-\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n-\t    list + edit_description + unset_upstream > 1)\n+\tnoncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +\n+\t\t\t    !!show_current + !!list + !!edit_description +\n+\t\t\t    !!unset_upstream;\n+\tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (filter.abbrev == -1)\n@@ -854,7 +857,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&buf, \"branch.%s.merge\", branch->name);\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n-\t} else if (argc > 0 && argc <= 2) {\n+\t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n-- \n2.33.GIT\n\n"},{"id":"444281","messageId":"20211216233324.65126-6-chooglen@google.com","threadId":"56962","inReplyTo":"20211216233324.65126-1-chooglen@google.com","subject":"[PATCH v5 5/5] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T23:33:24Z","receivedAt":"2021-12-16T23:33:41Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"To improve the submodules UX, we would like to teach Git to handle\nbranches in submodules. Start this process by teaching `git branch` the\n--recurse-submodules option so that `git branch --recurse-submodules\ntopic` will create the \"topic\" branch in the superproject and its\nsubmodules.\n\nAlthough this commit does not introduce breaking changes, it is\nincompatible with existing --recurse-submodules semantics e.g. `git\ncheckout` does not recursively checkout the expected branches created by\n`git branch` yet. To ensure that the correct set of semantics is used,\nthis commit introduces a new configuration value,\n`submodule.propagateBranches`, which enables submodule branching when\ntrue (defaults to false).\n\nThis commit includes changes that allow Git to work with submodules\nthat are in trees (and not just the index):\n\n* add a submodules_of_tree() helper that gives the relevant\n  information of an in-tree submodule (e.g. path and oid) and\n  initializes the repository\n* add is_tree_submodule_active() by adding a treeish_name parameter to\n  is_submodule_active()\n* add the \"submoduleNotUpdated\" advice to advise users to update the\n  submodules in their trees\n\nIncidentally, fix an incorrect usage string that combined the 'list'\nusage of git branch (-l) with the 'create' usage; this string has been\nincorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\nparse_options., 2007-10-07).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  24 ++-\n Documentation/git-branch.txt       |  11 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 130 +++++++++++++\n branch.h                           |  22 +++\n builtin/branch.c                   |  44 ++++-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3207-branch-submodule.sh        | 291 +++++++++++++++++++++++++++++\n 14 files changed, 659 insertions(+), 14 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex 063eec2511..adee26fbbb 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -116,6 +116,9 @@ advice.*::\n \tsubmoduleAlternateErrorStrategyDie::\n \t\tAdvice shown when a submodule.alternateErrorStrategy option\n \t\tconfigured to \"die\" causes a fatal error.\n+\tsubmodulesNotUpdated::\n+\t\tAdvice shown when a user runs a submodule command that fails\n+\t\tbecause `git submodule update --init` was not run.\n \taddIgnoredFile::\n \t\tAdvice shown if a user attempts to add an ignored file to\n \t\tthe index.\ndiff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\nindex 61d975745e..61492d09a8 100644\n--- a/Documentation/config/submodule.txt\n+++ b/Documentation/config/submodule.txt\n@@ -59,11 +59,8 @@ submodule.active::\n \n submodule.recurse::\n \tA boolean indicating if commands should enable the `--recurse-submodules`\n-\toption by default.\n-\tApplies to all commands that support this option\n-\t(`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,\n-\t`restore` and `switch`) except `clone` and `ls-files`.\n-\tDefaults to false.\n+\toption by default. Defaults to false.\n+\t+\n \tWhen set to true, it can be deactivated via the\n \t`--no-recurse-submodules` option. Note that some Git commands\n \tlacking this option may call some of the above commands affected by\n@@ -71,6 +68,23 @@ submodule.recurse::\n \t`git fetch` but does not have a `--no-recurse-submodules` option.\n \tFor these commands a workaround is to temporarily change the\n \tconfiguration value by using `git -c submodule.recurse=0`.\n+\t+\n+\tThe following list shows the commands that accept\n+\t`--recurse-submodules` and whether they are supported by this\n+\tsetting.\n+\t* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n+\t`reset`, `restore` and `switch` are always supported.\n+\t* `clone` and `ls-files` are not supported.\n+\t* `branch` is supported only if `submodule.propagateBranches` is\n+\tenabled\n+\n+submodule.propagateBranches::\n+\t[EXPERIMENTAL] A boolean that enables branching support when\n+\tusing `--recurse-submodules` or `submodule.recurse=true`.\n+\tEnabling this will allow certain commands to accept\n+\t`--recurse-submodules` and certain commands that already accept\n+\t`--recurse-submodules` will now consider branches.\n+\tDefaults to false.\n \n submodule.fetchJobs::\n \tSpecifies how many submodules are fetched/cloned at the same time.\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 2d52ae396b..435dc2b20e 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,8 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f]\n+\t[--recurse-submodules] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -235,6 +236,14 @@ how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n \tDo not set up \"upstream\" configuration, even if the\n \tbranch.autoSetupMerge configuration variable is set.\n \n+--recurse-submodules::\n+\tTHIS OPTION IS EXPERIMENTAL! Causes the current command to\n+\trecurse into submodules if `submodule.propagateBranches` is\n+\tenabled. See `submodule.propagateBranches` in\n+\tlinkgit:git-config[1].\n+\t+\n+\tCurrently, only branch creation is supported.\n+\n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\n \tPlease use `--track` or `--set-upstream-to` instead.\ndiff --git a/advice.c b/advice.c\nindex 1dfc91d176..e00d30254c 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -70,6 +70,7 @@ static struct {\n \t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n \t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n \t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n+\t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n };\ndiff --git a/advice.h b/advice.h\nindex 601265fd10..a7521d6087 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -44,6 +44,7 @@ struct string_list;\n \tADVICE_STATUS_HINTS,\n \tADVICE_STATUS_U_OPTION,\n \tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_SUBMODULES_NOT_UPDATED,\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\ndiff --git a/branch.c b/branch.c\nindex 047d9958fc..78cf5af8e2 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -8,6 +8,8 @@\n #include \"sequencer.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n+#include \"submodule-config.h\"\n+#include \"run-command.h\"\n \n struct tracking {\n \tstruct refspec_item spec;\n@@ -478,6 +480,134 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n \tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n }\n \n+/**\n+ * Creates a branch in a submodule by calling\n+ * create_branches_recursively() in a child process. The child process\n+ * is necessary because install_branch_config() (and its variants) do\n+ * not support writing configs to submodules.\n+ */\n+static int submodule_create_branch(struct repository *r,\n+\t\t\t\t   const struct submodule *submodule,\n+\t\t\t\t   const char *name, const char *start_oid,\n+\t\t\t\t   const char *start_name, int force,\n+\t\t\t\t   int reflog, int quiet,\n+\t\t\t\t   enum branch_track track, int dry_run)\n+{\n+\tint ret = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tstruct strbuf child_err = STRBUF_INIT;\n+\tstruct strbuf out_buf = STRBUF_INIT;\n+\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n+\tchild.git_cmd = 1;\n+\tchild.err = -1;\n+\tchild.stdout_to_stderr = 1;\n+\n+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n+\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n+\tif (dry_run)\n+\t\tstrvec_push(&child.args, \"--dry-run\");\n+\tif (force)\n+\t\tstrvec_push(&child.args, \"--force\");\n+\tif (quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\tif (reflog)\n+\t\tstrvec_push(&child.args, \"--create-reflog\");\n+\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n+\t\tstrvec_push(&child.args, \"--track\");\n+\n+\tstrvec_pushl(&child.args, name, start_oid, start_name, NULL);\n+\n+\tif ((ret = start_command(&child)))\n+\t\treturn ret;\n+\tret = finish_command(&child);\n+\tstrbuf_read(&child_err, child.err, 0);\n+\tstrbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);\n+\n+\tif (ret)\n+\t\tfprintf(stderr, \"%s\", out_buf.buf);\n+\telse\n+\t\tprintf(\"%s\", out_buf.buf);\n+\n+\tstrbuf_release(&child_err);\n+\tstrbuf_release(&out_buf);\n+\treturn ret;\n+}\n+\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run)\n+{\n+\tint i = 0;\n+\tchar *branch_point = NULL;\n+\tstruct object_id super_oid;\n+\tstruct submodule_entry_list submodule_entry_list;\n+\n+\t/* Perform dwim on start_name to get super_oid and branch_point. */\n+\tdwim_branch_start(r, start_name, BRANCH_TRACK_NEVER, &branch_point,\n+\t\t\t  &super_oid);\n+\n+\t/*\n+\t * If we were not given an explicit name to track, then assume we are at\n+\t * the top level and, just like the non-recursive case, the tracking\n+\t * name is the branch point.\n+\t */\n+\tif (!tracking_name)\n+\t\ttracking_name = branch_point;\n+\n+\tsubmodules_of_tree(r, &super_oid, &submodule_entry_list);\n+\t/*\n+\t * Before creating any branches, first check that the branch can\n+\t * be created in every submodule.\n+\t */\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n+\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n+\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n+\t\t\t\t       start_name);\n+\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name);\n+\t\t}\n+\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t}\n+\n+\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n+\t\t      BRANCH_TRACK_NEVER, dry_run);\n+\tif (dry_run)\n+\t\treturn;\n+\t/*\n+\t * NEEDSWORK If tracking was set up in the superproject but not the\n+\t * submodule, users might expect \"git branch --recurse-submodules\" to\n+\t * fail or give a warning, but this is not yet implemented because it is\n+\t * tedious to determine whether or not tracking was set up in the\n+\t * superproject.\n+\t */\n+\tsetup_tracking(name, tracking_name, track, quiet);\n+\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t\trepo_clear(submodule_entry_list.entries[i].repo);\n+\t}\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 8009266343..63448e4278 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -69,6 +69,28 @@ void create_branch(struct repository *r, const char *name,\n \t\t   const char *start_name, int force, int clobber_head_ok,\n \t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n \n+/*\n+ * Creates a new branch in repository and its submodules (and its\n+ * submodules, recursively). Besides these exceptions, the parameters\n+ * function identically to create_branch():\n+ *\n+ * - start_name is the name of the ref, in repository r, that the new\n+ *   branch should start from. In submodules, branches will start from\n+ *   the respective gitlink commit ids in start_name's tree.\n+ *\n+ * - tracking_name is the name used of the ref that will be used to set\n+ *   up tracking, e.g. origin/main. This is propagated to submodules so\n+ *   that tracking information will appear as if the branch branched off\n+ *   tracking_name instead of start_name (which is a plain commit id for\n+ *   submodules). If omitted, start_name is used for tracking (just like\n+ *   create_branch()).\n+ *\n+ */\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run);\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 6c0069bf00..47155e126f 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -27,7 +27,8 @@\n \n static const char * const builtin_branch_usage[] = {\n \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n-\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n@@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = {\n \n static const char *head;\n static struct object_id head_oid;\n+static int recurse_submodules = 0;\n+static int submodule_propagate_branches = 0;\n \n static int branch_use_color = -1;\n static char branch_colors[][COLOR_MAXLEN] = {\n@@ -99,6 +102,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\t\treturn config_error_nonbool(var);\n \t\treturn color_parse(value, branch_colors[slot]);\n \t}\n+\tif (!strcmp(var, \"submodule.recurse\")) {\n+\t\trecurse_submodules = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcasecmp(var, \"submodule.propagateBranches\")) {\n+\t\tsubmodule_propagate_branches = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_color_default_config(var, value, cb);\n }\n \n@@ -627,7 +639,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tconst char *new_upstream = NULL;\n \tint noncreate_actions = 0;\n \t/* possible options */\n-\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0,\n+\t    recurse_submodules_explicit = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n \tstatic struct ref_sorting *sorting;\n@@ -678,6 +691,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n+\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT_END(),\n \t};\n@@ -720,6 +734,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n+\tif (recurse_submodules_explicit) {\n+\t\tif (!submodule_propagate_branches)\n+\t\t\tdie(_(\"branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\"));\n+\t\tif (noncreate_actions)\n+\t\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n+\t}\n+\n+\trecurse_submodules =\n+\t\t(recurse_submodules || recurse_submodules_explicit) &&\n+\t\tsubmodule_propagate_branches;\n+\n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n \tfilter.ignore_case = icase;\n@@ -858,6 +883,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n+\t\tconst char *branch_name = argv[0];\n+\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n+\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n@@ -865,10 +893,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (track == BRANCH_TRACK_OVERRIDE)\n \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n \n-\t\tcreate_branch(the_repository,\n-\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track, 0);\n-\n+\t\tif (recurse_submodules) {\n+\t\t\tcreate_branches_recursively(the_repository, branch_name,\n+\t\t\t\t\t\t    start_name, NULL, force,\n+\t\t\t\t\t\t    reflog, quiet, track, 0);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n+\t\t\t      reflog, quiet, track, 0);\n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 6a66c8363c..cc9a2bdbaf 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -20,6 +20,7 @@\n #include \"diff.h\"\n #include \"object-store.h\"\n #include \"advice.h\"\n+#include \"branch.h\"\n \n #define OPT_QUIET (1 << 0)\n #define OPT_CACHED (1 << 1)\n@@ -2995,6 +2996,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n \treturn !!ret;\n }\n \n+static int module_create_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tenum branch_track track;\n+\tint quiet = 0, force = 0, reflog = 0, dry_run = 0;\n+\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n+\t\tOPT__FORCE(&force, N_(\"force creation\"), 0),\n+\t\tOPT_BOOL(0, \"create-reflog\", &reflog,\n+\t\t\t N_(\"create the branch's reflog\")),\n+\t\tOPT_SET_INT('t', \"track\", &track,\n+\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\t    BRANCH_TRACK_EXPLICIT),\n+\t\tOPT__DRY_RUN(&dry_run,\n+\t\t\t     N_(\"show whether the branch would be created\")),\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>\"),\n+\t\tNULL\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\ttrack = git_branch_track;\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\n+\tif (argc != 3)\n+\t\tusage_with_options(usage, options);\n+\n+\tif (!quiet && !dry_run)\n+\t\tprintf_ln(_(\"creating branch '%s'\"), argv[0]);\n+\n+\tcreate_branches_recursively(the_repository, argv[0], argv[1], argv[2],\n+\t\t\t\t    force, reflog, quiet, track, dry_run);\n+\treturn 0;\n+}\n struct add_data {\n \tconst char *prefix;\n \tconst char *branch;\n@@ -3401,6 +3438,7 @@ static struct cmd_struct commands[] = {\n \t{\"config\", module_config, 0},\n \t{\"set-url\", module_set_url, 0},\n \t{\"set-branch\", module_set_branch, 0},\n+\t{\"create-branch\", module_create_branch, 0},\n };\n \n int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f95344028b..24b8d1a700 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -7,6 +7,7 @@\n #include \"strbuf.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n+#include \"tree-walk.h\"\n \n /*\n  * submodule cache lookup structure\n@@ -726,6 +727,65 @@ const struct submodule *submodule_from_path(struct repository *r,\n \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n+/**\n+ * Used internally by submodules_of_tree(). Recurses into 'treeish_name'\n+ * and appends submodule entries to 'out'. The submodule_cache expects\n+ * a root-level treeish_name and paths, so keep track of these values\n+ * with 'root_tree' and 'prefix'.\n+ */\n+static void traverse_tree_submodules(struct repository *r,\n+\t\t\t\t     const struct object_id *root_tree,\n+\t\t\t\t     char *prefix,\n+\t\t\t\t     const struct object_id *treeish_name,\n+\t\t\t\t     struct submodule_entry_list *out)\n+{\n+\tstruct tree_desc tree;\n+\tstruct submodule_tree_entry *st_entry;\n+\tstruct name_entry *name_entry;\n+\tchar *tree_path = NULL;\n+\n+\tname_entry = xmalloc(sizeof(*name_entry));\n+\n+\tfill_tree_descriptor(r, &tree, treeish_name);\n+\twhile (tree_entry(&tree, name_entry)) {\n+\t\tif (prefix)\n+\t\t\ttree_path =\n+\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n+\t\telse\n+\t\t\ttree_path = xstrdup(name_entry->path);\n+\n+\t\tif (S_ISGITLINK(name_entry->mode) &&\n+\t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n+\t\t\tst_entry = xmalloc(sizeof(*st_entry));\n+\t\t\tst_entry->name_entry = name_entry;\n+\t\t\tst_entry->submodule =\n+\t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n+\t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n+\t\t\tif (repo_submodule_init(st_entry->repo, r, tree_path,\n+\t\t\t\t\t\troot_tree))\n+\t\t\t\tFREE_AND_NULL(st_entry->repo);\n+\n+\t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n+\t\t\t\t   out->entry_alloc);\n+\t\t\tout->entries[out->entry_nr++] = *st_entry;\n+\t\t} else if (S_ISDIR(name_entry->mode))\n+\t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n+\t\t\t\t\t\t &name_entry->oid, out);\n+\t\tfree(tree_path);\n+\t}\n+}\n+\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *out)\n+{\n+\tCALLOC_ARRAY(out->entries, 0);\n+\tout->entry_nr = 0;\n+\tout->entry_alloc = 0;\n+\n+\ttraverse_tree_submodules(r, treeish_name, NULL, treeish_name, out);\n+}\n+\n void submodule_free(struct repository *r)\n {\n \tif (r->submodule_cache)\ndiff --git a/submodule-config.h b/submodule-config.h\nindex 65875b94ea..fa229a8b97 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"submodule.h\"\n #include \"strbuf.h\"\n+#include \"tree-walk.h\"\n \n /**\n  * The submodule config cache API allows to read submodule\n@@ -101,4 +102,37 @@ int check_submodule_name(const char *name);\n void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n void update_clone_config_from_gitmodules(int *max_jobs);\n \n+/*\n+ * Submodule entry that contains relevant information about a\n+ * submodule in a tree.\n+ */\n+struct submodule_tree_entry {\n+\t/* The submodule's tree entry. */\n+\tstruct name_entry *name_entry;\n+\t/*\n+\t * A struct repository corresponding to the submodule. May be\n+\t * NULL if the submodule has not been updated.\n+\t */\n+\tstruct repository *repo;\n+\t/*\n+\t * A struct submodule containing the submodule config in the\n+\t * tree's .gitmodules.\n+\t */\n+\tconst struct submodule *submodule;\n+};\n+\n+struct submodule_entry_list {\n+\tstruct submodule_tree_entry *entries;\n+\tint entry_nr;\n+\tint entry_alloc;\n+};\n+\n+/**\n+ * Given a treeish, return all submodules in the tree and its subtrees,\n+ * but excluding nested submodules. Callers that require nested\n+ * submodules are expected to recurse into the submodules themselves.\n+ */\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *ret);\n #endif /* SUBMODULE_CONFIG_H */\ndiff --git a/submodule.c b/submodule.c\nindex ad95cdda07..95af84347d 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -267,7 +267,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n  * ie, the config looks like: \"[submodule] active\\n\".\n  * Since that is an invalid pathspec, we should inform the user.\n  */\n-int is_submodule_active(struct repository *repo, const char *path)\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path)\n {\n \tint ret = 0;\n \tchar *key = NULL;\n@@ -275,7 +277,7 @@ int is_submodule_active(struct repository *repo, const char *path)\n \tconst struct string_list *sl;\n \tconst struct submodule *module;\n \n-\tmodule = submodule_from_path(repo, null_oid(), path);\n+\tmodule = submodule_from_path(repo, treeish_name, path);\n \n \t/* early return if there isn't a path->module mapping */\n \tif (!module)\n@@ -317,6 +319,11 @@ int is_submodule_active(struct repository *repo, const char *path)\n \treturn ret;\n }\n \n+int is_submodule_active(struct repository *repo, const char *path)\n+{\n+\treturn is_tree_submodule_active(repo, null_oid(), path);\n+}\n+\n int is_submodule_populated_gently(const char *path, int *return_error_code)\n {\n \tint ret = 0;\ndiff --git a/submodule.h b/submodule.h\nindex 6bd2c99fd9..784ceffc0e 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb);\n struct option;\n int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n \t\t\t\t\t\t     const char *arg, int unset);\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path);\n int is_submodule_active(struct repository *repo, const char *path);\n /*\n  * Determine if a submodule has been populated at a given 'path' by checking if\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nnew file mode 100755\nindex 0000000000..0407220f5c\n--- /dev/null\n+++ b/t/t3207-branch-submodule.sh\n@@ -0,0 +1,291 @@\n+#!/bin/sh\n+\n+test_description='git branch submodule tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup superproject and submodule' '\n+\tgit init super &&\n+\ttest_commit foo &&\n+\tgit init sub-sub-upstream &&\n+\ttest_commit -C sub-sub-upstream foo &&\n+\tgit init sub-upstream &&\n+\t# Submodule in a submodule\n+\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n+\tgit -C sub-upstream commit -m \"add submodule\" &&\n+\t# Regular submodule\n+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n+\t# Submodule in a subdirectory\n+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" second/sub &&\n+\tgit -C super commit -m \"add submodule\" &&\n+\tgit -C super config submodule.propagateBranches true &&\n+\tgit -C super/sub submodule update --init\n+'\n+\n+CLEANUP_SCRIPT_PATH=\"$TRASH_DIRECTORY/cleanup_branches.sh\"\n+\n+cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n+\t#!/bin/sh\n+\n+\tsuper_dir=\"$1\"\n+\tshift\n+\t(\n+\t\tcd \"$super_dir\" &&\n+\t\tgit checkout main &&\n+\t\tfor branch_name in \"$@\"; do\n+\t\t\tgit branch -D \"$branch_name\"\n+\t\t\tgit submodule foreach \"$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true\"\n+\t\tdone\n+\t)\n+EOF\n+chmod +x \"$CLEANUP_SCRIPT_PATH\"\n+\n+cleanup_branches() {\n+\tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n+} >/dev/null 2>/dev/null\n+\n+# Test the argument parsing\n+test_expect_success '--recurse-submodules should create branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a &&\n+\t\tgit -C sub/sub-sub rev-parse branch-a &&\n+\t\tgit -C second/sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\techo \"fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\" >expected &&\n+\t\ttest_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should fail when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n+\t\t# Assert that the branches were not deleted\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should respect submodule.recurse when creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c submodule.recurse=true branch branch-a &&\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should ignore submodule.recurse when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit -c submodule.recurse=true branch -D branch-a &&\n+\t\ttest_must_fail git rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+# Test branch creation behavior\n+test_expect_success 'should create branches based off commit id in superproject' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit checkout --recurse-submodules branch-a &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\t# Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a\n+\t\tgit -C sub checkout branch-a &&\n+\t\ttest_commit -C sub bar &&\n+\t\t# Create a new branch-b branch with start-point=branch-a\n+\t\tgit branch --recurse-submodules branch-b branch-a &&\n+\t\tgit rev-parse branch-b &&\n+\t\tgit -C sub rev-parse branch-b >actual &&\n+\t\t# Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should not create any branches if branch is not valid for all repos' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub branch branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_must_fail git rev-parse branch-a &&\n+\n+\t\tcat >expected <<-EOF &&\n+\t\tsubmodule ${SQ}sub${SQ}: fatal: a branch named ${SQ}branch-a${SQ} already exists\n+\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branches if branch exists and --force is given' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\ttest_commit -C sub baz &&\n+\t\tgit -C sub branch branch-a HEAD~1 &&\n+\t\tgit branch --recurse-submodules --force branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\t# assert that sub:branch-a was moved\n+\t\tgit -C sub rev-parse branch-a >actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\tgit -C sub2 submodule update --init &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\" &&\n+\t\tgit checkout branch-a &&\n+\t\tgit branch --recurse-submodules branch-c branch-b &&\n+\t\tgit rev-parse branch-c &&\n+\t\tgit -C sub rev-parse branch-c &&\n+\t\tgit -C second/sub rev-parse branch-c &&\n+\t\tgit checkout --recurse-submodules branch-c &&\n+\t\tgit -C sub2 rev-parse branch-c &&\n+\t\tgit -C sub2/sub-sub rev-parse branch-c\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with track=always' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with explicit track' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --track --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should not set up unnecessary tracking of local branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_expect_success 'should not create branches in inactive submodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\ttest_config -C super submodule.sub.active false &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\ttest_must_fail git -C sub branch-a\n+\t)\n+'\n+\n+test_expect_success 'setup remote-tracking tests' '\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\"\n+\t) &&\n+\tgit clone --branch main --recurse-submodules super super-clone &&\n+\tgit -C super-clone config submodule.propagateBranches true\n+'\n+\n+test_expect_success 'should not create branch when submodule is not in .git/modules' '\n+\t# The cleanup needs to delete sub2 separately because main does not have sub2\n+\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n+\t\tgit -C super-clone/sub2/sub-sub branch -D branch-b && \\\n+\t\tcleanup_branches super-clone branch-a branch-b\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\t# This should succeed because super-clone has sub.\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\t# This should fail because super-clone does not have sub2.\n+\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n+\t\tcat >expected <<-EOF &&\n+\t\thint: You may try updating the submodules using ${SQ}git checkout origin/branch-b && git submodule update --init${SQ}\n+\t\tfatal: submodule ${SQ}sub2${SQ}: unable to find submodule\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\t\ttest_must_fail git rev-parse branch-b &&\n+\t\ttest_must_fail git -C sub rev-parse branch-b &&\n+\t\t# User can fix themselves by initializing the submodule\n+\t\tgit checkout origin/branch-b &&\n+\t\tgit submodule update --init --recursive &&\n+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of remote-tracking branches' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\t# \"origin/branch-a\" does not exist for \"sub\", but it matches the refspec\n+\t\t# so tracking should be set up\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.merge)\" = refs/heads/branch-a\n+\t)\n+'\n+\n+test_expect_success 'should not fail when unable to set up tracking in submodule' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a && \\\n+\t\tgit -C super-clone remote rename ex-origin origin\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit remote rename origin ex-origin &&\n+\t\tgit branch --recurse-submodules branch-a ex-origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = ex-origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_done\n-- \n2.33.GIT\n\n"},{"id":"444304","messageId":"xmqq35ms6pd3.fsf@gitster.g","threadId":"56962","inReplyTo":"20211216233324.65126-1-chooglen@google.com","subject":"Re: [PATCH v5 0/5] implement branch --recurse-submodules","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-17T00:34:32Z","receivedAt":"2021-12-17T00:34:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> This series implements branch --recurse-submodules as laid out in the\n> Submodule branching RFC (linked above). If there are concerns about the\n> UX/behavior, I would appreciate feedback on the RFC thread as well :)\n>\n> This series is based off js/branch-track-inherit.\n\nSigh.\n\nWhen a series is labelled as \"based off of X\", I expect that the\nseries either apply on the tip of branch X I have, or it applies on\ntop of the merge result of branch X into 'master'.  It shouldn't be\nforked at a random point on the 'seen' or 'next' branch, as you'd\nend up depending on not just X but all the other topics that are\nmerged before X is merged to these integration branches.\n\nThis seems not apply on either c99fa303 (config: require lowercase\nfor branch.autosetupmerge, 2021-12-14), which is the tip of the\njs/branch-track-inherit topic, or 47e85bee (Merge branch\n'js/branch-track-inherit' into gc/branch-recurse-submodules,\n2021-12-15), which is a merge of that topic into 'master' I prepared\nto queue the previous round of this topic the other day.\n"},{"id":"444305","messageId":"xmqqwnk45aah.fsf@gitster.g","threadId":"56962","inReplyTo":"xmqq35ms6pd3.fsf@gitster.g","subject":"Re: [PATCH v5 0/5] implement branch --recurse-submodules","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-17T00:45:26Z","receivedAt":"2021-12-17T00:45:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>\n>> This series implements branch --recurse-submodules as laid out in the\n>> Submodule branching RFC (linked above). If there are concerns about the\n>> UX/behavior, I would appreciate feedback on the RFC thread as well :)\n>>\n>> This series is based off js/branch-track-inherit.\n>\n> Sigh.\n>\n> When a series is labelled as \"based off of X\", I expect that the\n> series either apply on the tip of branch X I have, or it applies on\n> top of the merge result of branch X into 'master'.  It shouldn't be\n> forked at a random point on the 'seen' or 'next' branch, as you'd\n> end up depending on not just X but all the other topics that are\n> merged before X is merged to these integration branches.\n>\n> This seems not apply on either c99fa303 (config: require lowercase\n> for branch.autosetupmerge, 2021-12-14), which is the tip of the\n> js/branch-track-inherit topic, or 47e85bee (Merge branch\n> 'js/branch-track-inherit' into gc/branch-recurse-submodules,\n> 2021-12-15), which is a merge of that topic into 'master' I prepared\n> to queue the previous round of this topic the other day.\n\nAh, I figured it out.\n\nThese are based on the merge of the other branch into 'seen'.  I\nhave (deliberately) merged js/branch-track-inherit and the previous\nround of this tipc in 'seen' next to each other.\n\nAnd when these five are applied on top of that merge of the other\ntopic into 'seen', we get an identical tree as the merge of the\nprevious round of this topic into 'seen'.\n\nSo unless you updated some commit log message, nothing is lost if I\nignore this round.  Just to save time for both of us the next time,\nplesae fetch from any of the public tree, find on the first parent\nchain leading to 'origin/seen' a commit labelled as \"Merge branch\n'gc/branch-recurse-submodules'\", and check out its second parent,\nand what we have there.\n\n    $ git checkout \"origin/seen^{/^Merge branch .gc/branch-rec}^2\"\n    $ git log --first-parent --oneline origin/main..\n    35bb9f67f9 branch: add --recurse-submodules option for branch creation\n    ce3a710d42 builtin/branch: clean up action-picking logic in cmd_branch()\n    f368230ca9 branch: add a dry_run parameter to create_branch()\n    d77f8a125b branch: make create_branch() always create a branch\n    f8a88a03b9 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n    47e85beee9 Merge branch 'js/branch-track-inherit' into gc/branch-recurse-submodules\n\nIf you \"rebase -i 47e85beee9\" (the exact object name might differ,\nas that commit needs to be recreated when 'js/branch-track-inherit'\nis updated) these five commits, and format-patch everything on the\ntopic with --base=47e85beee9, it is guaranteed that I'll be able to\ncleanly apply what you meant to send out on top of 47e85beee9.\n\nThanks.\n\n\n\n\n\n\n"},{"id":"444522","messageId":"kl6lwnjzytyl.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"xmqqwnk45aah.fsf@gitster.g","subject":"Re: [PATCH v5 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T19:09:06Z","receivedAt":"2021-12-20T19:09:12Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Glen Choo <chooglen@google.com> writes:\n>>\n>>> This series implements branch --recurse-submodules as laid out in the\n>>> Submodule branching RFC (linked above). If there are concerns about the\n>>> UX/behavior, I would appreciate feedback on the RFC thread as well :)\n>>>\n>>> This series is based off js/branch-track-inherit.\n>>\n>> Sigh.\n>>\n>> When a series is labelled as \"based off of X\", I expect that the\n>> series either apply on the tip of branch X I have, or it applies on\n>> top of the merge result of branch X into 'master'.  It shouldn't be\n>> forked at a random point on the 'seen' or 'next' branch, as you'd\n>> end up depending on not just X but all the other topics that are\n>> merged before X is merged to these integration branches.\n>>\n>> This seems not apply on either c99fa303 (config: require lowercase\n>> for branch.autosetupmerge, 2021-12-14), which is the tip of the\n>> js/branch-track-inherit topic, or 47e85bee (Merge branch\n>> 'js/branch-track-inherit' into gc/branch-recurse-submodules,\n>> 2021-12-15), which is a merge of that topic into 'master' I prepared\n>> to queue the previous round of this topic the other day.\n>\n> Ah, I figured it out.\n>\n> These are based on the merge of the other branch into 'seen'.  I\n> have (deliberately) merged js/branch-track-inherit and the previous\n> round of this tipc in 'seen' next to each other.\n\nOh my goodness.. I'm sorry, I didn't mean to complicate matters like\nthis.\n\nIf it's alright with you, I'd like to check my understanding so that I\ncan avoid this mistake in the future.\n\nWhat happened was that I got confused by [1], where it reads:\n\n  [...]\n  find the tip of js/branch-track-inherit from 'seen' [*]\n  [...]\n\n  [Footnote]\n\n  * One way to do so would be:\n\n    $ git fetch\n    $ git show 'remote/origin/seen^{/^Merge branch .js/branch-track-inherit.}'\n\nThe commit that I got was the \"merge of js/branch-track-inherit into\n'seen'\", but what you intended was the \"merge of js/branch-track-inherit\ninto gc/branch-recurse-submodules\"; I didn't realize that there might\nhave been more than commit matching that regex.\n\nThis makes much more sense in the context of your comment:\n\n  That's OK; please do not ever rebase anything on top of 'seen' or\n  'next'.\n\n> And when these five are applied on top of that merge of the other\n> topic into 'seen', we get an identical tree as the merge of the\n> previous round of this topic into 'seen'.\n>\n> So unless you updated some commit log message, nothing is lost if I\n> ignore this round.\n\nI made some commit message changes. Unless you think it's a good idea, I\nwon't re-roll this to fix the issue.\n\n> Just to save time for both of us the next time,\n> plesae fetch from any of the public tree, find on the first parent\n> chain leading to 'origin/seen' a commit labelled as \"Merge branch\n> 'gc/branch-recurse-submodules'\", and check out its second parent,\n> and what we have there.\n>\n>     $ git checkout \"origin/seen^{/^Merge branch .gc/branch-rec}^2\"\n>     $ git log --first-parent --oneline origin/main..\n>     35bb9f67f9 branch: add --recurse-submodules option for branch creation\n>     ce3a710d42 builtin/branch: clean up action-picking logic in cmd_branch()\n>     f368230ca9 branch: add a dry_run parameter to create_branch()\n>     d77f8a125b branch: make create_branch() always create a branch\n>     f8a88a03b9 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n>     47e85beee9 Merge branch 'js/branch-track-inherit' into gc/branch-recurse-submodules\n>\n> If you \"rebase -i 47e85beee9\" (the exact object name might differ,\n> as that commit needs to be recreated when 'js/branch-track-inherit'\n> is updated) these five commits, and format-patch everything on the\n> topic with --base=47e85beee9, it is guaranteed that I'll be able to\n> cleanly apply what you meant to send out on top of 47e85beee9.\n\nSo if my branch were not in 'seen', I should have based my changes on\n'origin/js/branch-track-inherit'. If my branch is in 'seen', I should\nbase it off the merge of js/branch-track-inherit' into my my branch?\n\nI'll continue to use format-patch --base because I see how that can be\nuseful for you.\n\n[1] https://lore.kernel.org/git/xmqqlf0lz6os.fsf@gitster.g/\n"},{"id":"444530","messageId":"xmqq8rwfvyxf.fsf@gitster.g","threadId":"56962","inReplyTo":"kl6lwnjzytyl.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v5 0/5] implement branch --recurse-submodules","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-20T19:50:04Z","receivedAt":"2021-12-20T19:50:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> What happened was that I got confused by [1], where it reads:\n>\n>   [...]\n>   find the tip of js/branch-track-inherit from 'seen' [*]\n>   [...]\n>\n>   [Footnote]\n>\n>   * One way to do so would be:\n>\n>     $ git fetch\n>     $ git show 'remote/origin/seen^{/^Merge branch .js/branch-track-inherit.}'\n>\n> The commit that I got was the \"merge of js/branch-track-inherit into\n> 'seen'\", but what you intended was the \"merge of js/branch-track-inherit\n> into gc/branch-recurse-submodules\"; I didn't realize that there might\n> have been more than commit matching that regex.\n\nYeah, that was not quite clearly written.  The way it was showing\nwas to find the tip of the other branch.  The instruction was to\nprepare you (and others reading from the sidelines) for a case where\nyour branch depends on somebody else's work that is *not* even in\n'seen' (e.g. I may have an older version of 'seen' but there is a\nnewer and clearly improved version on the list that is likely to\nreplace).  In such a case, you'd \n\n (1) \"find\" the tip of the other branch, either by traversing from\n     the tip of 'seen' to find the merge and taking its second\n     parent, or applying the latest from the list to a locally\n     created topic branch forked off of 'main',\n\n (2) create your topic branch, forked off of 'main', and merge (1)\n     into it, and\n\n (3) build your series on it.\n\nIf I have your previous round, and if the other topic you depend on\nhasn't changed, you can omit (2) and instead find the equivalent of\n(2) I created for your topic the last time I queued it.\n\n> I made some commit message changes. Unless you think it's a good idea, I\n> won't re-roll this to fix the issue.\n\nLet's not waste your message changes to clarify the patches.\n\n> So if my branch were not in 'seen', I should have based my changes on\n> 'origin/js/branch-track-inherit'. If my branch is in 'seen', I should\n> base it off the merge of js/branch-track-inherit' into my my branch?\n\nHopefully the above is clear now?  Sorry for the trouble.\n\nThanks.\n\n"},{"id":"444535","messageId":"kl6lr1a7yqev.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"xmqq8rwfvyxf.fsf@gitster.g","subject":"Re: [PATCH v5 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T20:25:44Z","receivedAt":"2021-12-20T20:25:55Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>\n>> What happened was that I got confused by [1], where it reads:\n>>\n>>   [...]\n>>   find the tip of js/branch-track-inherit from 'seen' [*]\n>>   [...]\n>>\n>>   [Footnote]\n>>\n>>   * One way to do so would be:\n>>\n>>     $ git fetch\n>>     $ git show 'remote/origin/seen^{/^Merge branch .js/branch-track-inherit.}'\n>>\n>> The commit that I got was the \"merge of js/branch-track-inherit into\n>> 'seen'\", but what you intended was the \"merge of js/branch-track-inherit\n>> into gc/branch-recurse-submodules\"; I didn't realize that there might\n>> have been more than commit matching that regex.\n>\n> Yeah, that was not quite clearly written.  The way it was showing\n> was to find the tip of the other branch.  The instruction was to\n> prepare you (and others reading from the sidelines) for a case where\n> your branch depends on somebody else's work that is *not* even in\n> 'seen' (e.g. I may have an older version of 'seen' but there is a\n> newer and clearly improved version on the list that is likely to\n> replace).  In such a case, you'd \n>\n>  (1) \"find\" the tip of the other branch, either by traversing from\n>      the tip of 'seen' to find the merge and taking its second\n>      parent, or applying the latest from the list to a locally\n>      created topic branch forked off of 'main',\n>\n>  (2) create your topic branch, forked off of 'main', and merge (1)\n>      into it, and\n>\n>  (3) build your series on it.\n>\n> If I have your previous round, and if the other topic you depend on\n> hasn't changed, you can omit (2) and instead find the equivalent of\n> (2) I created for your topic the last time I queued it.\n>\n>> I made some commit message changes. Unless you think it's a good idea, I\n>> won't re-roll this to fix the issue.\n>\n> Let's not waste your message changes to clarify the patches.\n>\n>> So if my branch were not in 'seen', I should have based my changes on\n>> 'origin/js/branch-track-inherit'. If my branch is in 'seen', I should\n>> base it off the merge of js/branch-track-inherit' into my my branch?\n>\n> Hopefully the above is clear now?  Sorry for the trouble.\n>\n> Thanks.\n\nIt's no trouble for me. I should be thanking you for taking the time to\nmake it clear :) I really appreciate it.\n"},{"id":"444557","messageId":"20211220233459.45739-1-chooglen@google.com","threadId":"56962","inReplyTo":"20211216233324.65126-1-chooglen@google.com","subject":"[PATCH v6 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T23:34:54Z","receivedAt":"2021-12-20T23:35:13Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Submodule branching RFC:\nhttps://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/\n\nOriginal Submodule UX RFC/Discussion:\nhttps://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n\nContributor Summit submodules Notes:\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/\n\nSubmodule UX overhaul updates:\nhttps://lore.kernel.org/git/?q=Submodule+UX+overhaul+update\n\nThis series implements branch --recurse-submodules as laid out in the\nSubmodule branching RFC (linked above). If there are concerns about the\nUX/behavior, I would appreciate feedback on the RFC thread as well :)\n\nv6 fixes v5's bad rebase; it was based off a merge commit in 'seen'\ninstead of js/branch-track-inherit. Since v5 is mostly a no-op, I will\nalso include a range-diff against v4, which is the last version that\nanyone except Junio would care about.\n\nThis version is based off Josh's js/branch-track-inherit v7. Because that\nis not yet in 'seen', I applied those patches onto abe6bb3905 (The first\nbatch to start the current cycle, 2021-11-29). The base commit can be\nfound at https://github.com/chooglen/git/tree/js/branch-track-inherit-v7.\nI really hope I got right this time, but if not, hopefully the paper\ntrail is obvious.\n\nFuture work:\n* `git branch -d --recurse-submodules` so that users can clean up\n  extraneous branches.\n* `git [checkout | switch] --recurse-submodules` +\n  submodule.propagateBranches so that users can actually checkout the\n  branches.\n* After [1], it seems clear that --recurse-submodules parsing could\n  really benefit from some standardization. It's not obvious which\n  RECURSE_SUBMODULE_* enums are applicable to which commands, and there\n  is no way to distinguish between an explicit --recurse-submodules from\n  argv vs submodule.recurse from the config.\n\n  I chose not to use them in this series because their usage is already\n  inconsistent (grep.c doesn't use them either), and it would be _more_\n  confusing to use the enum (handling RECURSE_SUBMODULES_DEFAULT = 1 is\n  trickier than boolean 0 and 1).\n\n  At this point, I think it would be too noisy to introduce the enum,\n  but this would be a nice cleanup to do later.\n* As documented in branch.c, we create branches using a child process\n  only because install_branch_config() does not support submodules.\n  It should be possible to remove the child process once we make the\n  appropriate changes to config.c. I attempted this in [2] but chose to\n  punt it because it was too time-consuming at the time.\n\nChanges since v5:\n* Rebase onto v7 of js/branch-track-inherit\n  (https://lore.kernel.org/git/cover.1639717481.git.steadmon@google.com)\n\nChanges since v4:\n* Rebase correctly onto 'gitster/seen^{/^Merge branch .js/branch-track-inherit.}'\n  (see base-commit) as suggested in [3] (thanks Junio!)\n* These patches were also verified on top of 'next'.\n\nChanges since v3:\n* Split up the old patch 1. Patch 1 had a big diff because it used to\n  move lines, remove dead code and introduce repo_* functions (thanks\n  Jonathan!)\n** repo_* functions have been dropped; they added noise and are not\n   necessary for correctness.\n* Use a new, harder-to-misuse function in --set-upstream-to,\n  dwim_and_setup_tracking(). Now, setup_tracking() never does DWIM and\n  dwim_and_setup_tracking() always does DWIM.\n* Move create_branch() dry_run to its own patch.\n* Fix an oversight where submodules in subtrees were ignored. This was\n  because submodules_of_tree() and tree_entry() didn't recurse into\n  subtrees. Test this accordingly (thanks Jonathan!).\n* cmd_branch() possible actions are more consistently ordered.\n* Documentation fixes (thanks Philippe!).\n* Additional comments and explanation.\n* Drop patch 5 (optional cleanup).\n* Rebase onto js/branch-track-inherit v6.\n\nChanges since v2:\n* Rebase onto js/branch-track-inherit. This series should continue to be\n  the case going forward.\n* Patch 1 has a smaller diff because the introduction of\n  validate_branch_start() no longer changes the function order thanks to a\n  forward declaration. This artificial forward declaration is removed in a\n  patch 2 (which can just be squashed into patch 1).\n* Optional cleanup: fix questionable exit codes in patch 5.\n\nChanges since v1:\n* Move the functionality of \"git branch --dry-run\" into \"git submodule-helper create-branch --dry-run\"\n* Add more fields to the submodules_of_tree() struct to reduce the\n  number of allocations made by the caller. Move this functionality\n  to patch 3 (formerly patch 4) and drop patch 1.\n* Make submodules_of_tree() ignore inactive submodules\n* Structure the output of the submodules a bit better by adding prefixes\n  to the child process' output (instead of inconsistently indenting the\n  output).\n** I wasn't able to find a good way to interleave stdout/stderr\n   correctly, so a less-than-desirable workaround was to route the child\n   process output to stdout/stderr depending on the exit code.\n** Eventually, I would like to structure the output of submodules in a\n   report, as Ævar suggested. But at this stage, I think that it's\n   better to spend time getting user feedback on the submodules\n   branching UX and it'll be easier to standardize the output when we've\n   implemented more of the UX :)\n\n[1] https://lore.kernel.org/git/kl6lbl1p9zjf.fsf@chooglen-macbookpro.roam.corp.google.com/\n[2] https://lore.kernel.org/git/kl6lv90ytd4v.fsf@chooglen-macbookpro.roam.corp.google.com/\n[3] https://lore.kernel.org/git/xmqqlf0lz6os.fsf@gitster.g \n\nGlen Choo (5):\n  branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n  branch: make create_branch() always create a branch\n  branch: add a dry_run parameter to create_branch()\n  builtin/branch: clean up action-picking logic in cmd_branch()\n  branch: add --recurse-submodules option for branch creation\n\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  24 ++-\n Documentation/git-branch.txt       |  11 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 257 ++++++++++++++++++++-----\n branch.h                           |  57 +++++-\n builtin/branch.c                   |  70 +++++--\n builtin/checkout.c                 |   3 +-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3200-branch.sh                  |  17 ++\n t/t3207-branch-submodule.sh        | 291 +++++++++++++++++++++++++++++\n 16 files changed, 805 insertions(+), 76 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\nRange-diff against v5:\n1:  a9d1108b3e ! 1:  29669c57b4 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n    @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n      \n      \tif (tracking.matches > 1)\n     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n    - \t\t\t      tracking.srcs) < 0)\n    + \t\t\t\ttracking.remote, tracking.srcs) < 0)\n      \t\texit(-1);\n      \n     +cleanup:\n    @@ branch.c: N_(\"\\n\"\n     @@ branch.c: void create_branch(struct repository *r,\n      \n      \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n    - \t\tdie(_(\"not a valid branch point: '%s'\"), start_name);\n    + \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n     -\toidcpy(&oid, &commit->object.oid);\n     +\tif (out_real_ref)\n     +\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n2:  c543c1412a = 2:  ac2532a953 branch: make create_branch() always create a branch\n3:  dddd434d7a = 3:  a0ed3fa438 branch: add a dry_run parameter to create_branch()\n4:  41cca3bd52 = 4:  ebded31c96 builtin/branch: clean up action-picking logic in cmd_branch()\n5:  540eeab183 ! 5:  0a7ec6ee75 branch: add --recurse-submodules option for branch creation\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\ttest_must_fail git rev-parse branch-a &&\n     +\n     +\t\tcat >expected <<-EOF &&\n    -+\t\tsubmodule ${SQ}sub${SQ}: fatal: a branch named ${SQ}branch-a${SQ} already exists\n    ++\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n     +\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n     +\t\tEOF\n     +\t\ttest_cmp expected actual\n\nbase-commit: e1c6879cf24f90693ca108542ec1db970764e1dc\n-- \n2.33.GIT\n\n"},{"id":"444558","messageId":"20211220233459.45739-2-chooglen@google.com","threadId":"56962","inReplyTo":"20211220233459.45739-1-chooglen@google.com","subject":"[PATCH v6 1/5] branch: move --set-upstream-to behavior to dwim_and_setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T23:34:55Z","receivedAt":"2021-12-20T23:35:15Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"This refactor is motivated by a desire to add a \"dry_run\" parameter to\ncreate_branch() that will validate whether or not a branch can be\ncreated without actually creating it - this behavior will be used in a\nsubsequent commit that adds `git branch --recurse-submodules topic`.\n\nAdding \"dry_run\" is not obvious because create_branch() is also used to\nset tracking information without creating a branch, i.e. when using\n--set-upstream-to. This appears to be a leftover from 4fc5006676 (Add\nbranch --set-upstream, 2010-01-18), when --set-upstream would sometimes\ncreate a branch and sometimes update tracking information without\ncreating a branch. However, we no longer support --set-upstream, so it\nmakes more sense to set tracking information with another function and\nuse create_branch() only to create branches. In a later commit, we will\nremove the now-unnecessary logic from create_branch() so that \"dry_run\"\nbecomes trivial to implement.\n\nIntroduce dwim_and_setup_tracking(), which replaces create_branch()\nin `git branch --set-upstream-to`. Ensure correctness by moving the DWIM\nand branch validation logic from create_branch() into a helper function,\ndwim_branch_start(), so that the logic is shared by both functions.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c         | 87 ++++++++++++++++++++++++++++++++++++------------\n branch.h         | 22 ++++++++++++\n builtin/branch.c |  9 ++---\n 3 files changed, 91 insertions(+), 27 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex fa339b64e2..e271a4e0a7 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -217,9 +217,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n }\n \n /*\n- * This is called when new_ref is branched off of orig_ref, and tries\n- * to infer the settings for branch.<new_ref>.{remote,merge} from the\n- * config.\n+ * Used internally to set the branch.<new_ref>.{remote,merge} config\n+ * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike\n+ * dwim_and_setup_tracking(), this does not do DWIM, i.e. \"origin/main\"\n+ * will not be expanded to \"refs/remotes/origin/main\", so it is not safe\n+ * for 'orig_ref' to be raw user input.\n  */\n static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t   enum branch_track track, int quiet)\n@@ -244,7 +246,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tcase BRANCH_TRACK_INHERIT:\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\treturn;\n+\t\t\tgoto cleanup;\n \t\t}\n \n \tif (tracking.matches > 1)\n@@ -257,6 +259,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t\ttracking.remote, tracking.srcs) < 0)\n \t\texit(-1);\n \n+cleanup:\n \tstring_list_clear(tracking.srcs, 0);\n }\n \n@@ -340,31 +343,37 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+/**\n+ * DWIMs a user-provided ref to determine the starting point for a\n+ * branch and validates it, where:\n+ *\n+ *   - r is the repository to validate the branch for\n+ *\n+ *   - start_name is the ref that we would like to test. This is\n+ *     expanded with DWIM and assigned to out_real_ref.\n+ *\n+ *   - track is the tracking mode of the new branch. If tracking is\n+ *     explicitly requested, start_name must be a branch (because\n+ *     otherwise start_name cannot be tracked)\n+ *\n+ *   - out_oid is an out parameter containing the object_id of start_name\n+ *\n+ *   - out_real_ref is an out parameter containing the full, 'real' form\n+ *     of start_name e.g. refs/heads/main instead of main\n+ *\n+ */\n+static void dwim_branch_start(struct repository *r, const char *start_name,\n+\t\t\t   enum branch_track track, char **out_real_ref,\n+\t\t\t   struct object_id *out_oid)\n {\n \tstruct commit *commit;\n \tstruct object_id oid;\n \tchar *real_ref;\n-\tstruct strbuf ref = STRBUF_INIT;\n-\tint forcing = 0;\n-\tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n \n \tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n \t\texplicit_tracking = 1;\n \n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n-\t}\n-\n \treal_ref = NULL;\n \tif (get_oid_mb(start_name, &oid)) {\n \t\tif (explicit_tracking) {\n@@ -401,7 +410,34 @@ void create_branch(struct repository *r,\n \n \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n-\toidcpy(&oid, &commit->object.oid);\n+\tif (out_real_ref)\n+\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n+\tif (out_oid)\n+\t\toidcpy(out_oid, &commit->object.oid);\n+\n+\tFREE_AND_NULL(real_ref);\n+}\n+\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track)\n+{\n+\tstruct object_id oid;\n+\tchar *real_ref;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tint forcing = 0;\n+\tint dont_change_ref = 0;\n+\n+\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n+\t    ? validate_branchname(name, &ref)\n+\t    : validate_new_branchname(name, &ref, force)) {\n+\t\tif (!force)\n+\t\t\tdont_change_ref = 1;\n+\t\telse\n+\t\t\tforcing = 1;\n+\t}\n+\n+\tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -435,6 +471,15 @@ void create_branch(struct repository *r,\n \tfree(real_ref);\n }\n \n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet)\n+{\n+\tchar *real_orig_ref;\n+\tdwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL);\n+\tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 815dcd40c0..ab2315c611 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -18,6 +18,28 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+/**\n+ * Sets branch.<new_ref>.{remote,merge} config settings such that\n+ * new_ref tracks orig_ref according to the specified tracking mode.\n+ *\n+ *   - new_ref is the name of the branch that we are setting tracking\n+ *     for.\n+ *\n+ *   - orig_ref is the name of the ref that is 'upstream' of new_ref.\n+ *     orig_ref will be expanded with DWIM so that the config settings\n+ *     are in the correct format e.g. \"refs/remotes/origin/main\" instead\n+ *     of \"origin/main\".\n+ *\n+ *   - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes\n+ *     new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT\n+ *     causes new_ref to track whatever orig_ref tracks.\n+ *\n+ *   - quiet suppresses tracking information.\n+ */\n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet);\n+\n /*\n  * Creates a new branch, where:\n  *\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 81a29edb4a..16a7e80df5 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -823,12 +823,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (!ref_exists(branch->refname))\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \n-\t\t/*\n-\t\t * create_branch takes care of setting up the tracking\n-\t\t * info and making sure new_upstream is correct\n-\t\t */\n-\t\tcreate_branch(the_repository, branch->name, new_upstream,\n-\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n+\t\tdwim_and_setup_tracking(the_repository, branch->name,\n+\t\t\t\t\tnew_upstream, BRANCH_TRACK_OVERRIDE,\n+\t\t\t\t\tquiet);\n \t} else if (unset_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n \t\tstruct strbuf buf = STRBUF_INIT;\n-- \n2.33.GIT\n\n"},{"id":"444559","messageId":"20211220233459.45739-3-chooglen@google.com","threadId":"56962","inReplyTo":"20211220233459.45739-1-chooglen@google.com","subject":"[PATCH v6 2/5] branch: make create_branch() always create a branch","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T23:34:56Z","receivedAt":"2021-12-20T23:35:22Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"create_branch() was formerly used to set tracking without creating a\nbranch. Since the previous commit replaces this use case with\ndwim_and_setup_tracking(), we can simplify create_branch() so that it\nalways creates a branch.\n\nDo this simplification, in particular:\n\n* remove the special handling of BRANCH_TRACK_OVERRIDE because it is no\n  longer used\n* assert that clobber_head_ok can only be provided with force\n* check that we're handling clobber_head_ok and force correctly by\n  introducing tests for `git branch --force`\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c          | 55 +++++++++++++++++++++--------------------------\n branch.h          |  4 ++--\n t/t3200-branch.sh | 17 +++++++++++++++\n 3 files changed, 44 insertions(+), 32 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex e271a4e0a7..de680f311d 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -426,15 +426,17 @@ void create_branch(struct repository *r, const char *name,\n \tchar *real_ref;\n \tstruct strbuf ref = STRBUF_INIT;\n \tint forcing = 0;\n-\tint dont_change_ref = 0;\n-\n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *msg;\n+\n+\tif (clobber_head_ok && !force)\n+\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n+\n+\tif (clobber_head_ok ?\n+\t\t\t  validate_branchname(name, &ref) :\n+\t\t\t  validate_new_branchname(name, &ref, force)) {\n+\t\tforcing = 1;\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n@@ -442,27 +444,20 @@ void create_branch(struct repository *r, const char *name,\n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n-\tif (!dont_change_ref) {\n-\t\tstruct ref_transaction *transaction;\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\t\tchar *msg;\n-\n-\t\tif (forcing)\n-\t\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n-\t\telse\n-\t\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n-\n-\t\ttransaction = ref_transaction_begin(&err);\n-\t\tif (!transaction ||\n-\t\t    ref_transaction_update(transaction, ref.buf,\n-\t\t\t\t\t   &oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t   0, msg, &err) ||\n-\t\t    ref_transaction_commit(transaction, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\t\tref_transaction_free(transaction);\n-\t\tstrbuf_release(&err);\n-\t\tfree(msg);\n-\t}\n+\tif (forcing)\n+\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n+\telse\n+\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t\tref_transaction_update(transaction, ref.buf,\n+\t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n+\t\t\t\t\t0, msg, &err) ||\n+\t\tref_transaction_commit(transaction, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tfree(msg);\n \n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\ndiff --git a/branch.h b/branch.h\nindex ab2315c611..cf3a4d3ff3 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -52,8 +52,8 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *\n  *   - force enables overwriting an existing (non-head) branch\n  *\n- *   - clobber_head_ok allows the currently checked out (hence existing)\n- *     branch to be overwritten; without 'force', it has no effect.\n+ *   - clobber_head_ok, when enabled with 'force', allows the currently\n+ *     checked out (head) branch to be overwritten\n  *\n  *   - reflog creates a reflog for the branch\n  *\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 09ab132377..71a72efcb2 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n \tgit branch abc && test_path_is_file .git/refs/heads/abc\n '\n \n+test_expect_success 'git branch abc should fail when abc exists' '\n+\ttest_must_fail git branch abc\n+'\n+\n+test_expect_success 'git branch --force abc should fail when abc is checked out' '\n+\ttest_when_finished git switch main &&\n+\tgit switch abc &&\n+\ttest_must_fail git branch --force abc HEAD~1\n+'\n+\n+test_expect_success 'git branch --force abc should succeed when abc exists' '\n+\tgit rev-parse HEAD~1 >expect &&\n+\tgit branch --force abc HEAD~1 &&\n+\tgit rev-parse abc >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n '\n-- \n2.33.GIT\n\n"},{"id":"444560","messageId":"20211220233459.45739-4-chooglen@google.com","threadId":"56962","inReplyTo":"20211220233459.45739-1-chooglen@google.com","subject":"[PATCH v6 3/5] branch: add a dry_run parameter to create_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T23:34:57Z","receivedAt":"2021-12-20T23:35:25Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a dry_run parameter to create_branch() such that dry_run = 1 will\nvalidate a new branch without trying to create it. This will be used in\n`git branch --recurse-submodules` to ensure that the new branch can be\ncreated in all submodules.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c           | 5 ++++-\n branch.h           | 9 +++++----\n builtin/branch.c   | 2 +-\n builtin/checkout.c | 3 ++-\n 4 files changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex de680f311d..55c7ba4a25 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -420,7 +420,7 @@ static void dwim_branch_start(struct repository *r, const char *start_name,\n \n void create_branch(struct repository *r, const char *name,\n \t\t   const char *start_name, int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track)\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run)\n {\n \tstruct object_id oid;\n \tchar *real_ref;\n@@ -440,6 +440,8 @@ void create_branch(struct repository *r, const char *name,\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n+\tif (dry_run)\n+\t\tgoto cleanup;\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -462,6 +464,7 @@ void create_branch(struct repository *r, const char *name,\n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n \n+cleanup:\n \tstrbuf_release(&ref);\n \tfree(real_ref);\n }\ndiff --git a/branch.h b/branch.h\nindex cf3a4d3ff3..8009266343 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -62,11 +62,12 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *   - track causes the new branch to be configured to merge the remote branch\n  *     that start_name is a tracking branch for (if any).\n  *\n+ *   - dry_run causes the branch to be validated but not created.\n+ *\n  */\n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track);\n+void create_branch(struct repository *r, const char *name,\n+\t\t   const char *start_name, int force, int clobber_head_ok,\n+\t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n \n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 16a7e80df5..9b300cf42f 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -859,7 +859,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \t\tcreate_branch(the_repository,\n \t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track);\n+\t\t\t      force, 0, reflog, quiet, track, 0);\n \n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex bbbfabb871..629a288dd8 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -904,7 +904,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t      opts->new_branch_force ? 1 : 0,\n \t\t\t\t      opts->new_branch_log,\n \t\t\t\t      opts->quiet,\n-\t\t\t\t      opts->track);\n+\t\t\t\t      opts->track,\n+\t\t\t\t      0);\n \t\tfree(new_branch_info->name);\n \t\tfree(new_branch_info->refname);\n \t\tnew_branch_info->name = xstrdup(opts->new_branch);\n-- \n2.33.GIT\n\n"},{"id":"444561","messageId":"20211220233459.45739-5-chooglen@google.com","threadId":"56962","inReplyTo":"20211220233459.45739-1-chooglen@google.com","subject":"[PATCH v6 4/5] builtin/branch: clean up action-picking logic in cmd_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T23:34:58Z","receivedAt":"2021-12-20T23:35:26Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a variable to cmd_branch() that will tell us whether or not\ncmd_branch() will default to creating a branch (instead of performing\nanother action). Besides making the function more explicit, this allows\nus to validate options that can only be used when creating a branch.\nSuch an option does not exist yet, but one will be introduced in a\nsubsequent commit.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 19 +++++++++++--------\n 1 file changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 9b300cf42f..3b010c1d2c 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -616,14 +616,15 @@ static int edit_branch_description(const char *branch_name)\n \n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n-\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n-\tint show_current = 0;\n-\tint reflog = 0, edit_description = 0;\n-\tint quiet = 0, unset_upstream = 0;\n+\t/* possible actions */\n+\tint delete = 0, rename = 0, copy = 0, list = 0,\n+\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n \tconst char *new_upstream = NULL;\n+\tint noncreate_actions = 0;\n+\t/* possible options */\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n-\tint icase = 0;\n \tstatic struct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n \tstruct ref_format format = REF_FORMAT_INIT;\n@@ -708,8 +709,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)\n \t\tlist = 1;\n \n-\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n-\t    list + edit_description + unset_upstream > 1)\n+\tnoncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +\n+\t\t\t    !!show_current + !!list + !!edit_description +\n+\t\t\t    !!unset_upstream;\n+\tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (filter.abbrev == -1)\n@@ -849,7 +852,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&buf, \"branch.%s.merge\", branch->name);\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n-\t} else if (argc > 0 && argc <= 2) {\n+\t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n-- \n2.33.GIT\n\n"},{"id":"444562","messageId":"20211220233459.45739-6-chooglen@google.com","threadId":"56962","inReplyTo":"20211220233459.45739-1-chooglen@google.com","subject":"[PATCH v6 5/5] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T23:34:59Z","receivedAt":"2021-12-20T23:35:27Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"To improve the submodules UX, we would like to teach Git to handle\nbranches in submodules. Start this process by teaching `git branch` the\n--recurse-submodules option so that `git branch --recurse-submodules\ntopic` will create the \"topic\" branch in the superproject and its\nsubmodules.\n\nAlthough this commit does not introduce breaking changes, it is\nincompatible with existing --recurse-submodules semantics e.g. `git\ncheckout` does not recursively checkout the expected branches created by\n`git branch` yet. To ensure that the correct set of semantics is used,\nthis commit introduces a new configuration value,\n`submodule.propagateBranches`, which enables submodule branching when\ntrue (defaults to false).\n\nThis commit includes changes that allow Git to work with submodules\nthat are in trees (and not just the index):\n\n* add a submodules_of_tree() helper that gives the relevant\n  information of an in-tree submodule (e.g. path and oid) and\n  initializes the repository\n* add is_tree_submodule_active() by adding a treeish_name parameter to\n  is_submodule_active()\n* add the \"submoduleNotUpdated\" advice to advise users to update the\n  submodules in their trees\n\nIncidentally, fix an incorrect usage string that combined the 'list'\nusage of git branch (-l) with the 'create' usage; this string has been\nincorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\nparse_options., 2007-10-07).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  24 ++-\n Documentation/git-branch.txt       |  11 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 130 +++++++++++++\n branch.h                           |  22 +++\n builtin/branch.c                   |  44 ++++-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3207-branch-submodule.sh        | 291 +++++++++++++++++++++++++++++\n 14 files changed, 659 insertions(+), 14 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex 063eec2511..adee26fbbb 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -116,6 +116,9 @@ advice.*::\n \tsubmoduleAlternateErrorStrategyDie::\n \t\tAdvice shown when a submodule.alternateErrorStrategy option\n \t\tconfigured to \"die\" causes a fatal error.\n+\tsubmodulesNotUpdated::\n+\t\tAdvice shown when a user runs a submodule command that fails\n+\t\tbecause `git submodule update --init` was not run.\n \taddIgnoredFile::\n \t\tAdvice shown if a user attempts to add an ignored file to\n \t\tthe index.\ndiff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\nindex ee454f8126..0aa5946364 100644\n--- a/Documentation/config/submodule.txt\n+++ b/Documentation/config/submodule.txt\n@@ -59,11 +59,8 @@ submodule.active::\n \n submodule.recurse::\n \tA boolean indicating if commands should enable the `--recurse-submodules`\n-\toption by default.\n-\tApplies to all commands that support this option\n-\t(`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,\n-\t`restore` and `switch`) except `clone` and `ls-files`.\n-\tDefaults to false.\n+\toption by default. Defaults to false.\n+\t+\n \tWhen set to true, it can be deactivated via the\n \t`--no-recurse-submodules` option. Note that some Git commands\n \tlacking this option may call some of the above commands affected by\n@@ -71,6 +68,23 @@ submodule.recurse::\n \t`git fetch` but does not have a `--no-recurse-submodules` option.\n \tFor these commands a workaround is to temporarily change the\n \tconfiguration value by using `git -c submodule.recurse=0`.\n+\t+\n+\tThe following list shows the commands that accept\n+\t`--recurse-submodules` and whether they are supported by this\n+\tsetting.\n+\t* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n+\t`reset`, `restore` and `switch` are always supported.\n+\t* `clone` and `ls-files` are not supported.\n+\t* `branch` is supported only if `submodule.propagateBranches` is\n+\tenabled\n+\n+submodule.propagateBranches::\n+\t[EXPERIMENTAL] A boolean that enables branching support when\n+\tusing `--recurse-submodules` or `submodule.recurse=true`.\n+\tEnabling this will allow certain commands to accept\n+\t`--recurse-submodules` and certain commands that already accept\n+\t`--recurse-submodules` will now consider branches.\n+\tDefaults to false.\n \n submodule.fetchJobs::\n \tSpecifies how many submodules are fetched/cloned at the same time.\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 2d52ae396b..435dc2b20e 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,8 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f]\n+\t[--recurse-submodules] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -235,6 +236,14 @@ how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n \tDo not set up \"upstream\" configuration, even if the\n \tbranch.autoSetupMerge configuration variable is set.\n \n+--recurse-submodules::\n+\tTHIS OPTION IS EXPERIMENTAL! Causes the current command to\n+\trecurse into submodules if `submodule.propagateBranches` is\n+\tenabled. See `submodule.propagateBranches` in\n+\tlinkgit:git-config[1].\n+\t+\n+\tCurrently, only branch creation is supported.\n+\n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\n \tPlease use `--track` or `--set-upstream-to` instead.\ndiff --git a/advice.c b/advice.c\nindex 1dfc91d176..e00d30254c 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -70,6 +70,7 @@ static struct {\n \t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n \t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n \t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n+\t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n };\ndiff --git a/advice.h b/advice.h\nindex 601265fd10..a7521d6087 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -44,6 +44,7 @@ struct string_list;\n \tADVICE_STATUS_HINTS,\n \tADVICE_STATUS_U_OPTION,\n \tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_SUBMODULES_NOT_UPDATED,\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\ndiff --git a/branch.c b/branch.c\nindex 55c7ba4a25..6d0d9a8e1b 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -8,6 +8,8 @@\n #include \"sequencer.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n+#include \"submodule-config.h\"\n+#include \"run-command.h\"\n \n struct tracking {\n \tstruct refspec_item spec;\n@@ -478,6 +480,134 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n \tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n }\n \n+/**\n+ * Creates a branch in a submodule by calling\n+ * create_branches_recursively() in a child process. The child process\n+ * is necessary because install_branch_config() (and its variants) do\n+ * not support writing configs to submodules.\n+ */\n+static int submodule_create_branch(struct repository *r,\n+\t\t\t\t   const struct submodule *submodule,\n+\t\t\t\t   const char *name, const char *start_oid,\n+\t\t\t\t   const char *start_name, int force,\n+\t\t\t\t   int reflog, int quiet,\n+\t\t\t\t   enum branch_track track, int dry_run)\n+{\n+\tint ret = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tstruct strbuf child_err = STRBUF_INIT;\n+\tstruct strbuf out_buf = STRBUF_INIT;\n+\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n+\tchild.git_cmd = 1;\n+\tchild.err = -1;\n+\tchild.stdout_to_stderr = 1;\n+\n+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n+\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n+\tif (dry_run)\n+\t\tstrvec_push(&child.args, \"--dry-run\");\n+\tif (force)\n+\t\tstrvec_push(&child.args, \"--force\");\n+\tif (quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\tif (reflog)\n+\t\tstrvec_push(&child.args, \"--create-reflog\");\n+\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n+\t\tstrvec_push(&child.args, \"--track\");\n+\n+\tstrvec_pushl(&child.args, name, start_oid, start_name, NULL);\n+\n+\tif ((ret = start_command(&child)))\n+\t\treturn ret;\n+\tret = finish_command(&child);\n+\tstrbuf_read(&child_err, child.err, 0);\n+\tstrbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);\n+\n+\tif (ret)\n+\t\tfprintf(stderr, \"%s\", out_buf.buf);\n+\telse\n+\t\tprintf(\"%s\", out_buf.buf);\n+\n+\tstrbuf_release(&child_err);\n+\tstrbuf_release(&out_buf);\n+\treturn ret;\n+}\n+\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run)\n+{\n+\tint i = 0;\n+\tchar *branch_point = NULL;\n+\tstruct object_id super_oid;\n+\tstruct submodule_entry_list submodule_entry_list;\n+\n+\t/* Perform dwim on start_name to get super_oid and branch_point. */\n+\tdwim_branch_start(r, start_name, BRANCH_TRACK_NEVER, &branch_point,\n+\t\t\t  &super_oid);\n+\n+\t/*\n+\t * If we were not given an explicit name to track, then assume we are at\n+\t * the top level and, just like the non-recursive case, the tracking\n+\t * name is the branch point.\n+\t */\n+\tif (!tracking_name)\n+\t\ttracking_name = branch_point;\n+\n+\tsubmodules_of_tree(r, &super_oid, &submodule_entry_list);\n+\t/*\n+\t * Before creating any branches, first check that the branch can\n+\t * be created in every submodule.\n+\t */\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n+\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n+\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n+\t\t\t\t       start_name);\n+\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name);\n+\t\t}\n+\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t}\n+\n+\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n+\t\t      BRANCH_TRACK_NEVER, dry_run);\n+\tif (dry_run)\n+\t\treturn;\n+\t/*\n+\t * NEEDSWORK If tracking was set up in the superproject but not the\n+\t * submodule, users might expect \"git branch --recurse-submodules\" to\n+\t * fail or give a warning, but this is not yet implemented because it is\n+\t * tedious to determine whether or not tracking was set up in the\n+\t * superproject.\n+\t */\n+\tsetup_tracking(name, tracking_name, track, quiet);\n+\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t\trepo_clear(submodule_entry_list.entries[i].repo);\n+\t}\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 8009266343..63448e4278 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -69,6 +69,28 @@ void create_branch(struct repository *r, const char *name,\n \t\t   const char *start_name, int force, int clobber_head_ok,\n \t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n \n+/*\n+ * Creates a new branch in repository and its submodules (and its\n+ * submodules, recursively). Besides these exceptions, the parameters\n+ * function identically to create_branch():\n+ *\n+ * - start_name is the name of the ref, in repository r, that the new\n+ *   branch should start from. In submodules, branches will start from\n+ *   the respective gitlink commit ids in start_name's tree.\n+ *\n+ * - tracking_name is the name used of the ref that will be used to set\n+ *   up tracking, e.g. origin/main. This is propagated to submodules so\n+ *   that tracking information will appear as if the branch branched off\n+ *   tracking_name instead of start_name (which is a plain commit id for\n+ *   submodules). If omitted, start_name is used for tracking (just like\n+ *   create_branch()).\n+ *\n+ */\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run);\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 3b010c1d2c..c06de58873 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -27,7 +27,8 @@\n \n static const char * const builtin_branch_usage[] = {\n \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n-\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n@@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = {\n \n static const char *head;\n static struct object_id head_oid;\n+static int recurse_submodules = 0;\n+static int submodule_propagate_branches = 0;\n \n static int branch_use_color = -1;\n static char branch_colors[][COLOR_MAXLEN] = {\n@@ -99,6 +102,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\t\treturn config_error_nonbool(var);\n \t\treturn color_parse(value, branch_colors[slot]);\n \t}\n+\tif (!strcmp(var, \"submodule.recurse\")) {\n+\t\trecurse_submodules = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcasecmp(var, \"submodule.propagateBranches\")) {\n+\t\tsubmodule_propagate_branches = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_color_default_config(var, value, cb);\n }\n \n@@ -622,7 +634,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tconst char *new_upstream = NULL;\n \tint noncreate_actions = 0;\n \t/* possible options */\n-\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0,\n+\t    recurse_submodules_explicit = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n \tstatic struct ref_sorting *sorting;\n@@ -673,6 +686,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n+\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT_END(),\n \t};\n@@ -715,6 +729,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n+\tif (recurse_submodules_explicit) {\n+\t\tif (!submodule_propagate_branches)\n+\t\t\tdie(_(\"branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\"));\n+\t\tif (noncreate_actions)\n+\t\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n+\t}\n+\n+\trecurse_submodules =\n+\t\t(recurse_submodules || recurse_submodules_explicit) &&\n+\t\tsubmodule_propagate_branches;\n+\n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n \tfilter.ignore_case = icase;\n@@ -853,6 +878,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n+\t\tconst char *branch_name = argv[0];\n+\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n+\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n@@ -860,10 +888,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (track == BRANCH_TRACK_OVERRIDE)\n \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n \n-\t\tcreate_branch(the_repository,\n-\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track, 0);\n-\n+\t\tif (recurse_submodules) {\n+\t\t\tcreate_branches_recursively(the_repository, branch_name,\n+\t\t\t\t\t\t    start_name, NULL, force,\n+\t\t\t\t\t\t    reflog, quiet, track, 0);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n+\t\t\t      reflog, quiet, track, 0);\n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 9b25a508e6..d7e50af72f 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -20,6 +20,7 @@\n #include \"diff.h\"\n #include \"object-store.h\"\n #include \"advice.h\"\n+#include \"branch.h\"\n \n #define OPT_QUIET (1 << 0)\n #define OPT_CACHED (1 << 1)\n@@ -2984,6 +2985,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n \treturn !!ret;\n }\n \n+static int module_create_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tenum branch_track track;\n+\tint quiet = 0, force = 0, reflog = 0, dry_run = 0;\n+\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n+\t\tOPT__FORCE(&force, N_(\"force creation\"), 0),\n+\t\tOPT_BOOL(0, \"create-reflog\", &reflog,\n+\t\t\t N_(\"create the branch's reflog\")),\n+\t\tOPT_SET_INT('t', \"track\", &track,\n+\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\t    BRANCH_TRACK_EXPLICIT),\n+\t\tOPT__DRY_RUN(&dry_run,\n+\t\t\t     N_(\"show whether the branch would be created\")),\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>\"),\n+\t\tNULL\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\ttrack = git_branch_track;\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\n+\tif (argc != 3)\n+\t\tusage_with_options(usage, options);\n+\n+\tif (!quiet && !dry_run)\n+\t\tprintf_ln(_(\"creating branch '%s'\"), argv[0]);\n+\n+\tcreate_branches_recursively(the_repository, argv[0], argv[1], argv[2],\n+\t\t\t\t    force, reflog, quiet, track, dry_run);\n+\treturn 0;\n+}\n struct add_data {\n \tconst char *prefix;\n \tconst char *branch;\n@@ -3390,6 +3427,7 @@ static struct cmd_struct commands[] = {\n \t{\"config\", module_config, 0},\n \t{\"set-url\", module_set_url, 0},\n \t{\"set-branch\", module_set_branch, 0},\n+\t{\"create-branch\", module_create_branch, 0},\n };\n \n int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f95344028b..24b8d1a700 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -7,6 +7,7 @@\n #include \"strbuf.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n+#include \"tree-walk.h\"\n \n /*\n  * submodule cache lookup structure\n@@ -726,6 +727,65 @@ const struct submodule *submodule_from_path(struct repository *r,\n \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n+/**\n+ * Used internally by submodules_of_tree(). Recurses into 'treeish_name'\n+ * and appends submodule entries to 'out'. The submodule_cache expects\n+ * a root-level treeish_name and paths, so keep track of these values\n+ * with 'root_tree' and 'prefix'.\n+ */\n+static void traverse_tree_submodules(struct repository *r,\n+\t\t\t\t     const struct object_id *root_tree,\n+\t\t\t\t     char *prefix,\n+\t\t\t\t     const struct object_id *treeish_name,\n+\t\t\t\t     struct submodule_entry_list *out)\n+{\n+\tstruct tree_desc tree;\n+\tstruct submodule_tree_entry *st_entry;\n+\tstruct name_entry *name_entry;\n+\tchar *tree_path = NULL;\n+\n+\tname_entry = xmalloc(sizeof(*name_entry));\n+\n+\tfill_tree_descriptor(r, &tree, treeish_name);\n+\twhile (tree_entry(&tree, name_entry)) {\n+\t\tif (prefix)\n+\t\t\ttree_path =\n+\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n+\t\telse\n+\t\t\ttree_path = xstrdup(name_entry->path);\n+\n+\t\tif (S_ISGITLINK(name_entry->mode) &&\n+\t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n+\t\t\tst_entry = xmalloc(sizeof(*st_entry));\n+\t\t\tst_entry->name_entry = name_entry;\n+\t\t\tst_entry->submodule =\n+\t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n+\t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n+\t\t\tif (repo_submodule_init(st_entry->repo, r, tree_path,\n+\t\t\t\t\t\troot_tree))\n+\t\t\t\tFREE_AND_NULL(st_entry->repo);\n+\n+\t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n+\t\t\t\t   out->entry_alloc);\n+\t\t\tout->entries[out->entry_nr++] = *st_entry;\n+\t\t} else if (S_ISDIR(name_entry->mode))\n+\t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n+\t\t\t\t\t\t &name_entry->oid, out);\n+\t\tfree(tree_path);\n+\t}\n+}\n+\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *out)\n+{\n+\tCALLOC_ARRAY(out->entries, 0);\n+\tout->entry_nr = 0;\n+\tout->entry_alloc = 0;\n+\n+\ttraverse_tree_submodules(r, treeish_name, NULL, treeish_name, out);\n+}\n+\n void submodule_free(struct repository *r)\n {\n \tif (r->submodule_cache)\ndiff --git a/submodule-config.h b/submodule-config.h\nindex 65875b94ea..fa229a8b97 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"submodule.h\"\n #include \"strbuf.h\"\n+#include \"tree-walk.h\"\n \n /**\n  * The submodule config cache API allows to read submodule\n@@ -101,4 +102,37 @@ int check_submodule_name(const char *name);\n void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n void update_clone_config_from_gitmodules(int *max_jobs);\n \n+/*\n+ * Submodule entry that contains relevant information about a\n+ * submodule in a tree.\n+ */\n+struct submodule_tree_entry {\n+\t/* The submodule's tree entry. */\n+\tstruct name_entry *name_entry;\n+\t/*\n+\t * A struct repository corresponding to the submodule. May be\n+\t * NULL if the submodule has not been updated.\n+\t */\n+\tstruct repository *repo;\n+\t/*\n+\t * A struct submodule containing the submodule config in the\n+\t * tree's .gitmodules.\n+\t */\n+\tconst struct submodule *submodule;\n+};\n+\n+struct submodule_entry_list {\n+\tstruct submodule_tree_entry *entries;\n+\tint entry_nr;\n+\tint entry_alloc;\n+};\n+\n+/**\n+ * Given a treeish, return all submodules in the tree and its subtrees,\n+ * but excluding nested submodules. Callers that require nested\n+ * submodules are expected to recurse into the submodules themselves.\n+ */\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *ret);\n #endif /* SUBMODULE_CONFIG_H */\ndiff --git a/submodule.c b/submodule.c\nindex c689070524..5ace18a7d9 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -267,7 +267,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n  * ie, the config looks like: \"[submodule] active\\n\".\n  * Since that is an invalid pathspec, we should inform the user.\n  */\n-int is_submodule_active(struct repository *repo, const char *path)\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path)\n {\n \tint ret = 0;\n \tchar *key = NULL;\n@@ -275,7 +277,7 @@ int is_submodule_active(struct repository *repo, const char *path)\n \tconst struct string_list *sl;\n \tconst struct submodule *module;\n \n-\tmodule = submodule_from_path(repo, null_oid(), path);\n+\tmodule = submodule_from_path(repo, treeish_name, path);\n \n \t/* early return if there isn't a path->module mapping */\n \tif (!module)\n@@ -317,6 +319,11 @@ int is_submodule_active(struct repository *repo, const char *path)\n \treturn ret;\n }\n \n+int is_submodule_active(struct repository *repo, const char *path)\n+{\n+\treturn is_tree_submodule_active(repo, null_oid(), path);\n+}\n+\n int is_submodule_populated_gently(const char *path, int *return_error_code)\n {\n \tint ret = 0;\ndiff --git a/submodule.h b/submodule.h\nindex 6bd2c99fd9..784ceffc0e 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb);\n struct option;\n int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n \t\t\t\t\t\t     const char *arg, int unset);\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path);\n int is_submodule_active(struct repository *repo, const char *path);\n /*\n  * Determine if a submodule has been populated at a given 'path' by checking if\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nnew file mode 100755\nindex 0000000000..a2dfb5ad7f\n--- /dev/null\n+++ b/t/t3207-branch-submodule.sh\n@@ -0,0 +1,291 @@\n+#!/bin/sh\n+\n+test_description='git branch submodule tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+test_expect_success 'setup superproject and submodule' '\n+\tgit init super &&\n+\ttest_commit foo &&\n+\tgit init sub-sub-upstream &&\n+\ttest_commit -C sub-sub-upstream foo &&\n+\tgit init sub-upstream &&\n+\t# Submodule in a submodule\n+\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n+\tgit -C sub-upstream commit -m \"add submodule\" &&\n+\t# Regular submodule\n+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n+\t# Submodule in a subdirectory\n+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" second/sub &&\n+\tgit -C super commit -m \"add submodule\" &&\n+\tgit -C super config submodule.propagateBranches true &&\n+\tgit -C super/sub submodule update --init\n+'\n+\n+CLEANUP_SCRIPT_PATH=\"$TRASH_DIRECTORY/cleanup_branches.sh\"\n+\n+cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n+\t#!/bin/sh\n+\n+\tsuper_dir=\"$1\"\n+\tshift\n+\t(\n+\t\tcd \"$super_dir\" &&\n+\t\tgit checkout main &&\n+\t\tfor branch_name in \"$@\"; do\n+\t\t\tgit branch -D \"$branch_name\"\n+\t\t\tgit submodule foreach \"$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true\"\n+\t\tdone\n+\t)\n+EOF\n+chmod +x \"$CLEANUP_SCRIPT_PATH\"\n+\n+cleanup_branches() {\n+\tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n+} >/dev/null 2>/dev/null\n+\n+# Test the argument parsing\n+test_expect_success '--recurse-submodules should create branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a &&\n+\t\tgit -C sub/sub-sub rev-parse branch-a &&\n+\t\tgit -C second/sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\techo \"fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\" >expected &&\n+\t\ttest_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should fail when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n+\t\t# Assert that the branches were not deleted\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should respect submodule.recurse when creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c submodule.recurse=true branch branch-a &&\n+\t\tgit rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+test_expect_success 'should ignore submodule.recurse when not creating branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit -c submodule.recurse=true branch -D branch-a &&\n+\t\ttest_must_fail git rev-parse --abbrev-ref branch-a &&\n+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n+\t)\n+'\n+\n+# Test branch creation behavior\n+test_expect_success 'should create branches based off commit id in superproject' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit checkout --recurse-submodules branch-a &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\t# Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a\n+\t\tgit -C sub checkout branch-a &&\n+\t\ttest_commit -C sub bar &&\n+\t\t# Create a new branch-b branch with start-point=branch-a\n+\t\tgit branch --recurse-submodules branch-b branch-a &&\n+\t\tgit rev-parse branch-b &&\n+\t\tgit -C sub rev-parse branch-b >actual &&\n+\t\t# Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should not create any branches if branch is not valid for all repos' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub branch branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_must_fail git rev-parse branch-a &&\n+\n+\t\tcat >expected <<-EOF &&\n+\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n+\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branches if branch exists and --force is given' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\ttest_commit -C sub baz &&\n+\t\tgit -C sub branch branch-a HEAD~1 &&\n+\t\tgit branch --recurse-submodules --force branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\t# assert that sub:branch-a was moved\n+\t\tgit -C sub rev-parse branch-a >actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\tgit -C sub2 submodule update --init &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\" &&\n+\t\tgit checkout branch-a &&\n+\t\tgit branch --recurse-submodules branch-c branch-b &&\n+\t\tgit rev-parse branch-c &&\n+\t\tgit -C sub rev-parse branch-c &&\n+\t\tgit -C second/sub rev-parse branch-c &&\n+\t\tgit checkout --recurse-submodules branch-c &&\n+\t\tgit -C sub2 rev-parse branch-c &&\n+\t\tgit -C sub2/sub-sub rev-parse branch-c\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with track=always' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with explicit track' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --track --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should not set up unnecessary tracking of local branches' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_expect_success 'should not create branches in inactive submodules' '\n+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n+\ttest_config -C super submodule.sub.active false &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\ttest_must_fail git -C sub branch-a\n+\t)\n+'\n+\n+test_expect_success 'setup remote-tracking tests' '\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\"\n+\t) &&\n+\tgit clone --branch main --recurse-submodules super super-clone &&\n+\tgit -C super-clone config submodule.propagateBranches true\n+'\n+\n+test_expect_success 'should not create branch when submodule is not in .git/modules' '\n+\t# The cleanup needs to delete sub2 separately because main does not have sub2\n+\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n+\t\tgit -C super-clone/sub2/sub-sub branch -D branch-b && \\\n+\t\tcleanup_branches super-clone branch-a branch-b\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\t# This should succeed because super-clone has sub.\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\t# This should fail because super-clone does not have sub2.\n+\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n+\t\tcat >expected <<-EOF &&\n+\t\thint: You may try updating the submodules using ${SQ}git checkout origin/branch-b && git submodule update --init${SQ}\n+\t\tfatal: submodule ${SQ}sub2${SQ}: unable to find submodule\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\t\ttest_must_fail git rev-parse branch-b &&\n+\t\ttest_must_fail git -C sub rev-parse branch-b &&\n+\t\t# User can fix themselves by initializing the submodule\n+\t\tgit checkout origin/branch-b &&\n+\t\tgit submodule update --init --recursive &&\n+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of remote-tracking branches' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\t# \"origin/branch-a\" does not exist for \"sub\", but it matches the refspec\n+\t\t# so tracking should be set up\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.merge)\" = refs/heads/branch-a\n+\t)\n+'\n+\n+test_expect_success 'should not fail when unable to set up tracking in submodule' '\n+\ttest_when_finished \"cleanup_branches super-clone branch-a && \\\n+\t\tgit -C super-clone remote rename ex-origin origin\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit remote rename origin ex-origin &&\n+\t\tgit branch --recurse-submodules branch-a ex-origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = ex-origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_done\n-- \n2.33.GIT\n\n"},{"id":"444563","messageId":"kl6lilvizw5w.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20211220233459.45739-1-chooglen@google.com","subject":"Re: [PATCH v6 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T23:36:11Z","receivedAt":"2021-12-20T23:36:15Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> Submodule branching RFC:\n> https://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/\n>\n> Original Submodule UX RFC/Discussion:\n> https://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n>\n> Contributor Summit submodules Notes:\n> https://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/\n>\n> Submodule UX overhaul updates:\n> https://lore.kernel.org/git/?q=Submodule+UX+overhaul+update\n>\n> This series implements branch --recurse-submodules as laid out in the\n> Submodule branching RFC (linked above). If there are concerns about the\n> UX/behavior, I would appreciate feedback on the RFC thread as well :)\n>\n> v6 fixes v5's bad rebase; it was based off a merge commit in 'seen'\n> instead of js/branch-track-inherit. Since v5 is mostly a no-op, I will\n> also include a range-diff against v4, which is the last version that\n> anyone except Junio would care about.\n\nAs mentioned, here is the range-diff against v4\n\nRange-diff against v4:\n1:  751e8ae566 < -:  ---------- branch: accept multiple upstream branches for tracking\n2:  5d1ebe1495 < -:  ---------- branch: add flags and config to inherit tracking\n3:  0080a1fb35 < -:  ---------- config: require lowercase for branch.autosetupmerge\n4:  dfdbbaaca5 ! 1:  29669c57b4 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n    @@ Commit message\n     \n         This refactor is motivated by a desire to add a \"dry_run\" parameter to\n         create_branch() that will validate whether or not a branch can be\n    -    created without actually creating it - this behavior be used in a\n    +    created without actually creating it - this behavior will be used in a\n         subsequent commit that adds `git branch --recurse-submodules topic`.\n     \n         Adding \"dry_run\" is not obvious because create_branch() is also used to\n    @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n      \n      \tif (tracking.matches > 1)\n     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n    - \t\t\t      tracking.srcs) < 0)\n    + \t\t\t\ttracking.remote, tracking.srcs) < 0)\n      \t\texit(-1);\n      \n     +cleanup:\n5:  e22a177cb7 ! 2:  ac2532a953 branch: make create_branch() always create a branch\n    @@ Commit message\n     \n         create_branch() was formerly used to set tracking without creating a\n         branch. Since the previous commit replaces this use case with\n    -    setup_tracking(), we can simplify create_branch() so that it always\n    -    creates a branch.\n    +    dwim_and_setup_tracking(), we can simplify create_branch() so that it\n    +    always creates a branch.\n     \n         Do this simplification, in particular:\n     \n6:  8a895aa401 ! 3:  a0ed3fa438 branch: add a dry_run parameter to create_branch()\n    @@ builtin/checkout.c: static void update_refs_for_switch(const struct checkout_opt\n     -\t\t\t\t      opts->track);\n     +\t\t\t\t      opts->track,\n     +\t\t\t\t      0);\n    - \t\tnew_branch_info->name = opts->new_branch;\n    - \t\tsetup_branch_path(new_branch_info);\n    - \t}\n    + \t\tfree(new_branch_info->name);\n    + \t\tfree(new_branch_info->refname);\n    + \t\tnew_branch_info->name = xstrdup(opts->new_branch);\n7:  971c53ec85 = 4:  ebded31c96 builtin/branch: clean up action-picking logic in cmd_branch()\n8:  cd88f3ad92 = 5:  0a7ec6ee75 branch: add --recurse-submodules option for branch creation\n\n> This version is based off Josh's js/branch-track-inherit v7. Because that\n> is not yet in 'seen', I applied those patches onto abe6bb3905 (The first\n> batch to start the current cycle, 2021-11-29). The base commit can be\n> found at https://github.com/chooglen/git/tree/js/branch-track-inherit-v7.\n> I really hope I got right this time, but if not, hopefully the paper\n> trail is obvious.\n>\n> Future work:\n> * `git branch -d --recurse-submodules` so that users can clean up\n>   extraneous branches.\n> * `git [checkout | switch] --recurse-submodules` +\n>   submodule.propagateBranches so that users can actually checkout the\n>   branches.\n> * After [1], it seems clear that --recurse-submodules parsing could\n>   really benefit from some standardization. It's not obvious which\n>   RECURSE_SUBMODULE_* enums are applicable to which commands, and there\n>   is no way to distinguish between an explicit --recurse-submodules from\n>   argv vs submodule.recurse from the config.\n>\n>   I chose not to use them in this series because their usage is already\n>   inconsistent (grep.c doesn't use them either), and it would be _more_\n>   confusing to use the enum (handling RECURSE_SUBMODULES_DEFAULT = 1 is\n>   trickier than boolean 0 and 1).\n>\n>   At this point, I think it would be too noisy to introduce the enum,\n>   but this would be a nice cleanup to do later.\n> * As documented in branch.c, we create branches using a child process\n>   only because install_branch_config() does not support submodules.\n>   It should be possible to remove the child process once we make the\n>   appropriate changes to config.c. I attempted this in [2] but chose to\n>   punt it because it was too time-consuming at the time.\n>\n> Changes since v5:\n> * Rebase onto v7 of js/branch-track-inherit\n>   (https://lore.kernel.org/git/cover.1639717481.git.steadmon@google.com)\n>\n> Changes since v4:\n> * Rebase correctly onto 'gitster/seen^{/^Merge branch .js/branch-track-inherit.}'\n>   (see base-commit) as suggested in [3] (thanks Junio!)\n> * These patches were also verified on top of 'next'.\n>\n> Changes since v3:\n> * Split up the old patch 1. Patch 1 had a big diff because it used to\n>   move lines, remove dead code and introduce repo_* functions (thanks\n>   Jonathan!)\n> ** repo_* functions have been dropped; they added noise and are not\n>    necessary for correctness.\n> * Use a new, harder-to-misuse function in --set-upstream-to,\n>   dwim_and_setup_tracking(). Now, setup_tracking() never does DWIM and\n>   dwim_and_setup_tracking() always does DWIM.\n> * Move create_branch() dry_run to its own patch.\n> * Fix an oversight where submodules in subtrees were ignored. This was\n>   because submodules_of_tree() and tree_entry() didn't recurse into\n>   subtrees. Test this accordingly (thanks Jonathan!).\n> * cmd_branch() possible actions are more consistently ordered.\n> * Documentation fixes (thanks Philippe!).\n> * Additional comments and explanation.\n> * Drop patch 5 (optional cleanup).\n> * Rebase onto js/branch-track-inherit v6.\n>\n> Changes since v2:\n> * Rebase onto js/branch-track-inherit. This series should continue to be\n>   the case going forward.\n> * Patch 1 has a smaller diff because the introduction of\n>   validate_branch_start() no longer changes the function order thanks to a\n>   forward declaration. This artificial forward declaration is removed in a\n>   patch 2 (which can just be squashed into patch 1).\n> * Optional cleanup: fix questionable exit codes in patch 5.\n>\n> Changes since v1:\n> * Move the functionality of \"git branch --dry-run\" into \"git submodule-helper create-branch --dry-run\"\n> * Add more fields to the submodules_of_tree() struct to reduce the\n>   number of allocations made by the caller. Move this functionality\n>   to patch 3 (formerly patch 4) and drop patch 1.\n> * Make submodules_of_tree() ignore inactive submodules\n> * Structure the output of the submodules a bit better by adding prefixes\n>   to the child process' output (instead of inconsistently indenting the\n>   output).\n> ** I wasn't able to find a good way to interleave stdout/stderr\n>    correctly, so a less-than-desirable workaround was to route the child\n>    process output to stdout/stderr depending on the exit code.\n> ** Eventually, I would like to structure the output of submodules in a\n>    report, as Ævar suggested. But at this stage, I think that it's\n>    better to spend time getting user feedback on the submodules\n>    branching UX and it'll be easier to standardize the output when we've\n>    implemented more of the UX :)\n>\n> [1] https://lore.kernel.org/git/kl6lbl1p9zjf.fsf@chooglen-macbookpro.roam.corp.google.com/\n> [2] https://lore.kernel.org/git/kl6lv90ytd4v.fsf@chooglen-macbookpro.roam.corp.google.com/\n> [3] https://lore.kernel.org/git/xmqqlf0lz6os.fsf@gitster.g \n>\n> Glen Choo (5):\n>   branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n>   branch: make create_branch() always create a branch\n>   branch: add a dry_run parameter to create_branch()\n>   builtin/branch: clean up action-picking logic in cmd_branch()\n>   branch: add --recurse-submodules option for branch creation\n>\n>  Documentation/config/advice.txt    |   3 +\n>  Documentation/config/submodule.txt |  24 ++-\n>  Documentation/git-branch.txt       |  11 +-\n>  advice.c                           |   1 +\n>  advice.h                           |   1 +\n>  branch.c                           | 257 ++++++++++++++++++++-----\n>  branch.h                           |  57 +++++-\n>  builtin/branch.c                   |  70 +++++--\n>  builtin/checkout.c                 |   3 +-\n>  builtin/submodule--helper.c        |  38 ++++\n>  submodule-config.c                 |  60 ++++++\n>  submodule-config.h                 |  34 ++++\n>  submodule.c                        |  11 +-\n>  submodule.h                        |   3 +\n>  t/t3200-branch.sh                  |  17 ++\n>  t/t3207-branch-submodule.sh        | 291 +++++++++++++++++++++++++++++\n>  16 files changed, 805 insertions(+), 76 deletions(-)\n>  create mode 100755 t/t3207-branch-submodule.sh\n>\n> Range-diff against v5:\n> 1:  a9d1108b3e ! 1:  29669c57b4 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n>     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n>       \n>       \tif (tracking.matches > 1)\n>      @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n>     - \t\t\t      tracking.srcs) < 0)\n>     + \t\t\t\ttracking.remote, tracking.srcs) < 0)\n>       \t\texit(-1);\n>       \n>      +cleanup:\n>     @@ branch.c: N_(\"\\n\"\n>      @@ branch.c: void create_branch(struct repository *r,\n>       \n>       \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n>     - \t\tdie(_(\"not a valid branch point: '%s'\"), start_name);\n>     + \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n>      -\toidcpy(&oid, &commit->object.oid);\n>      +\tif (out_real_ref)\n>      +\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n> 2:  c543c1412a = 2:  ac2532a953 branch: make create_branch() always create a branch\n> 3:  dddd434d7a = 3:  a0ed3fa438 branch: add a dry_run parameter to create_branch()\n> 4:  41cca3bd52 = 4:  ebded31c96 builtin/branch: clean up action-picking logic in cmd_branch()\n> 5:  540eeab183 ! 5:  0a7ec6ee75 branch: add --recurse-submodules option for branch creation\n>     @@ t/t3207-branch-submodule.sh (new)\n>      +\t\ttest_must_fail git rev-parse branch-a &&\n>      +\n>      +\t\tcat >expected <<-EOF &&\n>     -+\t\tsubmodule ${SQ}sub${SQ}: fatal: a branch named ${SQ}branch-a${SQ} already exists\n>     ++\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n>      +\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n>      +\t\tEOF\n>      +\t\ttest_cmp expected actual\n>\n> base-commit: e1c6879cf24f90693ca108542ec1db970764e1dc\n> -- \n> 2.33.GIT\n"},{"id":"444566","messageId":"xmqqv8zisr43.fsf@gitster.g","threadId":"56962","inReplyTo":"20211220233459.45739-1-chooglen@google.com","subject":"Re: [PATCH v6 0/5] implement branch --recurse-submodules","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-21T01:07:08Z","receivedAt":"2021-12-21T01:07:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> v6 fixes v5's bad rebase; it was based off a merge commit in 'seen'\n> instead of js/branch-track-inherit. Since v5 is mostly a no-op, I will\n> also include a range-diff against v4, which is the last version that\n> anyone except Junio would care about.\n>\n> This version is based off Josh's js/branch-track-inherit v7. Because that\n> is not yet in 'seen',...\n\nThanks.  js/branch/track-inherit has been using v7 since Fri Dec 17\n15:47:47 2021 -0800, but unfortunately that was after the week's\nintegration work and the 'seen' shown to the public probably did not\nhave it.\n\nFYI at least since [*1*] was used to create 751363af (branch: add\nflags and config to inherit tracking, 2021-10-16), the topic\njs/branch-track-inherit has always been queued on top of f443b226\n(Thirteenth batch, 2021-10-14).  I try to keep the same base to keep\nthings stable, unless there is a strong enough reason why we should\ndepend on newer base commit.\n\nI've wiggled the patches to queue your v6 but haven't pushed the\nresult out yet.\n\nThanks.\n\n\n[Reference]\n\n*1* <b9356d9837479914bcf9a265f52afe170be7e2e2.1634445482.git.steadmon@google.com>\n"},{"id":"444632","messageId":"kl6lbl19zw07.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"xmqqv8zisr43.fsf@gitster.g","subject":"Re: [PATCH v6 0/5] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-21T17:51:52Z","receivedAt":"2021-12-21T17:51:55Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>\n>> v6 fixes v5's bad rebase; it was based off a merge commit in 'seen'\n>> instead of js/branch-track-inherit. Since v5 is mostly a no-op, I will\n>> also include a range-diff against v4, which is the last version that\n>> anyone except Junio would care about.\n>>\n>> This version is based off Josh's js/branch-track-inherit v7. Because that\n>> is not yet in 'seen',...\n>\n> Thanks.  js/branch/track-inherit has been using v7 since Fri Dec 17\n> 15:47:47 2021 -0800, but unfortunately that was after the week's\n> integration work and the 'seen' shown to the public probably did not\n> have it.\n>\n> FYI at least since [*1*] was used to create 751363af (branch: add\n> flags and config to inherit tracking, 2021-10-16), the topic\n> js/branch-track-inherit has always been queued on top of f443b226\n> (Thirteenth batch, 2021-10-14).  I try to keep the same base to keep\n> things stable, unless there is a strong enough reason why we should\n> depend on newer base commit.\n\nAh, I see, so it would make the most sense for me to apply the patches\non the the same base as you. I'll keep that in mind in the future.\n"},{"id":"444962","messageId":"xmqqpmpk2eii.fsf@gitster.g","threadId":"56962","inReplyTo":"20211220233459.45739-6-chooglen@google.com","subject":"Re: [PATCH v6 5/5] branch: add --recurse-submodules option for branch creation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-26T04:09:41Z","receivedAt":"2021-12-26T04:09:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"To be squashed if we were to take the series queued on 'seen'\notherwise as-is (or if the series will be rerolled, please squash it\ninto the new round on your end).\n\n----- >8 --------- >8 --------- >8 --------- >8 --------- >8 ----\nSubject: [PATCH] fixup! branch: add --recurse-submodules option for branch\n creation\n\nStyle fix to have SP on both sides of () in\n\n    shell_function () {\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t3207-branch-submodule.sh | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nindex a2dfb5ad7f..4256a95f4e 100755\n--- a/t/t3207-branch-submodule.sh\n+++ b/t/t3207-branch-submodule.sh\n@@ -44,7 +44,7 @@ cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n EOF\n chmod +x \"$CLEANUP_SCRIPT_PATH\"\n \n-cleanup_branches() {\n+cleanup_branches () {\n \tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n } >/dev/null 2>/dev/null\n \n-- \n2.34.1-568-g69e9fd72b5\n\n"},{"id":"445889","messageId":"20220111020953.1225789-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20211220233459.45739-2-chooglen@google.com","subject":"Re: [PATCH v6 1/5] branch: move --set-upstream-to behavior to dwim_and_setup_tracking()","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-01-11T02:09:53Z","receivedAt":"2022-01-11T02:10:01Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> This refactor is motivated by a desire to add a \"dry_run\" parameter to\n> create_branch() that will validate whether or not a branch can be\n> created without actually creating it - this behavior will be used in a\n> subsequent commit that adds `git branch --recurse-submodules topic`.\n\nMakes sense.\n\n> Adding \"dry_run\" is not obvious because create_branch() is also used to\n> set tracking information without creating a branch, i.e. when using\n> --set-upstream-to. \n\ncreate_branch() is complicated, OK.\n\n> This appears to be a leftover from 4fc5006676 (Add\n> branch --set-upstream, 2010-01-18), when --set-upstream would sometimes\n> create a branch and sometimes update tracking information without\n> creating a branch. However, we no longer support --set-upstream, so it\n> makes more sense to set tracking information with another function and\n> use create_branch() only to create branches. In a later commit, we will\n> remove the now-unnecessary logic from create_branch() so that \"dry_run\"\n> becomes trivial to implement.\n\nWhat do you mean by \"leftover\"?\n\nAside from that, the pertinent information is that the mentioned commit\nchanged create_branch() to no longer always create a branch, instead\nsometimes creating a branch and sometimes updating tracking information\n(and sometimes both). I don't think whether we support \"--set-upstream\"\nis material here.\n\nAlso, what is the now-unnecessary logic to be removed in a later commit?\n\n> Introduce dwim_and_setup_tracking(), which replaces create_branch()\n> in `git branch --set-upstream-to`. Ensure correctness by moving the DWIM\n> and branch validation logic from create_branch() into a helper function,\n> dwim_branch_start(), so that the logic is shared by both functions.\n\nI think it's clearer to just say what we're refactoring instead of\nsaying that we're introducing a function and making sure that it is\ncorrect, not by testing (as one would expect), but by moving logic.\n\nI would write the commit message like this:\n\n  This commit is in preparation for a future commit that adds a dry_run\n  parameter to create_branch() (that is needed for supporting \"git\n  branch --recurse-submodules\", to be introduced in another future\n  commit).\n\n  create_branch() used to always create a branch, but this was changed\n  in 4fc5006676 (Add branch --set-upstream, 2010-01-18), when it was\n  changed to be also able to set tracking information.\n\n  Refactor the code that sets tracking information into its own\n  functions dwim_branch_start() and dwim_and_setup_tracking(). Also\n  change an invocation of create_branch() in cmd_branch() in\n  builtin/branch.c to use dwim_and_setup_tracking(), since that\n  invocation is only for setting tracking information.\n\nAnd if this is true:\n\n  As of this commit, create_branch() still sometimes does not create\n  branches, but this will be fixed in a subsequent commit.\n\n> @@ -217,9 +217,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n>  }\n>  \n>  /*\n> - * This is called when new_ref is branched off of orig_ref, and tries\n> - * to infer the settings for branch.<new_ref>.{remote,merge} from the\n> - * config.\n> + * Used internally to set the branch.<new_ref>.{remote,merge} config\n> + * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike\n> + * dwim_and_setup_tracking(), this does not do DWIM, i.e. \"origin/main\"\n> + * will not be expanded to \"refs/remotes/origin/main\", so it is not safe\n> + * for 'orig_ref' to be raw user input.\n>   */\n>  static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\t\t   enum branch_track track, int quiet)\n\nThe comment makes sense.\n\n> @@ -244,7 +246,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\tcase BRANCH_TRACK_INHERIT:\n>  \t\t\tbreak;\n>  \t\tdefault:\n> -\t\t\treturn;\n> +\t\t\tgoto cleanup;\n>  \t\t}\n>  \n>  \tif (tracking.matches > 1)\n> @@ -257,6 +259,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\t\t\ttracking.remote, tracking.srcs) < 0)\n>  \t\texit(-1);\n>  \n> +cleanup:\n>  \tstring_list_clear(tracking.srcs, 0);\n>  }\n>  \n\nThis seems like it's just for avoiding a memory leak, and is unrelated\nto this commit, so it should go into its own commit.\n\n> @@ -340,31 +343,37 @@ N_(\"\\n\"\n>  \"will track its remote counterpart, you may want to use\\n\"\n>  \"\\\"git push -u\\\" to set the upstream config as you push.\");\n>  \n> -void create_branch(struct repository *r,\n> -\t\t   const char *name, const char *start_name,\n> -\t\t   int force, int clobber_head_ok, int reflog,\n> -\t\t   int quiet, enum branch_track track)\n\nThis seems to have the same parameters as the \"+\" version, but wrapped\ndifferently - don't rewrap unless you're also changing it.\n\n> +/**\n> + * DWIMs a user-provided ref to determine the starting point for a\n> + * branch and validates it, where:\n> + *\n> + *   - r is the repository to validate the branch for\n> + *\n> + *   - start_name is the ref that we would like to test. This is\n> + *     expanded with DWIM and assigned to out_real_ref.\n> + *\n> + *   - track is the tracking mode of the new branch. If tracking is\n> + *     explicitly requested, start_name must be a branch (because\n> + *     otherwise start_name cannot be tracked)\n> + *\n> + *   - out_oid is an out parameter containing the object_id of start_name\n> + *\n> + *   - out_real_ref is an out parameter containing the full, 'real' form\n> + *     of start_name e.g. refs/heads/main instead of main\n> + *\n> + */\n> +static void dwim_branch_start(struct repository *r, const char *start_name,\n> +\t\t\t   enum branch_track track, char **out_real_ref,\n> +\t\t\t   struct object_id *out_oid)\n\n[snip]\n\n> @@ -401,7 +410,34 @@ void create_branch(struct repository *r,\n>  \n>  \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n>  \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n> -\toidcpy(&oid, &commit->object.oid);\n> +\tif (out_real_ref)\n> +\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n\nI think you can just write \"*out_real_ref = real_ref; real_ref = NULL;\"\nhere, and then not need to xstrdup.\n\n> +\tif (out_oid)\n> +\t\toidcpy(out_oid, &commit->object.oid);\n> +\n> +\tFREE_AND_NULL(real_ref);\n> +}\n\nComparing dwim_branch_start()...\n\n> +void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n> +\t\t\t     const char *orig_ref, enum branch_track track,\n> +\t\t\t     int quiet)\n> +{\n> +\tchar *real_orig_ref;\n> +\tdwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL);\n> +\tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n> +}\n\n...and this...\n\n> @@ -823,12 +823,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>  \t\tif (!ref_exists(branch->refname))\n>  \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n>  \n> -\t\t/*\n> -\t\t * create_branch takes care of setting up the tracking\n> -\t\t * info and making sure new_upstream is correct\n> -\t\t */\n> -\t\tcreate_branch(the_repository, branch->name, new_upstream,\n> -\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n> +\t\tdwim_and_setup_tracking(the_repository, branch->name,\n> +\t\t\t\t\tnew_upstream, BRANCH_TRACK_OVERRIDE,\n> +\t\t\t\t\tquiet);\n>  \t} else if (unset_upstream) {\n>  \t\tstruct branch *branch = branch_get(argv[0]);\n>  \t\tstruct strbuf buf = STRBUF_INIT;\n\n...looking at this, I can see that dwim_and_setup_tracking() indeed does\neverything that this create_branch() invocation would do, so overall the\ncommit makes sense.\n"},{"id":"445894","messageId":"20220111021912.1228794-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20211220233459.45739-3-chooglen@google.com","subject":"Re: [PATCH v6 2/5] branch: make create_branch() always create a branch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-01-11T02:19:12Z","receivedAt":"2022-01-11T02:19:17Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> create_branch() was formerly used to set tracking without creating a\n> branch. Since the previous commit replaces this use case with\n> dwim_and_setup_tracking(), we can simplify create_branch() so that it\n> always creates a branch.\n> \n> Do this simplification, in particular:\n> \n> * remove the special handling of BRANCH_TRACK_OVERRIDE because it is no\n>   longer used\n> * assert that clobber_head_ok can only be provided with force\n> * check that we're handling clobber_head_ok and force correctly by\n>   introducing tests for `git branch --force`\n\nThis might have been more simply explained as:\n\n  With the previous commit, these are true of create_branch():\n   * BRANCH_TRACK_OVERRIDE is no longer passed\n   * if clobber_head_ok is true, force is also true\n\n  Assert these situations, delete dead code, and ensure that we're\n  handling clobber_head_ok and force correctly by introducing tests for\n  `git branch --force`. This also means that create_branch() now always\n  creates a branch.\n\n> @@ -426,15 +426,17 @@ void create_branch(struct repository *r, const char *name,\n>  \tchar *real_ref;\n>  \tstruct strbuf ref = STRBUF_INIT;\n>  \tint forcing = 0;\n> -\tint dont_change_ref = 0;\n> -\n> -\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n> -\t    ? validate_branchname(name, &ref)\n> -\t    : validate_new_branchname(name, &ref, force)) {\n> -\t\tif (!force)\n> -\t\t\tdont_change_ref = 1;\n> -\t\telse\n> -\t\t\tforcing = 1;\n> +\tstruct ref_transaction *transaction;\n> +\tstruct strbuf err = STRBUF_INIT;\n> +\tchar *msg;\n> +\n> +\tif (clobber_head_ok && !force)\n> +\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n> +\n> +\tif (clobber_head_ok ?\n> +\t\t\t  validate_branchname(name, &ref) :\n> +\t\t\t  validate_new_branchname(name, &ref, force)) {\n> +\t\tforcing = 1;\n>  \t}\n\nAlso assert that track is not BRANCH_TRACK_OVERRIDE.\n\n> @@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n>  \tgit branch abc && test_path_is_file .git/refs/heads/abc\n>  '\n>  \n> +test_expect_success 'git branch abc should fail when abc exists' '\n> +\ttest_must_fail git branch abc\n> +'\n> +\n> +test_expect_success 'git branch --force abc should fail when abc is checked out' '\n> +\ttest_when_finished git switch main &&\n> +\tgit switch abc &&\n> +\ttest_must_fail git branch --force abc HEAD~1\n> +'\n> +\n> +test_expect_success 'git branch --force abc should succeed when abc exists' '\n> +\tgit rev-parse HEAD~1 >expect &&\n> +\tgit branch --force abc HEAD~1 &&\n> +\tgit rev-parse abc >actual &&\n> +\ttest_cmp expect actual\n> +'\n\nThese tests make sense.\n"},{"id":"445901","messageId":"20220111032842.1247928-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20211220233459.45739-6-chooglen@google.com","subject":"Re: [PATCH v6 5/5] branch: add --recurse-submodules option for branch creation","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-01-11T03:28:42Z","receivedAt":"2022-01-11T03:28:47Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"I am not very familiar with branch tracking, so I'll review everything\nexcept that part.\n\nGlen Choo <chooglen@google.com> writes:\n> Although this commit does not introduce breaking changes, it is\n> incompatible with existing --recurse-submodules semantics e.g. `git\n> checkout` does not recursively checkout the expected branches created by\n> `git branch` yet. \n\nProbably worth explaining that it will not recursively checkout the\nexpected branches *if* any of them are subsequently updated. Maybe say:\n\n  If a user were to create branches in this way, create a commit on the\n  branch in a submodule, and run \"git checkout --recurse-submodules\" in\n  the superproject, the commits to be checked out (which are based on\n  the gitlink in the superproject, not on the ref store of the\n  submodule) probably wouldn't match the user's expectation.\n\n> To ensure that the correct set of semantics is used,\n> this commit introduces a new configuration value,\n> `submodule.propagateBranches`, which enables submodule branching when\n> true (defaults to false).\n\nAnd then this could be reworded to:\n\n  Because of this, this commit introduces a new configuration value\n  `submodule.propagateBranches`. The plan is for Git commands to\n  prioritize submodule ref store information over superproject gitlink\n  if this is true. Because \"git branch --recurse-submodules\" writes to\n  submodule ref stores, for the sake of clarity, it will not function\n  unless this configuration value is set.\n\n> @@ -71,6 +68,23 @@ submodule.recurse::\n>  \t`git fetch` but does not have a `--no-recurse-submodules` option.\n>  \tFor these commands a workaround is to temporarily change the\n>  \tconfiguration value by using `git -c submodule.recurse=0`.\n> +\t+\n> +\tThe following list shows the commands that accept\n> +\t`--recurse-submodules` and whether they are supported by this\n> +\tsetting.\n> +\t* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n> +\t`reset`, `restore` and `switch` are always supported.\n> +\t* `clone` and `ls-files` are not supported.\n> +\t* `branch` is supported only if `submodule.propagateBranches` is\n> +\tenabled\n\nOne oddity is that paragraphs after the \"+\" cannot be indented - see\nother documentation files for examples.\n\n> diff --git a/branch.c b/branch.c\n> index 55c7ba4a25..6d0d9a8e1b 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -8,6 +8,8 @@\n>  #include \"sequencer.h\"\n>  #include \"commit.h\"\n>  #include \"worktree.h\"\n> +#include \"submodule-config.h\"\n> +#include \"run-command.h\"\n>  \n>  struct tracking {\n>  \tstruct refspec_item spec;\n> @@ -478,6 +480,134 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n>  \tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n>  }\n>  \n> +/**\n> + * Creates a branch in a submodule by calling\n> + * create_branches_recursively() in a child process. The child process\n> + * is necessary because install_branch_config() (and its variants) do\n> + * not support writing configs to submodules.\n> + */\n\nMakes sense that we need a child process, but could the child process be\n\"branch\" instead of \"submodule--helper\"? If not, also mention why.\n\nAs for the reason, probably better to explicitly mention\ninstall_branch_config_multiple_remotes() and say \"(which is called by\nsetup_tracking())\".\n\n> diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\n> new file mode 100755\n> index 0000000000..a2dfb5ad7f\n> --- /dev/null\n> +++ b/t/t3207-branch-submodule.sh\n> @@ -0,0 +1,291 @@\n> +#!/bin/sh\n> +\n> +test_description='git branch submodule tests'\n> +\n> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> +\n> +. ./test-lib.sh\n> +. \"$TEST_DIRECTORY\"/lib-rebase.sh\n> +\n> +test_expect_success 'setup superproject and submodule' '\n> +\tgit init super &&\n> +\ttest_commit foo &&\n> +\tgit init sub-sub-upstream &&\n> +\ttest_commit -C sub-sub-upstream foo &&\n> +\tgit init sub-upstream &&\n> +\t# Submodule in a submodule\n> +\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n> +\tgit -C sub-upstream commit -m \"add submodule\" &&\n> +\t# Regular submodule\n> +\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n> +\t# Submodule in a subdirectory\n> +\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" second/sub &&\n> +\tgit -C super commit -m \"add submodule\" &&\n> +\tgit -C super config submodule.propagateBranches true &&\n> +\tgit -C super/sub submodule update --init\n> +'\n> +\n> +CLEANUP_SCRIPT_PATH=\"$TRASH_DIRECTORY/cleanup_branches.sh\"\n> +\n> +cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n> +\t#!/bin/sh\n> +\n> +\tsuper_dir=\"$1\"\n> +\tshift\n> +\t(\n> +\t\tcd \"$super_dir\" &&\n> +\t\tgit checkout main &&\n> +\t\tfor branch_name in \"$@\"; do\n> +\t\t\tgit branch -D \"$branch_name\"\n> +\t\t\tgit submodule foreach \"$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true\"\n> +\t\tdone\n> +\t)\n> +EOF\n> +chmod +x \"$CLEANUP_SCRIPT_PATH\"\n> +\n> +cleanup_branches() {\n> +\tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n> +} >/dev/null 2>/dev/null\n\nI don't think that the cleanup is saving much in process invocation\nanymore - could we just delete the whole thing and start anew in each\ntest?\n\nThe rest of the tests are assuming that the cleanup works as expected -\nI didn't take a close look.\n\nAlso, you should probably use \"$(pwd)\" instead of $TRASH_DIRECTORY.\n\n> +test_expect_success '--recurse-submodules should fail when not creating branches' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch --recurse-submodules branch-a &&\n> +\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n> +\t\t# Assert that the branches were not deleted\n> +\t\tgit rev-parse --abbrev-ref branch-a &&\n> +\t\tgit -C sub rev-parse --abbrev-ref branch-a\n> +\t)\n> +'\n\nIf we're just checking that the ref exists, no need for \"--abbrev-ref\".\nSame comment throughout the file.\n\n> +test_expect_success 'should not create any branches if branch is not valid for all repos' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit -C sub branch branch-a &&\n> +\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n> +\t\ttest_must_fail git rev-parse branch-a &&\n> +\n> +\t\tcat >expected <<-EOF &&\n> +\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n> +\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n> +\t\tEOF\n> +\t\ttest_cmp expected actual\n> +\t)\n> +'\n\nThe error message seems too specific - probably enough to grep for the\ninformation about the branch already existing.\n\n> +test_expect_success 'should create branches if branch exists and --force is given' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit -C sub rev-parse HEAD >expected &&\n> +\t\ttest_commit -C sub baz &&\n> +\t\tgit -C sub branch branch-a HEAD~1 &&\n> +\t\tgit branch --recurse-submodules --force branch-a &&\n> +\t\tgit rev-parse branch-a &&\n> +\t\t# assert that sub:branch-a was moved\n> +\t\tgit -C sub rev-parse branch-a >actual &&\n> +\t\ttest_cmp expected actual\n> +\t)\n> +'\n\nShould we create branch-a at HEAD instead of HEAD~1?\n\n> +test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '\n> +\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch branch-a &&\n> +\t\tgit checkout -b branch-b &&\n> +\t\tgit submodule add ../sub-upstream sub2 &&\n> +\t\tgit -C sub2 submodule update --init &&\n> +\t\t# branch-b now has a committed submodule not in branch-a\n> +\t\tgit commit -m \"add second submodule\" &&\n> +\t\tgit checkout branch-a &&\n> +\t\tgit branch --recurse-submodules branch-c branch-b &&\n> +\t\tgit rev-parse branch-c &&\n> +\t\tgit -C sub rev-parse branch-c &&\n> +\t\tgit -C second/sub rev-parse branch-c &&\n> +\t\tgit checkout --recurse-submodules branch-c &&\n> +\t\tgit -C sub2 rev-parse branch-c &&\n> +\t\tgit -C sub2/sub-sub rev-parse branch-c\n> +\t)\n> +'\n\nNo need to check so many repos, I think - just sub2 will do.\n\n[skip tracking tests]\n\n> +test_expect_success 'should not create branches in inactive submodules' '\n> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> +\ttest_config -C super submodule.sub.active false &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch --recurse-submodules branch-a &&\n> +\t\tgit rev-parse branch-a &&\n> +\t\ttest_must_fail git -C sub branch-a\n> +\t)\n> +'\n\nMakes sense, but could all the tracking tests be together in the file?\nOr is this order for a reason?\n\n[skip tracking tests]\n\n> +test_expect_success 'setup remote-tracking tests' '\n\nThis setup is not just for remote-tracking tests.\n\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch branch-a &&\n> +\t\tgit checkout -b branch-b &&\n> +\t\tgit submodule add ../sub-upstream sub2 &&\n> +\t\t# branch-b now has a committed submodule not in branch-a\n> +\t\tgit commit -m \"add second submodule\"\n> +\t) &&\n> +\tgit clone --branch main --recurse-submodules super super-clone &&\n> +\tgit -C super-clone config submodule.propagateBranches true\n> +'\n> +\n> +test_expect_success 'should not create branch when submodule is not in .git/modules' '\n\nI understand that no branch is created, but the title is ambiguous, to\nme, whether it is a fatal error or not. Maybe the title should be \"fatal\nerror upon branch creation when submodule is not in .git/modules\".\n\n> +\t# The cleanup needs to delete sub2 separately because main does not have sub2\n> +\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n> +\t\tgit -C super-clone/sub2/sub-sub branch -D branch-b && \\\n> +\t\tcleanup_branches super-clone branch-a branch-b\" &&\n> +\t(\n> +\t\tcd super-clone &&\n> +\t\t# This should succeed because super-clone has sub.\n\n\"has sub in .git/modules\", I think.\n\n> +\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n> +\t\t# This should fail because super-clone does not have sub2.\n\nLikewise.\n\n> +\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n> +\t\tcat >expected <<-EOF &&\n> +\t\thint: You may try updating the submodules using ${SQ}git checkout origin/branch-b && git submodule update --init${SQ}\n> +\t\tfatal: submodule ${SQ}sub2${SQ}: unable to find submodule\n> +\t\tEOF\n> +\t\ttest_cmp expected actual &&\n> +\t\ttest_must_fail git rev-parse branch-b &&\n> +\t\ttest_must_fail git -C sub rev-parse branch-b &&\n> +\t\t# User can fix themselves by initializing the submodule\n> +\t\tgit checkout origin/branch-b &&\n> +\t\tgit submodule update --init --recursive &&\n> +\t\tgit branch --recurse-submodules branch-b origin/branch-b\n> +\t)\n> +'\n\nApart from the branch tracking part (which I didn't review), overall,\nthis patch set looks good.\n"},{"id":"445935","messageId":"kl6la6g2kwp5.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20220111020953.1225789-1-jonathantanmy@google.com","subject":"Re: [PATCH v6 1/5] branch: move --set-upstream-to behavior to dwim_and_setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-11T17:29:26Z","receivedAt":"2022-01-11T17:29:33Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>> This appears to be a leftover from 4fc5006676 (Add\n>> branch --set-upstream, 2010-01-18), when --set-upstream would sometimes\n>> create a branch and sometimes update tracking information without\n>> creating a branch. However, we no longer support --set-upstream, so it\n>> makes more sense to set tracking information with another function and\n>> use create_branch() only to create branches. In a later commit, we will\n>> remove the now-unnecessary logic from create_branch() so that \"dry_run\"\n>> becomes trivial to implement.\n>\n> What do you mean by \"leftover\"?\n>\n> Aside from that, the pertinent information is that the mentioned commit\n> changed create_branch() to no longer always create a branch, instead\n> sometimes creating a branch and sometimes updating tracking information\n> (and sometimes both). I don't think whether we support \"--set-upstream\"\n> is material here.\n\nI was hoping to explain why we made the decision to reuse\ncreate_branch() to set tracking information (it made sense with\n--set-upstream) and why it now makes sense to use another function\n(because we no longer have --set-upstream).\n\nBut maybe this justification is unnecessary, and the desire to add a\ndry_run parameter is enough.\n\n>\n> Also, what is the now-unnecessary logic to be removed in a later commit?\n\nThis would be the logic that makes create_branch() not create branches,\nwhich is addressed in your proposed commit message.\n\n>> Introduce dwim_and_setup_tracking(), which replaces create_branch()\n>> in `git branch --set-upstream-to`. Ensure correctness by moving the DWIM\n>> and branch validation logic from create_branch() into a helper function,\n>> dwim_branch_start(), so that the logic is shared by both functions.\n>\n> I think it's clearer to just say what we're refactoring instead of\n> saying that we're introducing a function and making sure that it is\n> correct, not by testing (as one would expect), but by moving logic.\n>\n> I would write the commit message like this:\n>\n>   This commit is in preparation for a future commit that adds a dry_run\n>   parameter to create_branch() (that is needed for supporting \"git\n>   branch --recurse-submodules\", to be introduced in another future\n>   commit).\n>\n>   create_branch() used to always create a branch, but this was changed\n>   in 4fc5006676 (Add branch --set-upstream, 2010-01-18), when it was\n>   changed to be also able to set tracking information.\n>\n>   Refactor the code that sets tracking information into its own\n>   functions dwim_branch_start() and dwim_and_setup_tracking(). Also\n>   change an invocation of create_branch() in cmd_branch() in\n>   builtin/branch.c to use dwim_and_setup_tracking(), since that\n>   invocation is only for setting tracking information.\n>\n> And if this is true:\n>\n>   As of this commit, create_branch() still sometimes does not create\n>   branches, but this will be fixed in a subsequent commit.\n\nHm, I see. This makes sense, I'll incorporate some of your suggestions :)\n\n>> @@ -244,7 +246,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>>  \t\tcase BRANCH_TRACK_INHERIT:\n>>  \t\t\tbreak;\n>>  \t\tdefault:\n>> -\t\t\treturn;\n>> +\t\t\tgoto cleanup;\n>>  \t\t}\n>>  \n>>  \tif (tracking.matches > 1)\n>> @@ -257,6 +259,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>>  \t\t\t\ttracking.remote, tracking.srcs) < 0)\n>>  \t\texit(-1);\n>>  \n>> +cleanup:\n>>  \tstring_list_clear(tracking.srcs, 0);\n>>  }\n>>  \n>\n> This seems like it's just for avoiding a memory leak, and is unrelated\n> to this commit, so it should go into its own commit.\n\nThanks for the catch.\n\n>\n>> @@ -340,31 +343,37 @@ N_(\"\\n\"\n>>  \"will track its remote counterpart, you may want to use\\n\"\n>>  \"\\\"git push -u\\\" to set the upstream config as you push.\");\n>>  \n>> -void create_branch(struct repository *r,\n>> -\t\t   const char *name, const char *start_name,\n>> -\t\t   int force, int clobber_head_ok, int reflog,\n>> -\t\t   int quiet, enum branch_track track)\n>\n> This seems to have the same parameters as the \"+\" version, but wrapped\n> differently - don't rewrap unless you're also changing it.\n\nAh, I didn't realize it had rewrapped. Thanks for the catch.\n\n>> +/**\n>> + * DWIMs a user-provided ref to determine the starting point for a\n>> + * branch and validates it, where:\n>> + *\n>> + *   - r is the repository to validate the branch for\n>> + *\n>> + *   - start_name is the ref that we would like to test. This is\n>> + *     expanded with DWIM and assigned to out_real_ref.\n>> + *\n>> + *   - track is the tracking mode of the new branch. If tracking is\n>> + *     explicitly requested, start_name must be a branch (because\n>> + *     otherwise start_name cannot be tracked)\n>> + *\n>> + *   - out_oid is an out parameter containing the object_id of start_name\n>> + *\n>> + *   - out_real_ref is an out parameter containing the full, 'real' form\n>> + *     of start_name e.g. refs/heads/main instead of main\n>> + *\n>> + */\n>> +static void dwim_branch_start(struct repository *r, const char *start_name,\n>> +\t\t\t   enum branch_track track, char **out_real_ref,\n>> +\t\t\t   struct object_id *out_oid)\n>\n> [snip]\n>\n>> @@ -401,7 +410,34 @@ void create_branch(struct repository *r,\n>>  \n>>  \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n>>  \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n>> -\toidcpy(&oid, &commit->object.oid);\n>> +\tif (out_real_ref)\n>> +\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n>\n> I think you can just write \"*out_real_ref = real_ref; real_ref = NULL;\"\n> here, and then not need to xstrdup.\n\nHm, you are right. The xstrdup was added because the original function\ncalls FREE_AND_NULL(real_ref) and then checks if real_ref is NULL. But\nafter the refactor, real_ref is not referenced after the\nFREE_AND_NULL(real_ref), so that call can be removed.\n\nI intend to remove the xstrdup, though it will introduce a bit of noise\nbecause that block will no longer be moved wholesale.\n"},{"id":"445936","messageId":"kl6l7db6kvp2.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20220111021912.1228794-1-jonathantanmy@google.com","subject":"Re: [PATCH v6 2/5] branch: make create_branch() always create a branch","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-11T17:51:05Z","receivedAt":"2022-01-11T17:51:14Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>> create_branch() was formerly used to set tracking without creating a\n>> branch. Since the previous commit replaces this use case with\n>> dwim_and_setup_tracking(), we can simplify create_branch() so that it\n>> always creates a branch.\n>> \n>> Do this simplification, in particular:\n>> \n>> * remove the special handling of BRANCH_TRACK_OVERRIDE because it is no\n>>   longer used\n>> * assert that clobber_head_ok can only be provided with force\n>> * check that we're handling clobber_head_ok and force correctly by\n>>   introducing tests for `git branch --force`\n>\n> This might have been more simply explained as:\n>\n>   With the previous commit, these are true of create_branch():\n>    * BRANCH_TRACK_OVERRIDE is no longer passed\n>    * if clobber_head_ok is true, force is also true\n>\n>   Assert these situations, delete dead code, and ensure that we're\n>   handling clobber_head_ok and force correctly by introducing tests for\n>   `git branch --force`. This also means that create_branch() now always\n>   creates a branch.\n\nThis is a great suggestion, I'll incorporate some of this.\n\nAs an aside, it has always been the case that clobber_head_ok is only true\nwhen force is true, so it might be misleading to include it under the\numbrella of 'With the previous commit'. I'll move things around\naccordingly.\n\n>> @@ -426,15 +426,17 @@ void create_branch(struct repository *r, const char *name,\n>>  \tchar *real_ref;\n>>  \tstruct strbuf ref = STRBUF_INIT;\n>>  \tint forcing = 0;\n>> -\tint dont_change_ref = 0;\n>> -\n>> -\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n>> -\t    ? validate_branchname(name, &ref)\n>> -\t    : validate_new_branchname(name, &ref, force)) {\n>> -\t\tif (!force)\n>> -\t\t\tdont_change_ref = 1;\n>> -\t\telse\n>> -\t\t\tforcing = 1;\n>> +\tstruct ref_transaction *transaction;\n>> +\tstruct strbuf err = STRBUF_INIT;\n>> +\tchar *msg;\n>> +\n>> +\tif (clobber_head_ok && !force)\n>> +\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n>> +\n>> +\tif (clobber_head_ok ?\n>> +\t\t\t  validate_branchname(name, &ref) :\n>> +\t\t\t  validate_new_branchname(name, &ref, force)) {\n>> +\t\tforcing = 1;\n>>  \t}\n>\n> Also assert that track is not BRANCH_TRACK_OVERRIDE.\n\nMakes sense, I'll do that.\n"},{"id":"445947","messageId":"kl6l4k6akurn.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20220111032842.1247928-1-jonathantanmy@google.com","subject":"Re: [PATCH v6 5/5] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-11T18:11:08Z","receivedAt":"2022-01-11T18:11:17Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> I am not very familiar with branch tracking, so I'll review everything\n> except that part.\n>\n> Glen Choo <chooglen@google.com> writes:\n>> Although this commit does not introduce breaking changes, it is\n>> incompatible with existing --recurse-submodules semantics e.g. `git\n>> checkout` does not recursively checkout the expected branches created by\n>> `git branch` yet. \n>\n> Probably worth explaining that it will not recursively checkout the\n> expected branches *if* any of them are subsequently updated. Maybe say:\n>\n>   If a user were to create branches in this way, create a commit on the\n>   branch in a submodule, and run \"git checkout --recurse-submodules\" in\n>   the superproject, the commits to be checked out (which are based on\n>   the gitlink in the superproject, not on the ref store of the\n>   submodule) probably wouldn't match the user's expectation.\n>\n>> To ensure that the correct set of semantics is used,\n>> this commit introduces a new configuration value,\n>> `submodule.propagateBranches`, which enables submodule branching when\n>> true (defaults to false).\n>\n> And then this could be reworded to:\n>\n>   Because of this, this commit introduces a new configuration value\n>   `submodule.propagateBranches`. The plan is for Git commands to\n>   prioritize submodule ref store information over superproject gitlink\n>   if this is true. Because \"git branch --recurse-submodules\" writes to\n>   submodule ref stores, for the sake of clarity, it will not function\n>   unless this configuration value is set.\n\nThanks! I'll use this wording - otherwise it might not be clear to\nreaders what the difference in 'semantics' refers to.\n\n>\n>> @@ -71,6 +68,23 @@ submodule.recurse::\n>>  \t`git fetch` but does not have a `--no-recurse-submodules` option.\n>>  \tFor these commands a workaround is to temporarily change the\n>>  \tconfiguration value by using `git -c submodule.recurse=0`.\n>> +\t+\n>> +\tThe following list shows the commands that accept\n>> +\t`--recurse-submodules` and whether they are supported by this\n>> +\tsetting.\n>> +\t* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n>> +\t`reset`, `restore` and `switch` are always supported.\n>> +\t* `clone` and `ls-files` are not supported.\n>> +\t* `branch` is supported only if `submodule.propagateBranches` is\n>> +\tenabled\n>\n> One oddity is that paragraphs after the \"+\" cannot be indented - see\n> other documentation files for examples.\n\nAh, thanks for the catch. Interestingly, it still rendered as expected\nwhen I ran `make` - perhaps a deviation from the asciidoc spec.\n\n>> diff --git a/branch.c b/branch.c\n>> index 55c7ba4a25..6d0d9a8e1b 100644\n>> --- a/branch.c\n>> +++ b/branch.c\n>> @@ -8,6 +8,8 @@\n>>  #include \"sequencer.h\"\n>>  #include \"commit.h\"\n>>  #include \"worktree.h\"\n>> +#include \"submodule-config.h\"\n>> +#include \"run-command.h\"\n>>  \n>>  struct tracking {\n>>  \tstruct refspec_item spec;\n>> @@ -478,6 +480,134 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n>>  \tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n>>  }\n>>  \n>> +/**\n>> + * Creates a branch in a submodule by calling\n>> + * create_branches_recursively() in a child process. The child process\n>> + * is necessary because install_branch_config() (and its variants) do\n>> + * not support writing configs to submodules.\n>> + */\n>\n> Makes sense that we need a child process, but could the child process be\n> \"branch\" instead of \"submodule--helper\"? If not, also mention why.\n\nI'll mention why it has to be \"submodule--helper\"; I can see why a\nreader might wonder this.\n\n> As for the reason, probably better to explicitly mention\n> install_branch_config_multiple_remotes() and say \"(which is called by\n> setup_tracking())\".\n\nSounds good.\n\n>> diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\n>> new file mode 100755\n>> index 0000000000..a2dfb5ad7f\n>> --- /dev/null\n>> +++ b/t/t3207-branch-submodule.sh\n>> @@ -0,0 +1,291 @@\n>> +#!/bin/sh\n>> +\n>> +test_description='git branch submodule tests'\n>> +\n>> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n>> +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n>> +\n>> +. ./test-lib.sh\n>> +. \"$TEST_DIRECTORY\"/lib-rebase.sh\n>> +\n>> +test_expect_success 'setup superproject and submodule' '\n>> +\tgit init super &&\n>> +\ttest_commit foo &&\n>> +\tgit init sub-sub-upstream &&\n>> +\ttest_commit -C sub-sub-upstream foo &&\n>> +\tgit init sub-upstream &&\n>> +\t# Submodule in a submodule\n>> +\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n>> +\tgit -C sub-upstream commit -m \"add submodule\" &&\n>> +\t# Regular submodule\n>> +\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n>> +\t# Submodule in a subdirectory\n>> +\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" second/sub &&\n>> +\tgit -C super commit -m \"add submodule\" &&\n>> +\tgit -C super config submodule.propagateBranches true &&\n>> +\tgit -C super/sub submodule update --init\n>> +'\n>> +\n>> +CLEANUP_SCRIPT_PATH=\"$TRASH_DIRECTORY/cleanup_branches.sh\"\n>> +\n>> +cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n>> +\t#!/bin/sh\n>> +\n>> +\tsuper_dir=\"$1\"\n>> +\tshift\n>> +\t(\n>> +\t\tcd \"$super_dir\" &&\n>> +\t\tgit checkout main &&\n>> +\t\tfor branch_name in \"$@\"; do\n>> +\t\t\tgit branch -D \"$branch_name\"\n>> +\t\t\tgit submodule foreach \"$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true\"\n>> +\t\tdone\n>> +\t)\n>> +EOF\n>> +chmod +x \"$CLEANUP_SCRIPT_PATH\"\n>> +\n>> +cleanup_branches() {\n>> +\tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n>> +} >/dev/null 2>/dev/null\n>\n> I don't think that the cleanup is saving much in process invocation\n> anymore - could we just delete the whole thing and start anew in each\n> test?\n>\n> The rest of the tests are assuming that the cleanup works as expected -\n> I didn't take a close look.\n\nHm, you're right - deleting the branches is already quite slow, starting\nanew would be easier and might not be much slower. I'll test the\n'starting anew' approach to make sure it's not too slow.\n\n> Also, you should probably use \"$(pwd)\" instead of $TRASH_DIRECTORY.\n\nThanks!\n\n>> +test_expect_success '--recurse-submodules should fail when not creating branches' '\n>> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit branch --recurse-submodules branch-a &&\n>> +\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n>> +\t\t# Assert that the branches were not deleted\n>> +\t\tgit rev-parse --abbrev-ref branch-a &&\n>> +\t\tgit -C sub rev-parse --abbrev-ref branch-a\n>> +\t)\n>> +'\n>\n> If we're just checking that the ref exists, no need for \"--abbrev-ref\".\n> Same comment throughout the file.\n\nAh yes, thanks.\n\n>> +test_expect_success 'should not create any branches if branch is not valid for all repos' '\n>> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit -C sub branch branch-a &&\n>> +\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n>> +\t\ttest_must_fail git rev-parse branch-a &&\n>> +\n>> +\t\tcat >expected <<-EOF &&\n>> +\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n>> +\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n>> +\t\tEOF\n>> +\t\ttest_cmp expected actual\n>> +\t)\n>> +'\n>\n> The error message seems too specific - probably enough to grep for the\n> information about the branch already existing.\n\nMakes sense.\n\n>\n>> +test_expect_success 'should create branches if branch exists and --force is given' '\n>> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit -C sub rev-parse HEAD >expected &&\n>> +\t\ttest_commit -C sub baz &&\n>> +\t\tgit -C sub branch branch-a HEAD~1 &&\n>> +\t\tgit branch --recurse-submodules --force branch-a &&\n>> +\t\tgit rev-parse branch-a &&\n>> +\t\t# assert that sub:branch-a was moved\n>> +\t\tgit -C sub rev-parse branch-a >actual &&\n>> +\t\ttest_cmp expected actual\n>> +\t)\n>> +'\n>\n> Should we create branch-a at HEAD instead of HEAD~1?\n\nIf we create branch-a at HEAD, we won't be testing that --force moves\nthe branch head. This means that the test might pass if we simply ignore\nany existing branch-a - which is not the intended behavior of --force,\nbut this is behavior that we might want in the future (probably using\nanother option).\n\n>> +test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '\n>> +\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit branch branch-a &&\n>> +\t\tgit checkout -b branch-b &&\n>> +\t\tgit submodule add ../sub-upstream sub2 &&\n>> +\t\tgit -C sub2 submodule update --init &&\n>> +\t\t# branch-b now has a committed submodule not in branch-a\n>> +\t\tgit commit -m \"add second submodule\" &&\n>> +\t\tgit checkout branch-a &&\n>> +\t\tgit branch --recurse-submodules branch-c branch-b &&\n>> +\t\tgit rev-parse branch-c &&\n>> +\t\tgit -C sub rev-parse branch-c &&\n>> +\t\tgit -C second/sub rev-parse branch-c &&\n>> +\t\tgit checkout --recurse-submodules branch-c &&\n>> +\t\tgit -C sub2 rev-parse branch-c &&\n>> +\t\tgit -C sub2/sub-sub rev-parse branch-c\n>> +\t)\n>> +'\n>\n> No need to check so many repos, I think - just sub2 will do.\n\nHm yes, there isn't a reason to think that the branch wouldn't be\ncreated in the other repos.\n\n>\n> [skip tracking tests]\n>\n>> +test_expect_success 'should not create branches in inactive submodules' '\n>> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n>> +\ttest_config -C super submodule.sub.active false &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit branch --recurse-submodules branch-a &&\n>> +\t\tgit rev-parse branch-a &&\n>> +\t\ttest_must_fail git -C sub branch-a\n>> +\t)\n>> +'\n>\n> Makes sense, but could all the tracking tests be together in the file?\n> Or is this order for a reason?\n\nNo, you're right, the order doesn't make sense. I'll move this to before\nthe tracking tests.\n\n>> +test_expect_success 'setup remote-tracking tests' '\n>\n> This setup is not just for remote-tracking tests.\n\nYes, this is misleading, thanks.\n\n>\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit branch branch-a &&\n>> +\t\tgit checkout -b branch-b &&\n>> +\t\tgit submodule add ../sub-upstream sub2 &&\n>> +\t\t# branch-b now has a committed submodule not in branch-a\n>> +\t\tgit commit -m \"add second submodule\"\n>> +\t) &&\n>> +\tgit clone --branch main --recurse-submodules super super-clone &&\n>> +\tgit -C super-clone config submodule.propagateBranches true\n>> +'\n>> +\n>> +test_expect_success 'should not create branch when submodule is not in .git/modules' '\n>\n> I understand that no branch is created, but the title is ambiguous, to\n> me, whether it is a fatal error or not. Maybe the title should be \"fatal\n> error upon branch creation when submodule is not in .git/modules\".\n\nOk, makes sense.\n\n>\n>> +\t# The cleanup needs to delete sub2 separately because main does not have sub2\n>> +\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n>> +\t\tgit -C super-clone/sub2/sub-sub branch -D branch-b && \\\n>> +\t\tcleanup_branches super-clone branch-a branch-b\" &&\n>> +\t(\n>> +\t\tcd super-clone &&\n>> +\t\t# This should succeed because super-clone has sub.\n>\n> \"has sub in .git/modules\", I think.\n>\n>> +\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n>> +\t\t# This should fail because super-clone does not have sub2.\n>\n> Likewise.\n\nAh, yes, it might be confusing otherwise.\n"},{"id":"445962","messageId":"20220111200330.23366-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"kl6la6g2kwp5.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v6 1/5] branch: move --set-upstream-to behavior to dwim_and_setup_tracking()","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-01-11T20:03:30Z","receivedAt":"2022-01-11T20:03:37Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> >> @@ -401,7 +410,34 @@ void create_branch(struct repository *r,\n> >>  \n> >>  \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n> >>  \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n> >> -\toidcpy(&oid, &commit->object.oid);\n> >> +\tif (out_real_ref)\n> >> +\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n> >\n> > I think you can just write \"*out_real_ref = real_ref; real_ref = NULL;\"\n> > here, and then not need to xstrdup.\n> \n> Hm, you are right. The xstrdup was added because the original function\n> calls FREE_AND_NULL(real_ref) and then checks if real_ref is NULL. But\n> after the refactor, real_ref is not referenced after the\n> FREE_AND_NULL(real_ref), so that call can be removed.\n> \n> I intend to remove the xstrdup, though it will introduce a bit of noise\n> because that block will no longer be moved wholesale.\n\nAh, thanks for watching out for the diff. I think in this case, we'll be\nfine, since the xstrdup line is a new line (\"xstrdup\" only appears once\nin the diff, here).\n"},{"id":"445965","messageId":"20220111201556.26345-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"kl6l4k6akurn.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v6 5/5] branch: add --recurse-submodules option for branch creation","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-01-11T20:15:56Z","receivedAt":"2022-01-11T20:16:50Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> >> +test_expect_success 'should create branches if branch exists and --force is given' '\n> >> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n> >> +\t(\n> >> +\t\tcd super &&\n> >> +\t\tgit -C sub rev-parse HEAD >expected &&\n> >> +\t\ttest_commit -C sub baz &&\n> >> +\t\tgit -C sub branch branch-a HEAD~1 &&\n> >> +\t\tgit branch --recurse-submodules --force branch-a &&\n> >> +\t\tgit rev-parse branch-a &&\n> >> +\t\t# assert that sub:branch-a was moved\n> >> +\t\tgit -C sub rev-parse branch-a >actual &&\n> >> +\t\ttest_cmp expected actual\n> >> +\t)\n> >> +'\n> >\n> > Should we create branch-a at HEAD instead of HEAD~1?\n> \n> If we create branch-a at HEAD, we won't be testing that --force moves\n> the branch head. \n\nWalking through the lines of the test:\n\n> >> +\t\tgit -C sub rev-parse HEAD >expected &&\n\nSo \"expected\" is sub's HEAD at the start of the test. Let's call this\nold-head.\n\n> >> +\t\ttest_commit -C sub baz &&\n\nWe create a new commit on top, whose parent is old-head. Let's call this\nnew-head.\n\n> >> +\t\tgit -C sub branch branch-a HEAD~1 &&\n\nWe create a new branch at HEAD~1, which is new-head's parent, which is\nold-head. So this branch points to the same thing as \"expected\".\n\n> >> +\t\tgit branch --recurse-submodules --force branch-a &&\n\nWhen creating new branches with \"--force\", any branch information in the\nsubmodule is ignored. So we would expect \"branch-a\" in sub to be\noverridden from \"old-head\" to \"old-head\".\n\n> >> +\t\tgit rev-parse branch-a &&\n\nVerifying that branch-a exists, although upon second look, this would\nwork whether or not we recursed, so maybe this line can be deleted.\n\n> >> +\t\t# assert that sub:branch-a was moved\n> >> +\t\tgit -C sub rev-parse branch-a >actual &&\n> >> +\t\ttest_cmp expected actual\n\nA check, as expected.\n\nUnless I missed something, branch-a was never moved - it was created at\n\"old-head\" and then the \"branch --force\" is supposed to create it at\n\"old-head\" anyway. It would make more sense to me if the branch was\ncreated at \"new-head\", and then \"branch --force\" moved it to \"old-head\".\n\n> This means that the test might pass if we simply ignore\n> any existing branch-a - which is not the intended behavior of --force,\n> but this is behavior that we might want in the future (probably using\n> another option).\n\nBy \"ignore\", supposing that there is an existing branch-a, do you mean\noverwrite branch-a, or not create any branch at all?\n"},{"id":"445980","messageId":"kl6lzgo1j1ss.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20220111201556.26345-1-jonathantanmy@google.com","subject":"Re: [PATCH v6 5/5] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-11T23:22:11Z","receivedAt":"2022-01-11T23:22:24Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>> >> +test_expect_success 'should create branches if branch exists and --force is given' '\n>> >> +\ttest_when_finished \"cleanup_branches super branch-a\" &&\n>> >> +\t(\n>> >> +\t\tcd super &&\n>> >> +\t\tgit -C sub rev-parse HEAD >expected &&\n>> >> +\t\ttest_commit -C sub baz &&\n>> >> +\t\tgit -C sub branch branch-a HEAD~1 &&\n>> >> +\t\tgit branch --recurse-submodules --force branch-a &&\n>> >> +\t\tgit rev-parse branch-a &&\n>> >> +\t\t# assert that sub:branch-a was moved\n>> >> +\t\tgit -C sub rev-parse branch-a >actual &&\n>> >> +\t\ttest_cmp expected actual\n>> >> +\t)\n>> >> +'\n>> >\n>> > Should we create branch-a at HEAD instead of HEAD~1?\n>> \n>> If we create branch-a at HEAD, we won't be testing that --force moves\n>> the branch head. \n>\n> Walking through the lines of the test:\n>\n>> >> +\t\tgit -C sub rev-parse HEAD >expected &&\n>\n> So \"expected\" is sub's HEAD at the start of the test. Let's call this\n> old-head.\n>\n>> >> +\t\ttest_commit -C sub baz &&\n>\n> We create a new commit on top, whose parent is old-head. Let's call this\n> new-head.\n>\n>> >> +\t\tgit -C sub branch branch-a HEAD~1 &&\n>\n> We create a new branch at HEAD~1, which is new-head's parent, which is\n> old-head. So this branch points to the same thing as \"expected\".\n>\n>> >> +\t\tgit branch --recurse-submodules --force branch-a &&\n>\n> When creating new branches with \"--force\", any branch information in the\n> submodule is ignored. So we would expect \"branch-a\" in sub to be\n> overridden from \"old-head\" to \"old-head\".\n>\n>> >> +\t\tgit rev-parse branch-a &&\n>\n> Verifying that branch-a exists, although upon second look, this would\n> work whether or not we recursed, so maybe this line can be deleted.\n>\n>> >> +\t\t# assert that sub:branch-a was moved\n>> >> +\t\tgit -C sub rev-parse branch-a >actual &&\n>> >> +\t\ttest_cmp expected actual\n>\n> A check, as expected.\n>\n> Unless I missed something, branch-a was never moved - it was created at\n> \"old-head\" and then the \"branch --force\" is supposed to create it at\n> \"old-head\" anyway. It would make more sense to me if the branch was\n> created at \"new-head\", and then \"branch --force\" moved it to \"old-head\".\n\nOh, yes, I tested this and you're right. That's a really good catch - it\nwould have been hard to spot amongst all of the tests. Yes, I should\nhave set it up the way you described. I'll modify the test to assert\nthat \"branch --force\" moved the branch and instead of creating it where\nwe expected.\n\n>> This means that the test might pass if we simply ignore\n>> any existing branch-a - which is not the intended behavior of --force,\n>> but this is behavior that we might want in the future (probably using\n>> another option).\n>\n> By \"ignore\", supposing that there is an existing branch-a, do you mean\n> overwrite branch-a, or not create any branch at all?\n\nWhat I meant was that if branch-a was not moved, the test would pass\nif we did not create branch-a at all (and that we might want an option\nthat will not create a branch if it already exists).\n\nBut.. that comment was made under wrong assumptions - I had assumed that\nbranch-a was moved, and that your proposed fix would not move branch-a\n(which is the complete opposite of reality). We agree that the test\nmakes more sense if branch-a is moved.\n"},{"id":"446809","messageId":"20220124204442.39353-1-chooglen@google.com","threadId":"56962","inReplyTo":"20211220233459.45739-1-chooglen@google.com","subject":"[PATCH v7 0/6] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-24T20:44:36Z","receivedAt":"2022-01-24T22:19:12Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Thanks for the feedback, Jonathan! I believe I've incorporated all of your\nsuggested fixes in this version - the most notable of which is to recreate the\ntest repo setup in each test instead of deleting branches. To save CPU cycles in\nsetting up the test, the test suite writes the clean test setup to a\ndirectory, then copies the setup to the right location for each test. In my\ninformal testing, this is even faster than deleting the unused branches.\n\nThe range-diff for patches 3-4 have some irrelevant noise because this version\nis based off 'js/branch-track-inherit', but (due to a mistake when applying the\n'js/branch-track-inherit' patches), v6 is based off a different 'master' commit.\n\n= Description\n\nThis series implements \"git branch --recurse-submodules\", which is part of the\nsubmodule branching RFC (see \"See also\"). \"git branch\" (and some other features\nin that RFC) are incompatible with existing \"--recurse-submodules\" commands; \npatch 5 describes this incompatibility in more detail and how we plan to\nintroduce this new functionality in a manner.\n\nThis series is based off js/branch-track-inherit.\n\nPatches 1-4 are preparation for the implementation of \"branch\n--recurse-submodules\" in patch 5. Patch 6 cleans up a memory leak that I\nencountered on js/branch-track-inherit as I was moving code around; it isn't\nneeeded by \"branch --recurse-submodules\".\n\nSee the previous cover letter\n(https://lore.kernel.org/git/20211220233459.45739-1-chooglen@google.com)\nfor discussions of future work.\n\n= See also\n\nSubmodule branching RFC:\nhttps://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/\n\nOriginal Submodule UX RFC/Discussion:\nhttps://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n\nContributor Summit submodules Notes:\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/\n\nSubmodule UX overhaul updates:\nhttps://lore.kernel.org/git/?q=Submodule+UX+overhaul+update\n\n= Changes\n\nChanges since v6:\n* Move memory leak fix into its own patch (patch 6)\n* Eliminate unnecessary rewrapping and small style fixes\n* In branch --recurse-submodules tests, setup test repo anew instead of\n  deleting the unused branches\n* Numerous commit message wording changes\n\nChanges since v5:\n* Rebase onto v7 of js/branch-track-inherit\n  (https://lore.kernel.org/git/cover.1639717481.git.steadmon@google.com)\n\nChanges since v4:\n* Rebase correctly onto 'gitster/seen^{/^Merge branch .js/branch-track-inherit.}'\n  (see base-commit) as suggested in [1] (thanks Junio!)\n* These patches were also verified on top of 'next'.\n\nChanges since v3:\n* Split up the old patch 1. Patch 1 had a big diff because it used to\n  move lines, remove dead code and introduce repo_* functions (thanks\n  Jonathan!)\n** repo_* functions have been dropped; they added noise and are not\n   necessary for correctness.\n* Use a new, harder-to-misuse function in --set-upstream-to,\n  dwim_and_setup_tracking(). Now, setup_tracking() never does DWIM and\n  dwim_and_setup_tracking() always does DWIM.\n* Move create_branch() dry_run to its own patch.\n* Fix an oversight where submodules in subtrees were ignored. This was\n  because submodules_of_tree() and tree_entry() didn't recurse into\n  subtrees. Test this accordingly (thanks Jonathan!).\n* cmd_branch() possible actions are more consistently ordered.\n* Documentation fixes (thanks Philippe!).\n* Additional comments and explanation.\n* Drop patch 5 (optional cleanup).\n* Rebase onto js/branch-track-inherit v6.\n\nChanges since v2:\n* Rebase onto js/branch-track-inherit. This series should continue to be\n  the case going forward.\n* Patch 1 has a smaller diff because the introduction of\n  validate_branch_start() no longer changes the function order thanks to a\n  forward declaration. This artificial forward declaration is removed in a\n  patch 2 (which can just be squashed into patch 1).\n* Optional cleanup: fix questionable exit codes in patch 5.\n\nChanges since v1:\n* Move the functionality of \"git branch --dry-run\" into \"git submodule-helper create-branch --dry-run\"\n* Add more fields to the submodules_of_tree() struct to reduce the\n  number of allocations made by the caller. Move this functionality\n  to patch 3 (formerly patch 4) and drop patch 1.\n* Make submodules_of_tree() ignore inactive submodules\n* Structure the output of the submodules a bit better by adding prefixes\n  to the child process' output (instead of inconsistently indenting the\n  output).\n** I wasn't able to find a good way to interleave stdout/stderr\n   correctly, so a less-than-desirable workaround was to route the child\n   process output to stdout/stderr depending on the exit code.\n** Eventually, I would like to structure the output of submodules in a\n   report, as Ævar suggested. But at this stage, I think that it's\n   better to spend time getting user feedback on the submodules\n   branching UX and it'll be easier to standardize the output when we've\n   implemented more of the UX :)\n\n[1] https://lore.kernel.org/git/xmqqlf0lz6os.fsf@gitster.g \n\nGlen Choo (6):\n  branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n  branch: make create_branch() always create a branch\n  branch: add a dry_run parameter to create_branch()\n  builtin/branch: consolidate action-picking logic in cmd_branch()\n  branch: add --recurse-submodules option for branch creation\n  branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks\n\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  37 ++--\n Documentation/git-branch.txt       |  11 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 271 ++++++++++++++++++++++-----\n branch.h                           |  53 +++++-\n builtin/branch.c                   |  70 +++++--\n builtin/checkout.c                 |   3 +-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3200-branch.sh                  |  17 ++\n t/t3207-branch-submodule.sh        | 282 +++++++++++++++++++++++++++++\n 16 files changed, 815 insertions(+), 80 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\nRange-diff against v6:\n1:  29669c57b4 ! 1:  206175cfb3 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n    @@ Metadata\n      ## Commit message ##\n         branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n     \n    -    This refactor is motivated by a desire to add a \"dry_run\" parameter to\n    -    create_branch() that will validate whether or not a branch can be\n    -    created without actually creating it - this behavior will be used in a\n    -    subsequent commit that adds `git branch --recurse-submodules topic`.\n    +    This commit is preparation for a future commit that will simplify\n    +    create_branch() so that it always creates a branch. This will allow\n    +    create_branch() to accept a dry_run parameter (which is needed for \"git\n    +    branch --recurse-submodules\").\n     \n    -    Adding \"dry_run\" is not obvious because create_branch() is also used to\n    -    set tracking information without creating a branch, i.e. when using\n    -    --set-upstream-to. This appears to be a leftover from 4fc5006676 (Add\n    -    branch --set-upstream, 2010-01-18), when --set-upstream would sometimes\n    -    create a branch and sometimes update tracking information without\n    -    creating a branch. However, we no longer support --set-upstream, so it\n    -    makes more sense to set tracking information with another function and\n    -    use create_branch() only to create branches. In a later commit, we will\n    -    remove the now-unnecessary logic from create_branch() so that \"dry_run\"\n    -    becomes trivial to implement.\n    +    create_branch() used to always create a branch, but 4fc5006676 (Add\n    +    branch --set-upstream, 2010-01-18) changed it to also be able to set\n    +    tracking information without creating a branch.\n     \n    -    Introduce dwim_and_setup_tracking(), which replaces create_branch()\n    -    in `git branch --set-upstream-to`. Ensure correctness by moving the DWIM\n    -    and branch validation logic from create_branch() into a helper function,\n    -    dwim_branch_start(), so that the logic is shared by both functions.\n    +    Refactor the code that sets tracking information into its own functions\n    +    dwim_branch_start() and dwim_and_setup_tracking(). Also change an\n    +    invocation of create_branch() in cmd_branch() in builtin/branch.c to use\n    +    dwim_and_setup_tracking(), since that invocation is only for setting\n    +    tracking information (in \"git branch --set-upstream-to\").\n     \n    +    As of this commit, create_branch() is no longer invoked in a way that\n    +    does not create branches.\n    +\n    +    Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n      ## branch.c ##\n    @@ branch.c: static int inherit_tracking(struct tracking *tracking, const char *ori\n       */\n      static void setup_tracking(const char *new_ref, const char *orig_ref,\n      \t\t\t   enum branch_track track, int quiet)\n    -@@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n    - \t\tcase BRANCH_TRACK_INHERIT:\n    - \t\t\tbreak;\n    - \t\tdefault:\n    --\t\t\treturn;\n    -+\t\t\tgoto cleanup;\n    - \t\t}\n    - \n    - \tif (tracking.matches > 1)\n    -@@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n    - \t\t\t\ttracking.remote, tracking.srcs) < 0)\n    - \t\texit(-1);\n    - \n    -+cleanup:\n    - \tstring_list_clear(tracking.srcs, 0);\n    - }\n    - \n     @@ branch.c: N_(\"\\n\"\n      \"will track its remote counterpart, you may want to use\\n\"\n      \"\\\"git push -u\\\" to set the upstream config as you push.\");\n    @@ branch.c: void create_branch(struct repository *r,\n      \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n      \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n     -\toidcpy(&oid, &commit->object.oid);\n    -+\tif (out_real_ref)\n    -+\t\t*out_real_ref = real_ref ? xstrdup(real_ref) : NULL;\n    ++\tif (out_real_ref) {\n    ++\t\t*out_real_ref = real_ref;\n    ++\t\treal_ref = NULL;\n    ++\t}\n     +\tif (out_oid)\n     +\t\toidcpy(out_oid, &commit->object.oid);\n     +\n     +\tFREE_AND_NULL(real_ref);\n     +}\n     +\n    -+void create_branch(struct repository *r, const char *name,\n    -+\t\t   const char *start_name, int force, int clobber_head_ok,\n    -+\t\t   int reflog, int quiet, enum branch_track track)\n    ++void create_branch(struct repository *r,\n    ++\t\t   const char *name, const char *start_name,\n    ++\t\t   int force, int clobber_head_ok, int reflog,\n    ++\t\t   int quiet, enum branch_track track)\n     +{\n     +\tstruct object_id oid;\n     +\tchar *real_ref;\n2:  ac2532a953 ! 2:  2e02885eb3 branch: make create_branch() always create a branch\n    @@ Metadata\n      ## Commit message ##\n         branch: make create_branch() always create a branch\n     \n    -    create_branch() was formerly used to set tracking without creating a\n    -    branch. Since the previous commit replaces this use case with\n    -    dwim_and_setup_tracking(), we can simplify create_branch() so that it\n    -    always creates a branch.\n    +    With the previous commit, there are no more invocations of\n    +    create_branch() that do not create a branch because:\n     \n    -    Do this simplification, in particular:\n    +    * BRANCH_TRACK_OVERRIDE is no longer passed\n    +    * clobber_head_ok = true and force = false is never passed\n     \n    -    * remove the special handling of BRANCH_TRACK_OVERRIDE because it is no\n    -      longer used\n    -    * assert that clobber_head_ok can only be provided with force\n    -    * check that we're handling clobber_head_ok and force correctly by\n    -      introducing tests for `git branch --force`\n    +    Assert these situations, delete dead code and ensure that we're handling\n    +    clobber_head_ok and force correctly by introducing tests for `git branch\n    +    --force`. As a result, create_branch() now always creates a branch.\n     \n    +    Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n      ## branch.c ##\n    -@@ branch.c: void create_branch(struct repository *r, const char *name,\n    +@@ branch.c: void create_branch(struct repository *r,\n      \tchar *real_ref;\n      \tstruct strbuf ref = STRBUF_INIT;\n      \tint forcing = 0;\n    @@ branch.c: void create_branch(struct repository *r, const char *name,\n     +\tstruct strbuf err = STRBUF_INIT;\n     +\tchar *msg;\n     +\n    ++\tif (track == BRANCH_TRACK_OVERRIDE)\n    ++\t\tBUG(\"'track' cannot be BRANCH_TRACK_OVERRIDE. Did you mean to call dwim_and_setup_tracking()?\");\n     +\tif (clobber_head_ok && !force)\n     +\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n     +\n    @@ branch.c: void create_branch(struct repository *r, const char *name,\n      \t}\n      \n      \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n    -@@ branch.c: void create_branch(struct repository *r, const char *name,\n    +@@ branch.c: void create_branch(struct repository *r,\n      \tif (reflog)\n      \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n      \n3:  a0ed3fa438 ! 3:  cd43a9aaaa branch: add a dry_run parameter to create_branch()\n    @@ Commit message\n     \n      ## branch.c ##\n     @@ branch.c: static void dwim_branch_start(struct repository *r, const char *start_name,\n    - \n    - void create_branch(struct repository *r, const char *name,\n    - \t\t   const char *start_name, int force, int clobber_head_ok,\n    --\t\t   int reflog, int quiet, enum branch_track track)\n    -+\t\t   int reflog, int quiet, enum branch_track track, int dry_run)\n    + void create_branch(struct repository *r,\n    + \t\t   const char *name, const char *start_name,\n    + \t\t   int force, int clobber_head_ok, int reflog,\n    +-\t\t   int quiet, enum branch_track track)\n    ++\t\t   int quiet, enum branch_track track, int dry_run)\n      {\n      \tstruct object_id oid;\n      \tchar *real_ref;\n    -@@ branch.c: void create_branch(struct repository *r, const char *name,\n    +@@ branch.c: void create_branch(struct repository *r,\n      \t}\n      \n      \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n    @@ branch.c: void create_branch(struct repository *r, const char *name,\n      \n      \tif (reflog)\n      \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n    -@@ branch.c: void create_branch(struct repository *r, const char *name,\n    +@@ branch.c: void create_branch(struct repository *r,\n      \tif (real_ref && track)\n      \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n      \n    @@ branch.h: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     + *   - dry_run causes the branch to be validated but not created.\n     + *\n       */\n    --void create_branch(struct repository *r,\n    --\t\t   const char *name, const char *start_name,\n    --\t\t   int force, int clobber_head_ok,\n    + void create_branch(struct repository *r,\n    + \t\t   const char *name, const char *start_name,\n    + \t\t   int force, int clobber_head_ok,\n     -\t\t   int reflog, int quiet, enum branch_track track);\n    -+void create_branch(struct repository *r, const char *name,\n    -+\t\t   const char *start_name, int force, int clobber_head_ok,\n    -+\t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n    ++\t\t   int reflog, int quiet, enum branch_track track,\n    ++\t\t   int dry_run);\n      \n      /*\n       * Check if 'name' can be a valid name for a branch; die otherwise.\n    @@ builtin/checkout.c: static void update_refs_for_switch(const struct checkout_opt\n     -\t\t\t\t      opts->track);\n     +\t\t\t\t      opts->track,\n     +\t\t\t\t      0);\n    - \t\tfree(new_branch_info->name);\n    - \t\tfree(new_branch_info->refname);\n    - \t\tnew_branch_info->name = xstrdup(opts->new_branch);\n    + \t\tnew_branch_info->name = opts->new_branch;\n    + \t\tsetup_branch_path(new_branch_info);\n    + \t}\n4:  ebded31c96 ! 4:  8e5f750162 builtin/branch: clean up action-picking logic in cmd_branch()\n    @@ Metadata\n     Author: Glen Choo <chooglen@google.com>\n     \n      ## Commit message ##\n    -    builtin/branch: clean up action-picking logic in cmd_branch()\n    +    builtin/branch: consolidate action-picking logic in cmd_branch()\n     \n    -    Add a variable to cmd_branch() that will tell us whether or not\n    -    cmd_branch() will default to creating a branch (instead of performing\n    -    another action). Besides making the function more explicit, this allows\n    -    us to validate options that can only be used when creating a branch.\n    -    Such an option does not exist yet, but one will be introduced in a\n    -    subsequent commit.\n    +    Consolidate the logic for deciding when to create a new branch in\n    +    cmd_branch(), and save the result for reuse. Besides making the function\n    +    more explicit, this allows us to validate options that can only be used\n    +    when creating a branch. Such an option does not exist yet, but one will\n    +    be introduced in a subsequent commit.\n     \n    +    Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n      ## builtin/branch.c ##\n    @@ builtin/branch.c: static int edit_branch_description(const char *branch_name)\n      \tenum branch_track track;\n      \tstruct ref_filter filter;\n     -\tint icase = 0;\n    - \tstatic struct ref_sorting *sorting;\n    - \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n    + \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n      \tstruct ref_format format = REF_FORMAT_INIT;\n    + \n     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)\n      \t    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)\n      \t\tlist = 1;\n5:  0a7ec6ee75 ! 5:  c59de1fd9c branch: add --recurse-submodules option for branch creation\n    @@ Commit message\n         branch: add --recurse-submodules option for branch creation\n     \n         To improve the submodules UX, we would like to teach Git to handle\n    -    branches in submodules. Start this process by teaching `git branch` the\n    -    --recurse-submodules option so that `git branch --recurse-submodules\n    -    topic` will create the \"topic\" branch in the superproject and its\n    +    branches in submodules. Start this process by teaching \"git branch\" the\n    +    --recurse-submodules option so that \"git branch --recurse-submodules\n    +    topic\" will create the `topic` branch in the superproject and its\n         submodules.\n     \n         Although this commit does not introduce breaking changes, it is\n    -    incompatible with existing --recurse-submodules semantics e.g. `git\n    -    checkout` does not recursively checkout the expected branches created by\n    -    `git branch` yet. To ensure that the correct set of semantics is used,\n    -    this commit introduces a new configuration value,\n    -    `submodule.propagateBranches`, which enables submodule branching when\n    -    true (defaults to false).\n    +    incompatible with existing --recurse-submodules commands because \"git\n    +    branch --recurse-submodules\" writes to the submodule ref store, but most\n    +    commands only consider the superproject gitlink and ignore the submodule\n    +    ref store. For example, \"git checkout --recurse-submodules\" will check\n    +    out the commits in the superproject gitlinks (and put the submodules in\n    +    detached HEAD) instead of checking out the submodule branches.\n     \n    -    This commit includes changes that allow Git to work with submodules\n    -    that are in trees (and not just the index):\n    +    Because of this, this commit introduces a new configuration value,\n    +    `submodule.propagateBranches`. The plan is for Git commands to\n    +    prioritize submodule ref store information over superproject gitlinks if\n    +    this value is true. Because \"git branch --recurse-submodules\" writes to\n    +    submodule ref stores, for the sake of clarity, it will not function\n    +    unless this configuration value is set.\n    +\n    +    This commit also includes changes that support working with submodules\n    +    from a superproject commit because \"branch --recurse-submodules\" (and\n    +    future commands) need to read .gitmodules and gitlinks from the\n    +    superproject commit, but submodules are typically read from the\n    +    filesystem's .gitmodules and the index's gitlinks. These changes are:\n     \n         * add a submodules_of_tree() helper that gives the relevant\n           information of an in-tree submodule (e.g. path and oid) and\n    @@ Commit message\n         incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\n         parse_options., 2007-10-07).\n     \n    +    Helped-by: Jonathan Tan <jonathantanmy@google.com>\n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n      ## Documentation/config/advice.txt ##\n    @@ Documentation/config/submodule.txt: submodule.active::\n     -\tApplies to all commands that support this option\n     -\t(`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,\n     -\t`restore` and `switch`) except `clone` and `ls-files`.\n    --\tDefaults to false.\n     +\toption by default. Defaults to false.\n    -+\t+\n    - \tWhen set to true, it can be deactivated via the\n    - \t`--no-recurse-submodules` option. Note that some Git commands\n    - \tlacking this option may call some of the above commands affected by\n    -@@ Documentation/config/submodule.txt: submodule.recurse::\n    - \t`git fetch` but does not have a `--no-recurse-submodules` option.\n    - \tFor these commands a workaround is to temporarily change the\n    - \tconfiguration value by using `git -c submodule.recurse=0`.\n    -+\t+\n    -+\tThe following list shows the commands that accept\n    -+\t`--recurse-submodules` and whether they are supported by this\n    -+\tsetting.\n    -+\t* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n    -+\t`reset`, `restore` and `switch` are always supported.\n    -+\t* `clone` and `ls-files` are not supported.\n    -+\t* `branch` is supported only if `submodule.propagateBranches` is\n    -+\tenabled\n    +++\n    ++When set to true, it can be deactivated via the\n    ++`--no-recurse-submodules` option. Note that some Git commands\n    ++lacking this option may call some of the above commands affected by\n    ++`submodule.recurse`; for instance `git remote update` will call\n    ++`git fetch` but does not have a `--no-recurse-submodules` option.\n    ++For these commands a workaround is to temporarily change the\n    ++configuration value by using `git -c submodule.recurse=0`.\n    +++\n    ++The following list shows the commands that accept\n    ++`--recurse-submodules` and whether they are supported by this\n    ++setting.\n    ++\n    ++* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n    ++`reset`, `restore` and `switch` are always supported.\n    ++* `clone` and `ls-files` are not supported.\n    ++* `branch` is supported only if `submodule.propagateBranches` is\n    ++enabled\n     +\n     +submodule.propagateBranches::\n     +\t[EXPERIMENTAL] A boolean that enables branching support when\n    @@ Documentation/config/submodule.txt: submodule.recurse::\n     +\tEnabling this will allow certain commands to accept\n     +\t`--recurse-submodules` and certain commands that already accept\n     +\t`--recurse-submodules` will now consider branches.\n    -+\tDefaults to false.\n    + \tDefaults to false.\n    +-\tWhen set to true, it can be deactivated via the\n    +-\t`--no-recurse-submodules` option. Note that some Git commands\n    +-\tlacking this option may call some of the above commands affected by\n    +-\t`submodule.recurse`; for instance `git remote update` will call\n    +-\t`git fetch` but does not have a `--no-recurse-submodules` option.\n    +-\tFor these commands a workaround is to temporarily change the\n    +-\tconfiguration value by using `git -c submodule.recurse=0`.\n      \n      submodule.fetchJobs::\n      \tSpecifies how many submodules are fetched/cloned at the same time.\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     +/**\n     + * Creates a branch in a submodule by calling\n     + * create_branches_recursively() in a child process. The child process\n    -+ * is necessary because install_branch_config() (and its variants) do\n    -+ * not support writing configs to submodules.\n    ++ * is necessary because install_branch_config_multiple_remotes() (which\n    ++ * is called by setup_tracking()) does not support writing configs to\n    ++ * submodules.\n     + */\n     +static int submodule_create_branch(struct repository *r,\n     +\t\t\t\t   const struct submodule *submodule,\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     +\tchild.stdout_to_stderr = 1;\n     +\n     +\tprepare_other_repo_env(&child.env_array, r->gitdir);\n    ++\t/*\n    ++\t * submodule_create_branch() is indirectly invoked by \"git\n    ++\t * branch\", but we cannot invoke \"git branch\" in the child\n    ++\t * process because it does not let us set start_name and\n    ++\t * start_oid separately (see create_branches_recursively()).\n    ++\t */\n     +\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n     +\tif (dry_run)\n     +\t\tstrvec_push(&child.args, \"--dry-run\");\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n      \tunlink(git_path_merge_head(r));\n     \n      ## branch.h ##\n    -@@ branch.h: void create_branch(struct repository *r, const char *name,\n    - \t\t   const char *start_name, int force, int clobber_head_ok,\n    - \t\t   int reflog, int quiet, enum branch_track track, int dry_run);\n    +@@ branch.h: void create_branch(struct repository *r,\n    + \t\t   int reflog, int quiet, enum branch_track track,\n    + \t\t   int dry_run);\n      \n     +/*\n     + * Creates a new branch in repository and its submodules (and its\n    @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix\n     +\t    recurse_submodules_explicit = 0;\n      \tenum branch_track track;\n      \tstruct ref_filter filter;\n    - \tstatic struct ref_sorting *sorting;\n    + \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)\n      \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n      \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n    @@ t/t3207-branch-submodule.sh (new)\n     +. ./test-lib.sh\n     +. \"$TEST_DIRECTORY\"/lib-rebase.sh\n     +\n    -+test_expect_success 'setup superproject and submodule' '\n    -+\tgit init super &&\n    -+\ttest_commit foo &&\n    -+\tgit init sub-sub-upstream &&\n    -+\ttest_commit -C sub-sub-upstream foo &&\n    -+\tgit init sub-upstream &&\n    -+\t# Submodule in a submodule\n    -+\tgit -C sub-upstream submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" sub-sub &&\n    -+\tgit -C sub-upstream commit -m \"add submodule\" &&\n    -+\t# Regular submodule\n    -+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-upstream\" sub &&\n    -+\t# Submodule in a subdirectory\n    -+\tgit -C super submodule add \"$TRASH_DIRECTORY/sub-sub-upstream\" second/sub &&\n    -+\tgit -C super commit -m \"add submodule\" &&\n    -+\tgit -C super config submodule.propagateBranches true &&\n    -+\tgit -C super/sub submodule update --init\n    -+'\n    -+\n    -+CLEANUP_SCRIPT_PATH=\"$TRASH_DIRECTORY/cleanup_branches.sh\"\n    ++pwd=$(pwd)\n     +\n    -+cat >\"$CLEANUP_SCRIPT_PATH\" <<'EOF'\n    -+\t#!/bin/sh\n    ++# Creates a clean test environment in \"pwd\" by copying the repo setup\n    ++# from test_dirs.\n    ++reset_test () {\n    ++\trm -fr super &&\n    ++\trm -fr sub-sub-upstream &&\n    ++\trm -fr sub-upstream &&\n    ++\tcp -r test_dirs/* .\n    ++}\n     +\n    -+\tsuper_dir=\"$1\"\n    -+\tshift\n    ++test_expect_success 'setup superproject and submodule' '\n    ++\tmkdir test_dirs &&\n     +\t(\n    -+\t\tcd \"$super_dir\" &&\n    -+\t\tgit checkout main &&\n    -+\t\tfor branch_name in \"$@\"; do\n    -+\t\t\tgit branch -D \"$branch_name\"\n    -+\t\t\tgit submodule foreach \"$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true\"\n    -+\t\tdone\n    -+\t)\n    -+EOF\n    -+chmod +x \"$CLEANUP_SCRIPT_PATH\"\n    -+\n    -+cleanup_branches() {\n    -+\tTRASH_DIRECTORY=\"\\\"$TRASH_DIRECTORY\\\"\" \"$CLEANUP_SCRIPT_PATH\" \"$@\"\n    -+} >/dev/null 2>/dev/null\n    ++\t\tcd test_dirs &&\n    ++\t\tgit init super &&\n    ++\t\ttest_commit -C super foo &&\n    ++\t\tgit init sub-sub-upstream &&\n    ++\t\ttest_commit -C sub-sub-upstream foo &&\n    ++\t\tgit init sub-upstream &&\n    ++\t\t# Submodule in a submodule\n    ++\t\tgit -C sub-upstream submodule add \"${pwd}/test_dirs/sub-sub-upstream\" sub-sub &&\n    ++\t\tgit -C sub-upstream commit -m \"add submodule\" &&\n    ++\t\t# Regular submodule\n    ++\t\tgit -C super submodule add \"${pwd}/test_dirs/sub-upstream\" sub &&\n    ++\t\t# Submodule in a subdirectory\n    ++\t\tgit -C super submodule add \"${pwd}/test_dirs/sub-sub-upstream\" second/sub &&\n    ++\t\tgit -C super commit -m \"add submodule\" &&\n    ++\t\tgit -C super config submodule.propagateBranches true &&\n    ++\t\tgit -C super/sub submodule update --init\n    ++\t) &&\n    ++\treset_test\n    ++'\n     +\n     +# Test the argument parsing\n     +test_expect_success '--recurse-submodules should create branches' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +'\n     +\n     +test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\techo \"fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\" >expected &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +'\n     +\n     +test_expect_success '--recurse-submodules should fail when not creating branches' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a &&\n     +\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n     +\t\t# Assert that the branches were not deleted\n    -+\t\tgit rev-parse --abbrev-ref branch-a &&\n    -+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n    ++\t\tgit rev-parse branch-a &&\n    ++\t\tgit -C sub rev-parse branch-a\n     +\t)\n     +'\n     +\n     +test_expect_success 'should respect submodule.recurse when creating branches' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit -c submodule.recurse=true branch branch-a &&\n    -+\t\tgit rev-parse --abbrev-ref branch-a &&\n    -+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n    ++\t\tgit rev-parse branch-a &&\n    ++\t\tgit -C sub rev-parse branch-a\n     +\t)\n     +'\n     +\n     +test_expect_success 'should ignore submodule.recurse when not creating branches' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a &&\n     +\t\tgit -c submodule.recurse=true branch -D branch-a &&\n    -+\t\ttest_must_fail git rev-parse --abbrev-ref branch-a &&\n    -+\t\tgit -C sub rev-parse --abbrev-ref branch-a\n    ++\t\ttest_must_fail git rev-parse branch-a &&\n    ++\t\tgit -C sub rev-parse branch-a\n     +\t)\n     +'\n     +\n     +# Test branch creation behavior\n     +test_expect_success 'should create branches based off commit id in superproject' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a branch-b\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +'\n     +\n     +test_expect_success 'should not create any branches if branch is not valid for all repos' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit -C sub branch branch-a &&\n     +\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n     +\t\ttest_must_fail git rev-parse branch-a &&\n    -+\n    -+\t\tcat >expected <<-EOF &&\n    -+\t\tsubmodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.\n    -+\t\tfatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}\n    -+\t\tEOF\n    -+\t\ttest_cmp expected actual\n    ++\t\tgrep \"submodule .sub.: fatal: A branch named .branch-a. already exists\" actual\n     +\t)\n     +'\n     +\n     +test_expect_success 'should create branches if branch exists and --force is given' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit -C sub rev-parse HEAD >expected &&\n     +\t\ttest_commit -C sub baz &&\n    -+\t\tgit -C sub branch branch-a HEAD~1 &&\n    ++\t\t# branch-a in sub now points to a newer commit.\n    ++\t\tgit -C sub branch branch-a HEAD &&\n    ++\t\tgit -C sub rev-parse branch-a >actual-old-branch-a &&\n     +\t\tgit branch --recurse-submodules --force branch-a &&\n     +\t\tgit rev-parse branch-a &&\n    -+\t\t# assert that sub:branch-a was moved\n    -+\t\tgit -C sub rev-parse branch-a >actual &&\n    -+\t\ttest_cmp expected actual\n    ++\t\tgit -C sub rev-parse branch-a >actual-new-branch-a &&\n    ++\t\ttest_cmp expected actual-new-branch-a &&\n    ++\t\t# assert that branch --force actually moved the sub\n    ++\t\t# branch\n    ++\t\t! test_cmp expected actual-old-branch-a\n     +\t)\n     +'\n     +\n     +test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a branch-b branch-c\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch branch-a &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tgit commit -m \"add second submodule\" &&\n     +\t\tgit checkout branch-a &&\n     +\t\tgit branch --recurse-submodules branch-c branch-b &&\n    -+\t\tgit rev-parse branch-c &&\n    -+\t\tgit -C sub rev-parse branch-c &&\n    -+\t\tgit -C second/sub rev-parse branch-c &&\n     +\t\tgit checkout --recurse-submodules branch-c &&\n     +\t\tgit -C sub2 rev-parse branch-c &&\n     +\t\tgit -C sub2/sub-sub rev-parse branch-c\n     +\t)\n     +'\n     +\n    ++test_expect_success 'should not create branches in inactive submodules' '\n    ++\ttest_when_finished \"reset_test\" &&\n    ++\ttest_config -C super submodule.sub.active false &&\n    ++\t(\n    ++\t\tcd super &&\n    ++\t\tgit branch --recurse-submodules branch-a &&\n    ++\t\tgit rev-parse branch-a &&\n    ++\t\ttest_must_fail git -C sub branch-a\n    ++\t)\n    ++'\n    ++\n     +test_expect_success 'should set up tracking of local branches with track=always' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +'\n     +\n     +test_expect_success 'should set up tracking of local branches with explicit track' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch --track --recurse-submodules branch-a main &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +'\n     +\n     +test_expect_success 'should not set up unnecessary tracking of local branches' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    ++\ttest_when_finished \"reset_test\" &&\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a main &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t)\n     +'\n     +\n    -+test_expect_success 'should not create branches in inactive submodules' '\n    -+\ttest_when_finished \"cleanup_branches super branch-a\" &&\n    -+\ttest_config -C super submodule.sub.active false &&\n    -+\t(\n    -+\t\tcd super &&\n    -+\t\tgit branch --recurse-submodules branch-a &&\n    -+\t\tgit rev-parse branch-a &&\n    -+\t\ttest_must_fail git -C sub branch-a\n    -+\t)\n    -+'\n    ++reset_remote_test () {\n    ++\trm -fr super-clone &&\n    ++\treset_test\n    ++}\n     +\n    -+test_expect_success 'setup remote-tracking tests' '\n    ++test_expect_success 'setup tests with remotes' '\n     +\t(\n    -+\t\tcd super &&\n    -+\t\tgit branch branch-a &&\n    -+\t\tgit checkout -b branch-b &&\n    -+\t\tgit submodule add ../sub-upstream sub2 &&\n    -+\t\t# branch-b now has a committed submodule not in branch-a\n    -+\t\tgit commit -m \"add second submodule\"\n    ++\t\tcd test_dirs &&\n    ++\t\t(\n    ++\t\t\tcd super &&\n    ++\t\t\tgit branch branch-a &&\n    ++\t\t\tgit checkout -b branch-b &&\n    ++\t\t\tgit submodule add ../sub-upstream sub2 &&\n    ++\t\t\t# branch-b now has a committed submodule not in branch-a\n    ++\t\t\tgit commit -m \"add second submodule\"\n    ++\t\t) &&\n    ++\t\tgit clone --branch main --recurse-submodules super super-clone &&\n    ++\t\tgit -C super-clone config submodule.propagateBranches true\n     +\t) &&\n    -+\tgit clone --branch main --recurse-submodules super super-clone &&\n    -+\tgit -C super-clone config submodule.propagateBranches true\n    ++\treset_remote_test\n     +'\n     +\n    -+test_expect_success 'should not create branch when submodule is not in .git/modules' '\n    -+\t# The cleanup needs to delete sub2 separately because main does not have sub2\n    -+\ttest_when_finished \"git -C super-clone/sub2 branch -D branch-b && \\\n    -+\t\tgit -C super-clone/sub2/sub-sub branch -D branch-b && \\\n    -+\t\tcleanup_branches super-clone branch-a branch-b\" &&\n    ++test_expect_success 'should get fatal error upon branch creation when submodule is not in .git/modules' '\n    ++\ttest_when_finished \"reset_remote_test\" &&\n     +\t(\n     +\t\tcd super-clone &&\n    -+\t\t# This should succeed because super-clone has sub.\n    ++\t\t# This should succeed because super-clone has sub in .git/modules\n     +\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n    -+\t\t# This should fail because super-clone does not have sub2.\n    ++\t\t# This should fail because super-clone does not have sub2 .git/modules\n     +\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n    -+\t\tcat >expected <<-EOF &&\n    -+\t\thint: You may try updating the submodules using ${SQ}git checkout origin/branch-b && git submodule update --init${SQ}\n    -+\t\tfatal: submodule ${SQ}sub2${SQ}: unable to find submodule\n    -+\t\tEOF\n    -+\t\ttest_cmp expected actual &&\n    ++\t\tgrep \"fatal: submodule .sub2.: unable to find submodule\" actual &&\n     +\t\ttest_must_fail git rev-parse branch-b &&\n     +\t\ttest_must_fail git -C sub rev-parse branch-b &&\n     +\t\t# User can fix themselves by initializing the submodule\n    @@ t/t3207-branch-submodule.sh (new)\n     +'\n     +\n     +test_expect_success 'should set up tracking of remote-tracking branches' '\n    -+\ttest_when_finished \"cleanup_branches super-clone branch-a\" &&\n    ++\ttest_when_finished \"reset_remote_test\" &&\n     +\t(\n     +\t\tcd super-clone &&\n     +\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +'\n     +\n     +test_expect_success 'should not fail when unable to set up tracking in submodule' '\n    -+\ttest_when_finished \"cleanup_branches super-clone branch-a && \\\n    -+\t\tgit -C super-clone remote rename ex-origin origin\" &&\n    ++\ttest_when_finished \"reset_remote_test\" &&\n     +\t(\n     +\t\tcd super-clone &&\n     +\t\tgit remote rename origin ex-origin &&\n-:  ---------- > 6:  a4634f0493 branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks\n\nbase-commit: 44f14a9d24cd9f04a0a789e58968d52d44d7f332\n-- \n2.33.GIT\n\n"},{"id":"446810","messageId":"20220124204442.39353-3-chooglen@google.com","threadId":"56962","inReplyTo":"20220124204442.39353-1-chooglen@google.com","subject":"[PATCH v7 2/6] branch: make create_branch() always create a branch","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-24T20:44:38Z","receivedAt":"2022-01-24T22:32:58Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"With the previous commit, there are no more invocations of\ncreate_branch() that do not create a branch because:\n\n* BRANCH_TRACK_OVERRIDE is no longer passed\n* clobber_head_ok = true and force = false is never passed\n\nAssert these situations, delete dead code and ensure that we're handling\nclobber_head_ok and force correctly by introducing tests for `git branch\n--force`. As a result, create_branch() now always creates a branch.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c          | 57 ++++++++++++++++++++++-------------------------\n branch.h          |  4 ++--\n t/t3200-branch.sh | 17 ++++++++++++++\n 3 files changed, 46 insertions(+), 32 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex f3a31930fb..df24021f27 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -429,15 +429,19 @@ void create_branch(struct repository *r,\n \tchar *real_ref;\n \tstruct strbuf ref = STRBUF_INIT;\n \tint forcing = 0;\n-\tint dont_change_ref = 0;\n-\n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *msg;\n+\n+\tif (track == BRANCH_TRACK_OVERRIDE)\n+\t\tBUG(\"'track' cannot be BRANCH_TRACK_OVERRIDE. Did you mean to call dwim_and_setup_tracking()?\");\n+\tif (clobber_head_ok && !force)\n+\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n+\n+\tif (clobber_head_ok ?\n+\t\t\t  validate_branchname(name, &ref) :\n+\t\t\t  validate_new_branchname(name, &ref, force)) {\n+\t\tforcing = 1;\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n@@ -445,27 +449,20 @@ void create_branch(struct repository *r,\n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n-\tif (!dont_change_ref) {\n-\t\tstruct ref_transaction *transaction;\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\t\tchar *msg;\n-\n-\t\tif (forcing)\n-\t\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n-\t\telse\n-\t\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n-\n-\t\ttransaction = ref_transaction_begin(&err);\n-\t\tif (!transaction ||\n-\t\t    ref_transaction_update(transaction, ref.buf,\n-\t\t\t\t\t   &oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t   0, msg, &err) ||\n-\t\t    ref_transaction_commit(transaction, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\t\tref_transaction_free(transaction);\n-\t\tstrbuf_release(&err);\n-\t\tfree(msg);\n-\t}\n+\tif (forcing)\n+\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n+\telse\n+\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t\tref_transaction_update(transaction, ref.buf,\n+\t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n+\t\t\t\t\t0, msg, &err) ||\n+\t\tref_transaction_commit(transaction, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tfree(msg);\n \n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\ndiff --git a/branch.h b/branch.h\nindex ab2315c611..cf3a4d3ff3 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -52,8 +52,8 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *\n  *   - force enables overwriting an existing (non-head) branch\n  *\n- *   - clobber_head_ok allows the currently checked out (hence existing)\n- *     branch to be overwritten; without 'force', it has no effect.\n+ *   - clobber_head_ok, when enabled with 'force', allows the currently\n+ *     checked out (head) branch to be overwritten\n  *\n  *   - reflog creates a reflog for the branch\n  *\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex a74b2e06a1..9157382ad0 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n \tgit branch abc && test_path_is_file .git/refs/heads/abc\n '\n \n+test_expect_success 'git branch abc should fail when abc exists' '\n+\ttest_must_fail git branch abc\n+'\n+\n+test_expect_success 'git branch --force abc should fail when abc is checked out' '\n+\ttest_when_finished git switch main &&\n+\tgit switch abc &&\n+\ttest_must_fail git branch --force abc HEAD~1\n+'\n+\n+test_expect_success 'git branch --force abc should succeed when abc exists' '\n+\tgit rev-parse HEAD~1 >expect &&\n+\tgit branch --force abc HEAD~1 &&\n+\tgit rev-parse abc >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n '\n-- \n2.33.GIT\n\n"},{"id":"446811","messageId":"20220124204442.39353-2-chooglen@google.com","threadId":"56962","inReplyTo":"20220124204442.39353-1-chooglen@google.com","subject":"[PATCH v7 1/6] branch: move --set-upstream-to behavior to dwim_and_setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-24T20:44:37Z","receivedAt":"2022-01-24T22:33:03Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"This commit is preparation for a future commit that will simplify\ncreate_branch() so that it always creates a branch. This will allow\ncreate_branch() to accept a dry_run parameter (which is needed for \"git\nbranch --recurse-submodules\").\n\ncreate_branch() used to always create a branch, but 4fc5006676 (Add\nbranch --set-upstream, 2010-01-18) changed it to also be able to set\ntracking information without creating a branch.\n\nRefactor the code that sets tracking information into its own functions\ndwim_branch_start() and dwim_and_setup_tracking(). Also change an\ninvocation of create_branch() in cmd_branch() in builtin/branch.c to use\ndwim_and_setup_tracking(), since that invocation is only for setting\ntracking information (in \"git branch --set-upstream-to\").\n\nAs of this commit, create_branch() is no longer invoked in a way that\ndoes not create branches.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c         | 87 +++++++++++++++++++++++++++++++++++++-----------\n branch.h         | 22 ++++++++++++\n builtin/branch.c |  9 ++---\n 3 files changed, 92 insertions(+), 26 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex a4e4631ef1..f3a31930fb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -218,9 +218,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n }\n \n /*\n- * This is called when new_ref is branched off of orig_ref, and tries\n- * to infer the settings for branch.<new_ref>.{remote,merge} from the\n- * config.\n+ * Used internally to set the branch.<new_ref>.{remote,merge} config\n+ * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike\n+ * dwim_and_setup_tracking(), this does not do DWIM, i.e. \"origin/main\"\n+ * will not be expanded to \"refs/remotes/origin/main\", so it is not safe\n+ * for 'orig_ref' to be raw user input.\n  */\n static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t   enum branch_track track, int quiet)\n@@ -341,31 +343,37 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+/**\n+ * DWIMs a user-provided ref to determine the starting point for a\n+ * branch and validates it, where:\n+ *\n+ *   - r is the repository to validate the branch for\n+ *\n+ *   - start_name is the ref that we would like to test. This is\n+ *     expanded with DWIM and assigned to out_real_ref.\n+ *\n+ *   - track is the tracking mode of the new branch. If tracking is\n+ *     explicitly requested, start_name must be a branch (because\n+ *     otherwise start_name cannot be tracked)\n+ *\n+ *   - out_oid is an out parameter containing the object_id of start_name\n+ *\n+ *   - out_real_ref is an out parameter containing the full, 'real' form\n+ *     of start_name e.g. refs/heads/main instead of main\n+ *\n+ */\n+static void dwim_branch_start(struct repository *r, const char *start_name,\n+\t\t\t   enum branch_track track, char **out_real_ref,\n+\t\t\t   struct object_id *out_oid)\n {\n \tstruct commit *commit;\n \tstruct object_id oid;\n \tchar *real_ref;\n-\tstruct strbuf ref = STRBUF_INIT;\n-\tint forcing = 0;\n-\tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n \n \tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n \t\texplicit_tracking = 1;\n \n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n-\t}\n-\n \treal_ref = NULL;\n \tif (get_oid_mb(start_name, &oid)) {\n \t\tif (explicit_tracking) {\n@@ -402,7 +410,37 @@ void create_branch(struct repository *r,\n \n \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n-\toidcpy(&oid, &commit->object.oid);\n+\tif (out_real_ref) {\n+\t\t*out_real_ref = real_ref;\n+\t\treal_ref = NULL;\n+\t}\n+\tif (out_oid)\n+\t\toidcpy(out_oid, &commit->object.oid);\n+\n+\tFREE_AND_NULL(real_ref);\n+}\n+\n+void create_branch(struct repository *r,\n+\t\t   const char *name, const char *start_name,\n+\t\t   int force, int clobber_head_ok, int reflog,\n+\t\t   int quiet, enum branch_track track)\n+{\n+\tstruct object_id oid;\n+\tchar *real_ref;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tint forcing = 0;\n+\tint dont_change_ref = 0;\n+\n+\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n+\t    ? validate_branchname(name, &ref)\n+\t    : validate_new_branchname(name, &ref, force)) {\n+\t\tif (!force)\n+\t\t\tdont_change_ref = 1;\n+\t\telse\n+\t\t\tforcing = 1;\n+\t}\n+\n+\tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -436,6 +474,15 @@ void create_branch(struct repository *r,\n \tfree(real_ref);\n }\n \n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet)\n+{\n+\tchar *real_orig_ref;\n+\tdwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL);\n+\tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 815dcd40c0..ab2315c611 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -18,6 +18,28 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+/**\n+ * Sets branch.<new_ref>.{remote,merge} config settings such that\n+ * new_ref tracks orig_ref according to the specified tracking mode.\n+ *\n+ *   - new_ref is the name of the branch that we are setting tracking\n+ *     for.\n+ *\n+ *   - orig_ref is the name of the ref that is 'upstream' of new_ref.\n+ *     orig_ref will be expanded with DWIM so that the config settings\n+ *     are in the correct format e.g. \"refs/remotes/origin/main\" instead\n+ *     of \"origin/main\".\n+ *\n+ *   - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes\n+ *     new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT\n+ *     causes new_ref to track whatever orig_ref tracks.\n+ *\n+ *   - quiet suppresses tracking information.\n+ */\n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet);\n+\n /*\n  * Creates a new branch, where:\n  *\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 1890afd4e5..59768a3f67 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -822,12 +822,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (!ref_exists(branch->refname))\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \n-\t\t/*\n-\t\t * create_branch takes care of setting up the tracking\n-\t\t * info and making sure new_upstream is correct\n-\t\t */\n-\t\tcreate_branch(the_repository, branch->name, new_upstream,\n-\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n+\t\tdwim_and_setup_tracking(the_repository, branch->name,\n+\t\t\t\t\tnew_upstream, BRANCH_TRACK_OVERRIDE,\n+\t\t\t\t\tquiet);\n \t} else if (unset_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n \t\tstruct strbuf buf = STRBUF_INIT;\n-- \n2.33.GIT\n\n"},{"id":"446812","messageId":"20220124204442.39353-4-chooglen@google.com","threadId":"56962","inReplyTo":"20220124204442.39353-1-chooglen@google.com","subject":"[PATCH v7 3/6] branch: add a dry_run parameter to create_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-24T20:44:39Z","receivedAt":"2022-01-24T22:33:17Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a dry_run parameter to create_branch() such that dry_run = 1 will\nvalidate a new branch without trying to create it. This will be used in\n`git branch --recurse-submodules` to ensure that the new branch can be\ncreated in all submodules.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c           | 5 ++++-\n branch.h           | 5 ++++-\n builtin/branch.c   | 2 +-\n builtin/checkout.c | 3 ++-\n 4 files changed, 11 insertions(+), 4 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex df24021f27..02d46a69b8 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -423,7 +423,7 @@ static void dwim_branch_start(struct repository *r, const char *start_name,\n void create_branch(struct repository *r,\n \t\t   const char *name, const char *start_name,\n \t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+\t\t   int quiet, enum branch_track track, int dry_run)\n {\n \tstruct object_id oid;\n \tchar *real_ref;\n@@ -445,6 +445,8 @@ void create_branch(struct repository *r,\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n+\tif (dry_run)\n+\t\tgoto cleanup;\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -467,6 +469,7 @@ void create_branch(struct repository *r,\n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n \n+cleanup:\n \tstrbuf_release(&ref);\n \tfree(real_ref);\n }\ndiff --git a/branch.h b/branch.h\nindex cf3a4d3ff3..509cfcc34e 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -62,11 +62,14 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *   - track causes the new branch to be configured to merge the remote branch\n  *     that start_name is a tracking branch for (if any).\n  *\n+ *   - dry_run causes the branch to be validated but not created.\n+ *\n  */\n void create_branch(struct repository *r,\n \t\t   const char *name, const char *start_name,\n \t\t   int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track);\n+\t\t   int reflog, int quiet, enum branch_track track,\n+\t\t   int dry_run);\n \n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 59768a3f67..d1e3ad6ebf 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -858,7 +858,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \t\tcreate_branch(the_repository,\n \t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track);\n+\t\t\t      force, 0, reflog, quiet, track, 0);\n \n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 8d511aa6b7..fb4323dfca 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -893,7 +893,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t      opts->new_branch_force ? 1 : 0,\n \t\t\t\t      opts->new_branch_log,\n \t\t\t\t      opts->quiet,\n-\t\t\t\t      opts->track);\n+\t\t\t\t      opts->track,\n+\t\t\t\t      0);\n \t\tnew_branch_info->name = opts->new_branch;\n \t\tsetup_branch_path(new_branch_info);\n \t}\n-- \n2.33.GIT\n\n"},{"id":"446813","messageId":"20220124204442.39353-5-chooglen@google.com","threadId":"56962","inReplyTo":"20220124204442.39353-1-chooglen@google.com","subject":"[PATCH v7 4/6] builtin/branch: consolidate action-picking logic in cmd_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-24T20:44:40Z","receivedAt":"2022-01-24T22:33:29Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Consolidate the logic for deciding when to create a new branch in\ncmd_branch(), and save the result for reuse. Besides making the function\nmore explicit, this allows us to validate options that can only be used\nwhen creating a branch. Such an option does not exist yet, but one will\nbe introduced in a subsequent commit.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 19 +++++++++++--------\n 1 file changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex d1e3ad6ebf..c274fbdfcf 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -616,14 +616,15 @@ static int edit_branch_description(const char *branch_name)\n \n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n-\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n-\tint show_current = 0;\n-\tint reflog = 0, edit_description = 0;\n-\tint quiet = 0, unset_upstream = 0;\n+\t/* possible actions */\n+\tint delete = 0, rename = 0, copy = 0, list = 0,\n+\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n \tconst char *new_upstream = NULL;\n+\tint noncreate_actions = 0;\n+\t/* possible options */\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n-\tint icase = 0;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n \tstruct ref_format format = REF_FORMAT_INIT;\n \n@@ -707,8 +708,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)\n \t\tlist = 1;\n \n-\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n-\t    list + edit_description + unset_upstream > 1)\n+\tnoncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +\n+\t\t\t    !!show_current + !!list + !!edit_description +\n+\t\t\t    !!unset_upstream;\n+\tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (filter.abbrev == -1)\n@@ -848,7 +851,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&buf, \"branch.%s.merge\", branch->name);\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n-\t} else if (argc > 0 && argc <= 2) {\n+\t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n-- \n2.33.GIT\n\n"},{"id":"446814","messageId":"20220124204442.39353-6-chooglen@google.com","threadId":"56962","inReplyTo":"20220124204442.39353-1-chooglen@google.com","subject":"[PATCH v7 5/6] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-24T20:44:41Z","receivedAt":"2022-01-24T22:33:47Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"To improve the submodules UX, we would like to teach Git to handle\nbranches in submodules. Start this process by teaching \"git branch\" the\n--recurse-submodules option so that \"git branch --recurse-submodules\ntopic\" will create the `topic` branch in the superproject and its\nsubmodules.\n\nAlthough this commit does not introduce breaking changes, it is\nincompatible with existing --recurse-submodules commands because \"git\nbranch --recurse-submodules\" writes to the submodule ref store, but most\ncommands only consider the superproject gitlink and ignore the submodule\nref store. For example, \"git checkout --recurse-submodules\" will check\nout the commits in the superproject gitlinks (and put the submodules in\ndetached HEAD) instead of checking out the submodule branches.\n\nBecause of this, this commit introduces a new configuration value,\n`submodule.propagateBranches`. The plan is for Git commands to\nprioritize submodule ref store information over superproject gitlinks if\nthis value is true. Because \"git branch --recurse-submodules\" writes to\nsubmodule ref stores, for the sake of clarity, it will not function\nunless this configuration value is set.\n\nThis commit also includes changes that support working with submodules\nfrom a superproject commit because \"branch --recurse-submodules\" (and\nfuture commands) need to read .gitmodules and gitlinks from the\nsuperproject commit, but submodules are typically read from the\nfilesystem's .gitmodules and the index's gitlinks. These changes are:\n\n* add a submodules_of_tree() helper that gives the relevant\n  information of an in-tree submodule (e.g. path and oid) and\n  initializes the repository\n* add is_tree_submodule_active() by adding a treeish_name parameter to\n  is_submodule_active()\n* add the \"submoduleNotUpdated\" advice to advise users to update the\n  submodules in their trees\n\nIncidentally, fix an incorrect usage string that combined the 'list'\nusage of git branch (-l) with the 'create' usage; this string has been\nincorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\nparse_options., 2007-10-07).\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\nTo Jonathan: When describing the differences between \"git branch\" and \"git\ncheckout\", I opted to emphasize the fact that \"git checkout\" checks out the\ngitlink in detached HEAD and not the branches themselves (vs your suggestion of\nmentioning that checkout would check out the wrong version if the branch is\nsubsequently updated).\n\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  37 ++--\n Documentation/git-branch.txt       |  11 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 137 ++++++++++++++\n branch.h                           |  22 +++\n builtin/branch.c                   |  44 ++++-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3207-branch-submodule.sh        | 282 +++++++++++++++++++++++++++++\n 14 files changed, 664 insertions(+), 20 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex 063eec2511..adee26fbbb 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -116,6 +116,9 @@ advice.*::\n \tsubmoduleAlternateErrorStrategyDie::\n \t\tAdvice shown when a submodule.alternateErrorStrategy option\n \t\tconfigured to \"die\" causes a fatal error.\n+\tsubmodulesNotUpdated::\n+\t\tAdvice shown when a user runs a submodule command that fails\n+\t\tbecause `git submodule update --init` was not run.\n \taddIgnoredFile::\n \t\tAdvice shown if a user attempts to add an ignored file to\n \t\tthe index.\ndiff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\nindex ee454f8126..6490527b45 100644\n--- a/Documentation/config/submodule.txt\n+++ b/Documentation/config/submodule.txt\n@@ -59,18 +59,33 @@ submodule.active::\n \n submodule.recurse::\n \tA boolean indicating if commands should enable the `--recurse-submodules`\n-\toption by default.\n-\tApplies to all commands that support this option\n-\t(`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,\n-\t`restore` and `switch`) except `clone` and `ls-files`.\n+\toption by default. Defaults to false.\n++\n+When set to true, it can be deactivated via the\n+`--no-recurse-submodules` option. Note that some Git commands\n+lacking this option may call some of the above commands affected by\n+`submodule.recurse`; for instance `git remote update` will call\n+`git fetch` but does not have a `--no-recurse-submodules` option.\n+For these commands a workaround is to temporarily change the\n+configuration value by using `git -c submodule.recurse=0`.\n++\n+The following list shows the commands that accept\n+`--recurse-submodules` and whether they are supported by this\n+setting.\n+\n+* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n+`reset`, `restore` and `switch` are always supported.\n+* `clone` and `ls-files` are not supported.\n+* `branch` is supported only if `submodule.propagateBranches` is\n+enabled\n+\n+submodule.propagateBranches::\n+\t[EXPERIMENTAL] A boolean that enables branching support when\n+\tusing `--recurse-submodules` or `submodule.recurse=true`.\n+\tEnabling this will allow certain commands to accept\n+\t`--recurse-submodules` and certain commands that already accept\n+\t`--recurse-submodules` will now consider branches.\n \tDefaults to false.\n-\tWhen set to true, it can be deactivated via the\n-\t`--no-recurse-submodules` option. Note that some Git commands\n-\tlacking this option may call some of the above commands affected by\n-\t`submodule.recurse`; for instance `git remote update` will call\n-\t`git fetch` but does not have a `--no-recurse-submodules` option.\n-\tFor these commands a workaround is to temporarily change the\n-\tconfiguration value by using `git -c submodule.recurse=0`.\n \n submodule.fetchJobs::\n \tSpecifies how many submodules are fetched/cloned at the same time.\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 75beea7bac..871ca3c3eb 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,8 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f]\n+\t[--recurse-submodules] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -235,6 +236,14 @@ how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n \tDo not set up \"upstream\" configuration, even if the\n \tbranch.autoSetupMerge configuration variable is set.\n \n+--recurse-submodules::\n+\tTHIS OPTION IS EXPERIMENTAL! Causes the current command to\n+\trecurse into submodules if `submodule.propagateBranches` is\n+\tenabled. See `submodule.propagateBranches` in\n+\tlinkgit:git-config[1].\n+\t+\n+\tCurrently, only branch creation is supported.\n+\n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\n \tPlease use `--track` or `--set-upstream-to` instead.\ndiff --git a/advice.c b/advice.c\nindex 1dfc91d176..e00d30254c 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -70,6 +70,7 @@ static struct {\n \t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n \t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n \t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n+\t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n };\ndiff --git a/advice.h b/advice.h\nindex 601265fd10..a7521d6087 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -44,6 +44,7 @@ struct string_list;\n \tADVICE_STATUS_HINTS,\n \tADVICE_STATUS_U_OPTION,\n \tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_SUBMODULES_NOT_UPDATED,\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\ndiff --git a/branch.c b/branch.c\nindex 02d46a69b8..be33fe09fa 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -8,6 +8,8 @@\n #include \"sequencer.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n+#include \"submodule-config.h\"\n+#include \"run-command.h\"\n \n struct tracking {\n \tstruct refspec_item spec;\n@@ -483,6 +485,141 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n \tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n }\n \n+/**\n+ * Creates a branch in a submodule by calling\n+ * create_branches_recursively() in a child process. The child process\n+ * is necessary because install_branch_config_multiple_remotes() (which\n+ * is called by setup_tracking()) does not support writing configs to\n+ * submodules.\n+ */\n+static int submodule_create_branch(struct repository *r,\n+\t\t\t\t   const struct submodule *submodule,\n+\t\t\t\t   const char *name, const char *start_oid,\n+\t\t\t\t   const char *start_name, int force,\n+\t\t\t\t   int reflog, int quiet,\n+\t\t\t\t   enum branch_track track, int dry_run)\n+{\n+\tint ret = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tstruct strbuf child_err = STRBUF_INIT;\n+\tstruct strbuf out_buf = STRBUF_INIT;\n+\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n+\tchild.git_cmd = 1;\n+\tchild.err = -1;\n+\tchild.stdout_to_stderr = 1;\n+\n+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n+\t/*\n+\t * submodule_create_branch() is indirectly invoked by \"git\n+\t * branch\", but we cannot invoke \"git branch\" in the child\n+\t * process because it does not let us set start_name and\n+\t * start_oid separately (see create_branches_recursively()).\n+\t */\n+\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n+\tif (dry_run)\n+\t\tstrvec_push(&child.args, \"--dry-run\");\n+\tif (force)\n+\t\tstrvec_push(&child.args, \"--force\");\n+\tif (quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\tif (reflog)\n+\t\tstrvec_push(&child.args, \"--create-reflog\");\n+\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n+\t\tstrvec_push(&child.args, \"--track\");\n+\n+\tstrvec_pushl(&child.args, name, start_oid, start_name, NULL);\n+\n+\tif ((ret = start_command(&child)))\n+\t\treturn ret;\n+\tret = finish_command(&child);\n+\tstrbuf_read(&child_err, child.err, 0);\n+\tstrbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);\n+\n+\tif (ret)\n+\t\tfprintf(stderr, \"%s\", out_buf.buf);\n+\telse\n+\t\tprintf(\"%s\", out_buf.buf);\n+\n+\tstrbuf_release(&child_err);\n+\tstrbuf_release(&out_buf);\n+\treturn ret;\n+}\n+\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run)\n+{\n+\tint i = 0;\n+\tchar *branch_point = NULL;\n+\tstruct object_id super_oid;\n+\tstruct submodule_entry_list submodule_entry_list;\n+\n+\t/* Perform dwim on start_name to get super_oid and branch_point. */\n+\tdwim_branch_start(r, start_name, BRANCH_TRACK_NEVER, &branch_point,\n+\t\t\t  &super_oid);\n+\n+\t/*\n+\t * If we were not given an explicit name to track, then assume we are at\n+\t * the top level and, just like the non-recursive case, the tracking\n+\t * name is the branch point.\n+\t */\n+\tif (!tracking_name)\n+\t\ttracking_name = branch_point;\n+\n+\tsubmodules_of_tree(r, &super_oid, &submodule_entry_list);\n+\t/*\n+\t * Before creating any branches, first check that the branch can\n+\t * be created in every submodule.\n+\t */\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n+\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n+\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n+\t\t\t\t       start_name);\n+\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name);\n+\t\t}\n+\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t}\n+\n+\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n+\t\t      BRANCH_TRACK_NEVER, dry_run);\n+\tif (dry_run)\n+\t\treturn;\n+\t/*\n+\t * NEEDSWORK If tracking was set up in the superproject but not the\n+\t * submodule, users might expect \"git branch --recurse-submodules\" to\n+\t * fail or give a warning, but this is not yet implemented because it is\n+\t * tedious to determine whether or not tracking was set up in the\n+\t * superproject.\n+\t */\n+\tsetup_tracking(name, tracking_name, track, quiet);\n+\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t\trepo_clear(submodule_entry_list.entries[i].repo);\n+\t}\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 509cfcc34e..a59905a211 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -71,6 +71,28 @@ void create_branch(struct repository *r,\n \t\t   int reflog, int quiet, enum branch_track track,\n \t\t   int dry_run);\n \n+/*\n+ * Creates a new branch in repository and its submodules (and its\n+ * submodules, recursively). Besides these exceptions, the parameters\n+ * function identically to create_branch():\n+ *\n+ * - start_name is the name of the ref, in repository r, that the new\n+ *   branch should start from. In submodules, branches will start from\n+ *   the respective gitlink commit ids in start_name's tree.\n+ *\n+ * - tracking_name is the name used of the ref that will be used to set\n+ *   up tracking, e.g. origin/main. This is propagated to submodules so\n+ *   that tracking information will appear as if the branch branched off\n+ *   tracking_name instead of start_name (which is a plain commit id for\n+ *   submodules). If omitted, start_name is used for tracking (just like\n+ *   create_branch()).\n+ *\n+ */\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_name,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run);\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex c274fbdfcf..a720a6683d 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -27,7 +27,8 @@\n \n static const char * const builtin_branch_usage[] = {\n \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n-\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n@@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = {\n \n static const char *head;\n static struct object_id head_oid;\n+static int recurse_submodules = 0;\n+static int submodule_propagate_branches = 0;\n \n static int branch_use_color = -1;\n static char branch_colors[][COLOR_MAXLEN] = {\n@@ -100,6 +103,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\t\treturn config_error_nonbool(var);\n \t\treturn color_parse(value, branch_colors[slot]);\n \t}\n+\tif (!strcmp(var, \"submodule.recurse\")) {\n+\t\trecurse_submodules = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcasecmp(var, \"submodule.propagateBranches\")) {\n+\t\tsubmodule_propagate_branches = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_color_default_config(var, value, cb);\n }\n \n@@ -622,7 +634,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tconst char *new_upstream = NULL;\n \tint noncreate_actions = 0;\n \t/* possible options */\n-\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0,\n+\t    recurse_submodules_explicit = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n@@ -672,6 +685,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n+\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT_END(),\n \t};\n@@ -714,6 +728,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n+\tif (recurse_submodules_explicit) {\n+\t\tif (!submodule_propagate_branches)\n+\t\t\tdie(_(\"branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\"));\n+\t\tif (noncreate_actions)\n+\t\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n+\t}\n+\n+\trecurse_submodules =\n+\t\t(recurse_submodules || recurse_submodules_explicit) &&\n+\t\tsubmodule_propagate_branches;\n+\n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n \tfilter.ignore_case = icase;\n@@ -852,6 +877,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n+\t\tconst char *branch_name = argv[0];\n+\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n+\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n@@ -859,10 +887,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (track == BRANCH_TRACK_OVERRIDE)\n \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n \n-\t\tcreate_branch(the_repository,\n-\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track, 0);\n-\n+\t\tif (recurse_submodules) {\n+\t\t\tcreate_branches_recursively(the_repository, branch_name,\n+\t\t\t\t\t\t    start_name, NULL, force,\n+\t\t\t\t\t\t    reflog, quiet, track, 0);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n+\t\t\t      reflog, quiet, track, 0);\n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 6298cbdd4e..eba2a6e9b0 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -20,6 +20,7 @@\n #include \"diff.h\"\n #include \"object-store.h\"\n #include \"advice.h\"\n+#include \"branch.h\"\n \n #define OPT_QUIET (1 << 0)\n #define OPT_CACHED (1 << 1)\n@@ -2983,6 +2984,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n \treturn !!ret;\n }\n \n+static int module_create_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tenum branch_track track;\n+\tint quiet = 0, force = 0, reflog = 0, dry_run = 0;\n+\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n+\t\tOPT__FORCE(&force, N_(\"force creation\"), 0),\n+\t\tOPT_BOOL(0, \"create-reflog\", &reflog,\n+\t\t\t N_(\"create the branch's reflog\")),\n+\t\tOPT_SET_INT('t', \"track\", &track,\n+\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\t    BRANCH_TRACK_EXPLICIT),\n+\t\tOPT__DRY_RUN(&dry_run,\n+\t\t\t     N_(\"show whether the branch would be created\")),\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>\"),\n+\t\tNULL\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\ttrack = git_branch_track;\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\n+\tif (argc != 3)\n+\t\tusage_with_options(usage, options);\n+\n+\tif (!quiet && !dry_run)\n+\t\tprintf_ln(_(\"creating branch '%s'\"), argv[0]);\n+\n+\tcreate_branches_recursively(the_repository, argv[0], argv[1], argv[2],\n+\t\t\t\t    force, reflog, quiet, track, dry_run);\n+\treturn 0;\n+}\n struct add_data {\n \tconst char *prefix;\n \tconst char *branch;\n@@ -3379,6 +3416,7 @@ static struct cmd_struct commands[] = {\n \t{\"config\", module_config, 0},\n \t{\"set-url\", module_set_url, 0},\n \t{\"set-branch\", module_set_branch, 0},\n+\t{\"create-branch\", module_create_branch, 0},\n };\n \n int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f95344028b..24b8d1a700 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -7,6 +7,7 @@\n #include \"strbuf.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n+#include \"tree-walk.h\"\n \n /*\n  * submodule cache lookup structure\n@@ -726,6 +727,65 @@ const struct submodule *submodule_from_path(struct repository *r,\n \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n+/**\n+ * Used internally by submodules_of_tree(). Recurses into 'treeish_name'\n+ * and appends submodule entries to 'out'. The submodule_cache expects\n+ * a root-level treeish_name and paths, so keep track of these values\n+ * with 'root_tree' and 'prefix'.\n+ */\n+static void traverse_tree_submodules(struct repository *r,\n+\t\t\t\t     const struct object_id *root_tree,\n+\t\t\t\t     char *prefix,\n+\t\t\t\t     const struct object_id *treeish_name,\n+\t\t\t\t     struct submodule_entry_list *out)\n+{\n+\tstruct tree_desc tree;\n+\tstruct submodule_tree_entry *st_entry;\n+\tstruct name_entry *name_entry;\n+\tchar *tree_path = NULL;\n+\n+\tname_entry = xmalloc(sizeof(*name_entry));\n+\n+\tfill_tree_descriptor(r, &tree, treeish_name);\n+\twhile (tree_entry(&tree, name_entry)) {\n+\t\tif (prefix)\n+\t\t\ttree_path =\n+\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n+\t\telse\n+\t\t\ttree_path = xstrdup(name_entry->path);\n+\n+\t\tif (S_ISGITLINK(name_entry->mode) &&\n+\t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n+\t\t\tst_entry = xmalloc(sizeof(*st_entry));\n+\t\t\tst_entry->name_entry = name_entry;\n+\t\t\tst_entry->submodule =\n+\t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n+\t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n+\t\t\tif (repo_submodule_init(st_entry->repo, r, tree_path,\n+\t\t\t\t\t\troot_tree))\n+\t\t\t\tFREE_AND_NULL(st_entry->repo);\n+\n+\t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n+\t\t\t\t   out->entry_alloc);\n+\t\t\tout->entries[out->entry_nr++] = *st_entry;\n+\t\t} else if (S_ISDIR(name_entry->mode))\n+\t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n+\t\t\t\t\t\t &name_entry->oid, out);\n+\t\tfree(tree_path);\n+\t}\n+}\n+\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *out)\n+{\n+\tCALLOC_ARRAY(out->entries, 0);\n+\tout->entry_nr = 0;\n+\tout->entry_alloc = 0;\n+\n+\ttraverse_tree_submodules(r, treeish_name, NULL, treeish_name, out);\n+}\n+\n void submodule_free(struct repository *r)\n {\n \tif (r->submodule_cache)\ndiff --git a/submodule-config.h b/submodule-config.h\nindex 65875b94ea..fa229a8b97 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"submodule.h\"\n #include \"strbuf.h\"\n+#include \"tree-walk.h\"\n \n /**\n  * The submodule config cache API allows to read submodule\n@@ -101,4 +102,37 @@ int check_submodule_name(const char *name);\n void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n void update_clone_config_from_gitmodules(int *max_jobs);\n \n+/*\n+ * Submodule entry that contains relevant information about a\n+ * submodule in a tree.\n+ */\n+struct submodule_tree_entry {\n+\t/* The submodule's tree entry. */\n+\tstruct name_entry *name_entry;\n+\t/*\n+\t * A struct repository corresponding to the submodule. May be\n+\t * NULL if the submodule has not been updated.\n+\t */\n+\tstruct repository *repo;\n+\t/*\n+\t * A struct submodule containing the submodule config in the\n+\t * tree's .gitmodules.\n+\t */\n+\tconst struct submodule *submodule;\n+};\n+\n+struct submodule_entry_list {\n+\tstruct submodule_tree_entry *entries;\n+\tint entry_nr;\n+\tint entry_alloc;\n+};\n+\n+/**\n+ * Given a treeish, return all submodules in the tree and its subtrees,\n+ * but excluding nested submodules. Callers that require nested\n+ * submodules are expected to recurse into the submodules themselves.\n+ */\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *ret);\n #endif /* SUBMODULE_CONFIG_H */\ndiff --git a/submodule.c b/submodule.c\nindex f3c99634a9..07ae7c266e 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -265,7 +265,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n  * ie, the config looks like: \"[submodule] active\\n\".\n  * Since that is an invalid pathspec, we should inform the user.\n  */\n-int is_submodule_active(struct repository *repo, const char *path)\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path)\n {\n \tint ret = 0;\n \tchar *key = NULL;\n@@ -273,7 +275,7 @@ int is_submodule_active(struct repository *repo, const char *path)\n \tconst struct string_list *sl;\n \tconst struct submodule *module;\n \n-\tmodule = submodule_from_path(repo, null_oid(), path);\n+\tmodule = submodule_from_path(repo, treeish_name, path);\n \n \t/* early return if there isn't a path->module mapping */\n \tif (!module)\n@@ -315,6 +317,11 @@ int is_submodule_active(struct repository *repo, const char *path)\n \treturn ret;\n }\n \n+int is_submodule_active(struct repository *repo, const char *path)\n+{\n+\treturn is_tree_submodule_active(repo, null_oid(), path);\n+}\n+\n int is_submodule_populated_gently(const char *path, int *return_error_code)\n {\n \tint ret = 0;\ndiff --git a/submodule.h b/submodule.h\nindex 6bd2c99fd9..784ceffc0e 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb);\n struct option;\n int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n \t\t\t\t\t\t     const char *arg, int unset);\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path);\n int is_submodule_active(struct repository *repo, const char *path);\n /*\n  * Determine if a submodule has been populated at a given 'path' by checking if\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nnew file mode 100755\nindex 0000000000..047b0cd174\n--- /dev/null\n+++ b/t/t3207-branch-submodule.sh\n@@ -0,0 +1,282 @@\n+#!/bin/sh\n+\n+test_description='git branch submodule tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+pwd=$(pwd)\n+\n+# Creates a clean test environment in \"pwd\" by copying the repo setup\n+# from test_dirs.\n+reset_test () {\n+\trm -fr super &&\n+\trm -fr sub-sub-upstream &&\n+\trm -fr sub-upstream &&\n+\tcp -r test_dirs/* .\n+}\n+\n+test_expect_success 'setup superproject and submodule' '\n+\tmkdir test_dirs &&\n+\t(\n+\t\tcd test_dirs &&\n+\t\tgit init super &&\n+\t\ttest_commit -C super foo &&\n+\t\tgit init sub-sub-upstream &&\n+\t\ttest_commit -C sub-sub-upstream foo &&\n+\t\tgit init sub-upstream &&\n+\t\t# Submodule in a submodule\n+\t\tgit -C sub-upstream submodule add \"${pwd}/test_dirs/sub-sub-upstream\" sub-sub &&\n+\t\tgit -C sub-upstream commit -m \"add submodule\" &&\n+\t\t# Regular submodule\n+\t\tgit -C super submodule add \"${pwd}/test_dirs/sub-upstream\" sub &&\n+\t\t# Submodule in a subdirectory\n+\t\tgit -C super submodule add \"${pwd}/test_dirs/sub-sub-upstream\" second/sub &&\n+\t\tgit -C super commit -m \"add submodule\" &&\n+\t\tgit -C super config submodule.propagateBranches true &&\n+\t\tgit -C super/sub submodule update --init\n+\t) &&\n+\treset_test\n+'\n+\n+# Test the argument parsing\n+test_expect_success '--recurse-submodules should create branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a &&\n+\t\tgit -C sub/sub-sub rev-parse branch-a &&\n+\t\tgit -C second/sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\techo \"fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\" >expected &&\n+\t\ttest_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should fail when not creating branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n+\t\t# Assert that the branches were not deleted\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success 'should respect submodule.recurse when creating branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c submodule.recurse=true branch branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success 'should ignore submodule.recurse when not creating branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit -c submodule.recurse=true branch -D branch-a &&\n+\t\ttest_must_fail git rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a\n+\t)\n+'\n+\n+# Test branch creation behavior\n+test_expect_success 'should create branches based off commit id in superproject' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit checkout --recurse-submodules branch-a &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\t# Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a\n+\t\tgit -C sub checkout branch-a &&\n+\t\ttest_commit -C sub bar &&\n+\t\t# Create a new branch-b branch with start-point=branch-a\n+\t\tgit branch --recurse-submodules branch-b branch-a &&\n+\t\tgit rev-parse branch-b &&\n+\t\tgit -C sub rev-parse branch-b >actual &&\n+\t\t# Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should not create any branches if branch is not valid for all repos' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub branch branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_must_fail git rev-parse branch-a &&\n+\t\tgrep \"submodule .sub.: fatal: A branch named .branch-a. already exists\" actual\n+\t)\n+'\n+\n+test_expect_success 'should create branches if branch exists and --force is given' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\ttest_commit -C sub baz &&\n+\t\t# branch-a in sub now points to a newer commit.\n+\t\tgit -C sub branch branch-a HEAD &&\n+\t\tgit -C sub rev-parse branch-a >actual-old-branch-a &&\n+\t\tgit branch --recurse-submodules --force branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a >actual-new-branch-a &&\n+\t\ttest_cmp expected actual-new-branch-a &&\n+\t\t# assert that branch --force actually moved the sub\n+\t\t# branch\n+\t\t! test_cmp expected actual-old-branch-a\n+\t)\n+'\n+\n+test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\tgit -C sub2 submodule update --init &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\" &&\n+\t\tgit checkout branch-a &&\n+\t\tgit branch --recurse-submodules branch-c branch-b &&\n+\t\tgit checkout --recurse-submodules branch-c &&\n+\t\tgit -C sub2 rev-parse branch-c &&\n+\t\tgit -C sub2/sub-sub rev-parse branch-c\n+\t)\n+'\n+\n+test_expect_success 'should not create branches in inactive submodules' '\n+\ttest_when_finished \"reset_test\" &&\n+\ttest_config -C super submodule.sub.active false &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\ttest_must_fail git -C sub branch-a\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with track=always' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with explicit track' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --track --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n+\t)\n+'\n+\n+test_expect_success 'should not set up unnecessary tracking of local branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+reset_remote_test () {\n+\trm -fr super-clone &&\n+\treset_test\n+}\n+\n+test_expect_success 'setup tests with remotes' '\n+\t(\n+\t\tcd test_dirs &&\n+\t\t(\n+\t\t\tcd super &&\n+\t\t\tgit branch branch-a &&\n+\t\t\tgit checkout -b branch-b &&\n+\t\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\t\t# branch-b now has a committed submodule not in branch-a\n+\t\t\tgit commit -m \"add second submodule\"\n+\t\t) &&\n+\t\tgit clone --branch main --recurse-submodules super super-clone &&\n+\t\tgit -C super-clone config submodule.propagateBranches true\n+\t) &&\n+\treset_remote_test\n+'\n+\n+test_expect_success 'should get fatal error upon branch creation when submodule is not in .git/modules' '\n+\ttest_when_finished \"reset_remote_test\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\t# This should succeed because super-clone has sub in .git/modules\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\t# This should fail because super-clone does not have sub2 .git/modules\n+\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n+\t\tgrep \"fatal: submodule .sub2.: unable to find submodule\" actual &&\n+\t\ttest_must_fail git rev-parse branch-b &&\n+\t\ttest_must_fail git -C sub rev-parse branch-b &&\n+\t\t# User can fix themselves by initializing the submodule\n+\t\tgit checkout origin/branch-b &&\n+\t\tgit submodule update --init --recursive &&\n+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of remote-tracking branches' '\n+\ttest_when_finished \"reset_remote_test\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\t# \"origin/branch-a\" does not exist for \"sub\", but it matches the refspec\n+\t\t# so tracking should be set up\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.remote)\" = origin &&\n+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.merge)\" = refs/heads/branch-a\n+\t)\n+'\n+\n+test_expect_success 'should not fail when unable to set up tracking in submodule' '\n+\ttest_when_finished \"reset_remote_test\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit remote rename origin ex-origin &&\n+\t\tgit branch --recurse-submodules branch-a ex-origin/branch-a &&\n+\t\ttest \"$(git config branch.branch-a.remote)\" = ex-origin &&\n+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n+\t)\n+'\n+\n+test_done\n-- \n2.33.GIT\n\n"},{"id":"446815","messageId":"20220124204442.39353-7-chooglen@google.com","threadId":"56962","inReplyTo":"20220124204442.39353-1-chooglen@google.com","subject":"[PATCH v7 6/6] branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-24T20:44:42Z","receivedAt":"2022-01-24T22:33:54Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Signed-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex be33fe09fa..1e9a585633 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -239,7 +239,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \tif (track != BRANCH_TRACK_INHERIT)\n \t\tfor_each_remote(find_tracked_branch, &tracking);\n \telse if (inherit_tracking(&tracking, orig_ref))\n-\t\treturn;\n+\t\tgoto cleanup;\n \n \tif (!tracking.matches)\n \t\tswitch (track) {\n@@ -249,7 +249,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tcase BRANCH_TRACK_INHERIT:\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\treturn;\n+\t\t\tgoto cleanup;\n \t\t}\n \n \tif (tracking.matches > 1)\n@@ -262,6 +262,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t\ttracking.remote, tracking.srcs) < 0)\n \t\texit(-1);\n \n+cleanup:\n \tstring_list_clear(tracking.srcs, 0);\n }\n \n-- \n2.33.GIT\n\n"},{"id":"447126","messageId":"20220127202918.3099460-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20220124204442.39353-6-chooglen@google.com","subject":"Re: [PATCH v7 5/6] branch: add --recurse-submodules option for branch creation","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-01-27T20:29:18Z","receivedAt":"2022-01-27T20:29:22Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Patches up to this one look good, and the implementation looks good too.\nI just have minor comments on some of the text and tests.\n\nGlen Choo <chooglen@google.com> writes:\n> Although this commit does not introduce breaking changes, it is\n> incompatible with existing --recurse-submodules commands because \"git\n> branch --recurse-submodules\" writes to the submodule ref store, but most\n> commands only consider the superproject gitlink and ignore the submodule\n> ref store. For example, \"git checkout --recurse-submodules\" will check\n> out the commits in the superproject gitlinks (and put the submodules in\n> detached HEAD) instead of checking out the submodule branches.\n\nThe usual meaning of \"incompatible\" is \"cannot be used together\", I\nthink, which is not what's happening here - it's just that the user\nwould expect them to work together in a specific way but they don't do\nthat.\n\n> To Jonathan: When describing the differences between \"git branch\" and \"git\n> checkout\", I opted to emphasize the fact that \"git checkout\" checks out the\n> gitlink in detached HEAD and not the branches themselves (vs your suggestion of\n> mentioning that checkout would check out the wrong version if the branch is\n> subsequently updated).\n\nOK - makes sense.\n\n> +/**\n> + * Creates a branch in a submodule by calling\n> + * create_branches_recursively() in a child process. The child process\n> + * is necessary because install_branch_config_multiple_remotes() (which\n> + * is called by setup_tracking()) does not support writing configs to\n> + * submodules.\n\nOK, good explanation for why a child process is needed.\n\n> + */\n> +static int submodule_create_branch(struct repository *r,\n> +\t\t\t\t   const struct submodule *submodule,\n> +\t\t\t\t   const char *name, const char *start_oid,\n> +\t\t\t\t   const char *start_name, int force,\n> +\t\t\t\t   int reflog, int quiet,\n> +\t\t\t\t   enum branch_track track, int dry_run)\n> +{\n> +\tint ret = 0;\n> +\tstruct child_process child = CHILD_PROCESS_INIT;\n> +\tstruct strbuf child_err = STRBUF_INIT;\n> +\tstruct strbuf out_buf = STRBUF_INIT;\n> +\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n> +\tchild.git_cmd = 1;\n> +\tchild.err = -1;\n> +\tchild.stdout_to_stderr = 1;\n> +\n> +\tprepare_other_repo_env(&child.env_array, r->gitdir);\n> +\t/*\n> +\t * submodule_create_branch() is indirectly invoked by \"git\n> +\t * branch\", but we cannot invoke \"git branch\" in the child\n> +\t * process because it does not let us set start_name and\n> +\t * start_oid separately (see create_branches_recursively()).\n\nProbably clearer to enumerate the 3 different pieces of information\nneeded: the name of the branch to be created, the OID, and the name of\nthe branch that would be used for tracking purposes.\n\nAn argument could be made that \"git branch\" should be extended to be\nable to take in these 3 different pieces of information, but it also\nmakes sense to put this functionality in submodule--helper for now,\nsince the whole thing is marked as experimental.\n\n> +/*\n> + * Creates a new branch in repository and its submodules (and its\n> + * submodules, recursively). Besides these exceptions, the parameters\n> + * function identically to create_branch():\n> + *\n> + * - start_name is the name of the ref, in repository r, that the new\n> + *   branch should start from. In submodules, branches will start from\n> + *   the respective gitlink commit ids in start_name's tree.\n> + *\n> + * - tracking_name is the name used of the ref that will be used to set\n> + *   up tracking, e.g. origin/main. This is propagated to submodules so\n> + *   that tracking information will appear as if the branch branched off\n> + *   tracking_name instead of start_name (which is a plain commit id for\n> + *   submodules). If omitted, start_name is used for tracking (just like\n> + *   create_branch()).\n> + *\n> + */\n> +void create_branches_recursively(struct repository *r, const char *name,\n> +\t\t\t\t const char *start_name,\n> +\t\t\t\t const char *tracking_name, int force,\n> +\t\t\t\t int reflog, int quiet, enum branch_track track,\n> +\t\t\t\t int dry_run);\n\nInstead of taking in \"name\", \"start_name\", and \"tracking_name\", could we\ntake in \"name\", \"oid\", and \"tracking_name\"? That way, it's clearer what\neach parameter is used for.\n\n> +test_expect_success 'should not create branches in inactive submodules' '\n> +\ttest_when_finished \"reset_test\" &&\n> +\ttest_config -C super submodule.sub.active false &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit branch --recurse-submodules branch-a &&\n> +\t\tgit rev-parse branch-a &&\n> +\t\ttest_must_fail git -C sub branch-a\n> +\t)\n> +'\n\nThe \"test_must_fail\" line doesn't make sense - there is no such command\nbranch-a. To avoid errors like this, maybe make sure that all\ntest_must_fail invocations are accompanied by assertions on the error\nmessage. (And for \"test_must_fail git rev-parse\", we could have a helper\nfunction here that asserts the \"object not found\".)\n\n> +test_expect_success 'should set up tracking of local branches with track=always' '\n> +\ttest_when_finished \"reset_test\" &&\n> +\t(\n> +\t\tcd super &&\n> +\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n> +\t\tgit -C sub rev-parse main &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n> +\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n> +\t)\n> +'\n\nAs described in t/README line 671, this means that the inner command\ncould silently fail. You can do `REMOTE=$(...) &&` <newline> `test ...`.\nSame comment throughout this file.\n"},{"id":"447134","messageId":"kl6lr18szwxh.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20220127202918.3099460-1-jonathantanmy@google.com","subject":"Re: [PATCH v7 5/6] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-27T21:32:58Z","receivedAt":"2022-01-27T21:33:10Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> Although this commit does not introduce breaking changes, it is\n>> incompatible with existing --recurse-submodules commands because \"git\n>> branch --recurse-submodules\" writes to the submodule ref store, but most\n>> commands only consider the superproject gitlink and ignore the submodule\n>> ref store. For example, \"git checkout --recurse-submodules\" will check\n>> out the commits in the superproject gitlinks (and put the submodules in\n>> detached HEAD) instead of checking out the submodule branches.\n>\n> The usual meaning of \"incompatible\" is \"cannot be used together\", I\n> think, which is not what's happening here - it's just that the user\n> would expect them to work together in a specific way but they don't do\n> that.\n\nOk, I'll replace \"incompatible with\" with \"does not interact well with\".\n\n>> + */\n>> +static int submodule_create_branch(struct repository *r,\n>> +\t\t\t\t   const struct submodule *submodule,\n>> +\t\t\t\t   const char *name, const char *start_oid,\n>> +\t\t\t\t   const char *start_name, int force,\n>> +\t\t\t\t   int reflog, int quiet,\n>> +\t\t\t\t   enum branch_track track, int dry_run)\n>> +{\n>> +\tint ret = 0;\n>> +\tstruct child_process child = CHILD_PROCESS_INIT;\n>> +\tstruct strbuf child_err = STRBUF_INIT;\n>> +\tstruct strbuf out_buf = STRBUF_INIT;\n>> +\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n>> +\tchild.git_cmd = 1;\n>> +\tchild.err = -1;\n>> +\tchild.stdout_to_stderr = 1;\n>> +\n>> +\tprepare_other_repo_env(&child.env_array, r->gitdir);\n>> +\t/*\n>> +\t * submodule_create_branch() is indirectly invoked by \"git\n>> +\t * branch\", but we cannot invoke \"git branch\" in the child\n>> +\t * process because it does not let us set start_name and\n>> +\t * start_oid separately (see create_branches_recursively()).\n>\n> Probably clearer to enumerate the 3 different pieces of information\n> needed: the name of the branch to be created, the OID, and the name of\n> the branch that would be used for tracking purposes.\n\nMakes sense. Also the comment on create_branches_recursively() doesn't\ndo a great job of explaining why we need the additional piece of\ninformation (tracking name), so I'll tidy them up together.\n\n> An argument could be made that \"git branch\" should be extended to be\n> able to take in these 3 different pieces of information, but it also\n> makes sense to put this functionality in submodule--helper for now,\n> since the whole thing is marked as experimental.\n\nYes, I considered this, but I can't think of a good reason to expose\nthis functionality to users - users can set their upstream with the more\nintuitive \"git branch --set-upstream-to\".\n\n>> +/*\n>> + * Creates a new branch in repository and its submodules (and its\n>> + * submodules, recursively). Besides these exceptions, the parameters\n>> + * function identically to create_branch():\n>> + *\n>> + * - start_name is the name of the ref, in repository r, that the new\n>> + *   branch should start from. In submodules, branches will start from\n>> + *   the respective gitlink commit ids in start_name's tree.\n>> + *\n>> + * - tracking_name is the name used of the ref that will be used to set\n>> + *   up tracking, e.g. origin/main. This is propagated to submodules so\n>> + *   that tracking information will appear as if the branch branched off\n>> + *   tracking_name instead of start_name (which is a plain commit id for\n>> + *   submodules). If omitted, start_name is used for tracking (just like\n>> + *   create_branch()).\n>> + *\n>> + */\n>> +void create_branches_recursively(struct repository *r, const char *name,\n>> +\t\t\t\t const char *start_name,\n>> +\t\t\t\t const char *tracking_name, int force,\n>> +\t\t\t\t int reflog, int quiet, enum branch_track track,\n>> +\t\t\t\t int dry_run);\n>\n> Instead of taking in \"name\", \"start_name\", and \"tracking_name\", could we\n> take in \"name\", \"oid\", and \"tracking_name\"? That way, it's clearer what\n> each parameter is used for.\n\nI used start_name to mirror create_branches(), but start_name makes less\nsense here because it's ambiguous.\n\nSince the start_name is always an oid or NULL, this makes sense. I'll do\nthis.\n\n>> +test_expect_success 'should not create branches in inactive submodules' '\n>> +\ttest_when_finished \"reset_test\" &&\n>> +\ttest_config -C super submodule.sub.active false &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit branch --recurse-submodules branch-a &&\n>> +\t\tgit rev-parse branch-a &&\n>> +\t\ttest_must_fail git -C sub branch-a\n>> +\t)\n>> +'\n>\n> The \"test_must_fail\" line doesn't make sense - there is no such command\n> branch-a. To avoid errors like this, maybe make sure that all\n> test_must_fail invocations are accompanied by assertions on the error\n> message. (And for \"test_must_fail git rev-parse\", we could have a helper\n> function here that asserts the \"object not found\".)\n\nAh good catch. I'll make the test helper.\n\n>\n>> +test_expect_success 'should set up tracking of local branches with track=always' '\n>> +\ttest_when_finished \"reset_test\" &&\n>> +\t(\n>> +\t\tcd super &&\n>> +\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n>> +\t\tgit -C sub rev-parse main &&\n>> +\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n>> +\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n>> +\t)\n>> +'\n>\n> As described in t/README line 671, this means that the inner command\n> could silently fail. You can do `REMOTE=$(...) &&` <newline> `test ...`.\n> Same comment throughout this file.\n\nThanks for the catch and double thanks for the recommended fix.\n\nWill reroll soon.\n"},{"id":"447145","messageId":"xmqqfsp8zuyg.fsf@gitster.g","threadId":"56962","inReplyTo":"20220124204442.39353-7-chooglen@google.com","subject":"Re: [PATCH v7 6/6] branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-27T22:15:35Z","receivedAt":"2022-01-27T22:15:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  branch.c | 5 +++--\n>  1 file changed, 3 insertions(+), 2 deletions(-)\n>\n> diff --git a/branch.c b/branch.c\n> index be33fe09fa..1e9a585633 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -239,7 +239,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \tif (track != BRANCH_TRACK_INHERIT)\n>  \t\tfor_each_remote(find_tracked_branch, &tracking);\n>  \telse if (inherit_tracking(&tracking, orig_ref))\n> -\t\treturn;\n> +\t\tgoto cleanup;\n>  \n>  \tif (!tracking.matches)\n>  \t\tswitch (track) {\n> @@ -249,7 +249,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\tcase BRANCH_TRACK_INHERIT:\n>  \t\t\tbreak;\n>  \t\tdefault:\n> -\t\t\treturn;\n> +\t\t\tgoto cleanup;\n>  \t\t}\n>  \n>  \tif (tracking.matches > 1)\n> @@ -262,6 +262,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\t\t\ttracking.remote, tracking.srcs) < 0)\n>  \t\texit(-1);\n>  \n> +cleanup:\n>  \tstring_list_clear(tracking.srcs, 0);\n\nMakes sense.  There is no other exit paths out of the function after\nthe tracking_srcs variable gets initialized, so this should be\ncovering everything.\n\nTwo tangential findings:\n\n * I see exit(-1) in the precontext of the final hunk.  We probably\n   would want to fix it, as negative argument to exit(3) is\n   misleading (the standard makes it clear that only the least\n   significant 8 bits matter, so it is not that bad).\n\n * At the end, what is cleared is tracking.srcs, but because it is a\n   pointer to the real resource we allocated on our stack, it would\n   be cleaner to pass &tracking_srcs instead.\n\nBoth are not what this patch introduces, and are outside the scope\nof this series.\n\nThanks.\n"},{"id":"447146","messageId":"kl6lk0ekn6l4.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"kl6lr18szwxh.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v7 5/6] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-27T22:42:47Z","receivedAt":"2022-01-27T22:42:52Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> Jonathan Tan <jonathantanmy@google.com> writes:\n>\n>>> +/*\n>>> + * Creates a new branch in repository and its submodules (and its\n>>> + * submodules, recursively). Besides these exceptions, the parameters\n>>> + * function identically to create_branch():\n>>> + *\n>>> + * - start_name is the name of the ref, in repository r, that the new\n>>> + *   branch should start from. In submodules, branches will start from\n>>> + *   the respective gitlink commit ids in start_name's tree.\n>>> + *\n>>> + * - tracking_name is the name used of the ref that will be used to set\n>>> + *   up tracking, e.g. origin/main. This is propagated to submodules so\n>>> + *   that tracking information will appear as if the branch branched off\n>>> + *   tracking_name instead of start_name (which is a plain commit id for\n>>> + *   submodules). If omitted, start_name is used for tracking (just like\n>>> + *   create_branch()).\n>>> + *\n>>> + */\n>>> +void create_branches_recursively(struct repository *r, const char *name,\n>>> +\t\t\t\t const char *start_name,\n>>> +\t\t\t\t const char *tracking_name, int force,\n>>> +\t\t\t\t int reflog, int quiet, enum branch_track track,\n>>> +\t\t\t\t int dry_run);\n>>\n>> Instead of taking in \"name\", \"start_name\", and \"tracking_name\", could we\n>> take in \"name\", \"oid\", and \"tracking_name\"? That way, it's clearer what\n>> each parameter is used for.\n>\n> I used start_name to mirror create_branches(), but start_name makes less\n> sense here because it's ambiguous.\n>\n> Since the start_name is always an oid or NULL, this makes sense. I'll do\n> this.\n\nRereading the code, I realise that start_name isn't guaranteed to be an\noid or NULL. When create_branches_recursively() is called at the root\nlevel i.e. cmd_branch(), start_name is a user-given revision which\nmay or may not be an object id. In that scenario, tracking_name is NULL\nto indicate that we would like to track start_name.\n\nI can still pass only oids, but that would require parsing the revision\nin cmd_branch() itself, and that seems like a violation of the layering\nwe have set up.\n\nInstead, I'll try to find an accurate, but more descriptive name than\nstart_name.\n"},{"id":"447236","messageId":"kl6lh79nmyqa.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"xmqqfsp8zuyg.fsf@gitster.g","subject":"Re: [PATCH v7 6/6] branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-28T19:44:45Z","receivedAt":"2022-01-28T19:44:52Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>\n>> Signed-off-by: Glen Choo <chooglen@google.com>\n>> ---\n>>  branch.c | 5 +++--\n>>  1 file changed, 3 insertions(+), 2 deletions(-)\n>>\n>> diff --git a/branch.c b/branch.c\n>> index be33fe09fa..1e9a585633 100644\n>> --- a/branch.c\n>> +++ b/branch.c\n>> @@ -239,7 +239,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>>  \tif (track != BRANCH_TRACK_INHERIT)\n>>  \t\tfor_each_remote(find_tracked_branch, &tracking);\n>>  \telse if (inherit_tracking(&tracking, orig_ref))\n>> -\t\treturn;\n>> +\t\tgoto cleanup;\n>>  \n>>  \tif (!tracking.matches)\n>>  \t\tswitch (track) {\n>> @@ -249,7 +249,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>>  \t\tcase BRANCH_TRACK_INHERIT:\n>>  \t\t\tbreak;\n>>  \t\tdefault:\n>> -\t\t\treturn;\n>> +\t\t\tgoto cleanup;\n>>  \t\t}\n>>  \n>>  \tif (tracking.matches > 1)\n>> @@ -262,6 +262,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>>  \t\t\t\ttracking.remote, tracking.srcs) < 0)\n>>  \t\texit(-1);\n>>  \n>> +cleanup:\n>>  \tstring_list_clear(tracking.srcs, 0);\n>\n> Makes sense.  There is no other exit paths out of the function after\n> the tracking_srcs variable gets initialized, so this should be\n> covering everything.\n>\n> Two tangential findings:\n>\n>  * I see exit(-1) in the precontext of the final hunk.  We probably\n>    would want to fix it, as negative argument to exit(3) is\n>    misleading (the standard makes it clear that only the least\n>    significant 8 bits matter, so it is not that bad).\n\nThanks for the reminder. We had this discussion previously and the\nconclusion was that I would send this cleanup as a separate series [1].\nUnlike this relatively obvious cleanup, I think exit codes might spark a\nmore involved discussion.\n\n>\n>  * At the end, what is cleared is tracking.srcs, but because it is a\n>    pointer to the real resource we allocated on our stack, it would\n>    be cleaner to pass &tracking_srcs instead.\n\nAh yes, that is true. I'll do that. If you (or others) prefer, I can\nmove this patch to its own series, or possibly as part of a grab bag\nwith the exit code fix.\n\n>\n> Both are not what this patch introduces, and are outside the scope\n> of this series.\n>\n> Thanks.\n\n[1] https://lore.kernel.org/git/211210.86ee6ldwlc.gmgdl@evledraar.gmail.com\n"},{"id":"447249","messageId":"20220129000446.99261-1-chooglen@google.com","threadId":"56962","inReplyTo":"20220124204442.39353-1-chooglen@google.com","subject":"[PATCH v8 0/6] implement branch --recurse-submodules","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-29T00:04:40Z","receivedAt":"2022-01-29T00:05:02Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Thanks again for the feedback, Jonathan!\n\nThis version is rebased onto the updated js/branch-track-inherit, from\n44f14a9d24 (config: require lowercase for branch.*.autosetupmerge, 2021-12-20)\nto 6327f0efed (branch,checkout: fix --track documentation, 2022-01-20). As\nsuch, the range-diff for patch 5 shows some additional changes in the usage\nstring lines.\n\nJunio spotted some issues in the context that are worth fixing, but out of scope\nfor this series (https://lore.kernel.org/git/xmqqfsp8zuyg.fsf@gitster.g). I plan\nto send fixes for them after this series is merged. Patch 6 arguably fits in\nbetter with that fixup series than this one, but I'll leave the decision to\nkeep/drop it to Junio.\n\n= Description\n\nThis series implements \"git branch --recurse-submodules\", which is part of the\nsubmodule branching RFC (see \"See also\"). \"git branch\" (and some other features\nin that RFC) are incompatible with existing \"--recurse-submodules\" commands;\npatch 5 describes this incompatibility in more detail and how we plan to\nintroduce this new functionality in a manner.\n\nThis series is based off js/branch-track-inherit.\n\nPatches 1-4 are preparation for the implementation of \"branch\n--recurse-submodules\" in patch 5. Patch 6 cleans up a memory leak that I\nencountered on js/branch-track-inherit as I was moving code around; it isn't\nneeeded by \"branch --recurse-submodules\".\n\nSee the previous cover letter\n(https://lore.kernel.org/git/20211220233459.45739-1-chooglen@google.com)\nfor discussions of future work.\n\n= See also\n\nSubmodule branching RFC:\nhttps://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/\n\nOriginal Submodule UX RFC/Discussion:\nhttps://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n\nContributor Summit submodules Notes:\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/\n\nSubmodule UX overhaul updates:\nhttps://lore.kernel.org/git/?q=Submodule+UX+overhaul+update\n\n= Changes\n\nChanges since v7:\n* Reword patch 5's commit message\n* In comments and Documentation/git-branch.txt, clarify how branch\n  --recurse-submodules behaves and rename a parameter to better reflect\n  this behavior\n* In t3207, always assert on error messages when using test_must_fail\n* In t3207, assert on config values using test_cmp_config. This avoids\n  the silent failures discouraged by t/README:671.\n* In patch 6, be more explicit about freeing the stack-allocated\n  variable.\n\nChanges since v6:\n* Move memory leak fix into its own patch (patch 6)\n* Eliminate unnecessary rewrapping and small style fixes\n* In branch --recurse-submodules tests, setup test repo anew instead of\n  deleting the unused branches\n* Numerous commit message wording changes\n\nChanges since v5:\n* Rebase onto v7 of js/branch-track-inherit\n  (https://lore.kernel.org/git/cover.1639717481.git.steadmon@google.com)\n\nChanges since v4:\n* Rebase correctly onto 'gitster/seen^{/^Merge branch .js/branch-track-inherit.}'\n  (see base-commit) as suggested in [1] (thanks Junio!)\n* These patches were also verified on top of 'next'.\n\nChanges since v3:\n* Split up the old patch 1. Patch 1 had a big diff because it used to\n  move lines, remove dead code and introduce repo_* functions (thanks\n  Jonathan!)\n** repo_* functions have been dropped; they added noise and are not\n   necessary for correctness.\n* Use a new, harder-to-misuse function in --set-upstream-to,\n  dwim_and_setup_tracking(). Now, setup_tracking() never does DWIM and\n  dwim_and_setup_tracking() always does DWIM.\n* Move create_branch() dry_run to its own patch.\n* Fix an oversight where submodules in subtrees were ignored. This was\n  because submodules_of_tree() and tree_entry() didn't recurse into\n  subtrees. Test this accordingly (thanks Jonathan!).\n* cmd_branch() possible actions are more consistently ordered.\n* Documentation fixes (thanks Philippe!).\n* Additional comments and explanation.\n* Drop patch 5 (optional cleanup).\n* Rebase onto js/branch-track-inherit v6.\n\nChanges since v2:\n* Rebase onto js/branch-track-inherit. This series should continue to be\n  the case going forward.\n* Patch 1 has a smaller diff because the introduction of\n  validate_branch_start() no longer changes the function order thanks to a\n  forward declaration. This artificial forward declaration is removed in a\n  patch 2 (which can just be squashed into patch 1).\n* Optional cleanup: fix questionable exit codes in patch 5.\n\nChanges since v1:\n* Move the functionality of \"git branch --dry-run\" into \"git submodule-helper create-branch --dry-run\"\n* Add more fields to the submodules_of_tree() struct to reduce the\n  number of allocations made by the caller. Move this functionality\n  to patch 3 (formerly patch 4) and drop patch 1.\n* Make submodules_of_tree() ignore inactive submodules\n* Structure the output of the submodules a bit better by adding prefixes\n  to the child process' output (instead of inconsistently indenting the\n  output).\n** I wasn't able to find a good way to interleave stdout/stderr\n   correctly, so a less-than-desirable workaround was to route the child\n   process output to stdout/stderr depending on the exit code.\n** Eventually, I would like to structure the output of submodules in a\n   report, as Ævar suggested. But at this stage, I think that it's\n   better to spend time getting user feedback on the submodules\n   branching UX and it'll be easier to standardize the output when we've\n   implemented more of the UX :)\n\n[1] https://lore.kernel.org/git/xmqqlf0lz6os.fsf@gitster.g \n\nGlen Choo (6):\n  branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n  branch: make create_branch() always create a branch\n  branch: add a dry_run parameter to create_branch()\n  builtin/branch: consolidate action-picking logic in cmd_branch()\n  branch: add --recurse-submodules option for branch creation\n  branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks\n\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  37 ++--\n Documentation/git-branch.txt       |  19 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 277 ++++++++++++++++++++++-----\n branch.h                           |  60 +++++-\n builtin/branch.c                   |  70 +++++--\n builtin/checkout.c                 |   3 +-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3200-branch.sh                  |  17 ++\n t/t3207-branch-submodule.sh        | 292 +++++++++++++++++++++++++++++\n 16 files changed, 845 insertions(+), 81 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\nRange-diff against v7:\n1:  206175cfb3 = 1:  547055d55e branch: move --set-upstream-to behavior to dwim_and_setup_tracking()\n2:  2e02885eb3 = 2:  016f62681d branch: make create_branch() always create a branch\n3:  cd43a9aaaa = 3:  a459a030f6 branch: add a dry_run parameter to create_branch()\n4:  8e5f750162 = 4:  4903f8eb4b builtin/branch: consolidate action-picking logic in cmd_branch()\n5:  c59de1fd9c ! 5:  b95b77b7c1 branch: add --recurse-submodules option for branch creation\n    @@ Commit message\n         topic\" will create the `topic` branch in the superproject and its\n         submodules.\n     \n    -    Although this commit does not introduce breaking changes, it is\n    -    incompatible with existing --recurse-submodules commands because \"git\n    +    Although this commit does not introduce breaking changes, it does not\n    +    work well with existing --recurse-submodules commands because \"git\n         branch --recurse-submodules\" writes to the submodule ref store, but most\n         commands only consider the superproject gitlink and ignore the submodule\n         ref store. For example, \"git checkout --recurse-submodules\" will check\n    @@ Documentation/git-branch.txt: SYNOPSIS\n      \t[--points-at <object>] [--format=<format>]\n      \t[(-r | --remotes) | (-a | --all)]\n      \t[--list] [<pattern>...]\n    --'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n    -+'git branch' [--track [direct|inherit] | --no-track] [-f]\n    +-'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]\n    ++'git branch' [--track[=(direct|inherit)] | --no-track] [-f]\n     +\t[--recurse-submodules] <branchname> [<start-point>]\n      'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n      'git branch' --unset-upstream [<branchname>]\n    @@ Documentation/git-branch.txt: how the `branch.<name>.remote` and `branch.<name>.\n     +\tTHIS OPTION IS EXPERIMENTAL! Causes the current command to\n     +\trecurse into submodules if `submodule.propagateBranches` is\n     +\tenabled. See `submodule.propagateBranches` in\n    -+\tlinkgit:git-config[1].\n    -+\t+\n    -+\tCurrently, only branch creation is supported.\n    ++\tlinkgit:git-config[1]. Currently, only branch creation is\n    ++\tsupported.\n    +++\n    ++When used in branch creation, a new branch <branchname> will be created\n    ++in the superproject and all of the submodules in the superproject's\n    ++<start-point>. In submodules, the branch will point to the submodule\n    ++commit in the superproject's <start-point> but the branch's tracking\n    ++information will be set up based on the submodule's branches and remotes\n    ++e.g. `git branch --recurse-submodules topic origin/main` will create the\n    ++submodule branch \"topic\" that points to the submodule commit in the\n    ++superproject's \"origin/main\", but tracks the submodule's \"origin/main\".\n     +\n      --set-upstream::\n      \tAs this option had confusing syntax, it is no longer supported.\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     +static int submodule_create_branch(struct repository *r,\n     +\t\t\t\t   const struct submodule *submodule,\n     +\t\t\t\t   const char *name, const char *start_oid,\n    -+\t\t\t\t   const char *start_name, int force,\n    ++\t\t\t\t   const char *tracking_name, int force,\n     +\t\t\t\t   int reflog, int quiet,\n     +\t\t\t\t   enum branch_track track, int dry_run)\n     +{\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     +\t/*\n     +\t * submodule_create_branch() is indirectly invoked by \"git\n     +\t * branch\", but we cannot invoke \"git branch\" in the child\n    -+\t * process because it does not let us set start_name and\n    -+\t * start_oid separately (see create_branches_recursively()).\n    ++\t * process. \"git branch\" accepts a branch name and start point,\n    ++\t * where the start point is assumed to provide both the OID\n    ++\t * (start_oid) and the branch to use for tracking\n    ++\t * (tracking_name). But when recursing through submodules,\n    ++\t * start_oid and tracking name need to be specified separately\n    ++\t * (see create_branches_recursively()).\n     +\t */\n     +\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n     +\tif (dry_run)\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     +\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n     +\t\tstrvec_push(&child.args, \"--track\");\n     +\n    -+\tstrvec_pushl(&child.args, name, start_oid, start_name, NULL);\n    ++\tstrvec_pushl(&child.args, name, start_oid, tracking_name, NULL);\n     +\n     +\tif ((ret = start_command(&child)))\n     +\t\treturn ret;\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     +}\n     +\n     +void create_branches_recursively(struct repository *r, const char *name,\n    -+\t\t\t\t const char *start_name,\n    ++\t\t\t\t const char *start_commitish,\n     +\t\t\t\t const char *tracking_name, int force,\n     +\t\t\t\t int reflog, int quiet, enum branch_track track,\n     +\t\t\t\t int dry_run)\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     +\tstruct object_id super_oid;\n     +\tstruct submodule_entry_list submodule_entry_list;\n     +\n    -+\t/* Perform dwim on start_name to get super_oid and branch_point. */\n    -+\tdwim_branch_start(r, start_name, BRANCH_TRACK_NEVER, &branch_point,\n    -+\t\t\t  &super_oid);\n    ++\t/* Perform dwim on start_commitish to get super_oid and branch_point. */\n    ++\tdwim_branch_start(r, start_commitish, BRANCH_TRACK_NEVER,\n    ++\t\t\t  &branch_point, &super_oid);\n     +\n     +\t/*\n     +\t * If we were not given an explicit name to track, then assume we are at\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     +\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n     +\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n     +\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n    -+\t\t\t\t       start_name);\n    ++\t\t\t\t       start_commitish);\n     +\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n     +\t\t\t    submodule_entry_list.entries[i].submodule->name);\n     +\t\t}\n    @@ branch.c: void dwim_and_setup_tracking(struct repository *r, const char *new_ref\n     +\t\t\t    name);\n     +\t}\n     +\n    -+\tcreate_branch(the_repository, name, start_name, force, 0, reflog, quiet,\n    ++\tcreate_branch(the_repository, name, start_commitish, force, 0, reflog, quiet,\n     +\t\t      BRANCH_TRACK_NEVER, dry_run);\n     +\tif (dry_run)\n     +\t\treturn;\n    @@ branch.h: void create_branch(struct repository *r,\n      \t\t   int dry_run);\n      \n     +/*\n    -+ * Creates a new branch in repository and its submodules (and its\n    -+ * submodules, recursively). Besides these exceptions, the parameters\n    -+ * function identically to create_branch():\n    ++ * Creates a new branch in a repository and its submodules (and its\n    ++ * submodules, recursively). The parameters are mostly analogous to\n    ++ * those of create_branch() except for start_name, which is represented\n    ++ * by two different parameters:\n     + *\n    -+ * - start_name is the name of the ref, in repository r, that the new\n    -+ *   branch should start from. In submodules, branches will start from\n    -+ *   the respective gitlink commit ids in start_name's tree.\n    ++ * - start_commitish is the commit-ish, in repository r, that determines\n    ++ *   which commits the branches will point to. The superproject branch\n    ++ *   will point to the commit of start_commitish and the submodule\n    ++ *   branches will point to the gitlink commit oids in start_commitish's\n    ++ *   tree.\n     + *\n    -+ * - tracking_name is the name used of the ref that will be used to set\n    -+ *   up tracking, e.g. origin/main. This is propagated to submodules so\n    -+ *   that tracking information will appear as if the branch branched off\n    -+ *   tracking_name instead of start_name (which is a plain commit id for\n    -+ *   submodules). If omitted, start_name is used for tracking (just like\n    -+ *   create_branch()).\n    ++ * - tracking_name is the name of the ref, in repository r, that will be\n    ++ *   used to set up tracking information. This value is propagated to\n    ++ *   all submodules, which will evaluate the ref using their own ref\n    ++ *   stores. If NULL, this defaults to start_commitish.\n     + *\n    ++ * When this function is called on the superproject, start_commitish\n    ++ * can be any user-provided ref and tracking_name can be NULL (similar\n    ++ * to create_branches()). But when recursing through submodules,\n    ++ * start_commitish is the plain gitlink commit oid. Since the oid cannot\n    ++ * be used for tracking information, tracking_name is propagated and\n    ++ * used for tracking instead.\n     + */\n     +void create_branches_recursively(struct repository *r, const char *name,\n    -+\t\t\t\t const char *start_name,\n    ++\t\t\t\t const char *start_commitish,\n     +\t\t\t\t const char *tracking_name, int force,\n     +\t\t\t\t int reflog, int quiet, enum branch_track track,\n     +\t\t\t\t int dry_run);\n    @@ t/t3207-branch-submodule.sh (new)\n     +\tcp -r test_dirs/* .\n     +}\n     +\n    ++# Tests that the expected branch does not exist\n    ++test_no_branch () {\n    ++\tDIR=$1 &&\n    ++\tBRANCH_NAME=$2 &&\n    ++\ttest_must_fail git -C \"$DIR\" rev-parse \"$BRANCH_NAME\" 2>err &&\n    ++\tgrep \"ambiguous argument .$BRANCH_NAME.\" err\n    ++}\n    ++\n     +test_expect_success 'setup superproject and submodule' '\n     +\tmkdir test_dirs &&\n     +\t(\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t(\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a &&\n    -+\t\ttest_must_fail git branch --recurse-submodules -D branch-a &&\n    ++\t\techo \"fatal: --recurse-submodules can only be used to create branches\" >expected &&\n    ++\t\ttest_must_fail git branch --recurse-submodules -D branch-a 2>actual &&\n    ++\t\ttest_cmp expected actual &&\n     +\t\t# Assert that the branches were not deleted\n     +\t\tgit rev-parse branch-a &&\n     +\t\tgit -C sub rev-parse branch-a\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a &&\n     +\t\tgit -c submodule.recurse=true branch -D branch-a &&\n    -+\t\ttest_must_fail git rev-parse branch-a &&\n    ++\t\ttest_no_branch . branch-a &&\n     +\t\tgit -C sub rev-parse branch-a\n     +\t)\n     +'\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tcd super &&\n     +\t\tgit -C sub branch branch-a &&\n     +\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n    -+\t\ttest_must_fail git rev-parse branch-a &&\n    ++\t\ttest_no_branch . branch-a &&\n     +\t\tgrep \"submodule .sub.: fatal: A branch named .branch-a. already exists\" actual\n     +\t)\n     +'\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a &&\n     +\t\tgit rev-parse branch-a &&\n    -+\t\ttest_must_fail git -C sub branch-a\n    ++\t\ttest_no_branch sub branch-a\n     +\t)\n     +'\n     +\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tcd super &&\n     +\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n     +\t\tgit -C sub rev-parse main &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n    ++\t\ttest_cmp_config -C sub . branch.branch-a.remote &&\n    ++\t\ttest_cmp_config -C sub refs/heads/main branch.branch-a.merge\n     +\t)\n     +'\n     +\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tcd super &&\n     +\t\tgit branch --track --recurse-submodules branch-a main &&\n     +\t\tgit -C sub rev-parse main &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = . &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/main\n    ++\t\ttest_cmp_config -C sub . branch.branch-a.remote &&\n    ++\t\ttest_cmp_config -C sub refs/heads/main branch.branch-a.merge\n     +\t)\n     +'\n     +\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tcd super &&\n     +\t\tgit branch --recurse-submodules branch-a main &&\n     +\t\tgit -C sub rev-parse main &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n    ++\t\ttest_cmp_config -C sub \"\" --default \"\" branch.branch-a.remote &&\n    ++\t\ttest_cmp_config -C sub \"\" --default \"\" branch.branch-a.merge\n     +\t)\n     +'\n     +\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\t# This should fail because super-clone does not have sub2 .git/modules\n     +\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n     +\t\tgrep \"fatal: submodule .sub2.: unable to find submodule\" actual &&\n    -+\t\ttest_must_fail git rev-parse branch-b &&\n    -+\t\ttest_must_fail git -C sub rev-parse branch-b &&\n    ++\t\ttest_no_branch . branch-b &&\n    ++\t\ttest_no_branch sub branch-b &&\n     +\t\t# User can fix themselves by initializing the submodule\n     +\t\tgit checkout origin/branch-b &&\n     +\t\tgit submodule update --init --recursive &&\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t(\n     +\t\tcd super-clone &&\n     +\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n    -+\t\ttest \"$(git config branch.branch-a.remote)\" = origin &&\n    -+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n    ++\t\ttest_cmp_config origin branch.branch-a.remote &&\n    ++\t\ttest_cmp_config refs/heads/branch-a branch.branch-a.merge &&\n     +\t\t# \"origin/branch-a\" does not exist for \"sub\", but it matches the refspec\n     +\t\t# so tracking should be set up\n    -+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = origin &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = refs/heads/branch-a &&\n    -+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.remote)\" = origin &&\n    -+\t\ttest \"$(git -C sub/sub-sub config branch.branch-a.merge)\" = refs/heads/branch-a\n    ++\t\ttest_cmp_config -C sub origin branch.branch-a.remote &&\n    ++\t\ttest_cmp_config -C sub refs/heads/branch-a branch.branch-a.merge &&\n    ++\t\ttest_cmp_config -C sub/sub-sub origin branch.branch-a.remote &&\n    ++\t\ttest_cmp_config -C sub/sub-sub refs/heads/branch-a branch.branch-a.merge\n     +\t)\n     +'\n     +\n    @@ t/t3207-branch-submodule.sh (new)\n     +\t\tcd super-clone &&\n     +\t\tgit remote rename origin ex-origin &&\n     +\t\tgit branch --recurse-submodules branch-a ex-origin/branch-a &&\n    -+\t\ttest \"$(git config branch.branch-a.remote)\" = ex-origin &&\n    -+\t\ttest \"$(git config branch.branch-a.merge)\" = refs/heads/branch-a &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.remote)\" = \"\" &&\n    -+\t\ttest \"$(git -C sub config branch.branch-a.merge)\" = \"\"\n    ++\t\ttest_cmp_config ex-origin branch.branch-a.remote &&\n    ++\t\ttest_cmp_config refs/heads/branch-a branch.branch-a.merge &&\n    ++\t\ttest_cmp_config -C sub \"\" --default \"\" branch.branch-a.remote &&\n    ++\t\ttest_cmp_config -C sub \"\" --default \"\" branch.branch-a.merge\n     +\t)\n     +'\n     +\n6:  a4634f0493 ! 6:  bdf7be52be branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks\n    @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n      \t\t\t\ttracking.remote, tracking.srcs) < 0)\n      \t\texit(-1);\n      \n    +-\tstring_list_clear(tracking.srcs, 0);\n     +cleanup:\n    - \tstring_list_clear(tracking.srcs, 0);\n    ++\tstring_list_clear(&tracking_srcs, 0);\n      }\n      \n    + int read_branch_desc(struct strbuf *buf, const char *branch_name)\n\nbase-commit: 6327f0efed36c64d98a140110171362b7cb75a52\n-- \n2.33.GIT\n\n"},{"id":"447250","messageId":"20220129000446.99261-2-chooglen@google.com","threadId":"56962","inReplyTo":"20220129000446.99261-1-chooglen@google.com","subject":"[PATCH v8 1/6] branch: move --set-upstream-to behavior to dwim_and_setup_tracking()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-29T00:04:41Z","receivedAt":"2022-01-29T00:05:05Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"This commit is preparation for a future commit that will simplify\ncreate_branch() so that it always creates a branch. This will allow\ncreate_branch() to accept a dry_run parameter (which is needed for \"git\nbranch --recurse-submodules\").\n\ncreate_branch() used to always create a branch, but 4fc5006676 (Add\nbranch --set-upstream, 2010-01-18) changed it to also be able to set\ntracking information without creating a branch.\n\nRefactor the code that sets tracking information into its own functions\ndwim_branch_start() and dwim_and_setup_tracking(). Also change an\ninvocation of create_branch() in cmd_branch() in builtin/branch.c to use\ndwim_and_setup_tracking(), since that invocation is only for setting\ntracking information (in \"git branch --set-upstream-to\").\n\nAs of this commit, create_branch() is no longer invoked in a way that\ndoes not create branches.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c         | 87 +++++++++++++++++++++++++++++++++++++-----------\n branch.h         | 22 ++++++++++++\n builtin/branch.c |  9 ++---\n 3 files changed, 92 insertions(+), 26 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex a4e4631ef1..f3a31930fb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -218,9 +218,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n }\n \n /*\n- * This is called when new_ref is branched off of orig_ref, and tries\n- * to infer the settings for branch.<new_ref>.{remote,merge} from the\n- * config.\n+ * Used internally to set the branch.<new_ref>.{remote,merge} config\n+ * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike\n+ * dwim_and_setup_tracking(), this does not do DWIM, i.e. \"origin/main\"\n+ * will not be expanded to \"refs/remotes/origin/main\", so it is not safe\n+ * for 'orig_ref' to be raw user input.\n  */\n static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t   enum branch_track track, int quiet)\n@@ -341,31 +343,37 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n-void create_branch(struct repository *r,\n-\t\t   const char *name, const char *start_name,\n-\t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+/**\n+ * DWIMs a user-provided ref to determine the starting point for a\n+ * branch and validates it, where:\n+ *\n+ *   - r is the repository to validate the branch for\n+ *\n+ *   - start_name is the ref that we would like to test. This is\n+ *     expanded with DWIM and assigned to out_real_ref.\n+ *\n+ *   - track is the tracking mode of the new branch. If tracking is\n+ *     explicitly requested, start_name must be a branch (because\n+ *     otherwise start_name cannot be tracked)\n+ *\n+ *   - out_oid is an out parameter containing the object_id of start_name\n+ *\n+ *   - out_real_ref is an out parameter containing the full, 'real' form\n+ *     of start_name e.g. refs/heads/main instead of main\n+ *\n+ */\n+static void dwim_branch_start(struct repository *r, const char *start_name,\n+\t\t\t   enum branch_track track, char **out_real_ref,\n+\t\t\t   struct object_id *out_oid)\n {\n \tstruct commit *commit;\n \tstruct object_id oid;\n \tchar *real_ref;\n-\tstruct strbuf ref = STRBUF_INIT;\n-\tint forcing = 0;\n-\tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n \n \tif (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)\n \t\texplicit_tracking = 1;\n \n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n-\t}\n-\n \treal_ref = NULL;\n \tif (get_oid_mb(start_name, &oid)) {\n \t\tif (explicit_tracking) {\n@@ -402,7 +410,37 @@ void create_branch(struct repository *r,\n \n \tif ((commit = lookup_commit_reference(r, &oid)) == NULL)\n \t\tdie(_(\"Not a valid branch point: '%s'.\"), start_name);\n-\toidcpy(&oid, &commit->object.oid);\n+\tif (out_real_ref) {\n+\t\t*out_real_ref = real_ref;\n+\t\treal_ref = NULL;\n+\t}\n+\tif (out_oid)\n+\t\toidcpy(out_oid, &commit->object.oid);\n+\n+\tFREE_AND_NULL(real_ref);\n+}\n+\n+void create_branch(struct repository *r,\n+\t\t   const char *name, const char *start_name,\n+\t\t   int force, int clobber_head_ok, int reflog,\n+\t\t   int quiet, enum branch_track track)\n+{\n+\tstruct object_id oid;\n+\tchar *real_ref;\n+\tstruct strbuf ref = STRBUF_INIT;\n+\tint forcing = 0;\n+\tint dont_change_ref = 0;\n+\n+\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n+\t    ? validate_branchname(name, &ref)\n+\t    : validate_new_branchname(name, &ref, force)) {\n+\t\tif (!force)\n+\t\t\tdont_change_ref = 1;\n+\t\telse\n+\t\t\tforcing = 1;\n+\t}\n+\n+\tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -436,6 +474,15 @@ void create_branch(struct repository *r,\n \tfree(real_ref);\n }\n \n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet)\n+{\n+\tchar *real_orig_ref;\n+\tdwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL);\n+\tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 815dcd40c0..ab2315c611 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -18,6 +18,28 @@ extern enum branch_track git_branch_track;\n \n /* Functions for acting on the information about branches. */\n \n+/**\n+ * Sets branch.<new_ref>.{remote,merge} config settings such that\n+ * new_ref tracks orig_ref according to the specified tracking mode.\n+ *\n+ *   - new_ref is the name of the branch that we are setting tracking\n+ *     for.\n+ *\n+ *   - orig_ref is the name of the ref that is 'upstream' of new_ref.\n+ *     orig_ref will be expanded with DWIM so that the config settings\n+ *     are in the correct format e.g. \"refs/remotes/origin/main\" instead\n+ *     of \"origin/main\".\n+ *\n+ *   - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes\n+ *     new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT\n+ *     causes new_ref to track whatever orig_ref tracks.\n+ *\n+ *   - quiet suppresses tracking information.\n+ */\n+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n+\t\t\t     const char *orig_ref, enum branch_track track,\n+\t\t\t     int quiet);\n+\n /*\n  * Creates a new branch, where:\n  *\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex c5de1207e4..62b1a70b05 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -822,12 +822,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (!ref_exists(branch->refname))\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \n-\t\t/*\n-\t\t * create_branch takes care of setting up the tracking\n-\t\t * info and making sure new_upstream is correct\n-\t\t */\n-\t\tcreate_branch(the_repository, branch->name, new_upstream,\n-\t\t\t      0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);\n+\t\tdwim_and_setup_tracking(the_repository, branch->name,\n+\t\t\t\t\tnew_upstream, BRANCH_TRACK_OVERRIDE,\n+\t\t\t\t\tquiet);\n \t} else if (unset_upstream) {\n \t\tstruct branch *branch = branch_get(argv[0]);\n \t\tstruct strbuf buf = STRBUF_INIT;\n-- \n2.33.GIT\n\n"},{"id":"447251","messageId":"20220129000446.99261-3-chooglen@google.com","threadId":"56962","inReplyTo":"20220129000446.99261-1-chooglen@google.com","subject":"[PATCH v8 2/6] branch: make create_branch() always create a branch","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-29T00:04:42Z","receivedAt":"2022-01-29T00:05:10Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"With the previous commit, there are no more invocations of\ncreate_branch() that do not create a branch because:\n\n* BRANCH_TRACK_OVERRIDE is no longer passed\n* clobber_head_ok = true and force = false is never passed\n\nAssert these situations, delete dead code and ensure that we're handling\nclobber_head_ok and force correctly by introducing tests for `git branch\n--force`. As a result, create_branch() now always creates a branch.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c          | 57 ++++++++++++++++++++++-------------------------\n branch.h          |  4 ++--\n t/t3200-branch.sh | 17 ++++++++++++++\n 3 files changed, 46 insertions(+), 32 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex f3a31930fb..df24021f27 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -429,15 +429,19 @@ void create_branch(struct repository *r,\n \tchar *real_ref;\n \tstruct strbuf ref = STRBUF_INIT;\n \tint forcing = 0;\n-\tint dont_change_ref = 0;\n-\n-\tif ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)\n-\t    ? validate_branchname(name, &ref)\n-\t    : validate_new_branchname(name, &ref, force)) {\n-\t\tif (!force)\n-\t\t\tdont_change_ref = 1;\n-\t\telse\n-\t\t\tforcing = 1;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *msg;\n+\n+\tif (track == BRANCH_TRACK_OVERRIDE)\n+\t\tBUG(\"'track' cannot be BRANCH_TRACK_OVERRIDE. Did you mean to call dwim_and_setup_tracking()?\");\n+\tif (clobber_head_ok && !force)\n+\t\tBUG(\"'clobber_head_ok' can only be used with 'force'\");\n+\n+\tif (clobber_head_ok ?\n+\t\t\t  validate_branchname(name, &ref) :\n+\t\t\t  validate_new_branchname(name, &ref, force)) {\n+\t\tforcing = 1;\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n@@ -445,27 +449,20 @@ void create_branch(struct repository *r,\n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n-\tif (!dont_change_ref) {\n-\t\tstruct ref_transaction *transaction;\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\t\tchar *msg;\n-\n-\t\tif (forcing)\n-\t\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n-\t\telse\n-\t\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n-\n-\t\ttransaction = ref_transaction_begin(&err);\n-\t\tif (!transaction ||\n-\t\t    ref_transaction_update(transaction, ref.buf,\n-\t\t\t\t\t   &oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t   0, msg, &err) ||\n-\t\t    ref_transaction_commit(transaction, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\t\tref_transaction_free(transaction);\n-\t\tstrbuf_release(&err);\n-\t\tfree(msg);\n-\t}\n+\tif (forcing)\n+\t\tmsg = xstrfmt(\"branch: Reset to %s\", start_name);\n+\telse\n+\t\tmsg = xstrfmt(\"branch: Created from %s\", start_name);\n+\ttransaction = ref_transaction_begin(&err);\n+\tif (!transaction ||\n+\t\tref_transaction_update(transaction, ref.buf,\n+\t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n+\t\t\t\t\t0, msg, &err) ||\n+\t\tref_transaction_commit(transaction, &err))\n+\t\tdie(\"%s\", err.buf);\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n+\tfree(msg);\n \n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\ndiff --git a/branch.h b/branch.h\nindex ab2315c611..cf3a4d3ff3 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -52,8 +52,8 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *\n  *   - force enables overwriting an existing (non-head) branch\n  *\n- *   - clobber_head_ok allows the currently checked out (hence existing)\n- *     branch to be overwritten; without 'force', it has no effect.\n+ *   - clobber_head_ok, when enabled with 'force', allows the currently\n+ *     checked out (head) branch to be overwritten\n  *\n  *   - reflog creates a reflog for the branch\n  *\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex a74b2e06a1..9157382ad0 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '\n \tgit branch abc && test_path_is_file .git/refs/heads/abc\n '\n \n+test_expect_success 'git branch abc should fail when abc exists' '\n+\ttest_must_fail git branch abc\n+'\n+\n+test_expect_success 'git branch --force abc should fail when abc is checked out' '\n+\ttest_when_finished git switch main &&\n+\tgit switch abc &&\n+\ttest_must_fail git branch --force abc HEAD~1\n+'\n+\n+test_expect_success 'git branch --force abc should succeed when abc exists' '\n+\tgit rev-parse HEAD~1 >expect &&\n+\tgit branch --force abc HEAD~1 &&\n+\tgit rev-parse abc >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n '\n-- \n2.33.GIT\n\n"},{"id":"447252","messageId":"20220129000446.99261-4-chooglen@google.com","threadId":"56962","inReplyTo":"20220129000446.99261-1-chooglen@google.com","subject":"[PATCH v8 3/6] branch: add a dry_run parameter to create_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-29T00:04:43Z","receivedAt":"2022-01-29T00:05:12Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Add a dry_run parameter to create_branch() such that dry_run = 1 will\nvalidate a new branch without trying to create it. This will be used in\n`git branch --recurse-submodules` to ensure that the new branch can be\ncreated in all submodules.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c           | 5 ++++-\n branch.h           | 5 ++++-\n builtin/branch.c   | 2 +-\n builtin/checkout.c | 3 ++-\n 4 files changed, 11 insertions(+), 4 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex df24021f27..02d46a69b8 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -423,7 +423,7 @@ static void dwim_branch_start(struct repository *r, const char *start_name,\n void create_branch(struct repository *r,\n \t\t   const char *name, const char *start_name,\n \t\t   int force, int clobber_head_ok, int reflog,\n-\t\t   int quiet, enum branch_track track)\n+\t\t   int quiet, enum branch_track track, int dry_run)\n {\n \tstruct object_id oid;\n \tchar *real_ref;\n@@ -445,6 +445,8 @@ void create_branch(struct repository *r,\n \t}\n \n \tdwim_branch_start(r, start_name, track, &real_ref, &oid);\n+\tif (dry_run)\n+\t\tgoto cleanup;\n \n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n@@ -467,6 +469,7 @@ void create_branch(struct repository *r,\n \tif (real_ref && track)\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n \n+cleanup:\n \tstrbuf_release(&ref);\n \tfree(real_ref);\n }\ndiff --git a/branch.h b/branch.h\nindex cf3a4d3ff3..509cfcc34e 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -62,11 +62,14 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n  *   - track causes the new branch to be configured to merge the remote branch\n  *     that start_name is a tracking branch for (if any).\n  *\n+ *   - dry_run causes the branch to be validated but not created.\n+ *\n  */\n void create_branch(struct repository *r,\n \t\t   const char *name, const char *start_name,\n \t\t   int force, int clobber_head_ok,\n-\t\t   int reflog, int quiet, enum branch_track track);\n+\t\t   int reflog, int quiet, enum branch_track track,\n+\t\t   int dry_run);\n \n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 62b1a70b05..c6fffb99ab 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -858,7 +858,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \t\tcreate_branch(the_repository,\n \t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track);\n+\t\t\t      force, 0, reflog, quiet, track, 0);\n \n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 1809ac12df..8600860629 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -893,7 +893,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t      opts->new_branch_force ? 1 : 0,\n \t\t\t\t      opts->new_branch_log,\n \t\t\t\t      opts->quiet,\n-\t\t\t\t      opts->track);\n+\t\t\t\t      opts->track,\n+\t\t\t\t      0);\n \t\tnew_branch_info->name = opts->new_branch;\n \t\tsetup_branch_path(new_branch_info);\n \t}\n-- \n2.33.GIT\n\n"},{"id":"447253","messageId":"20220129000446.99261-5-chooglen@google.com","threadId":"56962","inReplyTo":"20220129000446.99261-1-chooglen@google.com","subject":"[PATCH v8 4/6] builtin/branch: consolidate action-picking logic in cmd_branch()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-29T00:04:44Z","receivedAt":"2022-01-29T00:05:14Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Consolidate the logic for deciding when to create a new branch in\ncmd_branch(), and save the result for reuse. Besides making the function\nmore explicit, this allows us to validate options that can only be used\nwhen creating a branch. Such an option does not exist yet, but one will\nbe introduced in a subsequent commit.\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 19 +++++++++++--------\n 1 file changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex c6fffb99ab..b352cc789a 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -616,14 +616,15 @@ static int edit_branch_description(const char *branch_name)\n \n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n-\tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\n-\tint show_current = 0;\n-\tint reflog = 0, edit_description = 0;\n-\tint quiet = 0, unset_upstream = 0;\n+\t/* possible actions */\n+\tint delete = 0, rename = 0, copy = 0, list = 0,\n+\t    unset_upstream = 0, show_current = 0, edit_description = 0;\n \tconst char *new_upstream = NULL;\n+\tint noncreate_actions = 0;\n+\t/* possible options */\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n-\tint icase = 0;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n \tstruct ref_format format = REF_FORMAT_INIT;\n \n@@ -707,8 +708,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)\n \t\tlist = 1;\n \n-\tif (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +\n-\t    list + edit_description + unset_upstream > 1)\n+\tnoncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +\n+\t\t\t    !!show_current + !!list + !!edit_description +\n+\t\t\t    !!unset_upstream;\n+\tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (filter.abbrev == -1)\n@@ -848,7 +851,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_addf(&buf, \"branch.%s.merge\", branch->name);\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n-\t} else if (argc > 0 && argc <= 2) {\n+\t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n-- \n2.33.GIT\n\n"},{"id":"447255","messageId":"20220129000446.99261-6-chooglen@google.com","threadId":"56962","inReplyTo":"20220129000446.99261-1-chooglen@google.com","subject":"[PATCH v8 5/6] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-29T00:04:45Z","receivedAt":"2022-01-29T00:05:15Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"To improve the submodules UX, we would like to teach Git to handle\nbranches in submodules. Start this process by teaching \"git branch\" the\n--recurse-submodules option so that \"git branch --recurse-submodules\ntopic\" will create the `topic` branch in the superproject and its\nsubmodules.\n\nAlthough this commit does not introduce breaking changes, it does not\nwork well with existing --recurse-submodules commands because \"git\nbranch --recurse-submodules\" writes to the submodule ref store, but most\ncommands only consider the superproject gitlink and ignore the submodule\nref store. For example, \"git checkout --recurse-submodules\" will check\nout the commits in the superproject gitlinks (and put the submodules in\ndetached HEAD) instead of checking out the submodule branches.\n\nBecause of this, this commit introduces a new configuration value,\n`submodule.propagateBranches`. The plan is for Git commands to\nprioritize submodule ref store information over superproject gitlinks if\nthis value is true. Because \"git branch --recurse-submodules\" writes to\nsubmodule ref stores, for the sake of clarity, it will not function\nunless this configuration value is set.\n\nThis commit also includes changes that support working with submodules\nfrom a superproject commit because \"branch --recurse-submodules\" (and\nfuture commands) need to read .gitmodules and gitlinks from the\nsuperproject commit, but submodules are typically read from the\nfilesystem's .gitmodules and the index's gitlinks. These changes are:\n\n* add a submodules_of_tree() helper that gives the relevant\n  information of an in-tree submodule (e.g. path and oid) and\n  initializes the repository\n* add is_tree_submodule_active() by adding a treeish_name parameter to\n  is_submodule_active()\n* add the \"submoduleNotUpdated\" advice to advise users to update the\n  submodules in their trees\n\nIncidentally, fix an incorrect usage string that combined the 'list'\nusage of git branch (-l) with the 'create' usage; this string has been\nincorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use\nparse_options., 2007-10-07).\n\nHelped-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/config/advice.txt    |   3 +\n Documentation/config/submodule.txt |  37 ++--\n Documentation/git-branch.txt       |  19 +-\n advice.c                           |   1 +\n advice.h                           |   1 +\n branch.c                           | 141 ++++++++++++++\n branch.h                           |  29 +++\n builtin/branch.c                   |  44 ++++-\n builtin/submodule--helper.c        |  38 ++++\n submodule-config.c                 |  60 ++++++\n submodule-config.h                 |  34 ++++\n submodule.c                        |  11 +-\n submodule.h                        |   3 +\n t/t3207-branch-submodule.sh        | 292 +++++++++++++++++++++++++++++\n 14 files changed, 693 insertions(+), 20 deletions(-)\n create mode 100755 t/t3207-branch-submodule.sh\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex 063eec2511..adee26fbbb 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -116,6 +116,9 @@ advice.*::\n \tsubmoduleAlternateErrorStrategyDie::\n \t\tAdvice shown when a submodule.alternateErrorStrategy option\n \t\tconfigured to \"die\" causes a fatal error.\n+\tsubmodulesNotUpdated::\n+\t\tAdvice shown when a user runs a submodule command that fails\n+\t\tbecause `git submodule update --init` was not run.\n \taddIgnoredFile::\n \t\tAdvice shown if a user attempts to add an ignored file to\n \t\tthe index.\ndiff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt\nindex ee454f8126..6490527b45 100644\n--- a/Documentation/config/submodule.txt\n+++ b/Documentation/config/submodule.txt\n@@ -59,18 +59,33 @@ submodule.active::\n \n submodule.recurse::\n \tA boolean indicating if commands should enable the `--recurse-submodules`\n-\toption by default.\n-\tApplies to all commands that support this option\n-\t(`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,\n-\t`restore` and `switch`) except `clone` and `ls-files`.\n+\toption by default. Defaults to false.\n++\n+When set to true, it can be deactivated via the\n+`--no-recurse-submodules` option. Note that some Git commands\n+lacking this option may call some of the above commands affected by\n+`submodule.recurse`; for instance `git remote update` will call\n+`git fetch` but does not have a `--no-recurse-submodules` option.\n+For these commands a workaround is to temporarily change the\n+configuration value by using `git -c submodule.recurse=0`.\n++\n+The following list shows the commands that accept\n+`--recurse-submodules` and whether they are supported by this\n+setting.\n+\n+* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,\n+`reset`, `restore` and `switch` are always supported.\n+* `clone` and `ls-files` are not supported.\n+* `branch` is supported only if `submodule.propagateBranches` is\n+enabled\n+\n+submodule.propagateBranches::\n+\t[EXPERIMENTAL] A boolean that enables branching support when\n+\tusing `--recurse-submodules` or `submodule.recurse=true`.\n+\tEnabling this will allow certain commands to accept\n+\t`--recurse-submodules` and certain commands that already accept\n+\t`--recurse-submodules` will now consider branches.\n \tDefaults to false.\n-\tWhen set to true, it can be deactivated via the\n-\t`--no-recurse-submodules` option. Note that some Git commands\n-\tlacking this option may call some of the above commands affected by\n-\t`submodule.recurse`; for instance `git remote update` will call\n-\t`git fetch` but does not have a `--no-recurse-submodules` option.\n-\tFor these commands a workaround is to temporarily change the\n-\tconfiguration value by using `git -c submodule.recurse=0`.\n \n submodule.fetchJobs::\n \tSpecifies how many submodules are fetched/cloned at the same time.\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex c59687a4d9..f526573951 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,8 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track[=(direct|inherit)] | --no-track] [-f]\n+\t[--recurse-submodules] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -235,6 +236,22 @@ how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n \tDo not set up \"upstream\" configuration, even if the\n \tbranch.autoSetupMerge configuration variable is set.\n \n+--recurse-submodules::\n+\tTHIS OPTION IS EXPERIMENTAL! Causes the current command to\n+\trecurse into submodules if `submodule.propagateBranches` is\n+\tenabled. See `submodule.propagateBranches` in\n+\tlinkgit:git-config[1]. Currently, only branch creation is\n+\tsupported.\n++\n+When used in branch creation, a new branch <branchname> will be created\n+in the superproject and all of the submodules in the superproject's\n+<start-point>. In submodules, the branch will point to the submodule\n+commit in the superproject's <start-point> but the branch's tracking\n+information will be set up based on the submodule's branches and remotes\n+e.g. `git branch --recurse-submodules topic origin/main` will create the\n+submodule branch \"topic\" that points to the submodule commit in the\n+superproject's \"origin/main\", but tracks the submodule's \"origin/main\".\n+\n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\n \tPlease use `--track` or `--set-upstream-to` instead.\ndiff --git a/advice.c b/advice.c\nindex 1dfc91d176..e00d30254c 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -70,6 +70,7 @@ static struct {\n \t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n \t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n \t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n+\t[ADVICE_SUBMODULES_NOT_UPDATED] \t\t= { \"submodulesNotUpdated\", 1 },\n \t[ADVICE_UPDATE_SPARSE_PATH]\t\t\t= { \"updateSparsePath\", 1 },\n \t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n };\ndiff --git a/advice.h b/advice.h\nindex 601265fd10..a7521d6087 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -44,6 +44,7 @@ struct string_list;\n \tADVICE_STATUS_HINTS,\n \tADVICE_STATUS_U_OPTION,\n \tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_SUBMODULES_NOT_UPDATED,\n \tADVICE_UPDATE_SPARSE_PATH,\n \tADVICE_WAITING_FOR_EDITOR,\n \tADVICE_SKIPPED_CHERRY_PICKS,\ndiff --git a/branch.c b/branch.c\nindex 02d46a69b8..70026b3c79 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -8,6 +8,8 @@\n #include \"sequencer.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n+#include \"submodule-config.h\"\n+#include \"run-command.h\"\n \n struct tracking {\n \tstruct refspec_item spec;\n@@ -483,6 +485,145 @@ void dwim_and_setup_tracking(struct repository *r, const char *new_ref,\n \tsetup_tracking(new_ref, real_orig_ref, track, quiet);\n }\n \n+/**\n+ * Creates a branch in a submodule by calling\n+ * create_branches_recursively() in a child process. The child process\n+ * is necessary because install_branch_config_multiple_remotes() (which\n+ * is called by setup_tracking()) does not support writing configs to\n+ * submodules.\n+ */\n+static int submodule_create_branch(struct repository *r,\n+\t\t\t\t   const struct submodule *submodule,\n+\t\t\t\t   const char *name, const char *start_oid,\n+\t\t\t\t   const char *tracking_name, int force,\n+\t\t\t\t   int reflog, int quiet,\n+\t\t\t\t   enum branch_track track, int dry_run)\n+{\n+\tint ret = 0;\n+\tstruct child_process child = CHILD_PROCESS_INIT;\n+\tstruct strbuf child_err = STRBUF_INIT;\n+\tstruct strbuf out_buf = STRBUF_INIT;\n+\tchar *out_prefix = xstrfmt(\"submodule '%s': \", submodule->name);\n+\tchild.git_cmd = 1;\n+\tchild.err = -1;\n+\tchild.stdout_to_stderr = 1;\n+\n+\tprepare_other_repo_env(&child.env_array, r->gitdir);\n+\t/*\n+\t * submodule_create_branch() is indirectly invoked by \"git\n+\t * branch\", but we cannot invoke \"git branch\" in the child\n+\t * process. \"git branch\" accepts a branch name and start point,\n+\t * where the start point is assumed to provide both the OID\n+\t * (start_oid) and the branch to use for tracking\n+\t * (tracking_name). But when recursing through submodules,\n+\t * start_oid and tracking name need to be specified separately\n+\t * (see create_branches_recursively()).\n+\t */\n+\tstrvec_pushl(&child.args, \"submodule--helper\", \"create-branch\", NULL);\n+\tif (dry_run)\n+\t\tstrvec_push(&child.args, \"--dry-run\");\n+\tif (force)\n+\t\tstrvec_push(&child.args, \"--force\");\n+\tif (quiet)\n+\t\tstrvec_push(&child.args, \"--quiet\");\n+\tif (reflog)\n+\t\tstrvec_push(&child.args, \"--create-reflog\");\n+\tif (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)\n+\t\tstrvec_push(&child.args, \"--track\");\n+\n+\tstrvec_pushl(&child.args, name, start_oid, tracking_name, NULL);\n+\n+\tif ((ret = start_command(&child)))\n+\t\treturn ret;\n+\tret = finish_command(&child);\n+\tstrbuf_read(&child_err, child.err, 0);\n+\tstrbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);\n+\n+\tif (ret)\n+\t\tfprintf(stderr, \"%s\", out_buf.buf);\n+\telse\n+\t\tprintf(\"%s\", out_buf.buf);\n+\n+\tstrbuf_release(&child_err);\n+\tstrbuf_release(&out_buf);\n+\treturn ret;\n+}\n+\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_commitish,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run)\n+{\n+\tint i = 0;\n+\tchar *branch_point = NULL;\n+\tstruct object_id super_oid;\n+\tstruct submodule_entry_list submodule_entry_list;\n+\n+\t/* Perform dwim on start_commitish to get super_oid and branch_point. */\n+\tdwim_branch_start(r, start_commitish, BRANCH_TRACK_NEVER,\n+\t\t\t  &branch_point, &super_oid);\n+\n+\t/*\n+\t * If we were not given an explicit name to track, then assume we are at\n+\t * the top level and, just like the non-recursive case, the tracking\n+\t * name is the branch point.\n+\t */\n+\tif (!tracking_name)\n+\t\ttracking_name = branch_point;\n+\n+\tsubmodules_of_tree(r, &super_oid, &submodule_entry_list);\n+\t/*\n+\t * Before creating any branches, first check that the branch can\n+\t * be created in every submodule.\n+\t */\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_entry_list.entries[i].repo == NULL) {\n+\t\t\tif (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))\n+\t\t\t\tadvise(_(\"You may try updating the submodules using 'git checkout %s && git submodule update --init'\"),\n+\t\t\t\t       start_commitish);\n+\t\t\tdie(_(\"submodule '%s': unable to find submodule\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name);\n+\t\t}\n+\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 1))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t}\n+\n+\tcreate_branch(the_repository, name, start_commitish, force, 0, reflog, quiet,\n+\t\t      BRANCH_TRACK_NEVER, dry_run);\n+\tif (dry_run)\n+\t\treturn;\n+\t/*\n+\t * NEEDSWORK If tracking was set up in the superproject but not the\n+\t * submodule, users might expect \"git branch --recurse-submodules\" to\n+\t * fail or give a warning, but this is not yet implemented because it is\n+\t * tedious to determine whether or not tracking was set up in the\n+\t * superproject.\n+\t */\n+\tsetup_tracking(name, tracking_name, track, quiet);\n+\n+\tfor (i = 0; i < submodule_entry_list.entry_nr; i++) {\n+\t\tif (submodule_create_branch(\n+\t\t\t    submodule_entry_list.entries[i].repo,\n+\t\t\t    submodule_entry_list.entries[i].submodule, name,\n+\t\t\t    oid_to_hex(&submodule_entry_list.entries[i]\n+\t\t\t\t\t\t.name_entry->oid),\n+\t\t\t    tracking_name, force, reflog, quiet, track, 0))\n+\t\t\tdie(_(\"submodule '%s': cannot create branch '%s'\"),\n+\t\t\t    submodule_entry_list.entries[i].submodule->name,\n+\t\t\t    name);\n+\t\trepo_clear(submodule_entry_list.entries[i].repo);\n+\t}\n+}\n+\n void remove_merge_branch_state(struct repository *r)\n {\n \tunlink(git_path_merge_head(r));\ndiff --git a/branch.h b/branch.h\nindex 509cfcc34e..04df2aa5b5 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -71,6 +71,35 @@ void create_branch(struct repository *r,\n \t\t   int reflog, int quiet, enum branch_track track,\n \t\t   int dry_run);\n \n+/*\n+ * Creates a new branch in a repository and its submodules (and its\n+ * submodules, recursively). The parameters are mostly analogous to\n+ * those of create_branch() except for start_name, which is represented\n+ * by two different parameters:\n+ *\n+ * - start_commitish is the commit-ish, in repository r, that determines\n+ *   which commits the branches will point to. The superproject branch\n+ *   will point to the commit of start_commitish and the submodule\n+ *   branches will point to the gitlink commit oids in start_commitish's\n+ *   tree.\n+ *\n+ * - tracking_name is the name of the ref, in repository r, that will be\n+ *   used to set up tracking information. This value is propagated to\n+ *   all submodules, which will evaluate the ref using their own ref\n+ *   stores. If NULL, this defaults to start_commitish.\n+ *\n+ * When this function is called on the superproject, start_commitish\n+ * can be any user-provided ref and tracking_name can be NULL (similar\n+ * to create_branches()). But when recursing through submodules,\n+ * start_commitish is the plain gitlink commit oid. Since the oid cannot\n+ * be used for tracking information, tracking_name is propagated and\n+ * used for tracking instead.\n+ */\n+void create_branches_recursively(struct repository *r, const char *name,\n+\t\t\t\t const char *start_commitish,\n+\t\t\t\t const char *tracking_name, int force,\n+\t\t\t\t int reflog, int quiet, enum branch_track track,\n+\t\t\t\t int dry_run);\n /*\n  * Check if 'name' can be a valid name for a branch; die otherwise.\n  * Return 1 if the named branch already exists; return 0 otherwise.\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex b352cc789a..1517452060 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -27,7 +27,8 @@\n \n static const char * const builtin_branch_usage[] = {\n \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n-\tN_(\"git branch [<options>] [-l] [-f] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n \tN_(\"git branch [<options>] (-c | -C) [<old-branch>] <new-branch>\"),\n@@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = {\n \n static const char *head;\n static struct object_id head_oid;\n+static int recurse_submodules = 0;\n+static int submodule_propagate_branches = 0;\n \n static int branch_use_color = -1;\n static char branch_colors[][COLOR_MAXLEN] = {\n@@ -100,6 +103,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\t\treturn config_error_nonbool(var);\n \t\treturn color_parse(value, branch_colors[slot]);\n \t}\n+\tif (!strcmp(var, \"submodule.recurse\")) {\n+\t\trecurse_submodules = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\tif (!strcasecmp(var, \"submodule.propagateBranches\")) {\n+\t\tsubmodule_propagate_branches = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_color_default_config(var, value, cb);\n }\n \n@@ -622,7 +634,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tconst char *new_upstream = NULL;\n \tint noncreate_actions = 0;\n \t/* possible options */\n-\tint reflog = 0, quiet = 0, icase = 0, force = 0;\n+\tint reflog = 0, quiet = 0, icase = 0, force = 0,\n+\t    recurse_submodules_explicit = 0;\n \tenum branch_track track;\n \tstruct ref_filter filter;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n@@ -672,6 +685,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK(0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only branches of the object\"), parse_opt_object_name),\n \t\tOPT_BOOL('i', \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n+\t\tOPT_BOOL(0, \"recurse-submodules\", &recurse_submodules_explicit, N_(\"recurse through submodules\")),\n \t\tOPT_STRING(  0 , \"format\", &format.format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT_END(),\n \t};\n@@ -714,6 +728,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \tif (noncreate_actions > 1)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n+\tif (recurse_submodules_explicit) {\n+\t\tif (!submodule_propagate_branches)\n+\t\t\tdie(_(\"branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\"));\n+\t\tif (noncreate_actions)\n+\t\t\tdie(_(\"--recurse-submodules can only be used to create branches\"));\n+\t}\n+\n+\trecurse_submodules =\n+\t\t(recurse_submodules || recurse_submodules_explicit) &&\n+\t\tsubmodule_propagate_branches;\n+\n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\n \tfilter.ignore_case = icase;\n@@ -852,6 +877,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n \t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n+\t\tconst char *branch_name = argv[0];\n+\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n+\n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"The -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n \t\t\t\t  \"Did you mean to use: -a|-r --list <pattern>?\"));\n@@ -859,10 +887,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tif (track == BRANCH_TRACK_OVERRIDE)\n \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.\"));\n \n-\t\tcreate_branch(the_repository,\n-\t\t\t      argv[0], (argc == 2) ? argv[1] : head,\n-\t\t\t      force, 0, reflog, quiet, track, 0);\n-\n+\t\tif (recurse_submodules) {\n+\t\t\tcreate_branches_recursively(the_repository, branch_name,\n+\t\t\t\t\t\t    start_name, NULL, force,\n+\t\t\t\t\t\t    reflog, quiet, track, 0);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n+\t\t\t      reflog, quiet, track, 0);\n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 6298cbdd4e..eba2a6e9b0 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -20,6 +20,7 @@\n #include \"diff.h\"\n #include \"object-store.h\"\n #include \"advice.h\"\n+#include \"branch.h\"\n \n #define OPT_QUIET (1 << 0)\n #define OPT_CACHED (1 << 1)\n@@ -2983,6 +2984,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)\n \treturn !!ret;\n }\n \n+static int module_create_branch(int argc, const char **argv, const char *prefix)\n+{\n+\tenum branch_track track;\n+\tint quiet = 0, force = 0, reflog = 0, dry_run = 0;\n+\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet, N_(\"print only error messages\")),\n+\t\tOPT__FORCE(&force, N_(\"force creation\"), 0),\n+\t\tOPT_BOOL(0, \"create-reflog\", &reflog,\n+\t\t\t N_(\"create the branch's reflog\")),\n+\t\tOPT_SET_INT('t', \"track\", &track,\n+\t\t\t    N_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\t    BRANCH_TRACK_EXPLICIT),\n+\t\tOPT__DRY_RUN(&dry_run,\n+\t\t\t     N_(\"show whether the branch would be created\")),\n+\t\tOPT_END()\n+\t};\n+\tconst char *const usage[] = {\n+\t\tN_(\"git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>\"),\n+\t\tNULL\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\ttrack = git_branch_track;\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n+\n+\tif (argc != 3)\n+\t\tusage_with_options(usage, options);\n+\n+\tif (!quiet && !dry_run)\n+\t\tprintf_ln(_(\"creating branch '%s'\"), argv[0]);\n+\n+\tcreate_branches_recursively(the_repository, argv[0], argv[1], argv[2],\n+\t\t\t\t    force, reflog, quiet, track, dry_run);\n+\treturn 0;\n+}\n struct add_data {\n \tconst char *prefix;\n \tconst char *branch;\n@@ -3379,6 +3416,7 @@ static struct cmd_struct commands[] = {\n \t{\"config\", module_config, 0},\n \t{\"set-url\", module_set_url, 0},\n \t{\"set-branch\", module_set_branch, 0},\n+\t{\"create-branch\", module_create_branch, 0},\n };\n \n int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\ndiff --git a/submodule-config.c b/submodule-config.c\nindex f95344028b..24b8d1a700 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -7,6 +7,7 @@\n #include \"strbuf.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n+#include \"tree-walk.h\"\n \n /*\n  * submodule cache lookup structure\n@@ -726,6 +727,65 @@ const struct submodule *submodule_from_path(struct repository *r,\n \treturn config_from(r->submodule_cache, treeish_name, path, lookup_path);\n }\n \n+/**\n+ * Used internally by submodules_of_tree(). Recurses into 'treeish_name'\n+ * and appends submodule entries to 'out'. The submodule_cache expects\n+ * a root-level treeish_name and paths, so keep track of these values\n+ * with 'root_tree' and 'prefix'.\n+ */\n+static void traverse_tree_submodules(struct repository *r,\n+\t\t\t\t     const struct object_id *root_tree,\n+\t\t\t\t     char *prefix,\n+\t\t\t\t     const struct object_id *treeish_name,\n+\t\t\t\t     struct submodule_entry_list *out)\n+{\n+\tstruct tree_desc tree;\n+\tstruct submodule_tree_entry *st_entry;\n+\tstruct name_entry *name_entry;\n+\tchar *tree_path = NULL;\n+\n+\tname_entry = xmalloc(sizeof(*name_entry));\n+\n+\tfill_tree_descriptor(r, &tree, treeish_name);\n+\twhile (tree_entry(&tree, name_entry)) {\n+\t\tif (prefix)\n+\t\t\ttree_path =\n+\t\t\t\tmkpathdup(\"%s/%s\", prefix, name_entry->path);\n+\t\telse\n+\t\t\ttree_path = xstrdup(name_entry->path);\n+\n+\t\tif (S_ISGITLINK(name_entry->mode) &&\n+\t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n+\t\t\tst_entry = xmalloc(sizeof(*st_entry));\n+\t\t\tst_entry->name_entry = name_entry;\n+\t\t\tst_entry->submodule =\n+\t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n+\t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n+\t\t\tif (repo_submodule_init(st_entry->repo, r, tree_path,\n+\t\t\t\t\t\troot_tree))\n+\t\t\t\tFREE_AND_NULL(st_entry->repo);\n+\n+\t\t\tALLOC_GROW(out->entries, out->entry_nr + 1,\n+\t\t\t\t   out->entry_alloc);\n+\t\t\tout->entries[out->entry_nr++] = *st_entry;\n+\t\t} else if (S_ISDIR(name_entry->mode))\n+\t\t\ttraverse_tree_submodules(r, root_tree, tree_path,\n+\t\t\t\t\t\t &name_entry->oid, out);\n+\t\tfree(tree_path);\n+\t}\n+}\n+\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *out)\n+{\n+\tCALLOC_ARRAY(out->entries, 0);\n+\tout->entry_nr = 0;\n+\tout->entry_alloc = 0;\n+\n+\ttraverse_tree_submodules(r, treeish_name, NULL, treeish_name, out);\n+}\n+\n void submodule_free(struct repository *r)\n {\n \tif (r->submodule_cache)\ndiff --git a/submodule-config.h b/submodule-config.h\nindex 65875b94ea..fa229a8b97 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -6,6 +6,7 @@\n #include \"hashmap.h\"\n #include \"submodule.h\"\n #include \"strbuf.h\"\n+#include \"tree-walk.h\"\n \n /**\n  * The submodule config cache API allows to read submodule\n@@ -101,4 +102,37 @@ int check_submodule_name(const char *name);\n void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);\n void update_clone_config_from_gitmodules(int *max_jobs);\n \n+/*\n+ * Submodule entry that contains relevant information about a\n+ * submodule in a tree.\n+ */\n+struct submodule_tree_entry {\n+\t/* The submodule's tree entry. */\n+\tstruct name_entry *name_entry;\n+\t/*\n+\t * A struct repository corresponding to the submodule. May be\n+\t * NULL if the submodule has not been updated.\n+\t */\n+\tstruct repository *repo;\n+\t/*\n+\t * A struct submodule containing the submodule config in the\n+\t * tree's .gitmodules.\n+\t */\n+\tconst struct submodule *submodule;\n+};\n+\n+struct submodule_entry_list {\n+\tstruct submodule_tree_entry *entries;\n+\tint entry_nr;\n+\tint entry_alloc;\n+};\n+\n+/**\n+ * Given a treeish, return all submodules in the tree and its subtrees,\n+ * but excluding nested submodules. Callers that require nested\n+ * submodules are expected to recurse into the submodules themselves.\n+ */\n+void submodules_of_tree(struct repository *r,\n+\t\t\tconst struct object_id *treeish_name,\n+\t\t\tstruct submodule_entry_list *ret);\n #endif /* SUBMODULE_CONFIG_H */\ndiff --git a/submodule.c b/submodule.c\nindex f3c99634a9..07ae7c266e 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -265,7 +265,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n  * ie, the config looks like: \"[submodule] active\\n\".\n  * Since that is an invalid pathspec, we should inform the user.\n  */\n-int is_submodule_active(struct repository *repo, const char *path)\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path)\n {\n \tint ret = 0;\n \tchar *key = NULL;\n@@ -273,7 +275,7 @@ int is_submodule_active(struct repository *repo, const char *path)\n \tconst struct string_list *sl;\n \tconst struct submodule *module;\n \n-\tmodule = submodule_from_path(repo, null_oid(), path);\n+\tmodule = submodule_from_path(repo, treeish_name, path);\n \n \t/* early return if there isn't a path->module mapping */\n \tif (!module)\n@@ -315,6 +317,11 @@ int is_submodule_active(struct repository *repo, const char *path)\n \treturn ret;\n }\n \n+int is_submodule_active(struct repository *repo, const char *path)\n+{\n+\treturn is_tree_submodule_active(repo, null_oid(), path);\n+}\n+\n int is_submodule_populated_gently(const char *path, int *return_error_code)\n {\n \tint ret = 0;\ndiff --git a/submodule.h b/submodule.h\nindex 6bd2c99fd9..784ceffc0e 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb);\n struct option;\n int option_parse_recurse_submodules_worktree_updater(const struct option *opt,\n \t\t\t\t\t\t     const char *arg, int unset);\n+int is_tree_submodule_active(struct repository *repo,\n+\t\t\t     const struct object_id *treeish_name,\n+\t\t\t     const char *path);\n int is_submodule_active(struct repository *repo, const char *path);\n /*\n  * Determine if a submodule has been populated at a given 'path' by checking if\ndiff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh\nnew file mode 100755\nindex 0000000000..6ef2733396\n--- /dev/null\n+++ b/t/t3207-branch-submodule.sh\n@@ -0,0 +1,292 @@\n+#!/bin/sh\n+\n+test_description='git branch submodule tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+pwd=$(pwd)\n+\n+# Creates a clean test environment in \"pwd\" by copying the repo setup\n+# from test_dirs.\n+reset_test () {\n+\trm -fr super &&\n+\trm -fr sub-sub-upstream &&\n+\trm -fr sub-upstream &&\n+\tcp -r test_dirs/* .\n+}\n+\n+# Tests that the expected branch does not exist\n+test_no_branch () {\n+\tDIR=$1 &&\n+\tBRANCH_NAME=$2 &&\n+\ttest_must_fail git -C \"$DIR\" rev-parse \"$BRANCH_NAME\" 2>err &&\n+\tgrep \"ambiguous argument .$BRANCH_NAME.\" err\n+}\n+\n+test_expect_success 'setup superproject and submodule' '\n+\tmkdir test_dirs &&\n+\t(\n+\t\tcd test_dirs &&\n+\t\tgit init super &&\n+\t\ttest_commit -C super foo &&\n+\t\tgit init sub-sub-upstream &&\n+\t\ttest_commit -C sub-sub-upstream foo &&\n+\t\tgit init sub-upstream &&\n+\t\t# Submodule in a submodule\n+\t\tgit -C sub-upstream submodule add \"${pwd}/test_dirs/sub-sub-upstream\" sub-sub &&\n+\t\tgit -C sub-upstream commit -m \"add submodule\" &&\n+\t\t# Regular submodule\n+\t\tgit -C super submodule add \"${pwd}/test_dirs/sub-upstream\" sub &&\n+\t\t# Submodule in a subdirectory\n+\t\tgit -C super submodule add \"${pwd}/test_dirs/sub-sub-upstream\" second/sub &&\n+\t\tgit -C super commit -m \"add submodule\" &&\n+\t\tgit -C super config submodule.propagateBranches true &&\n+\t\tgit -C super/sub submodule update --init\n+\t) &&\n+\treset_test\n+'\n+\n+# Test the argument parsing\n+test_expect_success '--recurse-submodules should create branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a &&\n+\t\tgit -C sub/sub-sub rev-parse branch-a &&\n+\t\tgit -C second/sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\techo \"fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled\" >expected &&\n+\t\ttest_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success '--recurse-submodules should fail when not creating branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\techo \"fatal: --recurse-submodules can only be used to create branches\" >expected &&\n+\t\ttest_must_fail git branch --recurse-submodules -D branch-a 2>actual &&\n+\t\ttest_cmp expected actual &&\n+\t\t# Assert that the branches were not deleted\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success 'should respect submodule.recurse when creating branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c submodule.recurse=true branch branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a\n+\t)\n+'\n+\n+test_expect_success 'should ignore submodule.recurse when not creating branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit -c submodule.recurse=true branch -D branch-a &&\n+\t\ttest_no_branch . branch-a &&\n+\t\tgit -C sub rev-parse branch-a\n+\t)\n+'\n+\n+# Test branch creation behavior\n+test_expect_success 'should create branches based off commit id in superproject' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit checkout --recurse-submodules branch-a &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\t# Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a\n+\t\tgit -C sub checkout branch-a &&\n+\t\ttest_commit -C sub bar &&\n+\t\t# Create a new branch-b branch with start-point=branch-a\n+\t\tgit branch --recurse-submodules branch-b branch-a &&\n+\t\tgit rev-parse branch-b &&\n+\t\tgit -C sub rev-parse branch-b >actual &&\n+\t\t# Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n+test_expect_success 'should not create any branches if branch is not valid for all repos' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub branch branch-a &&\n+\t\ttest_must_fail git branch --recurse-submodules branch-a 2>actual &&\n+\t\ttest_no_branch . branch-a &&\n+\t\tgrep \"submodule .sub.: fatal: A branch named .branch-a. already exists\" actual\n+\t)\n+'\n+\n+test_expect_success 'should create branches if branch exists and --force is given' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -C sub rev-parse HEAD >expected &&\n+\t\ttest_commit -C sub baz &&\n+\t\t# branch-a in sub now points to a newer commit.\n+\t\tgit -C sub branch branch-a HEAD &&\n+\t\tgit -C sub rev-parse branch-a >actual-old-branch-a &&\n+\t\tgit branch --recurse-submodules --force branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\tgit -C sub rev-parse branch-a >actual-new-branch-a &&\n+\t\ttest_cmp expected actual-new-branch-a &&\n+\t\t# assert that branch --force actually moved the sub\n+\t\t# branch\n+\t\t! test_cmp expected actual-old-branch-a\n+\t)\n+'\n+\n+test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch branch-a &&\n+\t\tgit checkout -b branch-b &&\n+\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\tgit -C sub2 submodule update --init &&\n+\t\t# branch-b now has a committed submodule not in branch-a\n+\t\tgit commit -m \"add second submodule\" &&\n+\t\tgit checkout branch-a &&\n+\t\tgit branch --recurse-submodules branch-c branch-b &&\n+\t\tgit checkout --recurse-submodules branch-c &&\n+\t\tgit -C sub2 rev-parse branch-c &&\n+\t\tgit -C sub2/sub-sub rev-parse branch-c\n+\t)\n+'\n+\n+test_expect_success 'should not create branches in inactive submodules' '\n+\ttest_when_finished \"reset_test\" &&\n+\ttest_config -C super submodule.sub.active false &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a &&\n+\t\tgit rev-parse branch-a &&\n+\t\ttest_no_branch sub branch-a\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with track=always' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest_cmp_config -C sub . branch.branch-a.remote &&\n+\t\ttest_cmp_config -C sub refs/heads/main branch.branch-a.merge\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of local branches with explicit track' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --track --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest_cmp_config -C sub . branch.branch-a.remote &&\n+\t\ttest_cmp_config -C sub refs/heads/main branch.branch-a.merge\n+\t)\n+'\n+\n+test_expect_success 'should not set up unnecessary tracking of local branches' '\n+\ttest_when_finished \"reset_test\" &&\n+\t(\n+\t\tcd super &&\n+\t\tgit branch --recurse-submodules branch-a main &&\n+\t\tgit -C sub rev-parse main &&\n+\t\ttest_cmp_config -C sub \"\" --default \"\" branch.branch-a.remote &&\n+\t\ttest_cmp_config -C sub \"\" --default \"\" branch.branch-a.merge\n+\t)\n+'\n+\n+reset_remote_test () {\n+\trm -fr super-clone &&\n+\treset_test\n+}\n+\n+test_expect_success 'setup tests with remotes' '\n+\t(\n+\t\tcd test_dirs &&\n+\t\t(\n+\t\t\tcd super &&\n+\t\t\tgit branch branch-a &&\n+\t\t\tgit checkout -b branch-b &&\n+\t\t\tgit submodule add ../sub-upstream sub2 &&\n+\t\t\t# branch-b now has a committed submodule not in branch-a\n+\t\t\tgit commit -m \"add second submodule\"\n+\t\t) &&\n+\t\tgit clone --branch main --recurse-submodules super super-clone &&\n+\t\tgit -C super-clone config submodule.propagateBranches true\n+\t) &&\n+\treset_remote_test\n+'\n+\n+test_expect_success 'should get fatal error upon branch creation when submodule is not in .git/modules' '\n+\ttest_when_finished \"reset_remote_test\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\t# This should succeed because super-clone has sub in .git/modules\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\t# This should fail because super-clone does not have sub2 .git/modules\n+\t\ttest_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&\n+\t\tgrep \"fatal: submodule .sub2.: unable to find submodule\" actual &&\n+\t\ttest_no_branch . branch-b &&\n+\t\ttest_no_branch sub branch-b &&\n+\t\t# User can fix themselves by initializing the submodule\n+\t\tgit checkout origin/branch-b &&\n+\t\tgit submodule update --init --recursive &&\n+\t\tgit branch --recurse-submodules branch-b origin/branch-b\n+\t)\n+'\n+\n+test_expect_success 'should set up tracking of remote-tracking branches' '\n+\ttest_when_finished \"reset_remote_test\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit branch --recurse-submodules branch-a origin/branch-a &&\n+\t\ttest_cmp_config origin branch.branch-a.remote &&\n+\t\ttest_cmp_config refs/heads/branch-a branch.branch-a.merge &&\n+\t\t# \"origin/branch-a\" does not exist for \"sub\", but it matches the refspec\n+\t\t# so tracking should be set up\n+\t\ttest_cmp_config -C sub origin branch.branch-a.remote &&\n+\t\ttest_cmp_config -C sub refs/heads/branch-a branch.branch-a.merge &&\n+\t\ttest_cmp_config -C sub/sub-sub origin branch.branch-a.remote &&\n+\t\ttest_cmp_config -C sub/sub-sub refs/heads/branch-a branch.branch-a.merge\n+\t)\n+'\n+\n+test_expect_success 'should not fail when unable to set up tracking in submodule' '\n+\ttest_when_finished \"reset_remote_test\" &&\n+\t(\n+\t\tcd super-clone &&\n+\t\tgit remote rename origin ex-origin &&\n+\t\tgit branch --recurse-submodules branch-a ex-origin/branch-a &&\n+\t\ttest_cmp_config ex-origin branch.branch-a.remote &&\n+\t\ttest_cmp_config refs/heads/branch-a branch.branch-a.merge &&\n+\t\ttest_cmp_config -C sub \"\" --default \"\" branch.branch-a.remote &&\n+\t\ttest_cmp_config -C sub \"\" --default \"\" branch.branch-a.merge\n+\t)\n+'\n+\n+test_done\n-- \n2.33.GIT\n\n"},{"id":"447254","messageId":"20220129000446.99261-7-chooglen@google.com","threadId":"56962","inReplyTo":"20220129000446.99261-1-chooglen@google.com","subject":"[PATCH v8 6/6] branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-29T00:04:46Z","receivedAt":"2022-01-29T00:05:17Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Signed-off-by: Glen Choo <chooglen@google.com>\n---\n branch.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 70026b3c79..47251669e1 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -239,7 +239,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \tif (track != BRANCH_TRACK_INHERIT)\n \t\tfor_each_remote(find_tracked_branch, &tracking);\n \telse if (inherit_tracking(&tracking, orig_ref))\n-\t\treturn;\n+\t\tgoto cleanup;\n \n \tif (!tracking.matches)\n \t\tswitch (track) {\n@@ -249,7 +249,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tcase BRANCH_TRACK_INHERIT:\n \t\t\tbreak;\n \t\tdefault:\n-\t\t\treturn;\n+\t\t\tgoto cleanup;\n \t\t}\n \n \tif (tracking.matches > 1)\n@@ -262,7 +262,8 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t\ttracking.remote, tracking.srcs) < 0)\n \t\texit(-1);\n \n-\tstring_list_clear(tracking.srcs, 0);\n+cleanup:\n+\tstring_list_clear(&tracking_srcs, 0);\n }\n \n int read_branch_desc(struct strbuf *buf, const char *branch_name)\n-- \n2.33.GIT\n\n"},{"id":"447435","messageId":"20220201174335.1578352-1-jonathantanmy@google.com","threadId":"56962","inReplyTo":"20220129000446.99261-1-chooglen@google.com","subject":"Re: [PATCH v8 0/6] implement branch --recurse-submodules","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-02-01T17:43:35Z","receivedAt":"2022-02-01T17:43:42Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> Changes since v7:\n> * Reword patch 5's commit message\n> * In comments and Documentation/git-branch.txt, clarify how branch\n>   --recurse-submodules behaves and rename a parameter to better reflect\n>   this behavior\n> * In t3207, always assert on error messages when using test_must_fail\n> * In t3207, assert on config values using test_cmp_config. This avoids\n>   the silent failures discouraged by t/README:671.\n> * In patch 6, be more explicit about freeing the stack-allocated\n>   variable.\n\nI had minor comments [1] which have been addressed, so:\n\nReviewed-by: Jonathan Tan <jonathantanmy@google.com>\n\nI'm OK with 6/6 being included or not being included.\n"},{"id":"447473","messageId":"xmqqmtjab54r.fsf@gitster.g","threadId":"56962","inReplyTo":"20220129000446.99261-3-chooglen@google.com","subject":"Re: [PATCH v8 2/6] branch: make create_branch() always create a branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-01T22:20:52Z","receivedAt":"2022-02-01T22:20:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> With the previous commit, there are no more invocations of\n> create_branch() that do not create a branch because:\n>\n> * BRANCH_TRACK_OVERRIDE is no longer passed\n> * clobber_head_ok = true and force = false is never passed\n>\n> Assert these situations, delete dead code and ensure that we're handling\n> clobber_head_ok and force correctly by introducing tests for `git branch\n> --force`. As a result, create_branch() now always creates a branch.\n\nA very nice simplifiation of semantics.\n\n"},{"id":"447693","messageId":"kl6lwnibzbb6.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"20220129000446.99261-6-chooglen@google.com","subject":"Re: [PATCH v8 5/6] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-02-04T01:10:21Z","receivedAt":"2022-02-04T01:10:34Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\nI tested this series some more and found a silly bug.\nLet me know if I should just send v9 instead.\n\n----- >8 --------- >8 --------- >8 --------- >8 --------- >8 ----\n\nSubject: [PATCH] submodule-config.c: remove accidental pointer reuse\n\nList entries allocated by traverse_tree_submodules() are sharing the\n.name_entry pointer, but each of them needs their own pointer. Fix this.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n submodule-config.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 24b8d1a700..c9f54bc72d 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -757,7 +757,8 @@ static void traverse_tree_submodules(struct repository *r,\n \t\tif (S_ISGITLINK(name_entry->mode) &&\n \t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n \t\t\tst_entry = xmalloc(sizeof(*st_entry));\n-\t\t\tst_entry->name_entry = name_entry;\n+\t\t\tst_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry));\n+\t\t\t*st_entry->name_entry = *name_entry;\n \t\t\tst_entry->submodule =\n \t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n \t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n-- \n2.35.0.263.gb82422642f-goog\n"},{"id":"447746","messageId":"xmqqmtj6obft.fsf@gitster.g","threadId":"56962","inReplyTo":"kl6lwnibzbb6.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v8 5/6] branch: add --recurse-submodules option for branch creation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-04T16:15:18Z","receivedAt":"2022-02-04T16:15:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> I tested this series some more and found a silly bug.\n> Let me know if I should just send v9 instead.\n\nAs long as the log message of this new thing does not have to be\nretained, I can squash it in for you.\n\nAnd in this case, we can lose the message below---it just talks\nabout a mistake in the version that was submitted, and will become\nirrelevant once the change is squashed in.\n\nThanks.\n\n> ----- >8 --------- >8 --------- >8 --------- >8 --------- >8 ----\n>\n> Subject: [PATCH] submodule-config.c: remove accidental pointer reuse\n>\n> List entries allocated by traverse_tree_submodules() are sharing the\n> .name_entry pointer, but each of them needs their own pointer. Fix this.\n>\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  submodule-config.c | 3 ++-\n>  1 file changed, 2 insertions(+), 1 deletion(-)\n>\n> diff --git a/submodule-config.c b/submodule-config.c\n> index 24b8d1a700..c9f54bc72d 100644\n> --- a/submodule-config.c\n> +++ b/submodule-config.c\n> @@ -757,7 +757,8 @@ static void traverse_tree_submodules(struct repository *r,\n>  \t\tif (S_ISGITLINK(name_entry->mode) &&\n>  \t\t    is_tree_submodule_active(r, root_tree, tree_path)) {\n>  \t\t\tst_entry = xmalloc(sizeof(*st_entry));\n> -\t\t\tst_entry->name_entry = name_entry;\n> +\t\t\tst_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry));\n> +\t\t\t*st_entry->name_entry = *name_entry;\n>  \t\t\tst_entry->submodule =\n>  \t\t\t\tsubmodule_from_path(r, root_tree, tree_path);\n>  \t\t\tst_entry->repo = xmalloc(sizeof(*st_entry->repo));\n"},{"id":"447760","messageId":"kl6ltudezeny.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56962","inReplyTo":"xmqqmtj6obft.fsf@gitster.g","subject":"Re: [PATCH v8 5/6] branch: add --recurse-submodules option for branch creation","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-02-04T18:10:09Z","receivedAt":"2022-02-04T18:10:13Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>\n> As long as the log message of this new thing does not have to be\n> retained, I can squash it in for you.\n>\n> And in this case, we can lose the message below---it just talks\n> about a mistake in the version that was submitted, and will become\n> irrelevant once the change is squashed in.\n>\n\nYes, the intention was to lose the message.\n\nThanks so much for being accommodating :)\n"}]}