{"thread":{"id":"58375","subject":"[PATCH 0/6] clone, submodule update: check out submodule branches","startedAt":"2022-08-29T20:54:23Z","lastAt":"2022-11-23T04:00:53Z","messageCount":56,"participants":["Glen Choo via GitGitGadget","Philippe Blain","Glen Choo","Jonathan Tan","Junio C Hamano","Taylor Blau"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"462106","messageId":"pull.1321.git.git.1661806456.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":null,"subject":"[PATCH 0/6] clone, submodule update: check out submodule branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-29T20:54:10Z","receivedAt":"2022-08-29T20:54:23Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"= Description\n\nThis series teaches \"git clone --recurse-submodules\" and \"git submodule\nupdate\" to understand \"submodule.propagateBranches\" (see Further Reading for\ncontext), i.e. if the superproject has a branch checked out and a submodule\nis cloned, the submodule will have the same branch checked out.\n\nTo do this, \"git submodule update\" checks if \"submodule.propagateBranches\"\nis true, and if so, gets the current superproject branch and updates the\nsubmodule by running \"git checkout -b my-superproject-branch\". Since \"git\nclone --recurse-submodules\" is implemented using \"git submodule update\", it\nalso learns this trick.\n\nThe main challenges with this approach are:\n\n * If the remote HEAD points to a branch, \"git clone\" always creates that\n   branch in the clone. But with \"submodule.propagateBranches\", we want\n   submodules to use the branch names of their superproject, not their\n   upstream.\n   \n   This is solved by adding a new flag to \"git clone\", \"--detach\", which\n   detaches the clone's HEAD at the branch and does not create it.\n\n * When \"git submodule update\" recurses into submodules, the parent process\n   has to propagate the value of \"submodule.propagateBranches\" to child\n   processes, otherwise the behavior will be inconsistent if the submodule\n   has the config unset.\n   \n   This is solved by adding an internal GIT_* environment variable and\n   passing it down via prepare_submodule_repo_env(). This is cleaner than\n   passing \"-c submodule.propagateBranches=true\", but an even cleaner\n   solution would be for submodules to read \"submodule.propagateBranches\"\n   from their superproject config. This would also be useful for\n   \"submodule.alternateLocation\" and \"submodule.alternateErrorStrategy\", as\n   we wouldn't have to set those values in newly-cloned submodules. This\n   requires teaching Git to treat submodules differently, which was the\n   subject of some WIP in [1]. That topic has stalled, but I don't mind\n   restarting it if others prefer that.\n\n[1]\nhttps://lore.kernel.org/git/20220310004423.2627181-1-emilyshaffer@google.com/\n\n= Patch organization\n\n * Patch 1/6 adds \"--detach\" to \"git clone\"\n * Patch 2/6 creates the environment variable and repository setting for\n   \"submodule.propagateBranches\"\n * Patches 3-5/6 are prep work, and 6/6 adds the actual\n   \"submodule.propagateBranches\" behavior\n\n= Future work\n\n * Patch 4, which refactors resolve_gitlink_ref(), notes that a better\n   interface would be to return the refname instead of using an \"out\"\n   parameter, but we use an \"out\" parameter so that any new callers trying\n   to use the old function signature will get stopped by the compiler. The\n   refactor can be finished at a later time.\n\n= Further reading\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\"git branch --recurse-submodules\":\nhttps://lore.kernel.org/git/20220129000446.99261-1-chooglen@google.com/\n\nGlen Choo (6):\n  clone: teach --detach option\n  repo-settings: add submodule_propagate_branches\n  t5617: drop references to remote-tracking branches\n  submodule: return target of submodule symref\n  submodule--helper: refactor up-to-date criterion\n  clone, submodule update: check out branches\n\n Documentation/git-clone.txt                   |  7 +++-\n builtin/branch.c                              | 11 ++----\n builtin/clone.c                               | 12 ++++--\n builtin/submodule--helper.c                   | 37 +++++++++++++++++--\n builtin/update-index.c                        |  4 +-\n cache.h                                       |  1 +\n combine-diff.c                                |  3 +-\n diff-lib.c                                    |  2 +-\n dir.c                                         |  2 +-\n object-file.c                                 |  2 +-\n read-cache.c                                  |  4 +-\n refs.c                                        | 10 +++--\n refs.h                                        |  5 ++-\n repo-settings.c                               | 11 +++---\n repository.h                                  |  1 +\n submodule.c                                   |  2 +\n t/t5601-clone.sh                              | 22 +++++++++++\n ...es-remote.sh => t5617-clone-submodules.sh} | 36 +++++++++++++++++-\n t/t7406-submodule-update.sh                   | 22 +++++++++++\n unpack-trees.c                                |  3 +-\n 20 files changed, 161 insertions(+), 36 deletions(-)\n rename t/{t5617-clone-submodules-remote.sh => t5617-clone-submodules.sh} (72%)\n\n\nbase-commit: 9bf691b78cf906751e65d65ba0c6ffdcd9a5a12c\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1321%2Fchooglen%2Fsubmodule%2Fclone-recursive-with-branch-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1321/chooglen/submodule/clone-recursive-with-branch-v1\nPull-Request: https://github.com/git/git/pull/1321\n-- \ngitgitgadget\n"},{"id":"462107","messageId":"936e237c716bddf3a5889829e0c907e881736336.1661806456.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.git.git.1661806456.gitgitgadget@gmail.com","subject":"[PATCH 1/6] clone: teach --detach option","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-29T20:54:11Z","receivedAt":"2022-08-29T20:54:24Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nTeach \"git clone\" the \"--detach\" option, which leaves the cloned repo in\ndetached HEAD (like \"git checkout --detach\"). If the clone is not bare,\nthe remote's HEAD branch is also not created (bare clones always copy\nall remote branches directly to local branches, so the branch is still\ncreated in the bare case).\n\nThis is especially useful in the \"submodule.propagateBranches\" workflow,\nwhere the submodule branch names match the superproject's branch names,\nso it makes no sense to name the branches after the submodule's remote's\nbranches.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/git-clone.txt |  7 ++++++-\n builtin/clone.c             | 12 +++++++++---\n t/t5601-clone.sh            | 22 ++++++++++++++++++++++\n 3 files changed, 37 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex 632bd1348ea..a3af90824b6 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t  [--depth <depth>] [--[no-]single-branch] [--no-tags]\n \t  [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]\n \t  [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]\n-\t  [--filter=<filter> [--also-filter-submodules]] [--] <repository>\n+\t  [--filter=<filter> [--also-filter-submodules] [--detach]] [--] <repository>\n \t  [<directory>]\n \n DESCRIPTION\n@@ -210,6 +210,11 @@ objects from the source repository into a pack in the cloned repository.\n \t`--branch` can also take tags and detaches the HEAD at that commit\n \tin the resulting repository.\n \n+--detach::\n+\tIf the cloned repository's HEAD points to a branch, point the newly\n+\tcreated HEAD to the branch's commit instead of the branch itself. In a\n+\tnon-bare repository, the branch will not be created.\n+\n -u <upload-pack>::\n --upload-pack <upload-pack>::\n \tWhen given, and the repository to clone from is accessed\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex c4ff4643ecd..1bc1807360e 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -77,6 +77,7 @@ static int option_filter_submodules = -1;    /* unspecified */\n static int config_filter_submodules = -1;    /* unspecified */\n static struct string_list server_options = STRING_LIST_INIT_NODUP;\n static int option_remote_submodules;\n+static int option_detach;\n \n static int recurse_submodules_cb(const struct option *opt,\n \t\t\t\t const char *arg, int unset)\n@@ -160,6 +161,8 @@ static struct option builtin_clone_options[] = {\n \t\t    N_(\"any cloned submodules will use their remote-tracking branch\")),\n \tOPT_BOOL(0, \"sparse\", &option_sparse_checkout,\n \t\t    N_(\"initialize sparse-checkout file to include only files at root\")),\n+\tOPT_BOOL(0, \"detach\", &option_detach,\n+\t\t N_(\"detach HEAD and don't create branch\")),\n \tOPT_END()\n };\n \n@@ -607,10 +610,12 @@ static void update_remote_refs(const struct ref *refs,\n }\n \n static void update_head(const struct ref *our, const struct ref *remote,\n-\t\t\tconst char *unborn, const char *msg)\n+\t\t\tconst char *unborn, int should_detach,\n+\t\t\tconst char *msg)\n {\n \tconst char *head;\n-\tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n+\tif (our && !should_detach &&\n+\t    skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n \t\tif (create_symref(\"HEAD\", our->name, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -1339,7 +1344,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\t\t   branch_top.buf, reflog_msg.buf, transport,\n \t\t\t   !is_local);\n \n-\tupdate_head(our_head_points_at, remote_head, unborn_head, reflog_msg.buf);\n+\tupdate_head(our_head_points_at, remote_head, unborn_head,\n+\t\t    option_detach, reflog_msg.buf);\n \n \t/*\n \t * We want to show progress for recursive submodule clones iff\ndiff --git a/t/t5601-clone.sh b/t/t5601-clone.sh\nindex cf3be0584f4..1e7e5143a76 100755\n--- a/t/t5601-clone.sh\n+++ b/t/t5601-clone.sh\n@@ -333,6 +333,28 @@ test_expect_success 'clone checking out a tag' '\n \ttest_cmp fetch.expected fetch.actual\n '\n \n+test_expect_success '--detach detaches and does not create branch' '\n+\ttest_when_finished \"rm -fr dst\" &&\n+\tgit clone --detach src dst &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git rev-parse main &&\n+\t\ttest_must_fail git symbolic-ref HEAD &&\n+\t\ttest_cmp_rev HEAD refs/remotes/origin/HEAD\n+\t)\n+'\n+\n+test_expect_success '--detach with --bare detaches but creates branch' '\n+\ttest_when_finished \"rm -fr dst\" &&\n+\tgit clone --bare --detach src dst &&\n+\t(\n+\t\tcd dst &&\n+\t\tgit rev-parse main &&\n+\t\ttest_must_fail git symbolic-ref HEAD &&\n+\t\ttest_cmp_rev HEAD refs/heads/main\n+\t)\n+'\n+\n test_expect_success 'set up ssh wrapper' '\n \tcp \"$GIT_BUILD_DIR/t/helper/test-fake-ssh$X\" \\\n \t\t\"$TRASH_DIRECTORY/ssh$X\" &&\n-- \ngitgitgadget\n\n"},{"id":"462108","messageId":"35f068eb0318653ee0474b30d3420d210f47f8e4.1661806456.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.git.git.1661806456.gitgitgadget@gmail.com","subject":"[PATCH 2/6] repo-settings: add submodule_propagate_branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-29T20:54:12Z","receivedAt":"2022-08-29T20:54:27Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nWhen processes recurse into submodules, the child processes have to\nuse the same value of \"submodule.propagateBranches\" as the parent\nprocess regardless of whether the process is spawned in the superproject\nor submodule, otherwise the behavior may be inconsistent if the\nrepositories don't agree on the config.\n\nWe haven't needed a way to propagate the config because because the only\ncommand that reads \"submodule.propagateBranches\" is \"git branch\", which\nonly has one mode of operation with \"--recurse-submodules\". However, a\nfuture commit will teach \"submodule.propagateBranches\" to \"git submodule\nupdate\", making this necessary.\n\nPropagate \"submodule.propagateBranches\" to child processes by adding a\ncorresponding GIT_INTERNAL_* environment variable and repository\nsetting, and setting the environment variable inside\nprepare_submodule_repo_env(). Then, refactor builtin/branch.c to read\nthe repository setting.\n\nUsing an internal environment variable is a potentially leaky\nabstraction because environment variables can come from sources besides\nthe parent process. A more robust solution would be to teach Git that\nthe repository is a submodule and to only read\n\"submodule.propagateBranches\" from the superproject config. There is WIP\nfor this on the ML [1].\n\nAnother alternative would be to pass \"-c submodule.propagateBranches\" to\nall child processes. This is error-prone because many different\nprocesses are invoked directly or indirectly by \"git submodule update\"\n(e.g. \"git submodule--helper clone\", \"git clone\", \"git checkout\"). With\nan environment variable, we can avoid this work because\nprepare_submodule_repo_env() is already called for submodule child\nprocesses.\n\n[1] https://lore.kernel.org/git/20220310004423.2627181-1-emilyshaffer@google.com/\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 11 +++--------\n cache.h          |  1 +\n repo-settings.c  | 11 ++++++-----\n repository.h     |  1 +\n submodule.c      |  2 ++\n 5 files changed, 13 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 55cd9a6e998..1201e41ef8c 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -40,7 +40,6 @@ static const char * const builtin_branch_usage[] = {\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@@ -106,10 +105,6 @@ static int git_branch_config(const char *var, const char *value, void *cb)\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@@ -714,7 +709,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, builtin_branch_usage,\n \t\t\t     0);\n-\n+\tprepare_repo_settings(the_repository);\n \tif (!delete && !rename && !copy && !edit_description && !new_upstream &&\n \t    !show_current && !unset_upstream && argc == 0)\n \t\tlist = 1;\n@@ -730,7 +725,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (recurse_submodules_explicit) {\n-\t\tif (!submodule_propagate_branches)\n+\t\tif (!the_repository->settings.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@@ -738,7 +733,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \trecurse_submodules =\n \t\t(recurse_submodules || recurse_submodules_explicit) &&\n-\t\tsubmodule_propagate_branches;\n+\t\tthe_repository->settings.submodule_propagate_branches;\n \n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\ndiff --git a/cache.h b/cache.h\nindex 302810b353a..023f8feba42 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -505,6 +505,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define GIT_WORK_TREE_ENVIRONMENT \"GIT_WORK_TREE\"\n #define GIT_PREFIX_ENVIRONMENT \"GIT_PREFIX\"\n #define GIT_SUPER_PREFIX_ENVIRONMENT \"GIT_INTERNAL_SUPER_PREFIX\"\n+#define GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT \"GIT_INTERNAL_SUBMODULE_PROPAGATE_BRANCHES\"\n #define DEFAULT_GIT_DIR_ENVIRONMENT \".git\"\n #define DB_ENVIRONMENT \"GIT_OBJECT_DIRECTORY\"\n #define INDEX_ENVIRONMENT \"GIT_INDEX_FILE\"\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 43bc881bfc9..2fde9bcdbf6 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -60,16 +60,17 @@ void prepare_repo_settings(struct repository *r)\n \trepo_cfg_bool(r, \"pack.usesparse\", &r->settings.pack_use_sparse, 1);\n \trepo_cfg_bool(r, \"core.multipackindex\", &r->settings.core_multi_pack_index, 1);\n \trepo_cfg_bool(r, \"index.sparse\", &r->settings.sparse_index, 0);\n+\trepo_cfg_bool(r, \"submodule.propagateBranches\", &r->settings.submodule_propagate_branches, 0);\n \n \t/*\n-\t * The GIT_TEST_MULTI_PACK_INDEX variable is special in that\n-\t * either it *or* the config sets\n-\t * r->settings.core_multi_pack_index if true. We don't take\n-\t * the environment variable if it exists (even if false) over\n-\t * any config, as in most other cases.\n+\t * Boolean settings from config and environment variables. Only\n+\t * take the environment variable if it is true, otherwise, use\n+\t * the config.\n \t */\n \tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))\n \t\tr->settings.core_multi_pack_index = 1;\n+\tif (git_env_bool(GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT, 0))\n+\t\tr->settings.submodule_propagate_branches = 1;\n \n \t/*\n \t * Non-boolean config\ndiff --git a/repository.h b/repository.h\nindex 797f471cce9..bf36b9ee6d7 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -36,6 +36,7 @@ struct repo_settings {\n \tint fetch_write_commit_graph;\n \tint command_requires_full_index;\n \tint sparse_index;\n+\tint submodule_propagate_branches;\n \n \tstruct fsmonitor_settings *fsmonitor; /* lazily loaded */\n \ndiff --git a/submodule.c b/submodule.c\nindex 3fa5db3ecdf..510c1b04a76 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -504,6 +504,8 @@ static void print_submodule_diff_summary(struct repository *r, struct rev_info *\n \n void prepare_submodule_repo_env(struct strvec *out)\n {\n+\tif (the_repository->settings.submodule_propagate_branches)\n+\t\tstrvec_pushf(out, \"%s=1\", GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT);\n \tprepare_other_repo_env(out, DEFAULT_GIT_DIR_ENVIRONMENT);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"462109","messageId":"d8a2faf9ce7de2562424181109120a6d84b0e470.1661806456.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.git.git.1661806456.gitgitgadget@gmail.com","subject":"[PATCH 3/6] t5617: drop references to remote-tracking branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-29T20:54:13Z","receivedAt":"2022-08-29T20:54:29Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nIt has included submodule cloning tests without remote-tracking branches\ntests since f05da2b48b (clone, submodule: pass partial clone filters to\nsubmodules, 2022-02-04) at least. Rename it accordingly so that we can\nput future submodule cloning tests there.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n ...617-clone-submodules-remote.sh => t5617-clone-submodules.sh} | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n rename t/{t5617-clone-submodules-remote.sh => t5617-clone-submodules.sh} (97%)\n\ndiff --git a/t/t5617-clone-submodules-remote.sh b/t/t5617-clone-submodules.sh\nsimilarity index 97%\nrename from t/t5617-clone-submodules-remote.sh\nrename to t/t5617-clone-submodules.sh\nindex ca8f80083a2..b5c66cb18cb 100755\n--- a/t/t5617-clone-submodules-remote.sh\n+++ b/t/t5617-clone-submodules.sh\n@@ -1,6 +1,6 @@\n #!/bin/sh\n \n-test_description='Test cloning repos with submodules using remote-tracking branches'\n+test_description='Test cloning repos with submodules'\n \n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n-- \ngitgitgadget\n\n"},{"id":"462110","messageId":"eab40dcb296fd6a6ea14d13107cdc3ca6e64e311.1661806456.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.git.git.1661806456.gitgitgadget@gmail.com","subject":"[PATCH 4/6] submodule: return target of submodule symref","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-29T20:54:14Z","receivedAt":"2022-08-29T20:54:32Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nresolve_gitlink_ref() can tell us which oid the submodule ref is\npointing to, but in a future commit, we would also like to know the\nsymbolic ref target if we are checking a symbolic ref. Teach\nresolve_gitlink_ref() to \"return\" the symbolic ref's target via an \"out\"\nparameter.\n\nThis changes resolve_gitlink_ref()'s signature so that new callers\ntrying to use the old signature will be stopped by the compiler. If we\nreturned the target instead (just like refs_resolve_ref_unsafe()), we\nwould be more consistent with refs_resolve_ref_unsafe(), but callers\nexpecting the old signature will get the opposite return value from what\nthey expect (since exit code 0 means success, but NULL pointer means\nfailure). We should do this refactor once we think that nobody will try\nto use the old signature.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c |  8 +++++---\n builtin/update-index.c      |  4 ++--\n combine-diff.c              |  3 ++-\n diff-lib.c                  |  2 +-\n dir.c                       |  2 +-\n object-file.c               |  2 +-\n read-cache.c                |  4 ++--\n refs.c                      | 10 ++++++----\n refs.h                      |  5 ++++-\n unpack-trees.c              |  3 ++-\n 10 files changed, 26 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex b63f420ecef..dd00a0db522 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2466,7 +2466,8 @@ static int update_submodule(struct update_data *update_data)\n \n \tif (update_data->just_cloned)\n \t\toidcpy(&update_data->suboid, null_oid());\n-\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &update_data->suboid))\n+\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n+\t\t\t\t     &update_data->suboid, NULL))\n \t\tdie(_(\"Unable to find current revision in submodule path '%s'\"),\n \t\t\tupdate_data->displaypath);\n \n@@ -2482,7 +2483,8 @@ static int update_submodule(struct update_data *update_data)\n \t\t\t\t    update_data->sm_path);\n \t\t}\n \n-\t\tif (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))\n+\t\tif (resolve_gitlink_ref(update_data->sm_path, remote_ref,\n+\t\t\t\t\t&update_data->oid, NULL))\n \t\t\tdie(_(\"Unable to find %s revision in submodule path '%s'\"),\n \t\t\t    remote_ref, update_data->sm_path);\n \n@@ -3231,7 +3233,7 @@ static void die_on_repo_without_commits(const char *path)\n \tstrbuf_addstr(&sb, path);\n \tif (is_nonbare_repository_dir(&sb)) {\n \t\tstruct object_id oid;\n-\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid) < 0)\n+\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid, NULL) < 0)\n \t\t\tdie(_(\"'%s' does not have a commit checked out\"), path);\n \t}\n \tstrbuf_release(&sb);\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex b62249905f1..19a21a4586c 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -339,7 +339,7 @@ static int process_directory(const char *path, int len, struct stat *st)\n \t\tif (S_ISGITLINK(ce->ce_mode)) {\n \n \t\t\t/* Do nothing to the index if there is no HEAD! */\n-\t\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid) < 0)\n+\t\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid, NULL) < 0)\n \t\t\t\treturn 0;\n \n \t\t\treturn add_one_path(ce, path, len, st);\n@@ -365,7 +365,7 @@ static int process_directory(const char *path, int len, struct stat *st)\n \t}\n \n \t/* No match - should we add it as a gitlink? */\n-\tif (!resolve_gitlink_ref(path, \"HEAD\", &oid))\n+\tif (!resolve_gitlink_ref(path, \"HEAD\", &oid, NULL))\n \t\treturn add_one_path(NULL, path, len, st);\n \n \t/* Error out. */\ndiff --git a/combine-diff.c b/combine-diff.c\nindex b0ece954808..88efcaeefa7 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -1060,7 +1060,8 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n \t\t\telem->mode = canon_mode(st.st_mode);\n \t\t} else if (S_ISDIR(st.st_mode)) {\n \t\t\tstruct object_id oid;\n-\t\t\tif (resolve_gitlink_ref(elem->path, \"HEAD\", &oid) < 0)\n+\t\t\tif (resolve_gitlink_ref(elem->path, \"HEAD\", &oid,\n+\t\t\t\t\t\tNULL) < 0)\n \t\t\t\tresult = grab_blob(opt->repo, &elem->oid,\n \t\t\t\t\t\t   elem->mode, &result_size,\n \t\t\t\t\t\t   NULL, NULL);\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 7eb66a417aa..0c1f76d5fe9 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -53,7 +53,7 @@ static int check_removed(const struct index_state *istate, const struct cache_en\n \t\t * a directory --- the blob was removed!\n \t\t */\n \t\tif (!S_ISGITLINK(ce->ce_mode) &&\n-\t\t    resolve_gitlink_ref(ce->name, \"HEAD\", &sub))\n+\t\t    resolve_gitlink_ref(ce->name, \"HEAD\", &sub, NULL))\n \t\t\treturn 1;\n \t}\n \treturn 0;\ndiff --git a/dir.c b/dir.c\nindex d7cfb08e441..33368b399e2 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3255,7 +3255,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)\n \tstruct object_id submodule_head;\n \n \tif ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&\n-\t    !resolve_gitlink_ref(path->buf, \"HEAD\", &submodule_head)) {\n+\t    !resolve_gitlink_ref(path->buf, \"HEAD\", &submodule_head, NULL)) {\n \t\t/* Do not descend and nuke a nested git work tree. */\n \t\tif (kept_up)\n \t\t\t*kept_up = 1;\ndiff --git a/object-file.c b/object-file.c\nindex 5b270f046dd..a10a49d7a2a 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2526,7 +2526,7 @@ int index_path(struct index_state *istate, struct object_id *oid,\n \t\tstrbuf_release(&sb);\n \t\tbreak;\n \tcase S_IFDIR:\n-\t\treturn resolve_gitlink_ref(path, \"HEAD\", oid);\n+\t\treturn resolve_gitlink_ref(path, \"HEAD\", oid, NULL);\n \tdefault:\n \t\treturn error(_(\"%s: unsupported file type\"), path);\n \t}\ndiff --git a/read-cache.c b/read-cache.c\nindex 4de207752dc..4b68bd0c9a6 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -285,7 +285,7 @@ static int ce_compare_gitlink(const struct cache_entry *ce)\n \t *\n \t * If so, we consider it always to match.\n \t */\n-\tif (resolve_gitlink_ref(ce->name, \"HEAD\", &oid) < 0)\n+\tif (resolve_gitlink_ref(ce->name, \"HEAD\", &oid, NULL) < 0)\n \t\treturn 0;\n \treturn !oideq(&oid, &ce->oid);\n }\n@@ -776,7 +776,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \n \tnamelen = strlen(path);\n \tif (S_ISDIR(st_mode)) {\n-\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid) < 0)\n+\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid, NULL) < 0)\n \t\t\treturn error(_(\"'%s' does not have a commit checked out\"), path);\n \t\twhile (namelen && path[namelen-1] == '/')\n \t\t\tnamelen--;\ndiff --git a/refs.c b/refs.c\nindex 90bcb271687..d72015c95e9 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1784,19 +1784,21 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n }\n \n int resolve_gitlink_ref(const char *submodule, const char *refname,\n-\t\t\tstruct object_id *oid)\n+\t\t\tstruct object_id *oid, const char **referent_out)\n {\n \tstruct ref_store *refs;\n \tint flags;\n+\tconst char *referent;\n \n \trefs = get_submodule_ref_store(submodule);\n \n \tif (!refs)\n \t\treturn -1;\n-\n-\tif (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||\n-\t    is_null_oid(oid))\n+\treferent = refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags);\n+\tif (!referent || is_null_oid(oid))\n \t\treturn -1;\n+\tif (referent_out)\n+\t\t*referent_out = referent;\n \treturn 0;\n }\n \ndiff --git a/refs.h b/refs.h\nindex 47cb9edbaa8..c5ae76654c5 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -136,9 +136,12 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled);\n  * submodule (which must be non-NULL). If the resolution is\n  * successful, return 0 and set oid to the name of the object;\n  * otherwise, return a non-zero value.\n+ *\n+ * FIXME: Return \"referent\" just like refs_resolve_ref_unsafe(). This will be\n+ * safe to do once we merge resolve_gitlink_ref() into master.\n  */\n int resolve_gitlink_ref(const char *submodule, const char *refname,\n-\t\t\tstruct object_id *oid);\n+\t\t\tstruct object_id *oid, const char **referent);\n \n /*\n  * Return true iff abbrev_name is a possible abbreviation for\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 8a454e03bff..43a1a056a19 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -2202,7 +2202,8 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,\n \n \tif (S_ISGITLINK(ce->ce_mode)) {\n \t\tstruct object_id oid;\n-\t\tint sub_head = resolve_gitlink_ref(ce->name, \"HEAD\", &oid);\n+\t\tint sub_head =\n+\t\t\tresolve_gitlink_ref(ce->name, \"HEAD\", &oid, NULL);\n \t\t/*\n \t\t * If we are not going to update the submodule, then\n \t\t * we don't care.\n-- \ngitgitgadget\n\n"},{"id":"462111","messageId":"6f7f2f9a3f19b6d874d644b7fb7feb3a72fc6227.1661806456.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.git.git.1661806456.gitgitgadget@gmail.com","subject":"[PATCH 6/6] clone, submodule update: check out branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-29T20:54:16Z","receivedAt":"2022-08-29T20:54:34Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nTeach \"git submodule update\" to update submodules by creating and\nchecking out the current superproject branch when\n\"submodule.propagateBranches=true\". \"git clone --recurse-submodules\"\nalso learns this trick because it is implemented with \"git submodule\nupdate --recursive\".\n\nWith \"submodule.propagateBranches=true\", submodules are cloned with\n\"--detach\" so that they do not contain branches from their upstream.\nThis prevents conflicts between branch names from the superproject and\nthe branch names from the submodule's upstream. Arguably, \"--detach\"\nshould also be the default for \"submodule.propagateBranches=false\"\nsince it doesn't make sense to create a submodule branch when the\nsubmodule is always expected to be in detached HEAD. But, to be\nconservative, this commit does not change the behavior of\n\"submodule.propagateBranches=false\".\n\n\"git submodule update\" tries to create the branch as long as it is not\ncurrently checked out, thus it will fail if the submodule has the\nbranch, but it is not checked out. This is fine because the main purpose\nof \"git submodule update\" is to clone new submodules (which have no\nbranches, and will never have this problem). \"git checkout\" with\n\"submodule.propagateBranches\" will cover the use case of recursively\nchecking out an existing branch.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c | 28 ++++++++++++++++++++++++++--\n t/t5617-clone-submodules.sh | 34 ++++++++++++++++++++++++++++++++++\n t/t7406-submodule-update.sh | 22 ++++++++++++++++++++++\n 3 files changed, 82 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex cbf6bda4850..7eb2c45900e 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1695,6 +1695,9 @@ static int clone_submodule(struct module_clone_data *clone_data)\n \t\t\tstrvec_push(&cp.args, clone_data->single_branch ?\n \t\t\t\t    \"--single-branch\" :\n \t\t\t\t    \"--no-single-branch\");\n+\t\tif (the_repository->settings.submodule_propagate_branches)\n+\t\t\tstrvec_push(&cp.args, \"--detach\");\n+\n \n \t\tstrvec_push(&cp.args, \"--\");\n \t\tstrvec_push(&cp.args, clone_data->url);\n@@ -1733,6 +1736,9 @@ static int clone_submodule(struct module_clone_data *clone_data)\n \tif (error_strategy)\n \t\tgit_config_set_in_file(p, \"submodule.alternateErrorStrategy\",\n \t\t\t\t       error_strategy);\n+\tif (the_repository->settings.submodule_propagate_branches)\n+\t\tgit_config_set_in_file(p, \"submodule.propagateBranches\",\n+\t\t\t\t       \"true\");\n \n \tfree(sm_alternate);\n \tfree(error_strategy);\n@@ -1792,6 +1798,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n \tmemset(&filter_options, 0, sizeof(filter_options));\n \targc = parse_options(argc, argv, prefix, module_clone_options,\n \t\t\t     git_submodule_helper_usage, 0);\n+\tprepare_repo_settings(the_repository);\n \n \tclone_data.dissociate = !!dissociate;\n \tclone_data.quiet = !!quiet;\n@@ -1872,6 +1879,7 @@ struct submodule_update_clone {\n struct update_data {\n \tconst char *prefix;\n \tconst char *displaypath;\n+\tconst char *super_branch;\n \tenum submodule_update_type update_default;\n \tstruct object_id suboid;\n \tstruct string_list references;\n@@ -2206,6 +2214,8 @@ static int run_update_command(struct update_data *ud, int subforce)\n \t\tstrvec_pushl(&cp.args, \"checkout\", \"-q\", NULL);\n \t\tif (subforce)\n \t\t\tstrvec_push(&cp.args, \"-f\");\n+\t\tif (ud->super_branch)\n+\t\t\tstrvec_pushl(&cp.args, \"-b\", ud->super_branch, NULL);\n \t\tbreak;\n \tcase SM_UPDATE_REBASE:\n \t\tcp.git_cmd = 1;\n@@ -2456,6 +2466,7 @@ static void update_data_to_args(struct update_data *update_data, struct strvec *\n static int update_submodule(struct update_data *update_data)\n {\n \tint submodule_up_to_date;\n+\tconst char *submodule_head = NULL;\n \n \tensure_core_worktree(update_data->sm_path);\n \n@@ -2469,7 +2480,7 @@ static int update_submodule(struct update_data *update_data)\n \tif (update_data->just_cloned)\n \t\toidcpy(&update_data->suboid, null_oid());\n \telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n-\t\t\t\t     &update_data->suboid, NULL))\n+\t\t\t\t     &update_data->suboid, &submodule_head))\n \t\tdie(_(\"Unable to find current revision in submodule path '%s'\"),\n \t\t\tupdate_data->displaypath);\n \n@@ -2493,7 +2504,13 @@ static int update_submodule(struct update_data *update_data)\n \t\tfree(remote_ref);\n \t}\n \n-\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n+\tif (update_data->super_branch &&\n+\t    submodule_head &&\n+\t    !skip_prefix(submodule_head, \"refs/heads/\", &submodule_head))\n+\t\tsubmodule_up_to_date = !strcmp(update_data->super_branch, submodule_head);\n+\telse\n+\t\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n+\n \tif (!submodule_up_to_date || update_data->force)\n \t\tif (run_update_procedure(update_data))\n \t\t\treturn 1;\n@@ -2551,6 +2568,12 @@ static int update_submodules(struct update_data *update_data)\n \t\tgoto cleanup;\n \t}\n \n+\tif (the_repository->settings.submodule_propagate_branches) {\n+\t\tstruct branch *current_branch = branch_get(NULL);\n+\t\tif (current_branch)\n+\t\t\tupdate_data->super_branch = current_branch->name;\n+\t}\n+\n \tfor (i = 0; i < suc.update_clone_nr; i++) {\n \t\tstruct update_clone_data ucd = suc.update_clone[i];\n \n@@ -2634,6 +2657,7 @@ static int module_update(int argc, const char **argv, const char *prefix)\n \tmemset(&filter_options, 0, sizeof(filter_options));\n \targc = parse_options(argc, argv, prefix, module_update_options,\n \t\t\t     git_submodule_helper_usage, 0);\n+\tprepare_repo_settings(the_repository);\n \n \tif (opt.require_init)\n \t\topt.init = 1;\ndiff --git a/t/t5617-clone-submodules.sh b/t/t5617-clone-submodules.sh\nindex b5c66cb18cb..215fb02e9fb 100755\n--- a/t/t5617-clone-submodules.sh\n+++ b/t/t5617-clone-submodules.sh\n@@ -12,10 +12,17 @@ pwd=$(pwd)\n test_expect_success 'setup' '\n \tgit checkout -b main &&\n \ttest_commit commit1 &&\n+\tmkdir subsub &&\n+\t(\n+\t\tcd subsub &&\n+\t\tgit init &&\n+\t\ttest_commit subsubcommit1\n+\t) &&\n \tmkdir sub &&\n \t(\n \t\tcd sub &&\n \t\tgit init &&\n+\t\tgit submodule add \"file://$pwd/subsub\" subsub &&\n \t\ttest_commit subcommit1 &&\n \t\tgit tag sub_when_added_to_super &&\n \t\tgit branch other\n@@ -106,4 +113,31 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule\n \ttest_cmp_config -C super_clone3/sub false --default false remote.origin.promisor\n '\n \n+test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '\n+\tgit -C sub checkout -b not-main &&\n+\tgit -C subsub checkout -b not-main &&\n+\tgit clone --recurse-submodules \\\n+\t\t-c submodule.propagateBranches=true \\\n+\t\t\"file://$pwd/.\" super_clone4 &&\n+\n+\t# Assert that each repo is pointing to \"main\"\n+\tfor REPO in \"super_clone4\" \"super_clone4/sub\" \"super_clone4/sub/subsub\"\n+\tdo\n+\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n+\t    test $HEAD_BRANCH = \"refs/heads/main\" || return 1\n+\tdone &&\n+\n+\t# Assert that the submodule branches are pointing to the right revs\n+\tEXPECT_SUB_OID=\"$(git -C super_clone4 rev-parse :sub)\" &&\n+\tACTUAL_SUB_OID=\"$(git -C super_clone4/sub rev-parse refs/heads/main)\" &&\n+\ttest $EXPECT_SUB_OID = $ACTUAL_SUB_OID &&\n+\tEXPECT_SUBSUB_OID=\"$(git -C super_clone4/sub rev-parse :subsub)\" &&\n+\tACTUAL_SUBSUB_OID=\"$(git -C super_clone4/sub/subsub rev-parse refs/heads/main)\" &&\n+\ttest $EXPECT_SUBSUB_OID = $ACTUAL_SUBSUB_OID &&\n+\n+\t# Assert that the submodules do not have branches from their upstream\n+\ttest_must_fail git -C super_clone4/sub rev-parse not-main &&\n+\ttest_must_fail git -C super_clone4/sub/subsub rev-parse not-main\n+'\n+\n test_done\ndiff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\nindex 6cc07460dd2..00a6fec8912 100755\n--- a/t/t7406-submodule-update.sh\n+++ b/t/t7406-submodule-update.sh\n@@ -1178,4 +1178,26 @@ test_expect_success 'submodule update --recursive skip submodules with strategy=\n \ttest_cmp expect.err actual.err\n '\n \n+test_expect_success 'submodule update with submodule.propagateBranches checks out branches' '\n+\ttest_when_finished \"rm -fr top-cloned\" &&\n+\tcp -r top-clean top-cloned &&\n+\n+\t# Create a new upstream submodule\n+\tgit init middle2 &&\n+\ttest_commit -C middle2 \"middle2\" &&\n+\tgit -C top submodule add ../middle2 middle2 &&\n+\tgit -C top commit -m \"add middle2\" &&\n+\n+\tgit -C top-cloned checkout -b \"new-branch\" &&\n+\tgit -C top-cloned pull origin main &&\n+\ttest_config -C top-cloned submodule.propagateBranches true &&\n+\tgit -C top-cloned submodule update --recursive &&\n+\n+\tfor REPO in \"top-cloned/middle2\" \"top-cloned/middle\" \"top-cloned/middle/bottom\"\n+\tdo\n+\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n+\t    test $HEAD_BRANCH = \"refs/heads/new-branch\" || return 1\n+\tdone\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"462112","messageId":"513d18e56d3b60d1b522af212fb8b7be232992d4.1661806456.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.git.git.1661806456.gitgitgadget@gmail.com","subject":"[PATCH 5/6] submodule--helper: refactor up-to-date criterion","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-08-29T20:54:15Z","receivedAt":"2022-08-29T20:54:36Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nRefactor builtin/submodule--helper.c:update_submodule() to check if the\nsubmodule is up to date using a variable instead of checking the oids\ndirectly. In a subsequent commit, we will expand the definition of \"up\nto date\" to include checked out branches.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex dd00a0db522..cbf6bda4850 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2455,6 +2455,8 @@ static void update_data_to_args(struct update_data *update_data, struct strvec *\n \n static int update_submodule(struct update_data *update_data)\n {\n+\tint submodule_up_to_date;\n+\n \tensure_core_worktree(update_data->sm_path);\n \n \tupdate_data->displaypath = get_submodule_displaypath(\n@@ -2491,7 +2493,8 @@ static int update_submodule(struct update_data *update_data)\n \t\tfree(remote_ref);\n \t}\n \n-\tif (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)\n+\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n+\tif (!submodule_up_to_date || update_data->force)\n \t\tif (run_update_procedure(update_data))\n \t\t\treturn 1;\n \n-- \ngitgitgadget\n\n"},{"id":"462127","messageId":"62ae295e-64ea-eaaf-646f-030e7b9028a3@gmail.com","threadId":"58375","inReplyTo":"936e237c716bddf3a5889829e0c907e881736336.1661806456.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/6] clone: teach --detach option","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2022-08-30T04:02:44Z","receivedAt":"2022-08-30T04:02:53Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Glen,\n\nLe 2022-08-29 à 16:54, Glen Choo via GitGitGadget a écrit :\n> From: Glen Choo <chooglen@google.com>\n> \n> Teach \"git clone\" the \"--detach\" option, which leaves the cloned repo in\n> detached HEAD (like \"git checkout --detach\"). If the clone is not bare,\n> the remote's HEAD branch is also not created (bare clones always copy\n> all remote branches directly to local branches, so the branch is still\n> created in the bare case).\n\nAt first reading I thought you meant the 'origin/HEAD' symref, which is \nnot the case here. I think something like this would maybe be clearer:\n\nIf the clone is not bare, skip the creation of a local branch corresponding to \nthe branch pointed to by the remote's HEAD symref (bare clones...\nto local branches, so that branch ...\n\n(OK this is very verbose but in my opinion it's clearer.)\n\n> This is especially useful in the \"submodule.propagateBranches\" workflow,\n> where the submodule branch names match the superproject's branch names,\n> so it makes no sense to name the branches after the submodule's remote's\n> branches.\n\nWe are just skipping the creation of a single branch here, so it's unclear \nto me which other branches are being talked about in this last paragraph.\nAll remote-tracking branches are unaffected by this flag, no?\n\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  Documentation/git-clone.txt |  7 ++++++-\n>  builtin/clone.c             | 12 +++++++++---\n>  t/t5601-clone.sh            | 22 ++++++++++++++++++++++\n>  3 files changed, 37 insertions(+), 4 deletions(-)\n> \n> diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\n> index 632bd1348ea..a3af90824b6 100644\n> --- a/Documentation/git-clone.txt\n> +++ b/Documentation/git-clone.txt\n> @@ -16,7 +16,7 @@ SYNOPSIS\n>  \t  [--depth <depth>] [--[no-]single-branch] [--no-tags]\n>  \t  [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]\n>  \t  [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]\n> -\t  [--filter=<filter> [--also-filter-submodules]] [--] <repository>\n> +\t  [--filter=<filter> [--also-filter-submodules] [--detach]] [--] <repository>\n>  \t  [<directory>]\n>  \n>  DESCRIPTION\n> @@ -210,6 +210,11 @@ objects from the source repository into a pack in the cloned repository.\n>  \t`--branch` can also take tags and detaches the HEAD at that commit\n>  \tin the resulting repository.\n>  \n> +--detach::\n> +\tIf the cloned repository's HEAD points to a branch, point the newly\n> +\tcreated HEAD to the branch's commit instead of the branch itself. In a\n> +\tnon-bare repository, the branch will not be created.\n\nAgain, I think the wording could be improved, maybe something along those lines:\n\nIf the cloned repository's HEAD points to a branch, detach the newly created HEAD\nat the commit at the tip of that branch. Additionnally, in a non-bare repository,\nskip creating a corresponding local branch.\n\n> +\n>  -u <upload-pack>::\n>  --upload-pack <upload-pack>::\n>  \tWhen given, and the repository to clone from is accessed\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index c4ff4643ecd..1bc1807360e 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -77,6 +77,7 @@ static int option_filter_submodules = -1;    /* unspecified */\n>  static int config_filter_submodules = -1;    /* unspecified */\n>  static struct string_list server_options = STRING_LIST_INIT_NODUP;\n>  static int option_remote_submodules;\n> +static int option_detach;\n>  \n>  static int recurse_submodules_cb(const struct option *opt,\n>  \t\t\t\t const char *arg, int unset)\n> @@ -160,6 +161,8 @@ static struct option builtin_clone_options[] = {\n>  \t\t    N_(\"any cloned submodules will use their remote-tracking branch\")),\n>  \tOPT_BOOL(0, \"sparse\", &option_sparse_checkout,\n>  \t\t    N_(\"initialize sparse-checkout file to include only files at root\")),\n> +\tOPT_BOOL(0, \"detach\", &option_detach,\n> +\t\t N_(\"detach HEAD and don't create branch\")),\n\nmaybe \"don't create any local branch\" ?\n\n>  \tOPT_END()\n>  };\n>  \n> @@ -607,10 +610,12 @@ static void update_remote_refs(const struct ref *refs,\n>  }\n>  \n>  static void update_head(const struct ref *our, const struct ref *remote,\n> -\t\t\tconst char *unborn, const char *msg)\n> +\t\t\tconst char *unborn, int should_detach,\n> +\t\t\tconst char *msg)\n>  {\n>  \tconst char *head;\n> -\tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n> +\tif (our && !should_detach &&\n> +\t    skip_prefix(our->name, \"refs/heads/\", &head)) {\n>  \t\t/* Local default branch link */\n>  \t\tif (create_symref(\"HEAD\", our->name, NULL) < 0)\n>  \t\t\tdie(_(\"unable to update HEAD\"));\n\nOK, so the addition of that condition means that if --detach was given, we now\ngo into the 'else if (our)' branch, as long as 'our' is non-null, which means\nthat the remote's HEAD points to a branch or we gave --branch. This makes sense.\nIf the remote's HEAD does not point to a branch and we did not give --branch,\nthen we go into 'else if (remote)', as before. \n\n> @@ -1339,7 +1344,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \t\t\t   branch_top.buf, reflog_msg.buf, transport,\n>  \t\t\t   !is_local);\n>  \n> -\tupdate_head(our_head_points_at, remote_head, unborn_head, reflog_msg.buf);\n> +\tupdate_head(our_head_points_at, remote_head, unborn_head,\n> +\t\t    option_detach, reflog_msg.buf);\n>  \n>  \t/*\n>  \t * We want to show progress for recursive submodule clones iff\n> diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh\n> index cf3be0584f4..1e7e5143a76 100755\n> --- a/t/t5601-clone.sh\n> +++ b/t/t5601-clone.sh\n> @@ -333,6 +333,28 @@ test_expect_success 'clone checking out a tag' '\n>  \ttest_cmp fetch.expected fetch.actual\n>  '\n>  \n> +test_expect_success '--detach detaches and does not create branch' '\n> +\ttest_when_finished \"rm -fr dst\" &&\n> +\tgit clone --detach src dst &&\n> +\t(\n> +\t\tcd dst &&\n> +\t\ttest_must_fail git rev-parse main &&\n> +\t\ttest_must_fail git symbolic-ref HEAD &&\n> +\t\ttest_cmp_rev HEAD refs/remotes/origin/HEAD\n> +\t)\n> +'\n> +\n> +test_expect_success '--detach with --bare detaches but creates branch' '\n> +\ttest_when_finished \"rm -fr dst\" &&\n> +\tgit clone --bare --detach src dst &&\n> +\t(\n> +\t\tcd dst &&\n> +\t\tgit rev-parse main &&\n> +\t\ttest_must_fail git symbolic-ref HEAD &&\n> +\t\ttest_cmp_rev HEAD refs/heads/main\n> +\t)\n> +'\n> +\n\nTests look good.\n"},{"id":"462128","messageId":"01a3cb4a-7af1-3723-dd91-e6218b7c10c5@gmail.com","threadId":"58375","inReplyTo":"35f068eb0318653ee0474b30d3420d210f47f8e4.1661806456.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/6] repo-settings: add submodule_propagate_branches","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2022-08-30T04:02:56Z","receivedAt":"2022-08-30T04:03:04Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"\n\nLe 2022-08-29 à 16:54, Glen Choo via GitGitGadget a écrit :\n> From: Glen Choo <chooglen@google.com>\n> \n> When processes recurse into submodules, the child processes have to\n> use the same value of \"submodule.propagateBranches\" as the parent\n> process regardless of whether the process is spawned in the superproject\n> or submodule, otherwise the behavior may be inconsistent if the\n> repositories don't agree on the config.\n> \n> We haven't needed a way to propagate the config because because the only\n\n\"because because\"\n\n> command that reads \"submodule.propagateBranches\" is \"git branch\", which\n> only has one mode of operation with \"--recurse-submodules\". However, a\n> future commit will teach \"submodule.propagateBranches\" to \"git submodule\n> update\", making this necessary.\n> \n> Propagate \"submodule.propagateBranches\" to child processes by adding a\n> corresponding GIT_INTERNAL_* environment variable and repository\n> setting, and setting the environment variable inside\n> prepare_submodule_repo_env(). Then, refactor builtin/branch.c to read\n> the repository setting.\n> \n> Using an internal environment variable is a potentially leaky\n> abstraction because environment variables can come from sources besides\n> the parent process. A more robust solution would be to teach Git that\n> the repository is a submodule and to only read\n> \"submodule.propagateBranches\" from the superproject config. There is WIP\n> for this on the ML [1].\n> \n> Another alternative would be to pass \"-c submodule.propagateBranches\" to\n> all child processes. This is error-prone because many different\n> processes are invoked directly or indirectly by \"git submodule update\"\n> (e.g. \"git submodule--helper clone\", \"git clone\", \"git checkout\"). With\n> an environment variable, we can avoid this work because\n> prepare_submodule_repo_env() is already called for submodule child\n> processes.\n\nI think this is a good justification. I agree adding '-c' everywhere would be\nerror-prone.\n\n"},{"id":"462129","messageId":"2a887485-a335-cc73-493a-5b835171db8a@gmail.com","threadId":"58375","inReplyTo":"d8a2faf9ce7de2562424181109120a6d84b0e470.1661806456.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/6] t5617: drop references to remote-tracking branches","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2022-08-30T04:03:03Z","receivedAt":"2022-08-30T04:03:09Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Glen,\n\nLe 2022-08-29 à 16:54, Glen Choo via GitGitGadget a écrit :\n> From: Glen Choo <chooglen@google.com>\n> \n> It has included submodule cloning tests without remote-tracking branches\n> tests since f05da2b48b (clone, submodule: pass partial clone filters to\n> submodules, 2022-02-04) at least. Rename it accordingly so that we can\n> put future submodule cloning tests there.\n\nI think it was named \"*-remote\" because it was introduced when \n'git clone --remote-submodules' was added in 4c6910163a \n(clone: add `--remote-submodules` flag, 2019-05-19).\n\nIn any case, nice clean-up!\n"},{"id":"462130","messageId":"4e63e180-d275-83c2-d498-47bb0cfd160d@gmail.com","threadId":"58375","inReplyTo":"6f7f2f9a3f19b6d874d644b7fb7feb3a72fc6227.1661806456.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 6/6] clone, submodule update: check out branches","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2022-08-30T04:03:11Z","receivedAt":"2022-08-30T04:03:21Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Glen,\n\nLe 2022-08-29 à 16:54, Glen Choo via GitGitGadget a écrit :\n> From: Glen Choo <chooglen@google.com>\n> \n> Teach \"git submodule update\" to update submodules by creating and\n> checking out the current superproject branch when\n> \"submodule.propagateBranches=true\". \"git clone --recurse-submodules\"\n> also learns this trick because it is implemented with \"git submodule\n> update --recursive\".\n\nOK. Maybe a more descriptive title would then be:\n\nclone, submodule update: create and check out submodule branches\n\n?\n\nAnother thing, 'git pull --recurse-submodules' is also implemented using\n'git submodule update --recursive'. But I don't think we want 'git pull'\nto start creating new branches in submodules, even with submodule.propagateBranches=true\n(though I haven't thought about it very hard). So maybe adding a word about\nthat would be nice.\n\n> \n> With \"submodule.propagateBranches=true\", submodules are cloned with\n> \"--detach\" so that they do not contain branches from their upstream.\n\nWe usually use the present tense to talk about the current state of the code base,\nand then the imperative to order to codebase to improve itself;\nhere you already used the imperative \"teach\" in the previous paragraph,\nso I'm assuming you are now talking about the new state of the code.\nMaybe just adding \"now\" i.e. \"submodules are now cloned\" would help\nreaders ?\n\n> This prevents conflicts between branch names from the superproject and\n> the branch names from the submodule's upstream. Arguably, \"--detach\"\n> should also be the default for \"submodule.propagateBranches=false\"\n> since it doesn't make sense to create a submodule branch when the\n> submodule is always expected to be in detached HEAD. But, to be\n> conservative, this commit does not change the behavior of\n> \"submodule.propagateBranches=false\".\n\nI agree that it would be \"cleaner\" to make the change also for\n\"submodule.propagateBranches=false\" eventually, but... let's not\nchange things just to change things :)\n\n> \"git submodule update\" tries to create the branch as long as it is not\n> currently checked out, thus it will fail if the submodule has the\n> branch, but it is not checked out. This is fine because the main purpose\n> of \"git submodule update\" is to clone new submodules (which have no\n> branches, and will never have this problem). \"git checkout\" with\n> \"submodule.propagateBranches\" will cover the use case of recursively\n> checking out an existing branch.\n\nI guess you mean \"in a future series\" for the last sentence ? FWIW I still have\nyour RFC from last Febryary about that [1] in my \"unread Git mailing list\" folder,\nI always seem to lack the time to sit down and read it through, sorry!\nIncidentally, I notice you did not link to it in the cover letter, \nany reasoon why?\n\n[1] https://lore.kernel.org/git/20220209065236.36494-1-chooglen@google.com/\n\nStepping back a bit, you write \"thus it will fail if the submodule has the\nbranch, but it is not checked out.\" If I read your patch correctly, this is\nimplicit in that 'git checkout -b super-branch' that is ran by 'run_update_command'\nwill error out if the branch already exists, right ? \n\nIs there anything more we should do in that case ? \nShould we remind the user, something like\n\"you have submodule.propagateBranches set, but the branch 'super-branch' already\nexists in submodule 'that-sub'\" ? \n\nI'm trying to think of a scenario in which this could happen...\n\nSay a user:\n1. clones a superproject with --recurse-submodules, but without 'submodule.propagateBranches'\n2. runs 'git checkout -b topic' in the superproject\n3. runs 'git branch topic' in the submodule\n4. runs 'git submodule update' with 'submodule.propagateBranches' in the superproject\n\nThis fails:\n\nfatal: a branch named 'topic' already exists\nfatal: Unable to checkout 'deadbeef' in submodule path 'sub'\n\nDo we need a more specific message ? I'm not sure.\n\n> \n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  builtin/submodule--helper.c | 28 ++++++++++++++++++++++++++--\n>  t/t5617-clone-submodules.sh | 34 ++++++++++++++++++++++++++++++++++\n>  t/t7406-submodule-update.sh | 22 ++++++++++++++++++++++\n>  3 files changed, 82 insertions(+), 2 deletions(-)\n> \n> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\n> index cbf6bda4850..7eb2c45900e 100644\n> --- a/builtin/submodule--helper.c\n> +++ b/builtin/submodule--helper.c\n> @@ -1695,6 +1695,9 @@ static int clone_submodule(struct module_clone_data *clone_data)\n>  \t\t\tstrvec_push(&cp.args, clone_data->single_branch ?\n>  \t\t\t\t    \"--single-branch\" :\n>  \t\t\t\t    \"--no-single-branch\");\n> +\t\tif (the_repository->settings.submodule_propagate_branches)\n> +\t\t\tstrvec_push(&cp.args, \"--detach\");\n> +\n>  \n>  \t\tstrvec_push(&cp.args, \"--\");\n>  \t\tstrvec_push(&cp.args, clone_data->url);\n> @@ -1733,6 +1736,9 @@ static int clone_submodule(struct module_clone_data *clone_data)\n>  \tif (error_strategy)\n>  \t\tgit_config_set_in_file(p, \"submodule.alternateErrorStrategy\",\n>  \t\t\t\t       error_strategy);\n> +\tif (the_repository->settings.submodule_propagate_branches)\n> +\t\tgit_config_set_in_file(p, \"submodule.propagateBranches\",\n> +\t\t\t\t       \"true\");\n\nWhy do we need to set that in the config of the submodule ? I'm guessing this \nis so that the new code also works for nested submodules, right ? \n\nI'm thinking about a user that would alternate between 'submodule.propagateBranches=true' and 'false'.\nMaybe they sometimes have to work on the superproject and the submodule(s), sometimes \nonly in the superproject. If they want to deactivate submodule.propagateBranches, would they have to\nremember to also deactivate it in all submodules, in case of nested submodules ?...  if so,\nthis is a little unfortunate. But I _think_ they wouldn't have to, because as long as \nit's false in the superproject config, then we won't get into the new code at all when running\nin the top level superproject...\n\n>  \tfree(sm_alternate);\n>  \tfree(error_strategy);\n> @@ -1792,6 +1798,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n>  \tmemset(&filter_options, 0, sizeof(filter_options));\n>  \targc = parse_options(argc, argv, prefix, module_clone_options,\n>  \t\t\t     git_submodule_helper_usage, 0);\n> +\tprepare_repo_settings(the_repository);\n>  \n>  \tclone_data.dissociate = !!dissociate;\n>  \tclone_data.quiet = !!quiet;\n> @@ -1872,6 +1879,7 @@ struct submodule_update_clone {\n>  struct update_data {\n>  \tconst char *prefix;\n>  \tconst char *displaypath;\n> +\tconst char *super_branch;\n>  \tenum submodule_update_type update_default;\n>  \tstruct object_id suboid;\n>  \tstruct string_list references;\n> @@ -2206,6 +2214,8 @@ static int run_update_command(struct update_data *ud, int subforce)\n>  \t\tstrvec_pushl(&cp.args, \"checkout\", \"-q\", NULL);\n>  \t\tif (subforce)\n>  \t\t\tstrvec_push(&cp.args, \"-f\");\n> +\t\tif (ud->super_branch)\n> +\t\t\tstrvec_pushl(&cp.args, \"-b\", ud->super_branch, NULL);\n>  \t\tbreak;\n>  \tcase SM_UPDATE_REBASE:\n>  \t\tcp.git_cmd = 1;\n> @@ -2456,6 +2466,7 @@ static void update_data_to_args(struct update_data *update_data, struct strvec *\n>  static int update_submodule(struct update_data *update_data)\n>  {\n>  \tint submodule_up_to_date;\n> +\tconst char *submodule_head = NULL;\n>  \n>  \tensure_core_worktree(update_data->sm_path);\n>  \n> @@ -2469,7 +2480,7 @@ static int update_submodule(struct update_data *update_data)\n>  \tif (update_data->just_cloned)\n>  \t\toidcpy(&update_data->suboid, null_oid());\n>  \telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n> -\t\t\t\t     &update_data->suboid, NULL))\n> +\t\t\t\t     &update_data->suboid, &submodule_head))\n>  \t\tdie(_(\"Unable to find current revision in submodule path '%s'\"),\n>  \t\t\tupdate_data->displaypath);\n>  \n> @@ -2493,7 +2504,13 @@ static int update_submodule(struct update_data *update_data)\n>  \t\tfree(remote_ref);\n>  \t}\n>  \n> -\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n> +\tif (update_data->super_branch &&\n> +\t    submodule_head &&\n> +\t    !skip_prefix(submodule_head, \"refs/heads/\", &submodule_head))\n> +\t\tsubmodule_up_to_date = !strcmp(update_data->super_branch, submodule_head);\n\nI'm not sure I understand this logic. We want to change the 'submodule_up_to_date' boolean,\nso that we compare branch names instead of oid's, and we do that only if:\n\n1. we are running with 'propagateBranches=true' (so update_data->super_branch will be set to the superproject's branch)\n2. a ref is checked out in the submodule (so submodule_head will hold its name)\n3. it's not a branch (so skip_prefix will return 0, and !skip_prefix will be 1). \n   In that case it must be simply \"HEAD\", i.e. the submodule's HEAD is detached.\n\nWhy do we need (2. + 3.) ? \n\nIf branch 'foo' is currently checked out in the superproject, and\nbranch 'bar' is currently checked out in the submodule, and someone\nruns 'git -c propagateBranches=true submodule update', wouldn't they expect\nthat 'bar' be checked out in the submodule ? Maybe not, but the commit message\nand the tests should be more explicit about the expected behaviour in this case, I think.\n\nAnd thinking about it more, won't this:\n\n    submodule_up_to_date = !strcmp(update_data->super_branch, submodule_head);\n\nalways be false, since we already know that submodule_head is \"HEAD\" ?... \nUnless I'm confused...\n \n> +\telse\n> +\t\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n> +\n>  \tif (!submodule_up_to_date || update_data->force)\n>  \t\tif (run_update_procedure(update_data))\n>  \t\t\treturn 1;\n> @@ -2551,6 +2568,12 @@ static int update_submodules(struct update_data *update_data)\n>  \t\tgoto cleanup;\n>  \t}\n>  \n> +\tif (the_repository->settings.submodule_propagate_branches) {\n> +\t\tstruct branch *current_branch = branch_get(NULL);\n> +\t\tif (current_branch)\n> +\t\t\tupdate_data->super_branch = current_branch->name;\n\nOK, so this condition means that super_branch won't get set if we are not\ncurrently on a branch, i.e. we are in detached HEAD. This makes sense as there\nwould be no branch to propagate. Do we need a test for this ? maybe a case where\nwe clone with '--recurse-submodules --branch some-tag' ?\n\n> +\t}\n> +\n>  \tfor (i = 0; i < suc.update_clone_nr; i++) {\n>  \t\tstruct update_clone_data ucd = suc.update_clone[i];\n>  \n> @@ -2634,6 +2657,7 @@ static int module_update(int argc, const char **argv, const char *prefix)\n>  \tmemset(&filter_options, 0, sizeof(filter_options));\n>  \targc = parse_options(argc, argv, prefix, module_update_options,\n>  \t\t\t     git_submodule_helper_usage, 0);\n> +\tprepare_repo_settings(the_repository);\n>  \n>  \tif (opt.require_init)\n>  \t\topt.init = 1;\n> diff --git a/t/t5617-clone-submodules.sh b/t/t5617-clone-submodules.sh\n> index b5c66cb18cb..215fb02e9fb 100755\n> --- a/t/t5617-clone-submodules.sh\n> +++ b/t/t5617-clone-submodules.sh\n> @@ -12,10 +12,17 @@ pwd=$(pwd)\n>  test_expect_success 'setup' '\n>  \tgit checkout -b main &&\n>  \ttest_commit commit1 &&\n> +\tmkdir subsub &&\n> +\t(\n> +\t\tcd subsub &&\n> +\t\tgit init &&\n> +\t\ttest_commit subsubcommit1\n> +\t) &&\n>  \tmkdir sub &&\n>  \t(\n>  \t\tcd sub &&\n>  \t\tgit init &&\n> +\t\tgit submodule add \"file://$pwd/subsub\" subsub &&\n>  \t\ttest_commit subcommit1 &&\n>  \t\tgit tag sub_when_added_to_super &&\n>  \t\tgit branch other\n> @@ -106,4 +113,31 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule\n>  \ttest_cmp_config -C super_clone3/sub false --default false remote.origin.promisor\n>  '\n>  \n> +test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '\n> +\tgit -C sub checkout -b not-main &&\n> +\tgit -C subsub checkout -b not-main &&\n> +\tgit clone --recurse-submodules \\\n> +\t\t-c submodule.propagateBranches=true \\\n> +\t\t\"file://$pwd/.\" super_clone4 &&\n> +\n> +\t# Assert that each repo is pointing to \"main\"\n> +\tfor REPO in \"super_clone4\" \"super_clone4/sub\" \"super_clone4/sub/subsub\"\n> +\tdo\n> +\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n> +\t    test $HEAD_BRANCH = \"refs/heads/main\" || return 1\n> +\tdone &&\n> +\n> +\t# Assert that the submodule branches are pointing to the right revs\n> +\tEXPECT_SUB_OID=\"$(git -C super_clone4 rev-parse :sub)\" &&\n> +\tACTUAL_SUB_OID=\"$(git -C super_clone4/sub rev-parse refs/heads/main)\" &&\n> +\ttest $EXPECT_SUB_OID = $ACTUAL_SUB_OID &&\n> +\tEXPECT_SUBSUB_OID=\"$(git -C super_clone4/sub rev-parse :subsub)\" &&\n> +\tACTUAL_SUBSUB_OID=\"$(git -C super_clone4/sub/subsub rev-parse refs/heads/main)\" &&\n> +\ttest $EXPECT_SUBSUB_OID = $ACTUAL_SUBSUB_OID &&\n> +\n> +\t# Assert that the submodules do not have branches from their upstream\n> +\ttest_must_fail git -C super_clone4/sub rev-parse not-main &&\n> +\ttest_must_fail git -C super_clone4/sub/subsub rev-parse not-main\n> +'\n> +\n>  test_done\n> diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\n> index 6cc07460dd2..00a6fec8912 100755\n> --- a/t/t7406-submodule-update.sh\n> +++ b/t/t7406-submodule-update.sh\n> @@ -1178,4 +1178,26 @@ test_expect_success 'submodule update --recursive skip submodules with strategy=\n>  \ttest_cmp expect.err actual.err\n>  '\n>  \n> +test_expect_success 'submodule update with submodule.propagateBranches checks out branches' '\n> +\ttest_when_finished \"rm -fr top-cloned\" &&\n> +\tcp -r top-clean top-cloned &&\n> +\n> +\t# Create a new upstream submodule\n> +\tgit init middle2 &&\n> +\ttest_commit -C middle2 \"middle2\" &&\n> +\tgit -C top submodule add ../middle2 middle2 &&\n> +\tgit -C top commit -m \"add middle2\" &&\n> +\n> +\tgit -C top-cloned checkout -b \"new-branch\" &&\n> +\tgit -C top-cloned pull origin main &&\n> +\ttest_config -C top-cloned submodule.propagateBranches true &&\n> +\tgit -C top-cloned submodule update --recursive &&\n> +\n> +\tfor REPO in \"top-cloned/middle2\" \"top-cloned/middle\" \"top-cloned/middle/bottom\"\n> +\tdo\n> +\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n> +\t    test $HEAD_BRANCH = \"refs/heads/new-branch\" || return 1\n> +\tdone\n> +'\n> +\n>  test_done\n> \n\nThese tests look good, but maybe more tests would be needed in \nthe light of my comments above... \n\nThanks again for working on improving submodules!\n\nCheers,\n\nPhilippe.\n\n"},{"id":"462249","messageId":"kl6l8rn5cpg3.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58375","inReplyTo":"4e63e180-d275-83c2-d498-47bb0cfd160d@gmail.com","subject":"Re: [PATCH 6/6] clone, submodule update: check out branches","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-08-30T22:54:36Z","receivedAt":"2022-08-30T22:54:52Z","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 2022-08-29 à 16:54, Glen Choo via GitGitGadget a écrit :\n>> From: Glen Choo <chooglen@google.com>\n>> \n>> Teach \"git submodule update\" to update submodules by creating and\n>> checking out the current superproject branch when\n>> \"submodule.propagateBranches=true\". \"git clone --recurse-submodules\"\n>> also learns this trick because it is implemented with \"git submodule\n>> update --recursive\".\n>\n> OK. Maybe a more descriptive title would then be:\n>\n> clone, submodule update: create and check out submodule branches\n>\n> ?\n\nAh, thanks. Your other wording suggestions upthread are also very\nhelpful.\n\n>\n> Another thing, 'git pull --recurse-submodules' is also implemented using\n> 'git submodule update --recursive'. But I don't think we want 'git pull'\n> to start creating new branches in submodules, even with submodule.propagateBranches=true\n> (though I haven't thought about it very hard). So maybe adding a word about\n> that would be nice.\n\nGood point. I thought that `git pull --recurse-submodules` used the\n`--merge` strategy (in which case, it wouldn't matter), but looks like\nit uses the `--checkout` strategy.\n\nI'm quite certain we want to replace this `git pull\n--recurse-submodules` implementation, aka non recursive `git merge` +\n`git submodule update`, with a recursive `git merge` (and possibly\nupdating the worktrees with `git checkout --recurse-submodules`). Since\nthis flag is still experimental and incomplete, I think we have the\nfreedom to say that we won't care about this for now, but either way\nI'll mention this somewhere.\n\n>> \"git submodule update\" tries to create the branch as long as it is not\n>> currently checked out, thus it will fail if the submodule has the\n>> branch, but it is not checked out. This is fine because the main purpose\n>> of \"git submodule update\" is to clone new submodules (which have no\n>> branches, and will never have this problem). \"git checkout\" with\n>> \"submodule.propagateBranches\" will cover the use case of recursively\n>> checking out an existing branch.\n>\n> I guess you mean \"in a future series\" for the last sentence ? FWIW I still have\n> your RFC from last Febryary about that [1] in my \"unread Git mailing list\" folder,\n> I always seem to lack the time to sit down and read it through, sorry!\n> Incidentally, I notice you did not link to it in the cover letter, \n> any reasoon why?\n>\n> [1] https://lore.kernel.org/git/20220209065236.36494-1-chooglen@google.com/\n\nAh, yes, I meant \"in a future series\". I didn't think that the RFC would\nbe very useful to reviewers since the non-RFC version is likely change a\nlot (I've done a lot of tinkering between then and now), and it didn't\ngain much traction in the first place anyway.\n\n>\n> Stepping back a bit, you write \"thus it will fail if the submodule has the\n> branch, but it is not checked out.\" If I read your patch correctly, this is\n> implicit in that 'git checkout -b super-branch' that is ran by 'run_update_command'\n> will error out if the branch already exists, right ? \n>\n> Is there anything more we should do in that case ? \n> Should we remind the user, something like\n> \"you have submodule.propagateBranches set, but the branch 'super-branch' already\n> exists in submodule 'that-sub'\" ? \n>\n> I'm trying to think of a scenario in which this could happen...\n>\n> Say a user:\n> 1. clones a superproject with --recurse-submodules, but without 'submodule.propagateBranches'\n> 2. runs 'git checkout -b topic' in the superproject\n> 3. runs 'git branch topic' in the submodule\n> 4. runs 'git submodule update' with 'submodule.propagateBranches' in the superproject\n>\n> This fails:\n>\n> fatal: a branch named 'topic' already exists\n> fatal: Unable to checkout 'deadbeef' in submodule path 'sub'\n>\n> Do we need a more specific message ? I'm not sure.\n\nHm, you're right, this does seem quite opaque to end users; this means\nnothing if they don't know that `git submodule update` uses `git checkout\n-b` under the hood, which they obviously shouldn't need to know.\n\nThe main simplifying assumption behind `submodule.propagateBranches` (or\nat least, this early version of it) is that users won't interact with\nbranches on the submodules directly outside of very specific scenarios,\ne.g. setting submodule-specific tracking info. So maybe the more\ncomprehensive solution would be to block users from creating branches if\nthe submodule's superproject uses `submodule.propagateBranches`.\n\n>> \n>> Signed-off-by: Glen Choo <chooglen@google.com>\n>> ---\n>>  builtin/submodule--helper.c | 28 ++++++++++++++++++++++++++--\n>>  t/t5617-clone-submodules.sh | 34 ++++++++++++++++++++++++++++++++++\n>>  t/t7406-submodule-update.sh | 22 ++++++++++++++++++++++\n>>  3 files changed, 82 insertions(+), 2 deletions(-)\n>> \n>> diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\n>> index cbf6bda4850..7eb2c45900e 100644\n>> --- a/builtin/submodule--helper.c\n>> +++ b/builtin/submodule--helper.c\n>> @@ -1695,6 +1695,9 @@ static int clone_submodule(struct module_clone_data *clone_data)\n>>  \t\t\tstrvec_push(&cp.args, clone_data->single_branch ?\n>>  \t\t\t\t    \"--single-branch\" :\n>>  \t\t\t\t    \"--no-single-branch\");\n>> +\t\tif (the_repository->settings.submodule_propagate_branches)\n>> +\t\t\tstrvec_push(&cp.args, \"--detach\");\n>> +\n>>  \n>>  \t\tstrvec_push(&cp.args, \"--\");\n>>  \t\tstrvec_push(&cp.args, clone_data->url);\n>> @@ -1733,6 +1736,9 @@ static int clone_submodule(struct module_clone_data *clone_data)\n>>  \tif (error_strategy)\n>>  \t\tgit_config_set_in_file(p, \"submodule.alternateErrorStrategy\",\n>>  \t\t\t\t       error_strategy);\n>> +\tif (the_repository->settings.submodule_propagate_branches)\n>> +\t\tgit_config_set_in_file(p, \"submodule.propagateBranches\",\n>> +\t\t\t\t       \"true\");\n>\n> Why do we need to set that in the config of the submodule ? I'm guessing this \n> is so that the new code also works for nested submodules, right ? \n\nAs long as the value is set in the superproject, the new code still\nworks. This is meant as a way of setting the user's preferred value in\nthe submodule. Although.. `git clone` doesn't automatically set this\nvalue in the superproject - it would have to be read off\nsystem/global/cli config, so maybe it's more coherent to acknowledge\nthat the user's preferred value probably isn't in the repo anyway, and\nmaybe I should just drop this.\n\n>\n> I'm thinking about a user that would alternate between 'submodule.propagateBranches=true' and 'false'.\n> Maybe they sometimes have to work on the superproject and the submodule(s), sometimes \n> only in the superproject. If they want to deactivate submodule.propagateBranches, would they have to\n> remember to also deactivate it in all submodules, in case of nested submodules ?...  if so,\n> this is a little unfortunate. But I _think_ they wouldn't have to, because as long as \n> it's false in the superproject config, then we won't get into the new code at all when running\n> in the top level superproject...\n\nHm, would a user want to alternate in the first place? Maybe? e.g. with\n`git checkout topic`, \"true\" would check out the submodule worktree at\nthe branch (including any WIP you have) but \"false\" would give you the\nworktree specified by the superproject. Both are useful.\n\nThe way it's written now, \"submodule.propagateBranches\" is only passed\nto submodule processes if it is \"true\", so it can be overrided by\nsubmodule config if superproject says \"false\" but submodule says \"true\".\nI should fix that..\n\n>>  \tfree(sm_alternate);\n>>  \tfree(error_strategy);\n>> @@ -1792,6 +1798,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n>>  \tmemset(&filter_options, 0, sizeof(filter_options));\n>>  \targc = parse_options(argc, argv, prefix, module_clone_options,\n>>  \t\t\t     git_submodule_helper_usage, 0);\n>> +\tprepare_repo_settings(the_repository);\n>>  \n>>  \tclone_data.dissociate = !!dissociate;\n>>  \tclone_data.quiet = !!quiet;\n>> @@ -1872,6 +1879,7 @@ struct submodule_update_clone {\n>>  struct update_data {\n>>  \tconst char *prefix;\n>>  \tconst char *displaypath;\n>> +\tconst char *super_branch;\n>>  \tenum submodule_update_type update_default;\n>>  \tstruct object_id suboid;\n>>  \tstruct string_list references;\n>> @@ -2206,6 +2214,8 @@ static int run_update_command(struct update_data *ud, int subforce)\n>>  \t\tstrvec_pushl(&cp.args, \"checkout\", \"-q\", NULL);\n>>  \t\tif (subforce)\n>>  \t\t\tstrvec_push(&cp.args, \"-f\");\n>> +\t\tif (ud->super_branch)\n>> +\t\t\tstrvec_pushl(&cp.args, \"-b\", ud->super_branch, NULL);\n>>  \t\tbreak;\n>>  \tcase SM_UPDATE_REBASE:\n>>  \t\tcp.git_cmd = 1;\n>> @@ -2456,6 +2466,7 @@ static void update_data_to_args(struct update_data *update_data, struct strvec *\n>>  static int update_submodule(struct update_data *update_data)\n>>  {\n>>  \tint submodule_up_to_date;\n>> +\tconst char *submodule_head = NULL;\n>>  \n>>  \tensure_core_worktree(update_data->sm_path);\n>>  \n>> @@ -2469,7 +2480,7 @@ static int update_submodule(struct update_data *update_data)\n>>  \tif (update_data->just_cloned)\n>>  \t\toidcpy(&update_data->suboid, null_oid());\n>>  \telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n>> -\t\t\t\t     &update_data->suboid, NULL))\n>> +\t\t\t\t     &update_data->suboid, &submodule_head))\n>>  \t\tdie(_(\"Unable to find current revision in submodule path '%s'\"),\n>>  \t\t\tupdate_data->displaypath);\n>>  \n>> @@ -2493,7 +2504,13 @@ static int update_submodule(struct update_data *update_data)\n>>  \t\tfree(remote_ref);\n>>  \t}\n>>  \n>> -\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n>> +\tif (update_data->super_branch &&\n>> +\t    submodule_head &&\n>> +\t    !skip_prefix(submodule_head, \"refs/heads/\", &submodule_head))\n>> +\t\tsubmodule_up_to_date = !strcmp(update_data->super_branch, submodule_head);\n>\n> I'm not sure I understand this logic. We want to change the 'submodule_up_to_date' boolean,\n> so that we compare branch names instead of oid's, and we do that only if:\n>\n> 1. we are running with 'propagateBranches=true' (so update_data->super_branch will be set to the superproject's branch)\n> 2. a ref is checked out in the submodule (so submodule_head will hold its name)\n> 3. it's not a branch (so skip_prefix will return 0, and !skip_prefix will be 1). \n>    In that case it must be simply \"HEAD\", i.e. the submodule's HEAD is detached.\n>\n> Why do we need (2. + 3.) ? \n\nOops I got skip_prefix() backwards, 3. should read \"if a branch is\nchecked out\". I'll add a test case for this (I could've sworn I had one\nat some point).\n\n>\n> If branch 'foo' is currently checked out in the superproject, and\n> branch 'bar' is currently checked out in the submodule, and someone\n> runs 'git -c propagateBranches=true submodule update', wouldn't they expect\n> that 'bar' be checked out in the submodule ? Maybe not, but the commit message\n> and the tests should be more explicit about the expected behaviour in this case, I think.\n\nYeah, I'll call it out. I think this case is better addressed by having\n`git checkout topic --recurse-submodules` automatically create \"topic\"\nin the submodules that don't have it. This is one of the reasons why the\n`git checkout` RFC isn't so relevant any more ;).\n\n>> +\telse\n>> +\t\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n>> +\n>>  \tif (!submodule_up_to_date || update_data->force)\n>>  \t\tif (run_update_procedure(update_data))\n>>  \t\t\treturn 1;\n>> @@ -2551,6 +2568,12 @@ static int update_submodules(struct update_data *update_data)\n>>  \t\tgoto cleanup;\n>>  \t}\n>>  \n>> +\tif (the_repository->settings.submodule_propagate_branches) {\n>> +\t\tstruct branch *current_branch = branch_get(NULL);\n>> +\t\tif (current_branch)\n>> +\t\t\tupdate_data->super_branch = current_branch->name;\n>\n> OK, so this condition means that super_branch won't get set if we are not\n> currently on a branch, i.e. we are in detached HEAD. This makes sense as there\n> would be no branch to propagate. Do we need a test for this ? maybe a case where\n> we clone with '--recurse-submodules --branch some-tag' ?\n\nGood point, I'll add a test for this.\n\n>> +\t}\n>> +\n>>  \tfor (i = 0; i < suc.update_clone_nr; i++) {\n>>  \t\tstruct update_clone_data ucd = suc.update_clone[i];\n>>  \n>> @@ -2634,6 +2657,7 @@ static int module_update(int argc, const char **argv, const char *prefix)\n>>  \tmemset(&filter_options, 0, sizeof(filter_options));\n>>  \targc = parse_options(argc, argv, prefix, module_update_options,\n>>  \t\t\t     git_submodule_helper_usage, 0);\n>> +\tprepare_repo_settings(the_repository);\n>>  \n>>  \tif (opt.require_init)\n>>  \t\topt.init = 1;\n>> diff --git a/t/t5617-clone-submodules.sh b/t/t5617-clone-submodules.sh\n>> index b5c66cb18cb..215fb02e9fb 100755\n>> --- a/t/t5617-clone-submodules.sh\n>> +++ b/t/t5617-clone-submodules.sh\n>> @@ -12,10 +12,17 @@ pwd=$(pwd)\n>>  test_expect_success 'setup' '\n>>  \tgit checkout -b main &&\n>>  \ttest_commit commit1 &&\n>> +\tmkdir subsub &&\n>> +\t(\n>> +\t\tcd subsub &&\n>> +\t\tgit init &&\n>> +\t\ttest_commit subsubcommit1\n>> +\t) &&\n>>  \tmkdir sub &&\n>>  \t(\n>>  \t\tcd sub &&\n>>  \t\tgit init &&\n>> +\t\tgit submodule add \"file://$pwd/subsub\" subsub &&\n>>  \t\ttest_commit subcommit1 &&\n>>  \t\tgit tag sub_when_added_to_super &&\n>>  \t\tgit branch other\n>> @@ -106,4 +113,31 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule\n>>  \ttest_cmp_config -C super_clone3/sub false --default false remote.origin.promisor\n>>  '\n>>  \n>> +test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '\n>> +\tgit -C sub checkout -b not-main &&\n>> +\tgit -C subsub checkout -b not-main &&\n>> +\tgit clone --recurse-submodules \\\n>> +\t\t-c submodule.propagateBranches=true \\\n>> +\t\t\"file://$pwd/.\" super_clone4 &&\n>> +\n>> +\t# Assert that each repo is pointing to \"main\"\n>> +\tfor REPO in \"super_clone4\" \"super_clone4/sub\" \"super_clone4/sub/subsub\"\n>> +\tdo\n>> +\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n>> +\t    test $HEAD_BRANCH = \"refs/heads/main\" || return 1\n>> +\tdone &&\n>> +\n>> +\t# Assert that the submodule branches are pointing to the right revs\n>> +\tEXPECT_SUB_OID=\"$(git -C super_clone4 rev-parse :sub)\" &&\n>> +\tACTUAL_SUB_OID=\"$(git -C super_clone4/sub rev-parse refs/heads/main)\" &&\n>> +\ttest $EXPECT_SUB_OID = $ACTUAL_SUB_OID &&\n>> +\tEXPECT_SUBSUB_OID=\"$(git -C super_clone4/sub rev-parse :subsub)\" &&\n>> +\tACTUAL_SUBSUB_OID=\"$(git -C super_clone4/sub/subsub rev-parse refs/heads/main)\" &&\n>> +\ttest $EXPECT_SUBSUB_OID = $ACTUAL_SUBSUB_OID &&\n>> +\n>> +\t# Assert that the submodules do not have branches from their upstream\n>> +\ttest_must_fail git -C super_clone4/sub rev-parse not-main &&\n>> +\ttest_must_fail git -C super_clone4/sub/subsub rev-parse not-main\n>> +'\n>> +\n>>  test_done\n>> diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\n>> index 6cc07460dd2..00a6fec8912 100755\n>> --- a/t/t7406-submodule-update.sh\n>> +++ b/t/t7406-submodule-update.sh\n>> @@ -1178,4 +1178,26 @@ test_expect_success 'submodule update --recursive skip submodules with strategy=\n>>  \ttest_cmp expect.err actual.err\n>>  '\n>>  \n>> +test_expect_success 'submodule update with submodule.propagateBranches checks out branches' '\n>> +\ttest_when_finished \"rm -fr top-cloned\" &&\n>> +\tcp -r top-clean top-cloned &&\n>> +\n>> +\t# Create a new upstream submodule\n>> +\tgit init middle2 &&\n>> +\ttest_commit -C middle2 \"middle2\" &&\n>> +\tgit -C top submodule add ../middle2 middle2 &&\n>> +\tgit -C top commit -m \"add middle2\" &&\n>> +\n>> +\tgit -C top-cloned checkout -b \"new-branch\" &&\n>> +\tgit -C top-cloned pull origin main &&\n>> +\ttest_config -C top-cloned submodule.propagateBranches true &&\n>> +\tgit -C top-cloned submodule update --recursive &&\n>> +\n>> +\tfor REPO in \"top-cloned/middle2\" \"top-cloned/middle\" \"top-cloned/middle/bottom\"\n>> +\tdo\n>> +\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n>> +\t    test $HEAD_BRANCH = \"refs/heads/new-branch\" || return 1\n>> +\tdone\n>> +'\n>> +\n>>  test_done\n>> \n>\n> These tests look good, but maybe more tests would be needed in \n> the light of my comments above... \n>\n> Thanks again for working on improving submodules!\n\nThanks for lending your time and attention :)\n>\n> Cheers,\n>\n> Philippe.\n"},{"id":"462486","messageId":"20220901200047.515294-1-jonathantanmy@google.com","threadId":"58375","inReplyTo":"6f7f2f9a3f19b6d874d644b7fb7feb3a72fc6227.1661806456.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 6/6] clone, submodule update: check out branches","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-09-01T20:00:47Z","receivedAt":"2022-09-01T20:00:55Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> Teach \"git submodule update\" to update submodules by creating and\n> checking out the current superproject branch when\n> \"submodule.propagateBranches=true\".\n\n\"git submodule update\" already knows how to update submodules; probably\nbetter to say:\n\n  Teach \"git submodule update\" to create and check out a branch of the\n  same name as the current superproject branch when updating a submodule\n  if \"submodule.propagateBranches=true\" is set on the superproject.\n\n> With \"submodule.propagateBranches=true\", submodules are cloned with\n> \"--detach\" so that they do not contain branches from their upstream.\n> This prevents conflicts between branch names from the superproject and\n> the branch names from the submodule's upstream. Arguably, \"--detach\"\n> should also be the default for \"submodule.propagateBranches=false\"\n> since it doesn't make sense to create a submodule branch when the\n> submodule is always expected to be in detached HEAD.\n\nThis paragraph made me think of the use case in which we cloned a\nsubmodule-using repo, made a commit in a submodule (thus advancing a\nbranch) without a corresponding commit in a superproject, and then\nrecloned our clone, hoping that the state will persist. It would not\npersist, but as stated here, the existing behavior is already that\nbranches in submodules are not cloned, so retaining this existing\nbehavior is not a problem.\n\n> \"git submodule update\" tries to create the branch as long as it is not\n> currently checked out, thus it will fail if the submodule has the\n> branch, but it is not checked out. This is fine because the main purpose\n> of \"git submodule update\" is to clone new submodules (which have no\n> branches, and will never have this problem). \"git checkout\" with\n> \"submodule.propagateBranches\" will cover the use case of recursively\n> checking out an existing branch.\n\nIn regular usage, the user will, as you say, run \"git checkout\". So when\n\"git submodule update\" is run, a submodule will either have no branches\n(because it was just cloned or because we have never switched to that\nbranch before in the superproject) or it will have the correct branch\nalready checked out, so it would already be considered up to date (no\nmatter whether the commit matches with the superproject's gitlink: only\nthe name of the branch matters).\n\nI'm concerned about the case in which the user, say, has created a\nbranch in a submodule for some reason. E.g.:\n\n  (cd sub; git branch my-branch)\n  git checkout my-branch\n\nso this would fail because we wouldn't be able to create \"my-branch\" in\nthe \"sub\" submodule. We might need a message explaining what can be done\nto fix this situation, but for now, maybe a NEEDSWORK will suffice.\n\n> @@ -2206,6 +2214,8 @@ static int run_update_command(struct update_data *ud, int subforce)\n>  \t\tstrvec_pushl(&cp.args, \"checkout\", \"-q\", NULL);\n>  \t\tif (subforce)\n>  \t\t\tstrvec_push(&cp.args, \"-f\");\n> +\t\tif (ud->super_branch)\n> +\t\t\tstrvec_pushl(&cp.args, \"-b\", ud->super_branch, NULL);\n\nHere is where the NEEDSWORK would go.\n\n> @@ -106,4 +113,31 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule\n>  \ttest_cmp_config -C super_clone3/sub false --default false remote.origin.promisor\n>  '\n>  \n> +test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '\n> +\tgit -C sub checkout -b not-main &&\n> +\tgit -C subsub checkout -b not-main &&\n> +\tgit clone --recurse-submodules \\\n> +\t\t-c submodule.propagateBranches=true \\\n> +\t\t\"file://$pwd/.\" super_clone4 &&\n> +\n> +\t# Assert that each repo is pointing to \"main\"\n> +\tfor REPO in \"super_clone4\" \"super_clone4/sub\" \"super_clone4/sub/subsub\"\n> +\tdo\n> +\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n> +\t    test $HEAD_BRANCH = \"refs/heads/main\" || return 1\n> +\tdone &&\n> +\n> +\t# Assert that the submodule branches are pointing to the right revs\n> +\tEXPECT_SUB_OID=\"$(git -C super_clone4 rev-parse :sub)\" &&\n> +\tACTUAL_SUB_OID=\"$(git -C super_clone4/sub rev-parse refs/heads/main)\" &&\n> +\ttest $EXPECT_SUB_OID = $ACTUAL_SUB_OID &&\n> +\tEXPECT_SUBSUB_OID=\"$(git -C super_clone4/sub rev-parse :subsub)\" &&\n> +\tACTUAL_SUBSUB_OID=\"$(git -C super_clone4/sub/subsub rev-parse refs/heads/main)\" &&\n> +\ttest $EXPECT_SUBSUB_OID = $ACTUAL_SUBSUB_OID &&\n> +\n> +\t# Assert that the submodules do not have branches from their upstream\n> +\ttest_must_fail git -C super_clone4/sub rev-parse not-main &&\n> +\ttest_must_fail git -C super_clone4/sub/subsub rev-parse not-main\n> +'\n\nInstead of reusing \"main\", can we use a branch name that exists in the\nsuperproject but not the submodule? Here, we cannot tell the difference\nbetween git reusing the referent of submodule's \"main\" versus git using\nthe gitlink in superproject's \"main\".\n\nI'll write some more comments on the other patches, but overall this\npatch set makes sense to me.\n"},{"id":"462487","messageId":"20220901200135.515907-1-jonathantanmy@google.com","threadId":"58375","inReplyTo":"eab40dcb296fd6a6ea14d13107cdc3ca6e64e311.1661806456.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 4/6] submodule: return target of submodule symref","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-09-01T20:01:35Z","receivedAt":"2022-09-01T20:01:42Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> diff --git a/refs.c b/refs.c\n> index 90bcb271687..d72015c95e9 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1784,19 +1784,21 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n>  }\n>  \n>  int resolve_gitlink_ref(const char *submodule, const char *refname,\n> -\t\t\tstruct object_id *oid)\n> +\t\t\tstruct object_id *oid, const char **referent_out)\n\ns/referent/target/ throughout this patch, I think.\n"},{"id":"462489","messageId":"kl6l35daddrq.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58375","inReplyTo":"20220901200135.515907-1-jonathantanmy@google.com","subject":"Re: [PATCH 4/6] submodule: return target of submodule symref","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-09-01T20:46:01Z","receivedAt":"2022-09-01T20:46:07Z","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 via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> diff --git a/refs.c b/refs.c\n>> index 90bcb271687..d72015c95e9 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -1784,19 +1784,21 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n>>  }\n>>  \n>>  int resolve_gitlink_ref(const char *submodule, const char *refname,\n>> -\t\t\tstruct object_id *oid)\n>> +\t\t\tstruct object_id *oid, const char **referent_out)\n>\n> s/referent/target/ throughout this patch, I think.\n\nI prefer the word \"target\", but this is a break from existing\nconventions, e.g.\n\n  int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,\n          struct strbuf *referent);\n\nWe can do this change, but I think we should also change this everywhere\nif we do.\n"},{"id":"465333","messageId":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.git.git.1661806456.gitgitgadget@gmail.com","subject":"[PATCH v2 0/7] clone, submodule update: check out submodule branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-20T20:20:31Z","receivedAt":"2022-10-20T20:20:50Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Thanks Philippe and Jonathan for your review on the previous version :)\nSorry for the long delay between iterations, I got wrapped up in other,\nunrelated work.\n\nThe biggest change in this version is that \"git submodule update\" now\ncreates the branch when the submodule is cloned, not when the submodule's\nworktree is updated. So when the worktree is updated, we use \"git checkout \"\ninstead of \"git checkout -b \". This is more robust to branches created\ndirectly in the submodule (which was pointed out by both reviewers), but\nalso allows us to add matching behavior to \"git submodule add \", which also\nclones + checks out submodules.\n\nDuring the v1 discussion, I realize that my idea of the new submodule UX has\nalready diverged from what was initially communicated to the list. I plan to\ncheck in a technical document describing the plans for new submodule UX,\nwhich should hopefully make these discussions smoother (e.g. the commit\nmessage in patch 7 can make reference to the doc).\n\n= Description\n\nThis series teaches \"git clone --recurse-submodules\" and \"git submodule\nupdate\" to understand \"submodule.propagateBranches\" (see Further Reading for\ncontext), i.e. if the superproject has a branch checked out and a submodule\nis cloned, the submodule will have the same branch checked out.\n\nTo do this, \"git submodule update\" checks if \"submodule.propagateBranches\"\nis true, and if so, gets the current superproject branch and updates the\nsubmodule by running \"git checkout -b my-superproject-branch\". Since \"git\nclone --recurse-submodules\" is implemented using \"git submodule update\", it\nalso learns this trick.\n\nThe main challenges with this approach are:\n\n * If the remote HEAD points to a branch, \"git clone\" always creates that\n   branch in the clone. But with \"submodule.propagateBranches\", we want\n   submodules to use the branch names of their superproject, not their\n   upstream.\n   \n   This is solved by adding a new flag to \"git clone\", \"--detach\", which\n   detaches the clone's HEAD at the branch and does not create it.\n\n * When \"git submodule update\" recurses into submodules, the parent process\n   has to propagate the value of \"submodule.propagateBranches\" to child\n   processes, otherwise the behavior will be inconsistent if the submodule\n   has the config unset.\n   \n   This is solved by adding an internal GIT_* environment variable and\n   passing it down via prepare_submodule_repo_env(). This is cleaner than\n   passing \"-c submodule.propagateBranches=true\", but an even cleaner\n   solution would be for submodules to read \"submodule.propagateBranches\"\n   from their superproject config. This would also be useful for\n   \"submodule.alternateLocation\" and \"submodule.alternateErrorStrategy\", as\n   we wouldn't have to set those values in newly-cloned submodules. This\n   requires teaching Git to treat submodules differently, which was the\n   subject of some WIP in [1]. That topic has stalled, but I don't mind\n   restarting it if others prefer that.\n\n[1]\nhttps://lore.kernel.org/git/20220310004423.2627181-1-emilyshaffer@google.com/\n\n= Patch organization\n\n * Patch 1/7 adds \"--detach\" to \"git clone\"\n * Patch 2/7 creates the environment variable and repository setting for\n   \"submodule.propagateBranches\"\n * Patch 3/7 adds a new \"--branch\" option to \"git submodule clone\", which\n   makes it create a named branch.\n * Patches 4-6/7 are prep work, and 7/7 adds the actual\n   \"submodule.propagateBranches\" behavior\n\n= Series history\n\nChanges in v2:\n\n * The superproject's \"submodule.propagateBranches\" value is always used,\n   even if false.\n * Branches are now created at clone time (by adding a new flag to \"git\n   submodule clone\"), instead of at update time.\n * Rebase onto newer master. This got adjusted slightly to incorporate\n   ab/submodule-helper-leakfix.\n * Add more tests to demonstrate edge case behavior.\n * Assorted commit message and doc improvements.\n\n= Future work\n\n * Patch 5, which refactors resolve_gitlink_ref(), notes that a better\n   interface would be to return the refname instead of using an \"out\"\n   parameter, but we use an \"out\" parameter so that any new callers trying\n   to use the old function signature will get stopped by the compiler. The\n   refactor can be finished at a later time.\n\n * Patch 5 uses the name \"target\" when we are talking about what a symref\n   points to, instead of \"referent\" like the other functions. \"target\" is\n   the better choice, since \"referent\" could also apply to non-symbolic\n   refs, but that cleanup is quite big.\n\n= Further reading\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\"git branch --recurse-submodules\":\nhttps://lore.kernel.org/git/20220129000446.99261-1-chooglen@google.com/\n\nGlen Choo (7):\n  clone: teach --detach option\n  repo-settings: add submodule_propagate_branches\n  submodule--helper clone: create named branch\n  t5617: drop references to remote-tracking branches\n  submodule: return target of submodule symref\n  submodule update: refactor update targets\n  clone, submodule update: create and check out branches\n\n Documentation/git-clone.txt                   |  8 +-\n builtin/branch.c                              | 11 +--\n builtin/clone.c                               | 12 ++-\n builtin/submodule--helper.c                   | 91 ++++++++++++++++---\n builtin/update-index.c                        |  4 +-\n cache.h                                       |  1 +\n combine-diff.c                                |  3 +-\n diff-lib.c                                    |  2 +-\n dir.c                                         |  2 +-\n object-file.c                                 |  2 +-\n read-cache.c                                  |  4 +-\n refs.c                                        | 10 +-\n refs.h                                        |  5 +-\n repo-settings.c                               | 10 ++\n repository.h                                  |  1 +\n submodule.c                                   |  2 +\n t/t5601-clone.sh                              | 22 +++++\n ...es-remote.sh => t5617-clone-submodules.sh} | 36 +++++++-\n t/t7406-submodule-update.sh                   | 64 +++++++++++++\n unpack-trees.c                                |  3 +-\n 20 files changed, 251 insertions(+), 42 deletions(-)\n rename t/{t5617-clone-submodules-remote.sh => t5617-clone-submodules.sh} (72%)\n\n\nbase-commit: 45c9f05c44b1cb6bd2d6cb95a22cf5e3d21d5b63\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1321%2Fchooglen%2Fsubmodule%2Fclone-recursive-with-branch-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1321/chooglen/submodule/clone-recursive-with-branch-v2\nPull-Request: https://github.com/git/git/pull/1321\n\nRange-diff vs v1:\n\n 1:  936e237c716 ! 1:  432bc7cb3a4 clone: teach --detach option\n     @@ Commit message\n          clone: teach --detach option\n      \n          Teach \"git clone\" the \"--detach\" option, which leaves the cloned repo in\n     -    detached HEAD (like \"git checkout --detach\"). If the clone is not bare,\n     -    the remote's HEAD branch is also not created (bare clones always copy\n     -    all remote branches directly to local branches, so the branch is still\n     -    created in the bare case).\n     +    detached HEAD (like \"git checkout --detach\"). In addition, if the clone\n     +    is not bare, do not create the local branch pointed to by the remote's\n     +    HEAD symref (bare clones always copy all remote branches directly to\n     +    local branches, so the branch is still created in the bare case).\n      \n          This is especially useful in the \"submodule.propagateBranches\" workflow,\n     -    where the submodule branch names match the superproject's branch names,\n     -    so it makes no sense to name the branches after the submodule's remote's\n     -    branches.\n     +    where local submodule branches are named after the superproject's\n     +    branches, so it makes no sense to create a local branch named after the\n     +    submodule's remote's branch.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ Documentation/git-clone.txt: objects from the source repository into a pack in t\n       \n      +--detach::\n      +\tIf the cloned repository's HEAD points to a branch, point the newly\n     -+\tcreated HEAD to the branch's commit instead of the branch itself. In a\n     -+\tnon-bare repository, the branch will not be created.\n     ++\tcreated HEAD to the branch's commit instead of the branch itself.\n     ++\tAdditionally, in a non-bare repository, the corresponding local branch\n     ++\twill not be created.\n      +\n       -u <upload-pack>::\n       --upload-pack <upload-pack>::\n     @@ builtin/clone.c: static int option_filter_submodules = -1;    /* unspecified */\n       static struct string_list server_options = STRING_LIST_INIT_NODUP;\n       static int option_remote_submodules;\n      +static int option_detach;\n     + static const char *bundle_uri;\n       \n       static int recurse_submodules_cb(const struct option *opt,\n     - \t\t\t\t const char *arg, int unset)\n      @@ builtin/clone.c: static struct option builtin_clone_options[] = {\n       \t\t    N_(\"any cloned submodules will use their remote-tracking branch\")),\n       \tOPT_BOOL(0, \"sparse\", &option_sparse_checkout,\n       \t\t    N_(\"initialize sparse-checkout file to include only files at root\")),\n      +\tOPT_BOOL(0, \"detach\", &option_detach,\n     -+\t\t N_(\"detach HEAD and don't create branch\")),\n     ++\t\t N_(\"detach HEAD and don't create a local branch\")),\n     + \tOPT_STRING(0, \"bundle-uri\", &bundle_uri,\n     + \t\t   N_(\"uri\"), N_(\"a URI for downloading bundles before fetching from origin remote\")),\n       \tOPT_END()\n     - };\n     - \n      @@ builtin/clone.c: static void update_remote_refs(const struct ref *refs,\n       }\n       \n 2:  35f068eb031 ! 2:  20499c62065 repo-settings: add submodule_propagate_branches\n     @@ Commit message\n          or submodule, otherwise the behavior may be inconsistent if the\n          repositories don't agree on the config.\n      \n     -    We haven't needed a way to propagate the config because because the only\n     -    command that reads \"submodule.propagateBranches\" is \"git branch\", which\n     -    only has one mode of operation with \"--recurse-submodules\". However, a\n     -    future commit will teach \"submodule.propagateBranches\" to \"git submodule\n     +    We haven't needed a way to propagate the config because the only command\n     +    that reads \"submodule.propagateBranches\" is \"git branch\", which only has\n     +    one mode of operation with \"--recurse-submodules\". However, a future\n     +    commit will teach \"submodule.propagateBranches\" to \"git submodule\n          update\", making this necessary.\n      \n          Propagate \"submodule.propagateBranches\" to child processes by adding a\n     @@ cache.h: static inline enum object_type object_type(unsigned int mode)\n      \n       ## repo-settings.c ##\n      @@ repo-settings.c: void prepare_repo_settings(struct repository *r)\n     - \trepo_cfg_bool(r, \"pack.usesparse\", &r->settings.pack_use_sparse, 1);\n     - \trepo_cfg_bool(r, \"core.multipackindex\", &r->settings.core_multi_pack_index, 1);\n     - \trepo_cfg_bool(r, \"index.sparse\", &r->settings.sparse_index, 0);\n     -+\trepo_cfg_bool(r, \"submodule.propagateBranches\", &r->settings.submodule_propagate_branches, 0);\n     - \n     - \t/*\n     --\t * The GIT_TEST_MULTI_PACK_INDEX variable is special in that\n     --\t * either it *or* the config sets\n     --\t * r->settings.core_multi_pack_index if true. We don't take\n     --\t * the environment variable if it exists (even if false) over\n     --\t * any config, as in most other cases.\n     -+\t * Boolean settings from config and environment variables. Only\n     -+\t * take the environment variable if it is true, otherwise, use\n     -+\t * the config.\n     - \t */\n       \tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))\n       \t\tr->settings.core_multi_pack_index = 1;\n     -+\tif (git_env_bool(GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT, 0))\n     -+\t\tr->settings.submodule_propagate_branches = 1;\n       \n     ++\t/*\n     ++\t * If the environment variable is set, assume that it came from the\n     ++\t * superproject and ignore the config.\n     ++\t */\n     ++\tr->settings.submodule_propagate_branches =\n     ++\t\tgit_env_bool(GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT, -1);\n     ++\tif (r->settings.submodule_propagate_branches == -1)\n     ++\t\trepo_cfg_bool(r, \"submodule.propagateBranches\",\n     ++\t\t\t      &r->settings.submodule_propagate_branches, 0);\n     ++\n       \t/*\n       \t * Non-boolean config\n     + \t */\n      \n       ## repository.h ##\n      @@ repository.h: struct repo_settings {\n -:  ----------- > 3:  a4056e200ed submodule--helper clone: create named branch\n 3:  d8a2faf9ce7 = 4:  affd0e24e1d t5617: drop references to remote-tracking branches\n 4:  eab40dcb296 ! 5:  6f769cb80ad submodule: return target of submodule symref\n     @@ builtin/submodule--helper.c: static int update_submodule(struct update_data *upd\n      -\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &update_data->suboid))\n      +\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n      +\t\t\t\t     &update_data->suboid, NULL))\n     - \t\tdie(_(\"Unable to find current revision in submodule path '%s'\"),\n     - \t\t\tupdate_data->displaypath);\n     + \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n     + \t\t\t\t   update_data->displaypath);\n       \n      @@ builtin/submodule--helper.c: static int update_submodule(struct update_data *update_data)\n     - \t\t\t\t    update_data->sm_path);\n     + \t\t\t\t\t\t   update_data->sm_path);\n       \t\t}\n       \n      -\t\tif (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))\n      +\t\tif (resolve_gitlink_ref(update_data->sm_path, remote_ref,\n      +\t\t\t\t\t&update_data->oid, NULL))\n     - \t\t\tdie(_(\"Unable to find %s revision in submodule path '%s'\"),\n     - \t\t\t    remote_ref, update_data->sm_path);\n     + \t\t\treturn die_message(_(\"Unable to find %s revision in submodule path '%s'\"),\n     + \t\t\t\t\t   remote_ref, update_data->sm_path);\n       \n      @@ builtin/submodule--helper.c: static void die_on_repo_without_commits(const char *path)\n       \tstrbuf_addstr(&sb, path);\n     @@ refs.c: const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n       \n       int resolve_gitlink_ref(const char *submodule, const char *refname,\n      -\t\t\tstruct object_id *oid)\n     -+\t\t\tstruct object_id *oid, const char **referent_out)\n     ++\t\t\tstruct object_id *oid, const char **target_out)\n       {\n       \tstruct ref_store *refs;\n       \tint flags;\n     -+\tconst char *referent;\n     ++\tconst char *target;\n       \n       \trefs = get_submodule_ref_store(submodule);\n       \n     @@ refs.c: const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n      -\n      -\tif (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||\n      -\t    is_null_oid(oid))\n     -+\treferent = refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags);\n     -+\tif (!referent || is_null_oid(oid))\n     ++\ttarget = refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags);\n     ++\tif (!target || is_null_oid(oid))\n       \t\treturn -1;\n     -+\tif (referent_out)\n     -+\t\t*referent_out = referent;\n     ++\tif (target_out)\n     ++\t\t*target_out = target;\n       \treturn 0;\n       }\n       \n     @@ refs.h: int peel_iterated_oid(const struct object_id *base, struct object_id *pe\n        * successful, return 0 and set oid to the name of the object;\n        * otherwise, return a non-zero value.\n      + *\n     -+ * FIXME: Return \"referent\" just like refs_resolve_ref_unsafe(). This will be\n     ++ * FIXME: Return \"target\" just like refs_resolve_ref_unsafe(). This will be\n      + * safe to do once we merge resolve_gitlink_ref() into master.\n        */\n       int resolve_gitlink_ref(const char *submodule, const char *refname,\n      -\t\t\tstruct object_id *oid);\n     -+\t\t\tstruct object_id *oid, const char **referent);\n     ++\t\t\tstruct object_id *oid, const char **target);\n       \n       /*\n        * Return true iff abbrev_name is a possible abbreviation for\n 5:  513d18e56d3 ! 6:  abdfa888ff5 submodule--helper: refactor up-to-date criterion\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    submodule--helper: refactor up-to-date criterion\n     +    submodule update: refactor update targets\n      \n     -    Refactor builtin/submodule--helper.c:update_submodule() to check if the\n     -    submodule is up to date using a variable instead of checking the oids\n     -    directly. In a subsequent commit, we will expand the definition of \"up\n     -    to date\" to include checked out branches.\n     +    Refactor two \"git submodule update\" code locations so that they no\n     +    longer refer to oids directly. This shrinks the next commit's diff,\n     +    where this code will need to handle branches.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n       ## builtin/submodule--helper.c ##\n     -@@ builtin/submodule--helper.c: static void update_data_to_args(struct update_data *update_data, struct strvec *\n     +@@ builtin/submodule--helper.c: static int fetch_in_submodule(const char *module_path, int depth, int quiet,\n     + static int run_update_command(const struct update_data *ud, int subforce)\n     + {\n     + \tstruct child_process cp = CHILD_PROCESS_INIT;\n     +-\tchar *oid = oid_to_hex(&ud->oid);\n     ++\tconst char *update_target = oid_to_hex(&ud->oid);;\n     + \tint ret;\n     + \n     + \tswitch (ud->update_strategy.type) {\n     +@@ builtin/submodule--helper.c: static int run_update_command(const struct update_data *ud, int subforce)\n     + \t\tBUG(\"unexpected update strategy type: %d\",\n     + \t\t    ud->update_strategy.type);\n     + \t}\n     +-\tstrvec_push(&cp.args, oid);\n     ++\tstrvec_push(&cp.args, update_target);\n     + \n     + \tcp.dir = ud->sm_path;\n     + \tprepare_submodule_repo_env(&cp.env);\n     +@@ builtin/submodule--helper.c: static int run_update_command(const struct update_data *ud, int subforce)\n     + \t\tswitch (ud->update_strategy.type) {\n     + \t\tcase SM_UPDATE_CHECKOUT:\n     + \t\t\tdie_message(_(\"Unable to checkout '%s' in submodule path '%s'\"),\n     +-\t\t\t\t    oid, ud->displaypath);\n     ++\t\t\t\t    update_target, ud->displaypath);\n     + \t\t\t/* No \"ret\" assignment, use \"git checkout\"'s */\n     + \t\t\tbreak;\n     + \t\tcase SM_UPDATE_REBASE:\n     + \t\t\tret = die_message(_(\"Unable to rebase '%s' in submodule path '%s'\"),\n     +-\t\t\t\t\t  oid, ud->displaypath);\n     ++\t\t\t\t\t  update_target, ud->displaypath);\n     + \t\t\tbreak;\n     + \t\tcase SM_UPDATE_MERGE:\n     + \t\t\tret = die_message(_(\"Unable to merge '%s' in submodule path '%s'\"),\n     +-\t\t\t\t\t  oid, ud->displaypath);\n     ++\t\t\t\t\t  update_target, ud->displaypath);\n     + \t\t\tbreak;\n     + \t\tcase SM_UPDATE_COMMAND:\n     + \t\t\tret = die_message(_(\"Execution of '%s %s' failed in submodule path '%s'\"),\n     +-\t\t\t\t\t  ud->update_strategy.command, oid, ud->displaypath);\n     ++\t\t\t\t\t  ud->update_strategy.command, update_target, ud->displaypath);\n     + \t\t\tbreak;\n     + \t\tdefault:\n     + \t\t\tBUG(\"unexpected update strategy type: %d\",\n     +@@ builtin/submodule--helper.c: static int run_update_command(const struct update_data *ud, int subforce)\n     + \tswitch (ud->update_strategy.type) {\n     + \tcase SM_UPDATE_CHECKOUT:\n     + \t\tprintf(_(\"Submodule path '%s': checked out '%s'\\n\"),\n     +-\t\t       ud->displaypath, oid);\n     ++\t\t       ud->displaypath, update_target);\n     + \t\tbreak;\n     + \tcase SM_UPDATE_REBASE:\n     + \t\tprintf(_(\"Submodule path '%s': rebased into '%s'\\n\"),\n     +-\t\t       ud->displaypath, oid);\n     ++\t\t       ud->displaypath, update_target);\n     + \t\tbreak;\n     + \tcase SM_UPDATE_MERGE:\n     + \t\tprintf(_(\"Submodule path '%s': merged in '%s'\\n\"),\n     +-\t\t       ud->displaypath, oid);\n     ++\t\t       ud->displaypath, update_target);\n     + \t\tbreak;\n     + \tcase SM_UPDATE_COMMAND:\n     + \t\tprintf(_(\"Submodule path '%s': '%s %s'\\n\"),\n     +-\t\t       ud->displaypath, ud->update_strategy.command, oid);\n     ++\t\t       ud->displaypath, ud->update_strategy.command, update_target);\n     + \t\tbreak;\n     + \tdefault:\n     + \t\tBUG(\"unexpected update strategy type: %d\",\n     +@@ builtin/submodule--helper.c: static void update_data_to_args(const struct update_data *update_data,\n       \n       static int update_submodule(struct update_data *update_data)\n       {\n      +\tint submodule_up_to_date;\n     -+\n     - \tensure_core_worktree(update_data->sm_path);\n     + \tint ret;\n       \n     - \tupdate_data->displaypath = get_submodule_displaypath(\n     + \tret = determine_submodule_update_strategy(the_repository,\n      @@ builtin/submodule--helper.c: static int update_submodule(struct update_data *update_data)\n       \t\tfree(remote_ref);\n       \t}\n       \n     --\tif (!oideq(&update_data->oid, &update_data->suboid) || update_data->force)\n     +-\tif (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) {\n      +\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n     -+\tif (!submodule_up_to_date || update_data->force)\n     - \t\tif (run_update_procedure(update_data))\n     - \t\t\treturn 1;\n     - \n     ++\tif (!submodule_up_to_date || update_data->force) {\n     + \t\tret = run_update_procedure(update_data);\n     + \t\tif (ret)\n     + \t\t\treturn ret;\n 6:  6f7f2f9a3f1 ! 7:  3f98b0d1739 clone, submodule update: check out branches\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    clone, submodule update: check out branches\n     +    clone, submodule update: create and check out branches\n      \n     -    Teach \"git submodule update\" to update submodules by creating and\n     -    checking out the current superproject branch when\n     -    \"submodule.propagateBranches=true\". \"git clone --recurse-submodules\"\n     -    also learns this trick because it is implemented with \"git submodule\n     -    update --recursive\".\n     +    Teach \"git submodule update\" to:\n      \n     -    With \"submodule.propagateBranches=true\", submodules are cloned with\n     -    \"--detach\" so that they do not contain branches from their upstream.\n     -    This prevents conflicts between branch names from the superproject and\n     -    the branch names from the submodule's upstream. Arguably, \"--detach\"\n     -    should also be the default for \"submodule.propagateBranches=false\"\n     -    since it doesn't make sense to create a submodule branch when the\n     -    submodule is always expected to be in detached HEAD. But, to be\n     -    conservative, this commit does not change the behavior of\n     -    \"submodule.propagateBranches=false\".\n     +    - create the branch with the same name as the current superproject\n     +      branch when cloning a submodule\n     +    - check out that branch (instead of the commit OID) when updating\n     +      the submodule worktree\n      \n     -    \"git submodule update\" tries to create the branch as long as it is not\n     -    currently checked out, thus it will fail if the submodule has the\n     -    branch, but it is not checked out. This is fine because the main purpose\n     -    of \"git submodule update\" is to clone new submodules (which have no\n     -    branches, and will never have this problem). \"git checkout\" with\n     -    \"submodule.propagateBranches\" will cover the use case of recursively\n     -    checking out an existing branch.\n     +    when submodule branching is enabled (submodule.propagateBranches = true)\n     +    on the superproject and a branch is checked out. \"git clone\n     +    --recurse-submodules\" also learns this trick because it is implemented\n     +    with \"git submodule update --recursive\".\n     +\n     +    This approach of checking out the branch will not result in a dirty\n     +    worktree for freshly cloned submodules because we can ensure that the\n     +    submodule branch points to the superproject gitlink. In other cases, it\n     +    does not work as well, but we can handle them incrementally:\n     +\n     +    - \"git pull --recurse-submodules\" merges the superproject tree (without\n     +      updating the submodule branches), and runs \"git submodule update\" to\n     +      update the worktrees, so it is almost guaranteed to result in a dirty\n     +      worktree.\n     +\n     +      The implementation of \"git pull --recurse-submodules\" is likely to\n     +      change drastically as submodule.propagateBranches work progresses\n     +      (e.g. \"git merge\" learns to recurse in to submodules), and we may be\n     +      able to replace the \"git submodule update\" invocation, or teach it new\n     +      tricks that make the update behave well.\n     +\n     +    - The user might make changes to the submodule branch without committing\n     +      them back to superproject. This is primarily affects \"git checkout\n     +      --recurse-submodules\", since that is the primary way of switching away\n     +      from a branch and leaving behind WIP (as opposed to \"git submodule\n     +      update\", which is run post-checkout).\n     +\n     +      In a future series, \"git checkout --recurse-submodules\" will learn to\n     +      consider submodule branches. We can introduce appropriate guardrails\n     +      then, e.g. requiring that the superproject working tree is not dirty\n     +      before switching away.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n       ## builtin/submodule--helper.c ##\n     -@@ builtin/submodule--helper.c: static int clone_submodule(struct module_clone_data *clone_data)\n     - \t\t\tstrvec_push(&cp.args, clone_data->single_branch ?\n     - \t\t\t\t    \"--single-branch\" :\n     - \t\t\t\t    \"--no-single-branch\");\n     -+\t\tif (the_repository->settings.submodule_propagate_branches)\n     -+\t\t\tstrvec_push(&cp.args, \"--detach\");\n     -+\n     - \n     - \t\tstrvec_push(&cp.args, \"--\");\n     - \t\tstrvec_push(&cp.args, clone_data->url);\n     -@@ builtin/submodule--helper.c: static int clone_submodule(struct module_clone_data *clone_data)\n     - \tif (error_strategy)\n     - \t\tgit_config_set_in_file(p, \"submodule.alternateErrorStrategy\",\n     - \t\t\t\t       error_strategy);\n     -+\tif (the_repository->settings.submodule_propagate_branches)\n     -+\t\tgit_config_set_in_file(p, \"submodule.propagateBranches\",\n     -+\t\t\t\t       \"true\");\n     - \n     - \tfree(sm_alternate);\n     - \tfree(error_strategy);\n     -@@ builtin/submodule--helper.c: static int module_clone(int argc, const char **argv, const char *prefix)\n     - \tmemset(&filter_options, 0, sizeof(filter_options));\n     - \targc = parse_options(argc, argv, prefix, module_clone_options,\n     - \t\t\t     git_submodule_helper_usage, 0);\n     -+\tprepare_repo_settings(the_repository);\n     - \n     - \tclone_data.dissociate = !!dissociate;\n     - \tclone_data.quiet = !!quiet;\n     -@@ builtin/submodule--helper.c: struct submodule_update_clone {\n     +@@ builtin/submodule--helper.c: static void submodule_update_clone_release(struct submodule_update_clone *suc)\n       struct update_data {\n       \tconst char *prefix;\n     - \tconst char *displaypath;\n     + \tchar *displaypath;\n      +\tconst char *super_branch;\n       \tenum submodule_update_type update_default;\n       \tstruct object_id suboid;\n       \tstruct string_list references;\n     -@@ builtin/submodule--helper.c: static int run_update_command(struct update_data *ud, int subforce)\n     - \t\tstrvec_pushl(&cp.args, \"checkout\", \"-q\", NULL);\n     - \t\tif (subforce)\n     - \t\t\tstrvec_push(&cp.args, \"-f\");\n     -+\t\tif (ud->super_branch)\n     -+\t\t\tstrvec_pushl(&cp.args, \"-b\", ud->super_branch, NULL);\n     - \t\tbreak;\n     - \tcase SM_UPDATE_REBASE:\n     +@@ builtin/submodule--helper.c: static int prepare_to_clone_next_submodule(const struct cache_entry *ce,\n     + \t\tstrvec_push(&child->args, suc->update_data->single_branch ?\n     + \t\t\t\t\t      \"--single-branch\" :\n     + \t\t\t\t\t      \"--no-single-branch\");\n     ++\tif (ud->super_branch) {\n     ++\t\tstrvec_pushf(&child->args, \"--branch=%s\", ud->super_branch);\n     ++\t\tstrvec_pushf(&child->args, \"--branch-oid=%s\", oid_to_hex(&ce->oid));\n     ++\t}\n     + \n     + cleanup:\n     + \tfree(displaypath);\n     +@@ builtin/submodule--helper.c: static int fetch_in_submodule(const char *module_path, int depth, int quiet,\n     + static int run_update_command(const struct update_data *ud, int subforce)\n     + {\n     + \tstruct child_process cp = CHILD_PROCESS_INIT;\n     +-\tconst char *update_target = oid_to_hex(&ud->oid);;\n     ++\tconst char *update_target;\n     + \tint ret;\n     + \n     ++\tif (ud->update_strategy.type == SM_UPDATE_CHECKOUT &&\n     ++\t    ud->super_branch)\n     ++\t\tupdate_target = ud->super_branch;\n     ++\telse\n     ++\t\tupdate_target = oid_to_hex(&ud->oid);\n     ++\n     + \tswitch (ud->update_strategy.type) {\n     + \tcase SM_UPDATE_CHECKOUT:\n       \t\tcp.git_cmd = 1;\n     -@@ builtin/submodule--helper.c: static void update_data_to_args(struct update_data *update_data, struct strvec *\n     - static int update_submodule(struct update_data *update_data)\n     +@@ builtin/submodule--helper.c: static int update_submodule(struct update_data *update_data)\n       {\n       \tint submodule_up_to_date;\n     -+\tconst char *submodule_head = NULL;\n     - \n     - \tensure_core_worktree(update_data->sm_path);\n     + \tint ret;\n     ++\tconst char *submodule_head = \"HEAD\";\n       \n     + \tret = determine_submodule_update_strategy(the_repository,\n     + \t\t\t\t\t\t  update_data->just_cloned,\n      @@ builtin/submodule--helper.c: static int update_submodule(struct update_data *update_data)\n       \tif (update_data->just_cloned)\n       \t\toidcpy(&update_data->suboid, null_oid());\n       \telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n      -\t\t\t\t     &update_data->suboid, NULL))\n      +\t\t\t\t     &update_data->suboid, &submodule_head))\n     - \t\tdie(_(\"Unable to find current revision in submodule path '%s'\"),\n     - \t\t\tupdate_data->displaypath);\n     + \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n     + \t\t\t\t   update_data->displaypath);\n       \n      @@ builtin/submodule--helper.c: static int update_submodule(struct update_data *update_data)\n       \t\tfree(remote_ref);\n       \t}\n       \n      -\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n     -+\tif (update_data->super_branch &&\n     -+\t    submodule_head &&\n     -+\t    !skip_prefix(submodule_head, \"refs/heads/\", &submodule_head))\n     ++\tif (!update_data->super_branch)\n     ++\t\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n     ++\telse if (skip_prefix(submodule_head, \"refs/heads/\", &submodule_head))\n      +\t\tsubmodule_up_to_date = !strcmp(update_data->super_branch, submodule_head);\n     ++\t/* submodule_branch is \"HEAD\"; the submodule is in detached HEAD */\n      +\telse\n     -+\t\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n     ++\t\tsubmodule_up_to_date = 0;\n      +\n     - \tif (!submodule_up_to_date || update_data->force)\n     - \t\tif (run_update_procedure(update_data))\n     - \t\t\treturn 1;\n     + \tif (!submodule_up_to_date || update_data->force) {\n     + \t\tret = run_update_procedure(update_data);\n     + \t\tif (ret)\n      @@ builtin/submodule--helper.c: static int update_submodules(struct update_data *update_data)\n     - \t\tgoto cleanup;\n     - \t}\n     + \tint i, ret = 0;\n     + \tstruct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;\n       \n      +\tif (the_repository->settings.submodule_propagate_branches) {\n      +\t\tstruct branch *current_branch = branch_get(NULL);\n     @@ builtin/submodule--helper.c: static int update_submodules(struct update_data *up\n      +\t\t\tupdate_data->super_branch = current_branch->name;\n      +\t}\n      +\n     - \tfor (i = 0; i < suc.update_clone_nr; i++) {\n     - \t\tstruct update_clone_data ucd = suc.update_clone[i];\n     - \n     + \tsuc.update_data = update_data;\n     + \trun_processes_parallel_tr2(suc.update_data->max_jobs, update_clone_get_next_task,\n     + \t\t\t\t   update_clone_start_failure,\n      @@ builtin/submodule--helper.c: static int module_update(int argc, const char **argv, const char *prefix)\n     - \tmemset(&filter_options, 0, sizeof(filter_options));\n     + \n       \targc = parse_options(argc, argv, prefix, module_update_options,\n       \t\t\t     git_submodule_helper_usage, 0);\n      +\tprepare_repo_settings(the_repository);\n     @@ builtin/submodule--helper.c: static int module_update(int argc, const char **arg\n       \t\topt.init = 1;\n      \n       ## t/t5617-clone-submodules.sh ##\n     -@@ t/t5617-clone-submodules.sh: pwd=$(pwd)\n     - test_expect_success 'setup' '\n     +@@ t/t5617-clone-submodules.sh: test_expect_success 'setup' '\n     + \tgit config --global protocol.file.allow always &&\n       \tgit checkout -b main &&\n       \ttest_commit commit1 &&\n      +\tmkdir subsub &&\n     @@ t/t7406-submodule-update.sh: test_expect_success 'submodule update --recursive s\n       \ttest_cmp expect.err actual.err\n       '\n       \n     -+test_expect_success 'submodule update with submodule.propagateBranches checks out branches' '\n     -+\ttest_when_finished \"rm -fr top-cloned\" &&\n     -+\tcp -r top-clean top-cloned &&\n     -+\n     -+\t# Create a new upstream submodule\n     -+\tgit init middle2 &&\n     -+\ttest_commit -C middle2 \"middle2\" &&\n     -+\tgit -C top submodule add ../middle2 middle2 &&\n     -+\tgit -C top commit -m \"add middle2\" &&\n     -+\n     -+\tgit -C top-cloned checkout -b \"new-branch\" &&\n     -+\tgit -C top-cloned pull origin main &&\n     -+\ttest_config -C top-cloned submodule.propagateBranches true &&\n     -+\tgit -C top-cloned submodule update --recursive &&\n     -+\n     -+\tfor REPO in \"top-cloned/middle2\" \"top-cloned/middle\" \"top-cloned/middle/bottom\"\n     -+\tdo\n     -+\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n     -+\t    test $HEAD_BRANCH = \"refs/heads/new-branch\" || return 1\n     -+\tdone\n     ++test_expect_success 'setup superproject with submodule.propagateBranches' '\n     ++\tgit init sub1 &&\n     ++\ttest_commit -C sub1 \"sub1\" &&\n     ++\tgit init branch-super &&\n     ++\tgit -C branch-super submodule add ../sub1 sub1 &&\n     ++\tgit -C branch-super commit -m \"super\" &&\n     ++\n     ++\t# Clone into a clean repo that we can cp around\n     ++\tgit clone --recurse-submodules \\\n     ++\t\t-c submodule.propagateBranches=true \\\n     ++\t\tbranch-super branch-super-clean &&\n     ++\tgit -C branch-super-clean config submodule.propagateBranches true &&\n     ++\n     ++\t# Create an upstream submodule not in the clone\n     ++\tgit init sub2 &&\n     ++\ttest_commit -C sub2 \"sub2\" &&\n     ++\tgit -C branch-super submodule add ../sub2 sub2 &&\n     ++\tgit -C branch-super commit -m \"add sub2\"\n     ++'\n     ++\n     ++test_expect_success 'submodule.propagateBranches - detached HEAD' '\n     ++\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n     ++\tcp -r branch-super-clean branch-super-cloned &&\n     ++\n     ++\tgit -C branch-super-cloned checkout --detach &&\n     ++\tgit -C branch-super-cloned pull origin main &&\n     ++\tgit -C branch-super-cloned submodule update &&\n     ++\n     ++\t# sub2 should be in detached HEAD\n     ++\tgit -C branch-super-cloned/sub2 rev-parse --verify HEAD &&\n     ++\ttest_must_fail git -C branch-super-cloned/sub2 symbolic-ref HEAD\n     ++'\n     ++\n     ++test_expect_success 'submodule.propagateBranches - branch checked out' '\n     ++\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n     ++\tcp -r branch-super-clean branch-super-cloned &&\n     ++\n     ++\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n     ++\tgit -C branch-super-cloned checkout --recurse-submodules new-branch &&\n     ++\tgit -C branch-super-cloned pull origin main &&\n     ++\tgit -C branch-super-cloned submodule update &&\n     ++\n     ++\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n     ++\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n     ++\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n     ++\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\"\n     ++'\n     ++\n     ++test_expect_success 'submodule.propagateBranches - other branch checked out' '\n     ++\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n     ++\tcp -r branch-super-clean branch-super-cloned &&\n     ++\n     ++\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n     ++\tgit -C branch-super-cloned checkout --recurse-submodules new-branch &&\n     ++\tgit -C branch-super-cloned/sub1 checkout -b other-branch &&\n     ++\tgit -C branch-super-cloned pull origin main &&\n     ++\tgit -C branch-super-cloned submodule update &&\n     ++\n     ++\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n     ++\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n     ++\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n     ++\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\"\n      +'\n      +\n       test_done\n\n-- \ngitgitgadget\n"},{"id":"465332","messageId":"432bc7cb3a42cf39d0033701c2cc677c9109b3dd.1666297239.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","subject":"[PATCH v2 1/7] clone: teach --detach option","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-20T20:20:32Z","receivedAt":"2022-10-20T20:20:51Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nTeach \"git clone\" the \"--detach\" option, which leaves the cloned repo in\ndetached HEAD (like \"git checkout --detach\"). In addition, if the clone\nis not bare, do not create the local branch pointed to by the remote's\nHEAD symref (bare clones always copy all remote branches directly to\nlocal branches, so the branch is still created in the bare case).\n\nThis is especially useful in the \"submodule.propagateBranches\" workflow,\nwhere local submodule branches are named after the superproject's\nbranches, so it makes no sense to create a local branch named after the\nsubmodule's remote's branch.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/git-clone.txt |  8 +++++++-\n builtin/clone.c             | 12 +++++++++---\n t/t5601-clone.sh            | 22 ++++++++++++++++++++++\n 3 files changed, 38 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex d6434d262d6..6a4e5d31b46 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t  [--depth <depth>] [--[no-]single-branch] [--no-tags]\n \t  [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]\n \t  [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]\n-\t  [--filter=<filter> [--also-filter-submodules]] [--] <repository>\n+\t  [--filter=<filter> [--also-filter-submodules] [--detach]] [--] <repository>\n \t  [<directory>]\n \n DESCRIPTION\n@@ -210,6 +210,12 @@ objects from the source repository into a pack in the cloned repository.\n \t`--branch` can also take tags and detaches the HEAD at that commit\n \tin the resulting repository.\n \n+--detach::\n+\tIf the cloned repository's HEAD points to a branch, point the newly\n+\tcreated HEAD to the branch's commit instead of the branch itself.\n+\tAdditionally, in a non-bare repository, the corresponding local branch\n+\twill not be created.\n+\n -u <upload-pack>::\n --upload-pack <upload-pack>::\n \tWhen given, and the repository to clone from is accessed\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 547d6464b3c..e624d3f49a2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -78,6 +78,7 @@ static int option_filter_submodules = -1;    /* unspecified */\n static int config_filter_submodules = -1;    /* unspecified */\n static struct string_list server_options = STRING_LIST_INIT_NODUP;\n static int option_remote_submodules;\n+static int option_detach;\n static const char *bundle_uri;\n \n static int recurse_submodules_cb(const struct option *opt,\n@@ -162,6 +163,8 @@ static struct option builtin_clone_options[] = {\n \t\t    N_(\"any cloned submodules will use their remote-tracking branch\")),\n \tOPT_BOOL(0, \"sparse\", &option_sparse_checkout,\n \t\t    N_(\"initialize sparse-checkout file to include only files at root\")),\n+\tOPT_BOOL(0, \"detach\", &option_detach,\n+\t\t N_(\"detach HEAD and don't create a local branch\")),\n \tOPT_STRING(0, \"bundle-uri\", &bundle_uri,\n \t\t   N_(\"uri\"), N_(\"a URI for downloading bundles before fetching from origin remote\")),\n \tOPT_END()\n@@ -613,10 +616,12 @@ static void update_remote_refs(const struct ref *refs,\n }\n \n static void update_head(const struct ref *our, const struct ref *remote,\n-\t\t\tconst char *unborn, const char *msg)\n+\t\t\tconst char *unborn, int should_detach,\n+\t\t\tconst char *msg)\n {\n \tconst char *head;\n-\tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n+\tif (our && !should_detach &&\n+\t    skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n \t\tif (create_symref(\"HEAD\", our->name, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -1357,7 +1362,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\t\t   branch_top.buf, reflog_msg.buf, transport,\n \t\t\t   !is_local);\n \n-\tupdate_head(our_head_points_at, remote_head, unborn_head, reflog_msg.buf);\n+\tupdate_head(our_head_points_at, remote_head, unborn_head,\n+\t\t    option_detach, reflog_msg.buf);\n \n \t/*\n \t * We want to show progress for recursive submodule clones iff\ndiff --git a/t/t5601-clone.sh b/t/t5601-clone.sh\nindex 45f0803ed4d..418cfd54717 100755\n--- a/t/t5601-clone.sh\n+++ b/t/t5601-clone.sh\n@@ -333,6 +333,28 @@ test_expect_success 'clone checking out a tag' '\n \ttest_cmp fetch.expected fetch.actual\n '\n \n+test_expect_success '--detach detaches and does not create branch' '\n+\ttest_when_finished \"rm -fr dst\" &&\n+\tgit clone --detach src dst &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git rev-parse main &&\n+\t\ttest_must_fail git symbolic-ref HEAD &&\n+\t\ttest_cmp_rev HEAD refs/remotes/origin/HEAD\n+\t)\n+'\n+\n+test_expect_success '--detach with --bare detaches but creates branch' '\n+\ttest_when_finished \"rm -fr dst\" &&\n+\tgit clone --bare --detach src dst &&\n+\t(\n+\t\tcd dst &&\n+\t\tgit rev-parse main &&\n+\t\ttest_must_fail git symbolic-ref HEAD &&\n+\t\ttest_cmp_rev HEAD refs/heads/main\n+\t)\n+'\n+\n test_expect_success 'set up ssh wrapper' '\n \tcp \"$GIT_BUILD_DIR/t/helper/test-fake-ssh$X\" \\\n \t\t\"$TRASH_DIRECTORY/ssh$X\" &&\n-- \ngitgitgadget\n\n"},{"id":"465334","messageId":"20499c62065d640b57bdcfd959e10b214f7e7297.1666297239.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","subject":"[PATCH v2 2/7] repo-settings: add submodule_propagate_branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-20T20:20:33Z","receivedAt":"2022-10-20T20:20:54Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nWhen processes recurse into submodules, the child processes have to\nuse the same value of \"submodule.propagateBranches\" as the parent\nprocess regardless of whether the process is spawned in the superproject\nor submodule, otherwise the behavior may be inconsistent if the\nrepositories don't agree on the config.\n\nWe haven't needed a way to propagate the config because the only command\nthat reads \"submodule.propagateBranches\" is \"git branch\", which only has\none mode of operation with \"--recurse-submodules\". However, a future\ncommit will teach \"submodule.propagateBranches\" to \"git submodule\nupdate\", making this necessary.\n\nPropagate \"submodule.propagateBranches\" to child processes by adding a\ncorresponding GIT_INTERNAL_* environment variable and repository\nsetting, and setting the environment variable inside\nprepare_submodule_repo_env(). Then, refactor builtin/branch.c to read\nthe repository setting.\n\nUsing an internal environment variable is a potentially leaky\nabstraction because environment variables can come from sources besides\nthe parent process. A more robust solution would be to teach Git that\nthe repository is a submodule and to only read\n\"submodule.propagateBranches\" from the superproject config. There is WIP\nfor this on the ML [1].\n\nAnother alternative would be to pass \"-c submodule.propagateBranches\" to\nall child processes. This is error-prone because many different\nprocesses are invoked directly or indirectly by \"git submodule update\"\n(e.g. \"git submodule--helper clone\", \"git clone\", \"git checkout\"). With\nan environment variable, we can avoid this work because\nprepare_submodule_repo_env() is already called for submodule child\nprocesses.\n\n[1] https://lore.kernel.org/git/20220310004423.2627181-1-emilyshaffer@google.com/\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 11 +++--------\n cache.h          |  1 +\n repo-settings.c  | 10 ++++++++++\n repository.h     |  1 +\n submodule.c      |  2 ++\n 5 files changed, 17 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e0e0af43202..8279d4eeb15 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -40,7 +40,6 @@ static const char * const builtin_branch_usage[] = {\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@@ -106,10 +105,6 @@ static int git_branch_config(const char *var, const char *value, void *cb)\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@@ -723,7 +718,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, builtin_branch_usage,\n \t\t\t     0);\n-\n+\tprepare_repo_settings(the_repository);\n \tif (!delete && !rename && !copy && !edit_description && !new_upstream &&\n \t    !show_current && !unset_upstream && argc == 0)\n \t\tlist = 1;\n@@ -739,7 +734,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (recurse_submodules_explicit) {\n-\t\tif (!submodule_propagate_branches)\n+\t\tif (!the_repository->settings.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@@ -747,7 +742,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \trecurse_submodules =\n \t\t(recurse_submodules || recurse_submodules_explicit) &&\n-\t\tsubmodule_propagate_branches;\n+\t\tthe_repository->settings.submodule_propagate_branches;\n \n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\ndiff --git a/cache.h b/cache.h\nindex 26ed03bd6de..151e1d49e77 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -505,6 +505,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define GIT_WORK_TREE_ENVIRONMENT \"GIT_WORK_TREE\"\n #define GIT_PREFIX_ENVIRONMENT \"GIT_PREFIX\"\n #define GIT_SUPER_PREFIX_ENVIRONMENT \"GIT_INTERNAL_SUPER_PREFIX\"\n+#define GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT \"GIT_INTERNAL_SUBMODULE_PROPAGATE_BRANCHES\"\n #define DEFAULT_GIT_DIR_ENVIRONMENT \".git\"\n #define DB_ENVIRONMENT \"GIT_OBJECT_DIRECTORY\"\n #define INDEX_ENVIRONMENT \"GIT_INDEX_FILE\"\ndiff --git a/repo-settings.c b/repo-settings.c\nindex e8b58151bc4..180df14e45f 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -71,6 +71,16 @@ void prepare_repo_settings(struct repository *r)\n \tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))\n \t\tr->settings.core_multi_pack_index = 1;\n \n+\t/*\n+\t * If the environment variable is set, assume that it came from the\n+\t * superproject and ignore the config.\n+\t */\n+\tr->settings.submodule_propagate_branches =\n+\t\tgit_env_bool(GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT, -1);\n+\tif (r->settings.submodule_propagate_branches == -1)\n+\t\trepo_cfg_bool(r, \"submodule.propagateBranches\",\n+\t\t\t      &r->settings.submodule_propagate_branches, 0);\n+\n \t/*\n \t * Non-boolean config\n \t */\ndiff --git a/repository.h b/repository.h\nindex 24316ac944e..3df6fdbdd5c 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -37,6 +37,7 @@ struct repo_settings {\n \tint fetch_write_commit_graph;\n \tint command_requires_full_index;\n \tint sparse_index;\n+\tint submodule_propagate_branches;\n \n \tstruct fsmonitor_settings *fsmonitor; /* lazily loaded */\n \ndiff --git a/submodule.c b/submodule.c\nindex bf7a2c79183..624404957fa 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -503,6 +503,8 @@ static void print_submodule_diff_summary(struct repository *r, struct rev_info *\n \n void prepare_submodule_repo_env(struct strvec *out)\n {\n+\tif (the_repository->settings.submodule_propagate_branches)\n+\t\tstrvec_pushf(out, \"%s=1\", GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT);\n \tprepare_other_repo_env(out, DEFAULT_GIT_DIR_ENVIRONMENT);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"465335","messageId":"a4056e200eda9958efe77801b39bdebfdc1497a2.1666297239.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","subject":"[PATCH v2 3/7] submodule--helper clone: create named branch","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-20T20:20:34Z","receivedAt":"2022-10-20T20:20:56Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nWhen submodule branching is enabled (i.e. \"submodule.propagateBranches =\ntrue\"), submodules are expected to have the same set of branches as the\nsuperproject. To support this behavior in newly cloned submodules, teach\n\"git submodule--helper clone\" to:\n\n- clone with the \"--detach\" flag (so that the submodule doesn't create a\n  branch corresponding to the remote's HEAD)\n- create a branch when using the --branch and --branch-oid flags\n\nThe --branch and --branch-oid flags are only allowed when submodule\nbranching is enabled, otherwise the named branch might conflict with the\nbranch from the submodule remote's HEAD.\n\nThese flags will be used by `git submodule update` in a later commit.\n\"git submodule add\" (which also invokes \"git submodule--helper clone\")\nshould also do something similar when submodule branching is enabled,\nbut this is left for a later series.\n\nArguably, \"--detach\" should also be the default for\n\"submodule.propagateBranches=false\" since it doesn't make sense to\ncreate a submodule branch when the submodule is always expected to be in\ndetached HEAD. But, to be conservative, this commit does not change the\nbehavior of \"submodule.propagateBranches=false\".\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c | 33 ++++++++++++++++++++++++++++++++-\n 1 file changed, 32 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 0b4acb442b2..1ce3458a29c 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1503,6 +1503,8 @@ struct module_clone_data {\n \tconst char *name;\n \tconst char *url;\n \tconst char *depth;\n+\tconst char *branch;\n+\tconst char *branch_oid;\n \tstruct list_objects_filter_options *filter_options;\n \tunsigned int quiet: 1;\n \tunsigned int progress: 1;\n@@ -1692,6 +1694,8 @@ static int clone_submodule(const struct module_clone_data *clone_data,\n \t\t\tstrvec_push(&cp.args, clone_data->single_branch ?\n \t\t\t\t    \"--single-branch\" :\n \t\t\t\t    \"--no-single-branch\");\n+\t\tif (the_repository->settings.submodule_propagate_branches)\n+\t\t\tstrvec_push(&cp.args, \"--detach\");\n \n \t\tstrvec_push(&cp.args, \"--\");\n \t\tstrvec_push(&cp.args, clone_data->url);\n@@ -1704,6 +1708,21 @@ static int clone_submodule(const struct module_clone_data *clone_data,\n \t\tif(run_command(&cp))\n \t\t\tdie(_(\"clone of '%s' into submodule path '%s' failed\"),\n \t\t\t    clone_data->url, clone_data_path);\n+\n+\t\tif (clone_data->branch) {\n+\t\t\tstruct child_process branch_cp = CHILD_PROCESS_INIT;\n+\n+\t\t\tbranch_cp.git_cmd = 1;\n+\t\t\tprepare_other_repo_env(&branch_cp.env, sm_gitdir);\n+\n+\t\t\tstrvec_pushl(&branch_cp.args, \"branch\",\n+\t\t\t\t     clone_data->branch, clone_data->branch_oid,\n+\t\t\t\t     NULL);\n+\n+\t\t\tif (run_command(&branch_cp))\n+\t\t\t\tdie(_(\"could not create branch '%s' in submodule path '%s'\"),\n+\t\t\t\t    clone_data->branch, clone_data_path);\n+\t\t}\n \t} else {\n \t\tchar *path;\n \n@@ -1778,6 +1797,12 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n \t\t\t   N_(\"disallow cloning into non-empty directory\")),\n \t\tOPT_BOOL(0, \"single-branch\", &clone_data.single_branch,\n \t\t\t N_(\"clone only one branch, HEAD or --branch\")),\n+\t\tOPT_STRING(0, \"branch\", &clone_data.branch,\n+\t\t\t   N_(\"string\"),\n+\t\t\t   N_(\"name of branch to be created\")),\n+\t\tOPT_STRING(0, \"branch-oid\", &clone_data.branch_oid,\n+\t\t\t   N_(\"object-id\"),\n+\t\t\t   N_(\"commit id for new branch\")),\n \t\tOPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),\n \t\tOPT_END()\n \t};\n@@ -1785,12 +1810,14 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n \t\tN_(\"git submodule--helper clone [--prefix=<path>] [--quiet] \"\n \t\t   \"[--reference <repository>] [--name <name>] [--depth <depth>] \"\n \t\t   \"[--single-branch] [--filter <filter-spec>] \"\n+\t\t   \"[--branch <branch> --branch-oid <oid>]\"\n \t\t   \"--url <url> --path <path>\"),\n \t\tNULL\n \t};\n \n \targc = parse_options(argc, argv, prefix, module_clone_options,\n \t\t\t     git_submodule_helper_usage, 0);\n+\tprepare_repo_settings(the_repository);\n \n \tclone_data.dissociate = !!dissociate;\n \tclone_data.quiet = !!quiet;\n@@ -1798,9 +1825,13 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n \tclone_data.require_init = !!require_init;\n \tclone_data.filter_options = &filter_options;\n \n-\tif (argc || !clone_data.url || !clone_data.path || !*(clone_data.path))\n+\tif (argc || !clone_data.url || !clone_data.path || !*(clone_data.path)\n+\t    || (!!clone_data.branch != !!clone_data.branch_oid))\n \t\tusage_with_options(git_submodule_helper_usage,\n \t\t\t\t   module_clone_options);\n+\tif ((clone_data.branch &&\n+\t     !the_repository->settings.submodule_propagate_branches))\n+\t\tBUG(\"--branch is only expected with submodule.propagateBranches\");\n \n \tclone_submodule(&clone_data, &reference);\n \tlist_objects_filter_release(&filter_options);\n-- \ngitgitgadget\n\n"},{"id":"465336","messageId":"affd0e24e1d41e85df434539ccd13fb9600abd77.1666297239.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","subject":"[PATCH v2 4/7] t5617: drop references to remote-tracking branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-20T20:20:35Z","receivedAt":"2022-10-20T20:20:59Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nIt has included submodule cloning tests without remote-tracking branches\ntests since f05da2b48b (clone, submodule: pass partial clone filters to\nsubmodules, 2022-02-04) at least. Rename it accordingly so that we can\nput future submodule cloning tests there.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n ...617-clone-submodules-remote.sh => t5617-clone-submodules.sh} | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n rename t/{t5617-clone-submodules-remote.sh => t5617-clone-submodules.sh} (97%)\n\ndiff --git a/t/t5617-clone-submodules-remote.sh b/t/t5617-clone-submodules.sh\nsimilarity index 97%\nrename from t/t5617-clone-submodules-remote.sh\nrename to t/t5617-clone-submodules.sh\nindex 68843382493..c43a5b26fab 100755\n--- a/t/t5617-clone-submodules-remote.sh\n+++ b/t/t5617-clone-submodules.sh\n@@ -1,6 +1,6 @@\n #!/bin/sh\n \n-test_description='Test cloning repos with submodules using remote-tracking branches'\n+test_description='Test cloning repos with submodules'\n \n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n-- \ngitgitgadget\n\n"},{"id":"465337","messageId":"6f769cb80ad0bd7df98b8c7d207922f8631133f2.1666297239.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","subject":"[PATCH v2 5/7] submodule: return target of submodule symref","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-20T20:20:36Z","receivedAt":"2022-10-20T20:21:15Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nresolve_gitlink_ref() can tell us which oid the submodule ref is\npointing to, but in a future commit, we would also like to know the\nsymbolic ref target if we are checking a symbolic ref. Teach\nresolve_gitlink_ref() to \"return\" the symbolic ref's target via an \"out\"\nparameter.\n\nThis changes resolve_gitlink_ref()'s signature so that new callers\ntrying to use the old signature will be stopped by the compiler. If we\nreturned the target instead (just like refs_resolve_ref_unsafe()), we\nwould be more consistent with refs_resolve_ref_unsafe(), but callers\nexpecting the old signature will get the opposite return value from what\nthey expect (since exit code 0 means success, but NULL pointer means\nfailure). We should do this refactor once we think that nobody will try\nto use the old signature.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c |  8 +++++---\n builtin/update-index.c      |  4 ++--\n combine-diff.c              |  3 ++-\n diff-lib.c                  |  2 +-\n dir.c                       |  2 +-\n object-file.c               |  2 +-\n read-cache.c                |  4 ++--\n refs.c                      | 10 ++++++----\n refs.h                      |  5 ++++-\n unpack-trees.c              |  3 ++-\n 10 files changed, 26 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 1ce3458a29c..138b133790f 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2531,7 +2531,8 @@ static int update_submodule(struct update_data *update_data)\n \n \tif (update_data->just_cloned)\n \t\toidcpy(&update_data->suboid, null_oid());\n-\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &update_data->suboid))\n+\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n+\t\t\t\t     &update_data->suboid, NULL))\n \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n \t\t\t\t   update_data->displaypath);\n \n@@ -2558,7 +2559,8 @@ static int update_submodule(struct update_data *update_data)\n \t\t\t\t\t\t   update_data->sm_path);\n \t\t}\n \n-\t\tif (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))\n+\t\tif (resolve_gitlink_ref(update_data->sm_path, remote_ref,\n+\t\t\t\t\t&update_data->oid, NULL))\n \t\t\treturn die_message(_(\"Unable to find %s revision in submodule path '%s'\"),\n \t\t\t\t\t   remote_ref, update_data->sm_path);\n \n@@ -3303,7 +3305,7 @@ static void die_on_repo_without_commits(const char *path)\n \tstrbuf_addstr(&sb, path);\n \tif (is_nonbare_repository_dir(&sb)) {\n \t\tstruct object_id oid;\n-\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid) < 0)\n+\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid, NULL) < 0)\n \t\t\tdie(_(\"'%s' does not have a commit checked out\"), path);\n \t}\n \tstrbuf_release(&sb);\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex b62249905f1..19a21a4586c 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -339,7 +339,7 @@ static int process_directory(const char *path, int len, struct stat *st)\n \t\tif (S_ISGITLINK(ce->ce_mode)) {\n \n \t\t\t/* Do nothing to the index if there is no HEAD! */\n-\t\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid) < 0)\n+\t\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid, NULL) < 0)\n \t\t\t\treturn 0;\n \n \t\t\treturn add_one_path(ce, path, len, st);\n@@ -365,7 +365,7 @@ static int process_directory(const char *path, int len, struct stat *st)\n \t}\n \n \t/* No match - should we add it as a gitlink? */\n-\tif (!resolve_gitlink_ref(path, \"HEAD\", &oid))\n+\tif (!resolve_gitlink_ref(path, \"HEAD\", &oid, NULL))\n \t\treturn add_one_path(NULL, path, len, st);\n \n \t/* Error out. */\ndiff --git a/combine-diff.c b/combine-diff.c\nindex b0ece954808..88efcaeefa7 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -1060,7 +1060,8 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n \t\t\telem->mode = canon_mode(st.st_mode);\n \t\t} else if (S_ISDIR(st.st_mode)) {\n \t\t\tstruct object_id oid;\n-\t\t\tif (resolve_gitlink_ref(elem->path, \"HEAD\", &oid) < 0)\n+\t\t\tif (resolve_gitlink_ref(elem->path, \"HEAD\", &oid,\n+\t\t\t\t\t\tNULL) < 0)\n \t\t\t\tresult = grab_blob(opt->repo, &elem->oid,\n \t\t\t\t\t\t   elem->mode, &result_size,\n \t\t\t\t\t\t   NULL, NULL);\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 2edea41a234..ac94b6234ca 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -53,7 +53,7 @@ static int check_removed(const struct index_state *istate, const struct cache_en\n \t\t * a directory --- the blob was removed!\n \t\t */\n \t\tif (!S_ISGITLINK(ce->ce_mode) &&\n-\t\t    resolve_gitlink_ref(ce->name, \"HEAD\", &sub))\n+\t\t    resolve_gitlink_ref(ce->name, \"HEAD\", &sub, NULL))\n \t\t\treturn 1;\n \t}\n \treturn 0;\ndiff --git a/dir.c b/dir.c\nindex d604d1bab98..81d232424c1 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3251,7 +3251,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)\n \tstruct object_id submodule_head;\n \n \tif ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&\n-\t    !resolve_gitlink_ref(path->buf, \"HEAD\", &submodule_head)) {\n+\t    !resolve_gitlink_ref(path->buf, \"HEAD\", &submodule_head, NULL)) {\n \t\t/* Do not descend and nuke a nested git work tree. */\n \t\tif (kept_up)\n \t\t\t*kept_up = 1;\ndiff --git a/object-file.c b/object-file.c\nindex 5e309602346..a3c6580c16e 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2522,7 +2522,7 @@ int index_path(struct index_state *istate, struct object_id *oid,\n \t\tstrbuf_release(&sb);\n \t\tbreak;\n \tcase S_IFDIR:\n-\t\treturn resolve_gitlink_ref(path, \"HEAD\", oid);\n+\t\treturn resolve_gitlink_ref(path, \"HEAD\", oid, NULL);\n \tdefault:\n \t\treturn error(_(\"%s: unsupported file type\"), path);\n \t}\ndiff --git a/read-cache.c b/read-cache.c\nindex 32024029274..4c1bf33ef48 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -285,7 +285,7 @@ static int ce_compare_gitlink(const struct cache_entry *ce)\n \t *\n \t * If so, we consider it always to match.\n \t */\n-\tif (resolve_gitlink_ref(ce->name, \"HEAD\", &oid) < 0)\n+\tif (resolve_gitlink_ref(ce->name, \"HEAD\", &oid, NULL) < 0)\n \t\treturn 0;\n \treturn !oideq(&oid, &ce->oid);\n }\n@@ -781,7 +781,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \n \tnamelen = strlen(path);\n \tif (S_ISDIR(st_mode)) {\n-\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid) < 0)\n+\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid, NULL) < 0)\n \t\t\treturn error(_(\"'%s' does not have a commit checked out\"), path);\n \t\twhile (namelen && path[namelen-1] == '/')\n \t\t\tnamelen--;\ndiff --git a/refs.c b/refs.c\nindex 1491ae937eb..a32a25ccb69 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1904,19 +1904,21 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n }\n \n int resolve_gitlink_ref(const char *submodule, const char *refname,\n-\t\t\tstruct object_id *oid)\n+\t\t\tstruct object_id *oid, const char **target_out)\n {\n \tstruct ref_store *refs;\n \tint flags;\n+\tconst char *target;\n \n \trefs = get_submodule_ref_store(submodule);\n \n \tif (!refs)\n \t\treturn -1;\n-\n-\tif (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||\n-\t    is_null_oid(oid))\n+\ttarget = refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags);\n+\tif (!target || is_null_oid(oid))\n \t\treturn -1;\n+\tif (target_out)\n+\t\t*target_out = target;\n \treturn 0;\n }\n \ndiff --git a/refs.h b/refs.h\nindex 8958717a17d..d5c32fac000 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -137,9 +137,12 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled);\n  * submodule (which must be non-NULL). If the resolution is\n  * successful, return 0 and set oid to the name of the object;\n  * otherwise, return a non-zero value.\n+ *\n+ * FIXME: Return \"target\" just like refs_resolve_ref_unsafe(). This will be\n+ * safe to do once we merge resolve_gitlink_ref() into master.\n  */\n int resolve_gitlink_ref(const char *submodule, const char *refname,\n-\t\t\tstruct object_id *oid);\n+\t\t\tstruct object_id *oid, const char **target);\n \n /*\n  * Return true iff abbrev_name is a possible abbreviation for\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex bae812156c4..db12bfcaffd 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -2288,7 +2288,8 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,\n \n \tif (S_ISGITLINK(ce->ce_mode)) {\n \t\tstruct object_id oid;\n-\t\tint sub_head = resolve_gitlink_ref(ce->name, \"HEAD\", &oid);\n+\t\tint sub_head =\n+\t\t\tresolve_gitlink_ref(ce->name, \"HEAD\", &oid, NULL);\n \t\t/*\n \t\t * If we are not going to update the submodule, then\n \t\t * we don't care.\n-- \ngitgitgadget\n\n"},{"id":"465338","messageId":"abdfa888ff5a8275dcd0884cea1df0aa5af108f7.1666297239.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","subject":"[PATCH v2 6/7] submodule update: refactor update targets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-20T20:20:37Z","receivedAt":"2022-10-20T20:21:17Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nRefactor two \"git submodule update\" code locations so that they no\nlonger refer to oids directly. This shrinks the next commit's diff,\nwhere this code will need to handle branches.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c | 24 +++++++++++++-----------\n 1 file changed, 13 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 138b133790f..990adeb2e19 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2253,7 +2253,7 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet,\n static int run_update_command(const struct update_data *ud, int subforce)\n {\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tchar *oid = oid_to_hex(&ud->oid);\n+\tconst char *update_target = oid_to_hex(&ud->oid);;\n \tint ret;\n \n \tswitch (ud->update_strategy.type) {\n@@ -2283,7 +2283,7 @@ static int run_update_command(const struct update_data *ud, int subforce)\n \t\tBUG(\"unexpected update strategy type: %d\",\n \t\t    ud->update_strategy.type);\n \t}\n-\tstrvec_push(&cp.args, oid);\n+\tstrvec_push(&cp.args, update_target);\n \n \tcp.dir = ud->sm_path;\n \tprepare_submodule_repo_env(&cp.env);\n@@ -2291,20 +2291,20 @@ static int run_update_command(const struct update_data *ud, int subforce)\n \t\tswitch (ud->update_strategy.type) {\n \t\tcase SM_UPDATE_CHECKOUT:\n \t\t\tdie_message(_(\"Unable to checkout '%s' in submodule path '%s'\"),\n-\t\t\t\t    oid, ud->displaypath);\n+\t\t\t\t    update_target, ud->displaypath);\n \t\t\t/* No \"ret\" assignment, use \"git checkout\"'s */\n \t\t\tbreak;\n \t\tcase SM_UPDATE_REBASE:\n \t\t\tret = die_message(_(\"Unable to rebase '%s' in submodule path '%s'\"),\n-\t\t\t\t\t  oid, ud->displaypath);\n+\t\t\t\t\t  update_target, ud->displaypath);\n \t\t\tbreak;\n \t\tcase SM_UPDATE_MERGE:\n \t\t\tret = die_message(_(\"Unable to merge '%s' in submodule path '%s'\"),\n-\t\t\t\t\t  oid, ud->displaypath);\n+\t\t\t\t\t  update_target, ud->displaypath);\n \t\t\tbreak;\n \t\tcase SM_UPDATE_COMMAND:\n \t\t\tret = die_message(_(\"Execution of '%s %s' failed in submodule path '%s'\"),\n-\t\t\t\t\t  ud->update_strategy.command, oid, ud->displaypath);\n+\t\t\t\t\t  ud->update_strategy.command, update_target, ud->displaypath);\n \t\t\tbreak;\n \t\tdefault:\n \t\t\tBUG(\"unexpected update strategy type: %d\",\n@@ -2320,19 +2320,19 @@ static int run_update_command(const struct update_data *ud, int subforce)\n \tswitch (ud->update_strategy.type) {\n \tcase SM_UPDATE_CHECKOUT:\n \t\tprintf(_(\"Submodule path '%s': checked out '%s'\\n\"),\n-\t\t       ud->displaypath, oid);\n+\t\t       ud->displaypath, update_target);\n \t\tbreak;\n \tcase SM_UPDATE_REBASE:\n \t\tprintf(_(\"Submodule path '%s': rebased into '%s'\\n\"),\n-\t\t       ud->displaypath, oid);\n+\t\t       ud->displaypath, update_target);\n \t\tbreak;\n \tcase SM_UPDATE_MERGE:\n \t\tprintf(_(\"Submodule path '%s': merged in '%s'\\n\"),\n-\t\t       ud->displaypath, oid);\n+\t\t       ud->displaypath, update_target);\n \t\tbreak;\n \tcase SM_UPDATE_COMMAND:\n \t\tprintf(_(\"Submodule path '%s': '%s %s'\\n\"),\n-\t\t       ud->displaypath, ud->update_strategy.command, oid);\n+\t\t       ud->displaypath, ud->update_strategy.command, update_target);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"unexpected update strategy type: %d\",\n@@ -2519,6 +2519,7 @@ static void update_data_to_args(const struct update_data *update_data,\n \n static int update_submodule(struct update_data *update_data)\n {\n+\tint submodule_up_to_date;\n \tint ret;\n \n \tret = determine_submodule_update_strategy(the_repository,\n@@ -2567,7 +2568,8 @@ static int update_submodule(struct update_data *update_data)\n \t\tfree(remote_ref);\n \t}\n \n-\tif (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) {\n+\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n+\tif (!submodule_up_to_date || update_data->force) {\n \t\tret = run_update_procedure(update_data);\n \t\tif (ret)\n \t\t\treturn ret;\n-- \ngitgitgadget\n\n"},{"id":"465339","messageId":"3f98b0d17397dbce85aa87a9591981c790f8f7a2.1666297239.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","subject":"[PATCH v2 7/7] clone, submodule update: create and check out branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-20T20:20:38Z","receivedAt":"2022-10-20T20:21:19Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nTeach \"git submodule update\" to:\n\n- create the branch with the same name as the current superproject\n  branch when cloning a submodule\n- check out that branch (instead of the commit OID) when updating\n  the submodule worktree\n\nwhen submodule branching is enabled (submodule.propagateBranches = true)\non the superproject and a branch is checked out. \"git clone\n--recurse-submodules\" also learns this trick because it is implemented\nwith \"git submodule update --recursive\".\n\nThis approach of checking out the branch will not result in a dirty\nworktree for freshly cloned submodules because we can ensure that the\nsubmodule branch points to the superproject gitlink. In other cases, it\ndoes not work as well, but we can handle them incrementally:\n\n- \"git pull --recurse-submodules\" merges the superproject tree (without\n  updating the submodule branches), and runs \"git submodule update\" to\n  update the worktrees, so it is almost guaranteed to result in a dirty\n  worktree.\n\n  The implementation of \"git pull --recurse-submodules\" is likely to\n  change drastically as submodule.propagateBranches work progresses\n  (e.g. \"git merge\" learns to recurse in to submodules), and we may be\n  able to replace the \"git submodule update\" invocation, or teach it new\n  tricks that make the update behave well.\n\n- The user might make changes to the submodule branch without committing\n  them back to superproject. This is primarily affects \"git checkout\n  --recurse-submodules\", since that is the primary way of switching away\n  from a branch and leaving behind WIP (as opposed to \"git submodule\n  update\", which is run post-checkout).\n\n  In a future series, \"git checkout --recurse-submodules\" will learn to\n  consider submodule branches. We can introduce appropriate guardrails\n  then, e.g. requiring that the superproject working tree is not dirty\n  before switching away.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c | 32 +++++++++++++++++--\n t/t5617-clone-submodules.sh | 34 ++++++++++++++++++++\n t/t7406-submodule-update.sh | 64 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 127 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 990adeb2e19..4576ba22544 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1915,6 +1915,7 @@ static void submodule_update_clone_release(struct submodule_update_clone *suc)\n struct update_data {\n \tconst char *prefix;\n \tchar *displaypath;\n+\tconst char *super_branch;\n \tenum submodule_update_type update_default;\n \tstruct object_id suboid;\n \tstruct string_list references;\n@@ -2090,6 +2091,10 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,\n \t\tstrvec_push(&child->args, suc->update_data->single_branch ?\n \t\t\t\t\t      \"--single-branch\" :\n \t\t\t\t\t      \"--no-single-branch\");\n+\tif (ud->super_branch) {\n+\t\tstrvec_pushf(&child->args, \"--branch=%s\", ud->super_branch);\n+\t\tstrvec_pushf(&child->args, \"--branch-oid=%s\", oid_to_hex(&ce->oid));\n+\t}\n \n cleanup:\n \tfree(displaypath);\n@@ -2253,9 +2258,15 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet,\n static int run_update_command(const struct update_data *ud, int subforce)\n {\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tconst char *update_target = oid_to_hex(&ud->oid);;\n+\tconst char *update_target;\n \tint ret;\n \n+\tif (ud->update_strategy.type == SM_UPDATE_CHECKOUT &&\n+\t    ud->super_branch)\n+\t\tupdate_target = ud->super_branch;\n+\telse\n+\t\tupdate_target = oid_to_hex(&ud->oid);\n+\n \tswitch (ud->update_strategy.type) {\n \tcase SM_UPDATE_CHECKOUT:\n \t\tcp.git_cmd = 1;\n@@ -2521,6 +2532,7 @@ static int update_submodule(struct update_data *update_data)\n {\n \tint submodule_up_to_date;\n \tint ret;\n+\tconst char *submodule_head = \"HEAD\";\n \n \tret = determine_submodule_update_strategy(the_repository,\n \t\t\t\t\t\t  update_data->just_cloned,\n@@ -2533,7 +2545,7 @@ static int update_submodule(struct update_data *update_data)\n \tif (update_data->just_cloned)\n \t\toidcpy(&update_data->suboid, null_oid());\n \telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n-\t\t\t\t     &update_data->suboid, NULL))\n+\t\t\t\t     &update_data->suboid, &submodule_head))\n \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n \t\t\t\t   update_data->displaypath);\n \n@@ -2568,7 +2580,14 @@ static int update_submodule(struct update_data *update_data)\n \t\tfree(remote_ref);\n \t}\n \n-\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n+\tif (!update_data->super_branch)\n+\t\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n+\telse if (skip_prefix(submodule_head, \"refs/heads/\", &submodule_head))\n+\t\tsubmodule_up_to_date = !strcmp(update_data->super_branch, submodule_head);\n+\t/* submodule_branch is \"HEAD\"; the submodule is in detached HEAD */\n+\telse\n+\t\tsubmodule_up_to_date = 0;\n+\n \tif (!submodule_up_to_date || update_data->force) {\n \t\tret = run_update_procedure(update_data);\n \t\tif (ret)\n@@ -2603,6 +2622,12 @@ static int update_submodules(struct update_data *update_data)\n \tint i, ret = 0;\n \tstruct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;\n \n+\tif (the_repository->settings.submodule_propagate_branches) {\n+\t\tstruct branch *current_branch = branch_get(NULL);\n+\t\tif (current_branch)\n+\t\t\tupdate_data->super_branch = current_branch->name;\n+\t}\n+\n \tsuc.update_data = update_data;\n \trun_processes_parallel_tr2(suc.update_data->max_jobs, update_clone_get_next_task,\n \t\t\t\t   update_clone_start_failure,\n@@ -2718,6 +2743,7 @@ static int module_update(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, module_update_options,\n \t\t\t     git_submodule_helper_usage, 0);\n+\tprepare_repo_settings(the_repository);\n \n \tif (opt.require_init)\n \t\topt.init = 1;\ndiff --git a/t/t5617-clone-submodules.sh b/t/t5617-clone-submodules.sh\nindex c43a5b26fab..51593376ce4 100755\n--- a/t/t5617-clone-submodules.sh\n+++ b/t/t5617-clone-submodules.sh\n@@ -13,10 +13,17 @@ test_expect_success 'setup' '\n \tgit config --global protocol.file.allow always &&\n \tgit checkout -b main &&\n \ttest_commit commit1 &&\n+\tmkdir subsub &&\n+\t(\n+\t\tcd subsub &&\n+\t\tgit init &&\n+\t\ttest_commit subsubcommit1\n+\t) &&\n \tmkdir sub &&\n \t(\n \t\tcd sub &&\n \t\tgit init &&\n+\t\tgit submodule add \"file://$pwd/subsub\" subsub &&\n \t\ttest_commit subcommit1 &&\n \t\tgit tag sub_when_added_to_super &&\n \t\tgit branch other\n@@ -107,4 +114,31 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule\n \ttest_cmp_config -C super_clone3/sub false --default false remote.origin.promisor\n '\n \n+test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '\n+\tgit -C sub checkout -b not-main &&\n+\tgit -C subsub checkout -b not-main &&\n+\tgit clone --recurse-submodules \\\n+\t\t-c submodule.propagateBranches=true \\\n+\t\t\"file://$pwd/.\" super_clone4 &&\n+\n+\t# Assert that each repo is pointing to \"main\"\n+\tfor REPO in \"super_clone4\" \"super_clone4/sub\" \"super_clone4/sub/subsub\"\n+\tdo\n+\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n+\t    test $HEAD_BRANCH = \"refs/heads/main\" || return 1\n+\tdone &&\n+\n+\t# Assert that the submodule branches are pointing to the right revs\n+\tEXPECT_SUB_OID=\"$(git -C super_clone4 rev-parse :sub)\" &&\n+\tACTUAL_SUB_OID=\"$(git -C super_clone4/sub rev-parse refs/heads/main)\" &&\n+\ttest $EXPECT_SUB_OID = $ACTUAL_SUB_OID &&\n+\tEXPECT_SUBSUB_OID=\"$(git -C super_clone4/sub rev-parse :subsub)\" &&\n+\tACTUAL_SUBSUB_OID=\"$(git -C super_clone4/sub/subsub rev-parse refs/heads/main)\" &&\n+\ttest $EXPECT_SUBSUB_OID = $ACTUAL_SUBSUB_OID &&\n+\n+\t# Assert that the submodules do not have branches from their upstream\n+\ttest_must_fail git -C super_clone4/sub rev-parse not-main &&\n+\ttest_must_fail git -C super_clone4/sub/subsub rev-parse not-main\n+'\n+\n test_done\ndiff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\nindex f094e3d7f36..54aa8c5cb54 100755\n--- a/t/t7406-submodule-update.sh\n+++ b/t/t7406-submodule-update.sh\n@@ -1179,4 +1179,68 @@ test_expect_success 'submodule update --recursive skip submodules with strategy=\n \ttest_cmp expect.err actual.err\n '\n \n+test_expect_success 'setup superproject with submodule.propagateBranches' '\n+\tgit init sub1 &&\n+\ttest_commit -C sub1 \"sub1\" &&\n+\tgit init branch-super &&\n+\tgit -C branch-super submodule add ../sub1 sub1 &&\n+\tgit -C branch-super commit -m \"super\" &&\n+\n+\t# Clone into a clean repo that we can cp around\n+\tgit clone --recurse-submodules \\\n+\t\t-c submodule.propagateBranches=true \\\n+\t\tbranch-super branch-super-clean &&\n+\tgit -C branch-super-clean config submodule.propagateBranches true &&\n+\n+\t# Create an upstream submodule not in the clone\n+\tgit init sub2 &&\n+\ttest_commit -C sub2 \"sub2\" &&\n+\tgit -C branch-super submodule add ../sub2 sub2 &&\n+\tgit -C branch-super commit -m \"add sub2\"\n+'\n+\n+test_expect_success 'submodule.propagateBranches - detached HEAD' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned checkout --detach &&\n+\tgit -C branch-super-cloned pull origin main &&\n+\tgit -C branch-super-cloned submodule update &&\n+\n+\t# sub2 should be in detached HEAD\n+\tgit -C branch-super-cloned/sub2 rev-parse --verify HEAD &&\n+\ttest_must_fail git -C branch-super-cloned/sub2 symbolic-ref HEAD\n+'\n+\n+test_expect_success 'submodule.propagateBranches - branch checked out' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n+\tgit -C branch-super-cloned checkout --recurse-submodules new-branch &&\n+\tgit -C branch-super-cloned pull origin main &&\n+\tgit -C branch-super-cloned submodule update &&\n+\n+\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n+\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n+\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n+\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\"\n+'\n+\n+test_expect_success 'submodule.propagateBranches - other branch checked out' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n+\tgit -C branch-super-cloned checkout --recurse-submodules new-branch &&\n+\tgit -C branch-super-cloned/sub1 checkout -b other-branch &&\n+\tgit -C branch-super-cloned pull origin main &&\n+\tgit -C branch-super-cloned submodule update &&\n+\n+\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n+\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n+\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n+\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\"\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"465356","messageId":"xmqqh6zyqftv.fsf@gitster.g","threadId":"58375","inReplyTo":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/7] clone, submodule update: check out submodule branches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-20T22:40:28Z","receivedAt":"2022-10-20T22:40:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> During the v1 discussion, I realize that my idea of the new submodule UX has\n> already diverged from what was initially communicated to the list. I plan to\n> check in a technical document describing the plans for new submodule UX,\n> which should hopefully make these discussions smoother (e.g. the commit\n> message in patch 7 can make reference to the doc).\n\nGood.\n\n> = Description\n>\n> This series teaches \"git clone --recurse-submodules\" and \"git submodule\n> update\" to understand \"submodule.propagateBranches\" (see Further Reading for\n> context), i.e. if the superproject has a branch checked out and a submodule\n> is cloned, the submodule will have the same branch checked out.\n>\n> To do this, \"git submodule update\" checks if ...\n> = Series history\n>\n> Changes in v2:\n>\n>  * The superproject's \"submodule.propagateBranches\" value is always used,\n>    even if false.\n>  * Branches are now created at clone time (by adding a new flag to \"git\n>    submodule clone\"), instead of at update time.\n>  * Rebase onto newer master. This got adjusted slightly to incorporate\n>    ab/submodule-helper-leakfix.\n>  * Add more tests to demonstrate edge case behavior.\n>  * Assorted commit message and doc improvements.\n\nAs the previous round was more than a month old and is clearly not a\nbugfix but is adding a new feature, I do not mind updating to the\nnewer base after a new feature release was made.  There isn't much\nto be gained, other than that we can easily sanity check by running\n\"git diff @{1} @{0}\" on the branch to compare the iterations, by\nkeeping the same base.  We are not going to merge this topic down to\nmaintenance tracks after it graduates to 'master' anyway.\n\nBut I got curious and tried to adjust these patches back on the\nprevious base 07ee72db (Sync with 'maint', 2022-08-26).  It turns\nout that the conflicts needed to be resolved were fairly trivial.\n\nMerging the topic that was recreated on top of the same old base\ninto today's 'master' of course needed the same conflict resolution\nbut that is something we've been doing every time we rebuild 'seen'\n(read: at least twice a day, but often more).  Applying these\npatches directly on today's 'master' of course produced the\nidentical tree as the tree of this trial merge.\n\n> = Future work\n>\n>  * Patch 5, which refactors resolve_gitlink_ref(), notes that a better\n>    interface would be to return the refname instead of using an \"out\"\n>    parameter, but we use an \"out\" parameter so that any new callers trying\n>    to use the old function signature will get stopped by the compiler. The\n>    refactor can be finished at a later time.\n\nOK.\n\n>  * Patch 5 uses the name \"target\" when we are talking about what a symref\n>    points to, instead of \"referent\" like the other functions. \"target\" is\n>    the better choice, since \"referent\" could also apply to non-symbolic\n>    refs, but that cleanup is quite big.\n\nI do not see a huge difference between the two.  \"target\" can be\nused in contexts that are not about symbolic refs, and \"referent\"\ncan be used in contexts that are not about symbolic refs, too.  As\nlong as they are unified in one way or another, it would be fine.\n"},{"id":"465389","messageId":"kl6lh6zyxdb2.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58375","inReplyTo":"xmqqh6zyqftv.fsf@gitster.g","subject":"Re: [PATCH v2 0/7] clone, submodule update: check out submodule branches","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-10-20T23:53:05Z","receivedAt":"2022-10-20T23:53:10Z","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 via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> = Description\n>>\n>> This series teaches \"git clone --recurse-submodules\" and \"git submodule\n>> update\" to understand \"submodule.propagateBranches\" (see Further Reading for\n>> context), i.e. if the superproject has a branch checked out and a submodule\n>> is cloned, the submodule will have the same branch checked out.\n>>\n>> To do this, \"git submodule update\" checks if ...\n>> = Series history\n>>\n>> Changes in v2:\n>>\n>>  * The superproject's \"submodule.propagateBranches\" value is always used,\n>>    even if false.\n>>  * Branches are now created at clone time (by adding a new flag to \"git\n>>    submodule clone\"), instead of at update time.\n>>  * Rebase onto newer master. This got adjusted slightly to incorporate\n>>    ab/submodule-helper-leakfix.\n>>  * Add more tests to demonstrate edge case behavior.\n>>  * Assorted commit message and doc improvements.\n>\n> As the previous round was more than a month old and is clearly not a\n> bugfix but is adding a new feature, I do not mind updating to the\n> newer base after a new feature release was made.  There isn't much\n> to be gained, other than that we can easily sanity check by running\n> \"git diff @{1} @{0}\" on the branch to compare the iterations, by\n> keeping the same base.  We are not going to merge this topic down to\n> maintenance tracks after it graduates to 'master' anyway.\n>\n> But I got curious and tried to adjust these patches back on the\n> previous base 07ee72db (Sync with 'maint', 2022-08-26).  It turns\n> out that the conflicts needed to be resolved were fairly trivial.\n>\n> Merging the topic that was recreated on top of the same old base\n> into today's 'master' of course needed the same conflict resolution\n> but that is something we've been doing every time we rebuild 'seen'\n> (read: at least twice a day, but often more).  Applying these\n> patches directly on today's 'master' of course produced the\n> identical tree as the tree of this trial merge.\n\nThanks for your patience. For future reference, do you have a preference\neither way? I suppose choosing a later base might make it easier for\nreviewers who don't have the bandwidth to remember what \"master\" used to\nlook like, but it's just churn to you, since you're already rebuilding\n\"seen\".\n"},{"id":"465390","messageId":"xmqqedv2oxif.fsf@gitster.g","threadId":"58375","inReplyTo":"kl6lh6zyxdb2.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 0/7] clone, submodule update: check out submodule branches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-21T00:01:28Z","receivedAt":"2022-10-21T00:01: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> Thanks for your patience. For future reference, do you have a preference\n> either way? I suppose choosing a later base might make it easier for\n> reviewers who don't have the bandwidth to remember what \"master\" used to\n> look like, ...\n\nThat cuts both ways.  For brand-new reviewers who starts from v2\nwithout ever seeing v1, and when the two iterations are far apart in\ntime, it may be true.\n\nBut reviewers who helped you with earlier rounds hopefully know what\nthey saw and commented on, and keeping the same base would help them\nto see what is different in the updated iteration, without having to\nsee distracting changes in the surrounding area brought in by using\nthe newer base.\n\n> but it's just churn to you, since you're already rebuilding\n> \"seen\".\n\nTo me, it does not make too much of a difference (unless it is\nclearly a fix for a grave issue that should eventually merge down to\nolder maintenance tracks, and this one is clearly not).  If you\nrebase, I would double check your rebase by rebasing the new\ninteration back to the old base and then merging the new base in to\nsee how the result compares, like I just did this time, before\nreplacing the topic with the application of patches on the updated\nbase, so it is a one-time extra cost to me, but other than that,\nwhat I do would not change all that much and it hopefully will make\nit easier to queue later iterations.\n\nThanks.\n\n"},{"id":"465719","messageId":"20221025175628.913542-1-jonathantanmy@google.com","threadId":"58375","inReplyTo":"3f98b0d17397dbce85aa87a9591981c790f8f7a2.1666297239.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 7/7] clone, submodule update: create and check out branches","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-10-25T17:56:27Z","receivedAt":"2022-10-25T17:56:38Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> Teach \"git submodule update\" to:\n> \n> - create the branch with the same name as the current superproject\n>   branch when cloning a submodule\n\nAh, this is when \"git submodule update\" needs to clone a submodule (as \nopposed to doing something with a submodule that is already cloned). \n\n> - check out that branch (instead of the commit OID) when updating\n>   the submodule worktree\n\nSo whenever we run \"git submodule update\" and a submodule already has a \nbranch of the same name as what's currently checked out in the \nsuperproject, we check out the submodule's branch, ignoring the gitlink \nin the superproject? \n\n> when submodule branching is enabled (submodule.propagateBranches = true)\n> on the superproject and a branch is checked out. \n\nOK.\n\n> \"git clone\n> --recurse-submodules\" also learns this trick because it is implemented\n> with \"git submodule update --recursive\".\n\nIs this sentence redundant now that you're specifically calling out the \ncloning part above?\n\n> This approach of checking out the branch will not result in a dirty\n> worktree for freshly cloned submodules because we can ensure that the\n> submodule branch points to the superproject gitlink. \n\nMakes sense.\n\n> In other cases, it\n> does not work as well, but we can handle them incrementally:\n> \n> - \"git pull --recurse-submodules\" merges the superproject tree (without\n>   updating the submodule branches), and runs \"git submodule update\" to\n>   update the worktrees, so it is almost guaranteed to result in a dirty\n>   worktree.\n\nIs this because when we \"git pull\", the superproject likely now has a \ndifferent gitlink, but the branch in the submodule hasn't changed? \n\n>   The implementation of \"git pull --recurse-submodules\" is likely to\n>   change drastically as submodule.propagateBranches work progresses\n>   (e.g. \"git merge\" learns to recurse in to submodules), and we may be\n>   able to replace the \"git submodule update\" invocation, or teach it new\n>   tricks that make the update behave well.\n> \n> - The user might make changes to the submodule branch without committing\n>   them back to superproject. This is primarily affects \"git checkout\n>   --recurse-submodules\", since that is the primary way of switching away\n>   from a branch and leaving behind WIP (as opposed to \"git submodule\n>   update\", which is run post-checkout).\n\nMakes sense - so in summary, there are (at least) two ways of the \nsuperproject's gitlink and the submodule's branch can go out of sync: \nthe user changing the submodule branch (here) or the user changing the \nsuperproject's gitlink (above). \n \n>   In a future series, \"git checkout --recurse-submodules\" will learn to\n>   consider submodule branches. We can introduce appropriate guardrails\n>   then, e.g. requiring that the superproject working tree is not dirty\n>   before switching away.\n> \n> Signed-off-by: Glen Choo <chooglen@google.com>\n\n[snip]\n\n> @@ -2521,6 +2532,7 @@ static int update_submodule(struct update_data *update_data)\n>  {\n>  \tint submodule_up_to_date;\n>  \tint ret;\n> +\tconst char *submodule_head = \"HEAD\";\n\nI think it's clearer if this is initialized to NULL. I don't think the \nsubmodule head is detached in all code paths when this is HEAD.  \n\n>  \tret = determine_submodule_update_strategy(the_repository,\n>  \t\t\t\t\t\t  update_data->just_cloned,\n> @@ -2533,7 +2545,7 @@ static int update_submodule(struct update_data *update_data)\n>  \tif (update_data->just_cloned)\n>  \t\toidcpy(&update_data->suboid, null_oid());\n>  \telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n> -\t\t\t\t     &update_data->suboid, NULL))\n> +\t\t\t\t     &update_data->suboid, &submodule_head))\n>  \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n>  \t\t\t\t   update_data->displaypath);\n>  \n> @@ -2568,7 +2580,14 @@ static int update_submodule(struct update_data *update_data)\n>  \t\tfree(remote_ref);\n>  \t}\n>  \n> -\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n> +\tif (!update_data->super_branch)\n> +\t\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n> +\telse if (skip_prefix(submodule_head, \"refs/heads/\", &submodule_head))\n> +\t\tsubmodule_up_to_date = !strcmp(update_data->super_branch, submodule_head);\n> +\t/* submodule_branch is \"HEAD\"; the submodule is in detached HEAD */\n> +\telse\n> +\t\tsubmodule_up_to_date = 0;\n\nI think there needs to be a better comment here. It doesn't matter that \nthe submodule is in detached HEAD; what matters is that the submodule \ndoesn't have the superproject's branch checked out. So maybe something \nlike: \n\n  if (update_data->super_branch) {\n    /* (format this appropriately) We also need to check that the \n       submodule's HEAD points to super_branch. */ \n    const char *submodule_head;\n    submodule_up_to_date = skip_prefix(...) && !strcmp(...)\n  } else {\n    submodule_up_to_date = oideq(...)\n  }\n\n> diff --git a/t/t5617-clone-submodules.sh b/t/t5617-clone-submodules.sh\n\n[snip]\n\n> @@ -107,4 +114,31 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule\n>  \ttest_cmp_config -C super_clone3/sub false --default false remote.origin.promisor\n>  '\n>  \n> +test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '\n> +\tgit -C sub checkout -b not-main &&\n> +\tgit -C subsub checkout -b not-main &&\n> +\tgit clone --recurse-submodules \\\n> +\t\t-c submodule.propagateBranches=true \\\n> +\t\t\"file://$pwd/.\" super_clone4 &&\n> +\n> +\t# Assert that each repo is pointing to \"main\"\n> +\tfor REPO in \"super_clone4\" \"super_clone4/sub\" \"super_clone4/sub/subsub\"\n> +\tdo\n> +\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n> +\t    test $HEAD_BRANCH = \"refs/heads/main\" || return 1\n> +\tdone &&\n\nAs I said in my earlier review [1], could we use a branch name that is \nnot  \"main\"? That way, we also check that the clone *creates* the \nbranches,  not just reuses something already there. \n\n[1] https://lore.kernel.org/git/20220901200047.515294-1-jonathantanmy@google.com/\n\n> diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\n\n[snip]\n\n> +test_expect_success 'submodule.propagateBranches - detached HEAD' '\n> +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n> +\tcp -r branch-super-clean branch-super-cloned &&\n> +\n> +\tgit -C branch-super-cloned checkout --detach &&\n> +\tgit -C branch-super-cloned pull origin main &&\n\nIf the behavior of \"pull\" is going to change soon (as stated in the \ncommit message), can we avoid using it in tests here? \n\n> +\tgit -C branch-super-cloned submodule update &&\n> +\n> +\t# sub2 should be in detached HEAD\n> +\tgit -C branch-super-cloned/sub2 rev-parse --verify HEAD &&\n> +\ttest_must_fail git -C branch-super-cloned/sub2 symbolic-ref HEAD\n> +'\n> +\n> +test_expect_success 'submodule.propagateBranches - branch checked out' '\n> +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n> +\tcp -r branch-super-clean branch-super-cloned &&\n> +\n> +\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n> +\tgit -C branch-super-cloned checkout --recurse-submodules new-branch &&\n> +\tgit -C branch-super-cloned pull origin main &&\n> +\tgit -C branch-super-cloned submodule update &&\n> +\n> +\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n> +\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n> +\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n> +\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\"\n\nI'm not sure of the behavior of \"pull\" here, so I didn't look too  \nclosely into what these tests test, but I think that you should cover \nthese situations: \n - submodule has correct OID and correct branch checked out\n - submodule has correct OID, but wrong branch checked out, and correct \n   branch doesn't exist \n - submodule has correct OID, but wrong branch checked out, and correct \n   branch exists at correct OID \n - submodule has correct OID, but wrong branch checked out, and correct \n   branch exists at wrong OID \n - submodule has incorrect OID but correct branch checked out (if this \n   currently doesn't work, maybe add a test_must_fail with a NEEDSWORK) \n\nThere are other combinations but I think these are the most important \nones. \n\nAlso, if there is a situation where you ignore the superproject's \ngitlink in favor of the submodule's branch, you should check the OIDs \nin all cases too, not just what the HEAD symbolic ref points to. \n"},{"id":"465720","messageId":"20221025180017.977401-1-jonathantanmy@google.com","threadId":"58375","inReplyTo":"a4056e200eda9958efe77801b39bdebfdc1497a2.1666297239.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 3/7] submodule--helper clone: create named branch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-10-25T18:00:17Z","receivedAt":"2022-10-25T18:00:25Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> Signed-off-by: Glen Choo <chooglen@google.com>\n\nAdd a note in the commit message that this will be used and tested in a \nsubsequent patch. \n\n> -\tif (argc || !clone_data.url || !clone_data.path || !*(clone_data.path))\n> +\tif (argc || !clone_data.url || !clone_data.path || !*(clone_data.path)\n> +\t    || (!!clone_data.branch != !!clone_data.branch_oid))\n>  \t\tusage_with_options(git_submodule_helper_usage,\n>  \t\t\t\t   module_clone_options);\n\nI know that this is just internal code, but could we have a better \ndiagnostic? You can leave the existing check alone, and then do the \n!!clone_data.branch != !!clone_data.branch_oid with a BUG() if the \nresult is not what you expect. \n"},{"id":"465722","messageId":"20221025180311.978182-1-jonathantanmy@google.com","threadId":"58375","inReplyTo":"20499c62065d640b57bdcfd959e10b214f7e7297.1666297239.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/7] repo-settings: add submodule_propagate_branches","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-10-25T18:03:10Z","receivedAt":"2022-10-25T18:03:18Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> +\t/*\n> +\t * If the environment variable is set, assume that it came from the\n> +\t * superproject and ignore the config.\n> +\t */\n\nMight be clearer to say:\n\n  If the environment variable is set, assume that it was set by an \n  invocation of git running in a superproject with \n  submodule.propagateBranches set and that is recursing into this repo as \n  a submodule. Therefore, we should ignore whatever is set in this  \n  repo's config. \n"},{"id":"465733","messageId":"kl6ltu3rr2u8.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58375","inReplyTo":"20221025175628.913542-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 7/7] clone, submodule update: create and check out branches","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-10-25T21:49:19Z","receivedAt":"2022-10-25T21:49:25Z","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 via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> Teach \"git submodule update\" to:\n>> \n>> - create the branch with the same name as the current superproject\n>>   branch when cloning a submodule\n\n[...]\n>> - check out that branch (instead of the commit OID) when updating\n>>   the submodule worktree\n\n[...]\n>> when submodule branching is enabled (submodule.propagateBranches = true)\n>> on the superproject and a branch is checked out. \n>> \"git clone\n>> --recurse-submodules\" also learns this trick because it is implemented\n>> with \"git submodule update --recursive\".\n>\n> Is this sentence redundant now that you're specifically calling out the \n> cloning part above?\n\nNot quite. The \"trick\" I'm referring to is \"clone, create the\nnamed branch and check it out\", which is implemented by \"git submodule\nupdate\" (and its constituent subprocesses, e.g. \"git submodule clone\"),\nso it is pertinent that \"git clone\" is implemented by \"git submodule\nupdate\".\n\n>> In other cases, it\n>> does not work as well, but we can handle them incrementally:\n>> \n>> - \"git pull --recurse-submodules\" merges the superproject tree (without\n>>   updating the submodule branches), and runs \"git submodule update\" to\n>>   update the worktrees, so it is almost guaranteed to result in a dirty\n>>   worktree.\n>\n> Is this because when we \"git pull\", the superproject likely now has a \n> different gitlink, but the branch in the submodule hasn't changed? \n\nYes. That sounds like a clarification I should make in v3.\n\n>>   The implementation of \"git pull --recurse-submodules\" is likely to\n>>   change drastically as submodule.propagateBranches work progresses\n>>   (e.g. \"git merge\" learns to recurse in to submodules), and we may be\n>>   able to replace the \"git submodule update\" invocation, or teach it new\n>>   tricks that make the update behave well.\n>> \n>> - The user might make changes to the submodule branch without committing\n>>   them back to superproject. This is primarily affects \"git checkout\n>>   --recurse-submodules\", since that is the primary way of switching away\n>>   from a branch and leaving behind WIP (as opposed to \"git submodule\n>>   update\", which is run post-checkout).\n>\n> Makes sense - so in summary, there are (at least) two ways of the \n> superproject's gitlink and the submodule's branch can go out of sync: \n> the user changing the submodule branch (here) or the user changing the \n> superproject's gitlink (above). \n\nYes. I didn't mention modifying the superproject's gitlink (outside of\n\"git pull\"), but I imagine that it would use the same guardrail I\nmentioned here. I could add mention of it if that seems useful.\n\n>  \n>>   In a future series, \"git checkout --recurse-submodules\" will learn to\n>>   consider submodule branches. We can introduce appropriate guardrails\n>>   then, e.g. requiring that the superproject working tree is not dirty\n>>   before switching away.\n>> \n>> Signed-off-by: Glen Choo <chooglen@google.com>\n>\n> [snip]\n>\n>> @@ -2521,6 +2532,7 @@ static int update_submodule(struct update_data *update_data)\n>>  {\n>>  \tint submodule_up_to_date;\n>>  \tint ret;\n>> +\tconst char *submodule_head = \"HEAD\";\n>\n> I think it's clearer if this is initialized to NULL. I don't think the \n> submodule head is detached in all code paths when this is HEAD.  \n\nOk, I'll initialize to NULL since refs_resolve_ref_unsafe() can return\nNULL. I'm quite convinced that it only returns NULL in the error case\nthough (and we need to handle that explicitly), otherwise detached HEAD\nalways results in HEAD.\n\n>\n>>  \tret = determine_submodule_update_strategy(the_repository,\n>>  \t\t\t\t\t\t  update_data->just_cloned,\n>> @@ -2533,7 +2545,7 @@ static int update_submodule(struct update_data *update_data)\n>>  \tif (update_data->just_cloned)\n>>  \t\toidcpy(&update_data->suboid, null_oid());\n>>  \telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n>> -\t\t\t\t     &update_data->suboid, NULL))\n>> +\t\t\t\t     &update_data->suboid, &submodule_head))\n>>  \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n>>  \t\t\t\t   update_data->displaypath);\n>>  \n>> @@ -2568,7 +2580,14 @@ static int update_submodule(struct update_data *update_data)\n>>  \t\tfree(remote_ref);\n>>  \t}\n>>  \n>> -\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n>> +\tif (!update_data->super_branch)\n>> +\t\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n>> +\telse if (skip_prefix(submodule_head, \"refs/heads/\", &submodule_head))\n>> +\t\tsubmodule_up_to_date = !strcmp(update_data->super_branch, submodule_head);\n>> +\t/* submodule_branch is \"HEAD\"; the submodule is in detached HEAD */\n>> +\telse\n>> +\t\tsubmodule_up_to_date = 0;\n>\n> I think there needs to be a better comment here. It doesn't matter that \n> the submodule is in detached HEAD; what matters is that the submodule \n> doesn't have the superproject's branch checked out. So maybe something \n> like: \n>\n>   if (update_data->super_branch) {\n>     /* (format this appropriately) We also need to check that the \n>        submodule's HEAD points to super_branch. */ \n>     const char *submodule_head;\n>     submodule_up_to_date = skip_prefix(...) && !strcmp(...)\n>   } else {\n>     submodule_up_to_date = oideq(...)\n>   }\n>\n\nI see, thanks for the suggestion.\n\n>> diff --git a/t/t5617-clone-submodules.sh b/t/t5617-clone-submodules.sh\n>\n> [snip]\n>\n>> @@ -107,4 +114,31 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule\n>>  \ttest_cmp_config -C super_clone3/sub false --default false remote.origin.promisor\n>>  '\n>>  \n>> +test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '\n>> +\tgit -C sub checkout -b not-main &&\n>> +\tgit -C subsub checkout -b not-main &&\n>> +\tgit clone --recurse-submodules \\\n>> +\t\t-c submodule.propagateBranches=true \\\n>> +\t\t\"file://$pwd/.\" super_clone4 &&\n>> +\n>> +\t# Assert that each repo is pointing to \"main\"\n>> +\tfor REPO in \"super_clone4\" \"super_clone4/sub\" \"super_clone4/sub/subsub\"\n>> +\tdo\n>> +\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n>> +\t    test $HEAD_BRANCH = \"refs/heads/main\" || return 1\n>> +\tdone &&\n>\n> As I said in my earlier review [1], could we use a branch name that is \n> not  \"main\"? That way, we also check that the clone *creates* the \n> branches,  not just reuses something already there. \n>\n> [1] https://lore.kernel.org/git/20220901200047.515294-1-jonathantanmy@google.com/\n\nAh I missed that, thanks.\n\n>\n>> diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\n>\n> [snip]\n>\n>> +test_expect_success 'submodule.propagateBranches - detached HEAD' '\n>> +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n>> +\tcp -r branch-super-clean branch-super-cloned &&\n>> +\n>> +\tgit -C branch-super-cloned checkout --detach &&\n>> +\tgit -C branch-super-cloned pull origin main &&\n>\n> If the behavior of \"pull\" is going to change soon (as stated in the \n> commit message), can we avoid using it in tests here? \n\nHere I've omitted \"--recurse-submodules\", so this is a shorthand for\n\"update the superproject's HEAD to the remote's main\". This behavior\nisn't going to change, but perhaps I shouldn't rely on such subtleties\nin test code (where readability is paramount).\n\n>\n>> +\tgit -C branch-super-cloned submodule update &&\n>> +\n>> +\t# sub2 should be in detached HEAD\n>> +\tgit -C branch-super-cloned/sub2 rev-parse --verify HEAD &&\n>> +\ttest_must_fail git -C branch-super-cloned/sub2 symbolic-ref HEAD\n>> +'\n>> +\n>> +test_expect_success 'submodule.propagateBranches - branch checked out' '\n>> +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n>> +\tcp -r branch-super-clean branch-super-cloned &&\n>> +\n>> +\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n>> +\tgit -C branch-super-cloned checkout --recurse-submodules new-branch &&\n>> +\tgit -C branch-super-cloned pull origin main &&\n>> +\tgit -C branch-super-cloned submodule update &&\n>> +\n>> +\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n>> +\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n>> +\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n>> +\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\"\n>\n> I'm not sure of the behavior of \"pull\" here, so I didn't look too  \n> closely into what these tests test, but I think that you should cover \n> these situations: \n>  - submodule has correct OID and correct branch checked out\n>  - submodule has correct OID, but wrong branch checked out, and correct \n>    branch doesn't exist \n>  - submodule has correct OID, but wrong branch checked out, and correct \n>    branch exists at correct OID \n>  - submodule has correct OID, but wrong branch checked out, and correct \n>    branch exists at wrong OID \n>  - submodule has incorrect OID but correct branch checked out (if this \n>    currently doesn't work, maybe add a test_must_fail with a NEEDSWORK) \n>\n> There are other combinations but I think these are the most important \n> ones. \n>\n> Also, if there is a situation where you ignore the superproject's \n> gitlink in favor of the submodule's branch, you should check the OIDs \n> in all cases too, not just what the HEAD symbolic ref points to. \n\nMakes sense. Thanks for the thorough review :)\n"},{"id":"465972","messageId":"432bc7cb3a42cf39d0033701c2cc677c9109b3dd.1666988096.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"[PATCH v3 1/8] clone: teach --detach option","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-28T20:14:49Z","receivedAt":"2022-10-28T20:15:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nTeach \"git clone\" the \"--detach\" option, which leaves the cloned repo in\ndetached HEAD (like \"git checkout --detach\"). In addition, if the clone\nis not bare, do not create the local branch pointed to by the remote's\nHEAD symref (bare clones always copy all remote branches directly to\nlocal branches, so the branch is still created in the bare case).\n\nThis is especially useful in the \"submodule.propagateBranches\" workflow,\nwhere local submodule branches are named after the superproject's\nbranches, so it makes no sense to create a local branch named after the\nsubmodule's remote's branch.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Documentation/git-clone.txt |  8 +++++++-\n builtin/clone.c             | 12 +++++++++---\n t/t5601-clone.sh            | 22 ++++++++++++++++++++++\n 3 files changed, 38 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex d6434d262d6..6a4e5d31b46 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t  [--depth <depth>] [--[no-]single-branch] [--no-tags]\n \t  [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]\n \t  [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]\n-\t  [--filter=<filter> [--also-filter-submodules]] [--] <repository>\n+\t  [--filter=<filter> [--also-filter-submodules] [--detach]] [--] <repository>\n \t  [<directory>]\n \n DESCRIPTION\n@@ -210,6 +210,12 @@ objects from the source repository into a pack in the cloned repository.\n \t`--branch` can also take tags and detaches the HEAD at that commit\n \tin the resulting repository.\n \n+--detach::\n+\tIf the cloned repository's HEAD points to a branch, point the newly\n+\tcreated HEAD to the branch's commit instead of the branch itself.\n+\tAdditionally, in a non-bare repository, the corresponding local branch\n+\twill not be created.\n+\n -u <upload-pack>::\n --upload-pack <upload-pack>::\n \tWhen given, and the repository to clone from is accessed\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 547d6464b3c..e624d3f49a2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -78,6 +78,7 @@ static int option_filter_submodules = -1;    /* unspecified */\n static int config_filter_submodules = -1;    /* unspecified */\n static struct string_list server_options = STRING_LIST_INIT_NODUP;\n static int option_remote_submodules;\n+static int option_detach;\n static const char *bundle_uri;\n \n static int recurse_submodules_cb(const struct option *opt,\n@@ -162,6 +163,8 @@ static struct option builtin_clone_options[] = {\n \t\t    N_(\"any cloned submodules will use their remote-tracking branch\")),\n \tOPT_BOOL(0, \"sparse\", &option_sparse_checkout,\n \t\t    N_(\"initialize sparse-checkout file to include only files at root\")),\n+\tOPT_BOOL(0, \"detach\", &option_detach,\n+\t\t N_(\"detach HEAD and don't create a local branch\")),\n \tOPT_STRING(0, \"bundle-uri\", &bundle_uri,\n \t\t   N_(\"uri\"), N_(\"a URI for downloading bundles before fetching from origin remote\")),\n \tOPT_END()\n@@ -613,10 +616,12 @@ static void update_remote_refs(const struct ref *refs,\n }\n \n static void update_head(const struct ref *our, const struct ref *remote,\n-\t\t\tconst char *unborn, const char *msg)\n+\t\t\tconst char *unborn, int should_detach,\n+\t\t\tconst char *msg)\n {\n \tconst char *head;\n-\tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n+\tif (our && !should_detach &&\n+\t    skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n \t\tif (create_symref(\"HEAD\", our->name, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n@@ -1357,7 +1362,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\t\t   branch_top.buf, reflog_msg.buf, transport,\n \t\t\t   !is_local);\n \n-\tupdate_head(our_head_points_at, remote_head, unborn_head, reflog_msg.buf);\n+\tupdate_head(our_head_points_at, remote_head, unborn_head,\n+\t\t    option_detach, reflog_msg.buf);\n \n \t/*\n \t * We want to show progress for recursive submodule clones iff\ndiff --git a/t/t5601-clone.sh b/t/t5601-clone.sh\nindex 45f0803ed4d..418cfd54717 100755\n--- a/t/t5601-clone.sh\n+++ b/t/t5601-clone.sh\n@@ -333,6 +333,28 @@ test_expect_success 'clone checking out a tag' '\n \ttest_cmp fetch.expected fetch.actual\n '\n \n+test_expect_success '--detach detaches and does not create branch' '\n+\ttest_when_finished \"rm -fr dst\" &&\n+\tgit clone --detach src dst &&\n+\t(\n+\t\tcd dst &&\n+\t\ttest_must_fail git rev-parse main &&\n+\t\ttest_must_fail git symbolic-ref HEAD &&\n+\t\ttest_cmp_rev HEAD refs/remotes/origin/HEAD\n+\t)\n+'\n+\n+test_expect_success '--detach with --bare detaches but creates branch' '\n+\ttest_when_finished \"rm -fr dst\" &&\n+\tgit clone --bare --detach src dst &&\n+\t(\n+\t\tcd dst &&\n+\t\tgit rev-parse main &&\n+\t\ttest_must_fail git symbolic-ref HEAD &&\n+\t\ttest_cmp_rev HEAD refs/heads/main\n+\t)\n+'\n+\n test_expect_success 'set up ssh wrapper' '\n \tcp \"$GIT_BUILD_DIR/t/helper/test-fake-ssh$X\" \\\n \t\t\"$TRASH_DIRECTORY/ssh$X\" &&\n-- \ngitgitgadget\n\n"},{"id":"465973","messageId":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v2.git.git.1666297238.gitgitgadget@gmail.com","subject":"[PATCH v3 0/8] clone, submodule update: check out submodule branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-28T20:14:48Z","receivedAt":"2022-10-28T20:15:10Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"This version has relatively few changes, and should address all of\nJonathan's comments (thanks!).\n\nIn the final patch, submodule_head is now initialized to NULL [1], which\nmeans that in the \"just_cloned\" case, it remains NULL and we have to be\ncareful not to call skip_prefix() on it. A new patch (7/8), prepares for\nthis and also cleans up \"struct update_data\" a little bit.\n\n= Description\n\nThis series teaches \"git clone --recurse-submodules\" and \"git submodule\nupdate\" to understand \"submodule.propagateBranches\" (see Further Reading for\ncontext), i.e. if the superproject has a branch checked out and a submodule\nis cloned, the submodule will have the same branch checked out.\n\nTo do this, \"git submodule update\" checks if \"submodule.propagateBranches\"\nis true. If so, and if the superproject has the branch 'topic' checked out,\nthen:\n\n * Submodules are cloned without their upstream branches\n * The 'topic' branch is created in the submodule\n * The submodule is updated via \"git checkout topic\" instead of checking out\n   the gitlink's OID.\n\nSince \"git clone --recurse-submodules\" is implemented using \"git submodule\nupdate\", it also learns to create and check out the branch in submodules.\n\nThe main challenges with this approach are:\n\n * If the remote HEAD points to a branch, \"git clone\" always creates that\n   branch in the clone. But with \"submodule.propagateBranches\", we want\n   submodules to use the branch names of their superproject, not their\n   upstream.\n   \n   This is solved by adding a new flag to \"git clone\", \"--detach\", which\n   detaches the clone's HEAD at the branch and does not create it.\n\n * When \"git submodule update\" recurses into submodules, the parent process\n   has to propagate the value of \"submodule.propagateBranches\" to child\n   processes, otherwise the behavior will be inconsistent if the submodule\n   has the config unset.\n   \n   This is solved by adding an internal GIT_* environment variable and\n   passing it down via prepare_submodule_repo_env(). This is cleaner than\n   passing \"-c submodule.propagateBranches=true\", but an even cleaner\n   solution would be for submodules to read \"submodule.propagateBranches\"\n   from their superproject config. This would also be useful for\n   \"submodule.alternateLocation\" and \"submodule.alternateErrorStrategy\", as\n   we wouldn't have to set those values in newly-cloned submodules. This\n   requires teaching Git to treat submodules differently, which was the\n   subject of some WIP in [2]. That topic has stalled, but I don't mind\n   restarting it if others prefer that.\n\n[1]\nhttps://lore.kernel.org/git/20221025175628.913542-1-jonathantanmy@google.com\n[2]\nhttps://lore.kernel.org/git/20220310004423.2627181-1-emilyshaffer@google.com/\n\n= Patch organization\n\n * Patch 1/8 adds \"--detach\" to \"git clone\"\n * Patch 2/8 creates the environment variable and repository setting for\n   \"submodule.propagateBranches\"\n * Patch 3/8 adds a new \"--branch\" option to \"git submodule clone\", which\n   makes it create a named branch.\n * Patches 4-7/8 are prep work, and 8/8 adds the actual\n   \"submodule.propagateBranches\" behavior\n\n= Series history\n\nChanges in v3:\n\n * Patch 3: Improve error messages for \"git submodule--helper clone\n   --branch\"\n * Patch 7 (new): Remove update_data.suboid to make it easier to initialize\n   submodule_head to NULL in the next patch.\n * Patch 8: Split tests into two parts - \"newly cloned\" and \"already\n   cloned\", and test various conditions in already cloned submodules.\n * Assorted commit message and comment changes.\n * Reformat with \"make style\" (sorry for not doing this sooner..)\n\nChanges in v2:\n\n * The superproject's \"submodule.propagateBranches\" value is always used,\n   even if false.\n * Branches are now created at clone time (by adding a new flag to \"git\n   submodule clone\"), instead of at update time.\n * Rebase onto newer master. This got adjusted slightly to incorporate\n   ab/submodule-helper-leakfix.\n * Add more tests to demonstrate edge case behavior.\n * Assorted commit message and doc improvements.\n\n= Future work\n\n * Patch 5, which refactors resolve_gitlink_ref(), notes that a better\n   interface would be to return the refname instead of using an \"out\"\n   parameter, but we use an \"out\" parameter so that any new callers trying\n   to use the old function signature will get stopped by the compiler. The\n   refactor can be finished at a later time.\n\n * Patch 5 uses the name \"target\" when we are talking about what a symref\n   points to, instead of \"referent\" like the other functions. \"target\" is\n   the better choice, since \"referent\" could also apply to non-symbolic\n   refs, but that cleanup is quite big.\n\n * Patch 8 notes that for already cloned submodules, the branch may not\n   point to the same OID as the superproject gitlink, and it may not even\n   exist. This will be addressed in a more comprehensive manner when we add\n   support for checking out branches with \"git checkout\n   --recurse-submodules\".\n\n= Further reading\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\"git branch --recurse-submodules\":\nhttps://lore.kernel.org/git/20220129000446.99261-1-chooglen@google.com/\n\nGlen Choo (8):\n  clone: teach --detach option\n  repo-settings: add submodule_propagate_branches\n  submodule--helper clone: create named branch\n  t5617: drop references to remote-tracking branches\n  submodule: return target of submodule symref\n  submodule update: refactor update targets\n  submodule--helper: remove update_data.suboid\n  clone, submodule update: create and check out branches\n\n Documentation/git-clone.txt                   |   8 +-\n builtin/branch.c                              |  11 +-\n builtin/clone.c                               |  12 +-\n builtin/submodule--helper.c                   | 102 +++++++++---\n builtin/update-index.c                        |   4 +-\n cache.h                                       |   1 +\n combine-diff.c                                |   3 +-\n diff-lib.c                                    |   2 +-\n dir.c                                         |   2 +-\n object-file.c                                 |   2 +-\n read-cache.c                                  |   4 +-\n refs.c                                        |  10 +-\n refs.h                                        |   5 +-\n repo-settings.c                               |  13 ++\n repository.h                                  |   1 +\n submodule.c                                   |   2 +\n t/t5601-clone.sh                              |  22 +++\n ...es-remote.sh => t5617-clone-submodules.sh} |  40 ++++-\n t/t7406-submodule-update.sh                   | 156 ++++++++++++++++++\n unpack-trees.c                                |   3 +-\n 20 files changed, 357 insertions(+), 46 deletions(-)\n rename t/{t5617-clone-submodules-remote.sh => t5617-clone-submodules.sh} (70%)\n\n\nbase-commit: 45c9f05c44b1cb6bd2d6cb95a22cf5e3d21d5b63\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1321%2Fchooglen%2Fsubmodule%2Fclone-recursive-with-branch-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1321/chooglen/submodule/clone-recursive-with-branch-v3\nPull-Request: https://github.com/git/git/pull/1321\n\nRange-diff vs v2:\n\n 1:  432bc7cb3a4 = 1:  432bc7cb3a4 clone: teach --detach option\n 2:  20499c62065 ! 2:  06d7e15e830 repo-settings: add submodule_propagate_branches\n     @@ repo-settings.c: void prepare_repo_settings(struct repository *r)\n       \t\tr->settings.core_multi_pack_index = 1;\n       \n      +\t/*\n     -+\t * If the environment variable is set, assume that it came from the\n     -+\t * superproject and ignore the config.\n     ++\t * If the environment variable is set, assume that it was set by an\n     ++\t * invocation of git running in a superproject with\n     ++\t * submodule.propagateBranches set and that is recursing into this repo\n     ++\t * as a submodule. Therefore, we should ignore whatever is set in this\n     ++\t * repo's config.\n      +\t */\n      +\tr->settings.submodule_propagate_branches =\n      +\t\tgit_env_bool(GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT, -1);\n 3:  a4056e200ed ! 3:  5a24d7e9255 submodule--helper clone: create named branch\n     @@ Commit message\n          branching is enabled, otherwise the named branch might conflict with the\n          branch from the submodule remote's HEAD.\n      \n     -    These flags will be used by `git submodule update` in a later commit.\n     -    \"git submodule add\" (which also invokes \"git submodule--helper clone\")\n     -    should also do something similar when submodule branching is enabled,\n     -    but this is left for a later series.\n     +    This functionality will be tested in a later commit where \"git submodule\n     +    update\" uses it to create and check out the correct branch when\n     +    submodule branching is enabled. \"git submodule add\" (which also invokes\n     +    \"git submodule--helper clone\") should also do something similar when\n     +    submodule branching is enabled, but this is left for a later series.\n      \n          Arguably, \"--detach\" should also be the default for\n          \"submodule.propagateBranches=false\" since it doesn't make sense to\n     @@ builtin/submodule--helper.c: static int module_clone(int argc, const char **argv\n       \tclone_data.dissociate = !!dissociate;\n       \tclone_data.quiet = !!quiet;\n      @@ builtin/submodule--helper.c: static int module_clone(int argc, const char **argv, const char *prefix)\n     - \tclone_data.require_init = !!require_init;\n     - \tclone_data.filter_options = &filter_options;\n     - \n     --\tif (argc || !clone_data.url || !clone_data.path || !*(clone_data.path))\n     -+\tif (argc || !clone_data.url || !clone_data.path || !*(clone_data.path)\n     -+\t    || (!!clone_data.branch != !!clone_data.branch_oid))\n       \t\tusage_with_options(git_submodule_helper_usage,\n       \t\t\t\t   module_clone_options);\n     + \n     ++\tif (!!clone_data.branch != !!clone_data.branch_oid)\n     ++\t\tBUG(\"--branch and --branch-oid must be set/unset together\");\n      +\tif ((clone_data.branch &&\n      +\t     !the_repository->settings.submodule_propagate_branches))\n      +\t\tBUG(\"--branch is only expected with submodule.propagateBranches\");\n     - \n     ++\n       \tclone_submodule(&clone_data, &reference);\n       \tlist_objects_filter_release(&filter_options);\n     + \tstring_list_clear(&reference, 1);\n 4:  affd0e24e1d = 4:  3a08f2ab776 t5617: drop references to remote-tracking branches\n 5:  6f769cb80ad = 5:  bd8ffd7cde2 submodule: return target of submodule symref\n 6:  abdfa888ff5 ! 6:  df1f7225f49 submodule update: refactor update targets\n     @@ Commit message\n          submodule update: refactor update targets\n      \n          Refactor two \"git submodule update\" code locations so that they no\n     -    longer refer to oids directly. This shrinks the next commit's diff,\n     +    longer refer to oids directly. This shrinks a subsequent commit's diff,\n          where this code will need to handle branches.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n -:  ----------- > 7:  4e402b67145 submodule--helper: remove update_data.suboid\n 7:  3f98b0d1739 ! 8:  7cdd6c4184d clone, submodule update: create and check out branches\n     @@ Commit message\n          submodule branch points to the superproject gitlink. In other cases, it\n          does not work as well, but we can handle them incrementally:\n      \n     -    - \"git pull --recurse-submodules\" merges the superproject tree (without\n     -      updating the submodule branches), and runs \"git submodule update\" to\n     -      update the worktrees, so it is almost guaranteed to result in a dirty\n     -      worktree.\n     +    - \"git pull --recurse-submodules\" merges the superproject tree,\n     +      (changing the gitlink without updating the submodule branches), and\n     +      runs \"git submodule update\" to update the worktrees, so it is almost\n     +      guaranteed to result in a dirty worktree.\n      \n            The implementation of \"git pull --recurse-submodules\" is likely to\n            change drastically as submodule.propagateBranches work progresses\n     @@ builtin/submodule--helper.c: static void submodule_update_clone_release(struct s\n       \tchar *displaypath;\n      +\tconst char *super_branch;\n       \tenum submodule_update_type update_default;\n     - \tstruct object_id suboid;\n       \tstruct string_list references;\n     + \tstruct submodule_update_strategy update_strategy;\n      @@ builtin/submodule--helper.c: static int prepare_to_clone_next_submodule(const struct cache_entry *ce,\n       \t\tstrvec_push(&child->args, suc->update_data->single_branch ?\n       \t\t\t\t\t      \"--single-branch\" :\n       \t\t\t\t\t      \"--no-single-branch\");\n      +\tif (ud->super_branch) {\n      +\t\tstrvec_pushf(&child->args, \"--branch=%s\", ud->super_branch);\n     -+\t\tstrvec_pushf(&child->args, \"--branch-oid=%s\", oid_to_hex(&ce->oid));\n     ++\t\tstrvec_pushf(&child->args, \"--branch-oid=%s\",\n     ++\t\t\t     oid_to_hex(&ce->oid));\n      +\t}\n       \n       cleanup:\n     @@ builtin/submodule--helper.c: static int fetch_in_submodule(const char *module_pa\n      +\tconst char *update_target;\n       \tint ret;\n       \n     -+\tif (ud->update_strategy.type == SM_UPDATE_CHECKOUT &&\n     -+\t    ud->super_branch)\n     ++\tif (ud->update_strategy.type == SM_UPDATE_CHECKOUT && ud->super_branch)\n      +\t\tupdate_target = ud->super_branch;\n      +\telse\n      +\t\tupdate_target = oid_to_hex(&ud->oid);\n     @@ builtin/submodule--helper.c: static int fetch_in_submodule(const char *module_pa\n       \tcase SM_UPDATE_CHECKOUT:\n       \t\tcp.git_cmd = 1;\n      @@ builtin/submodule--helper.c: static int update_submodule(struct update_data *update_data)\n     - {\n       \tint submodule_up_to_date;\n       \tint ret;\n     -+\tconst char *submodule_head = \"HEAD\";\n     + \tstruct object_id suboid;\n     ++\tconst char *submodule_head = NULL;\n       \n       \tret = determine_submodule_update_strategy(the_repository,\n       \t\t\t\t\t\t  update_data->just_cloned,\n      @@ builtin/submodule--helper.c: static int update_submodule(struct update_data *update_data)\n     - \tif (update_data->just_cloned)\n     - \t\toidcpy(&update_data->suboid, null_oid());\n     - \telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n     --\t\t\t\t     &update_data->suboid, NULL))\n     -+\t\t\t\t     &update_data->suboid, &submodule_head))\n     + \t\treturn ret;\n     + \n     + \tif (!update_data->just_cloned &&\n     +-\t    resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &suboid, NULL))\n     ++\t    resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &suboid,\n     ++\t\t\t\t&submodule_head))\n       \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n       \t\t\t\t   update_data->displaypath);\n       \n     @@ builtin/submodule--helper.c: static int update_submodule(struct update_data *upd\n       \t\tfree(remote_ref);\n       \t}\n       \n     --\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n     -+\tif (!update_data->super_branch)\n     -+\t\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n     -+\telse if (skip_prefix(submodule_head, \"refs/heads/\", &submodule_head))\n     -+\t\tsubmodule_up_to_date = !strcmp(update_data->super_branch, submodule_head);\n     -+\t/* submodule_branch is \"HEAD\"; the submodule is in detached HEAD */\n     -+\telse\n     +-\tsubmodule_up_to_date = !update_data->just_cloned &&\n     +-\t\toideq(&update_data->oid, &suboid);\n     ++\tif (update_data->just_cloned)\n      +\t\tsubmodule_up_to_date = 0;\n     ++\telse if (update_data->super_branch)\n     ++\t\t/* Check that the submodule's HEAD points to super_branch. */\n     ++\t\tsubmodule_up_to_date =\n     ++\t\t\tskip_prefix(submodule_head, \"refs/heads/\",\n     ++\t\t\t\t    &submodule_head) &&\n     ++\t\t\t!strcmp(update_data->super_branch, submodule_head);\n     ++\telse\n     ++\t\tsubmodule_up_to_date = oideq(&update_data->oid, &suboid);\n      +\n       \tif (!submodule_up_to_date || update_data->force) {\n       \t\tret = run_update_procedure(update_data);\n     @@ t/t5617-clone-submodules.sh: test_expect_success '--no-also-filter-submodules ov\n       '\n       \n      +test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '\n     -+\tgit -C sub checkout -b not-main &&\n     -+\tgit -C subsub checkout -b not-main &&\n     ++\ttest_when_finished \"git checkout main\" &&\n     ++\n     ++\tgit checkout -b checked-out &&\n     ++\tgit -C sub checkout -b not-in-clone &&\n     ++\tgit -C subsub checkout -b not-in-clone &&\n      +\tgit clone --recurse-submodules \\\n     ++\t\t--branch checked-out \\\n      +\t\t-c submodule.propagateBranches=true \\\n      +\t\t\"file://$pwd/.\" super_clone4 &&\n      +\n     -+\t# Assert that each repo is pointing to \"main\"\n     ++\t# Assert that each repo is pointing to \"checked-out\"\n      +\tfor REPO in \"super_clone4\" \"super_clone4/sub\" \"super_clone4/sub/subsub\"\n      +\tdo\n      +\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n     -+\t    test $HEAD_BRANCH = \"refs/heads/main\" || return 1\n     ++\t    test $HEAD_BRANCH = \"refs/heads/checked-out\" || return 1\n      +\tdone &&\n      +\n      +\t# Assert that the submodule branches are pointing to the right revs\n      +\tEXPECT_SUB_OID=\"$(git -C super_clone4 rev-parse :sub)\" &&\n     -+\tACTUAL_SUB_OID=\"$(git -C super_clone4/sub rev-parse refs/heads/main)\" &&\n     ++\tACTUAL_SUB_OID=\"$(git -C super_clone4/sub rev-parse refs/heads/checked-out)\" &&\n      +\ttest $EXPECT_SUB_OID = $ACTUAL_SUB_OID &&\n      +\tEXPECT_SUBSUB_OID=\"$(git -C super_clone4/sub rev-parse :subsub)\" &&\n     -+\tACTUAL_SUBSUB_OID=\"$(git -C super_clone4/sub/subsub rev-parse refs/heads/main)\" &&\n     ++\tACTUAL_SUBSUB_OID=\"$(git -C super_clone4/sub/subsub rev-parse refs/heads/checked-out)\" &&\n      +\ttest $EXPECT_SUBSUB_OID = $ACTUAL_SUBSUB_OID &&\n      +\n      +\t# Assert that the submodules do not have branches from their upstream\n     -+\ttest_must_fail git -C super_clone4/sub rev-parse not-main &&\n     -+\ttest_must_fail git -C super_clone4/sub/subsub rev-parse not-main\n     ++\ttest_must_fail git -C super_clone4/sub rev-parse not-in-clone &&\n     ++\ttest_must_fail git -C super_clone4/sub/subsub rev-parse not-in-clone\n      +'\n      +\n       test_done\n     @@ t/t7406-submodule-update.sh: test_expect_success 'submodule update --recursive s\n      +\t\tbranch-super branch-super-clean &&\n      +\tgit -C branch-super-clean config submodule.propagateBranches true &&\n      +\n     -+\t# Create an upstream submodule not in the clone\n     ++\t# sub2 will not be in the clone. We will fetch the containing\n     ++\t# superproject commit and clone sub2 with \"git submodule update\".\n      +\tgit init sub2 &&\n      +\ttest_commit -C sub2 \"sub2\" &&\n      +\tgit -C branch-super submodule add ../sub2 sub2 &&\n      +\tgit -C branch-super commit -m \"add sub2\"\n      +'\n      +\n     -+test_expect_success 'submodule.propagateBranches - detached HEAD' '\n     ++test_clean_submodule ()\n     ++{\n     ++\tlocal negate super_dir sub_dir expect_oid actual_oid &&\n     ++\tif test \"$1\" = \"!\"\n     ++\tthen\n     ++\t\tnegate=t\n     ++\t\tshift\n     ++\tfi\n     ++\tsuper_dir=\"$1\" &&\n     ++\tsub_dir=\"$2\" &&\n     ++\texpect_oid=\"$(git -C \"$super_dir\" rev-parse \":$sub_dir\")\" &&\n     ++\tactual_oid=\"$(git -C \"$super_dir/$sub_dir\" rev-parse HEAD)\" &&\n     ++\tif test -n \"$negate\"\n     ++\tthen\n     ++\t\t! test \"$expect_oid\" = \"$actual_oid\"\n     ++\telse\n     ++\t\ttest \"$expect_oid\" = \"$actual_oid\"\n     ++\tfi\n     ++}\n     ++\n     ++# Test the behavior of a newly cloned submodule\n     ++test_expect_success 'branches - newly-cloned submodule, detached HEAD' '\n      +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n      +\tcp -r branch-super-clean branch-super-cloned &&\n      +\n     -+\tgit -C branch-super-cloned checkout --detach &&\n     -+\tgit -C branch-super-cloned pull origin main &&\n     ++\tgit -C branch-super-cloned fetch origin main &&\n     ++\tgit -C branch-super-cloned checkout FETCH_HEAD &&\n     ++\tgit -C branch-super-cloned/sub1 checkout --detach &&\n      +\tgit -C branch-super-cloned submodule update &&\n      +\n     -+\t# sub2 should be in detached HEAD\n     ++\t# sub1 and sub2 should be in detached HEAD\n     ++\tgit -C branch-super-cloned/sub1 rev-parse --verify HEAD &&\n     ++\ttest_must_fail git -C branch-super-cloned/sub1 symbolic-ref HEAD &&\n     ++\ttest_clean_submodule branch-super-cloned sub1 &&\n      +\tgit -C branch-super-cloned/sub2 rev-parse --verify HEAD &&\n     -+\ttest_must_fail git -C branch-super-cloned/sub2 symbolic-ref HEAD\n     ++\ttest_must_fail git -C branch-super-cloned/sub2 symbolic-ref HEAD &&\n     ++\ttest_clean_submodule branch-super-cloned sub2\n     ++'\n     ++\n     ++test_expect_success 'branches - newly-cloned submodule, branch checked out' '\n     ++\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n     ++\tcp -r branch-super-clean branch-super-cloned &&\n     ++\n     ++\tgit -C branch-super-cloned fetch origin main &&\n     ++\tgit -C branch-super-cloned checkout FETCH_HEAD &&\n     ++\tgit -C branch-super-cloned branch new-branch &&\n     ++\tgit -C branch-super-cloned checkout new-branch &&\n     ++\tgit -C branch-super-cloned/sub1 branch new-branch &&\n     ++\tgit -C branch-super-cloned submodule update &&\n     ++\n     ++\t# Ignore sub1, we will test it later.\n     ++\t# sub2 should check out the branch\n     ++\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n     ++\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\" &&\n     ++\ttest_clean_submodule branch-super-cloned sub2\n      +'\n      +\n     -+test_expect_success 'submodule.propagateBranches - branch checked out' '\n     ++# Test the behavior of an already-cloned submodule.\n     ++# NEEDSWORK When updating with branches, we always use the branch instead of the\n     ++# gitlink's OID. This results in some imperfect behavior:\n     ++#\n     ++# - If the gitlink's OID disagrees with the branch OID, updating with branches\n     ++#   may result in a dirty worktree\n     ++# - If the branch does not exist, the update fails.\n     ++#\n     ++# We will reevaluate when \"git checkout --recurse-submodules\" supports branches\n     ++# For now, just test for this imperfect behavior.\n     ++test_expect_success 'branches - correct branch checked out, OIDs agree' '\n      +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n      +\tcp -r branch-super-clean branch-super-cloned &&\n      +\n      +\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n     -+\tgit -C branch-super-cloned checkout --recurse-submodules new-branch &&\n     -+\tgit -C branch-super-cloned pull origin main &&\n     ++\tgit -C branch-super-cloned checkout new-branch &&\n     ++\tgit -C branch-super-cloned/sub1 checkout new-branch &&\n      +\tgit -C branch-super-cloned submodule update &&\n      +\n      +\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n      +\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n     -+\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n     -+\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\"\n     ++\ttest_clean_submodule branch-super-cloned sub1\n      +'\n      +\n     -+test_expect_success 'submodule.propagateBranches - other branch checked out' '\n     ++test_expect_success 'branches - correct branch checked out, OIDs disagree' '\n      +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n      +\tcp -r branch-super-clean branch-super-cloned &&\n      +\n      +\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n     -+\tgit -C branch-super-cloned checkout --recurse-submodules new-branch &&\n     -+\tgit -C branch-super-cloned/sub1 checkout -b other-branch &&\n     -+\tgit -C branch-super-cloned pull origin main &&\n     ++\tgit -C branch-super-cloned checkout new-branch &&\n     ++\tgit -C branch-super-cloned/sub1 checkout new-branch &&\n     ++\ttest_commit -C branch-super-cloned/sub1 new-commit &&\n      +\tgit -C branch-super-cloned submodule update &&\n      +\n      +\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n      +\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n     -+\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n     -+\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\"\n     ++\ttest_clean_submodule ! branch-super-cloned sub1\n     ++'\n     ++\n     ++test_expect_success 'branches - other branch checked out, correct branch exists, OIDs agree' '\n     ++\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n     ++\tcp -r branch-super-clean branch-super-cloned &&\n     ++\n     ++\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n     ++\tgit -C branch-super-cloned checkout new-branch &&\n     ++\tgit -C branch-super-cloned/sub1 checkout main &&\n     ++\tgit -C branch-super-cloned submodule update &&\n     ++\n     ++\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n     ++\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n     ++\ttest_clean_submodule branch-super-cloned sub1\n     ++'\n     ++\n     ++test_expect_success 'branches - other branch checked out, correct branch exists, OIDs disagree' '\n     ++\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n     ++\tcp -r branch-super-clean branch-super-cloned &&\n     ++\n     ++\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n     ++\tgit -C branch-super-cloned checkout new-branch &&\n     ++\tgit -C branch-super-cloned/sub1 checkout new-branch &&\n     ++\ttest_commit -C branch-super-cloned/sub1 new-commit &&\n     ++\tgit -C branch-super-cloned/sub1 checkout main &&\n     ++\tgit -C branch-super-cloned submodule update &&\n     ++\n     ++\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n     ++\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n     ++\ttest_clean_submodule ! branch-super-cloned sub1\n     ++'\n     ++\n     ++test_expect_success 'branches - other branch checked out, correct branch does not exist' '\n     ++\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n     ++\tcp -r branch-super-clean branch-super-cloned &&\n     ++\n     ++\tgit -C branch-super-cloned branch new-branch &&\n     ++\tgit -C branch-super-cloned checkout new-branch &&\n     ++\ttest_must_fail git -C branch-super-cloned submodule update\n      +'\n      +\n       test_done\n\n-- \ngitgitgadget\n"},{"id":"465974","messageId":"06d7e15e830f7ede2aee43059b6d8666ecb00298.1666988096.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"[PATCH v3 2/8] repo-settings: add submodule_propagate_branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-28T20:14:50Z","receivedAt":"2022-10-28T20:15:12Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nWhen processes recurse into submodules, the child processes have to\nuse the same value of \"submodule.propagateBranches\" as the parent\nprocess regardless of whether the process is spawned in the superproject\nor submodule, otherwise the behavior may be inconsistent if the\nrepositories don't agree on the config.\n\nWe haven't needed a way to propagate the config because the only command\nthat reads \"submodule.propagateBranches\" is \"git branch\", which only has\none mode of operation with \"--recurse-submodules\". However, a future\ncommit will teach \"submodule.propagateBranches\" to \"git submodule\nupdate\", making this necessary.\n\nPropagate \"submodule.propagateBranches\" to child processes by adding a\ncorresponding GIT_INTERNAL_* environment variable and repository\nsetting, and setting the environment variable inside\nprepare_submodule_repo_env(). Then, refactor builtin/branch.c to read\nthe repository setting.\n\nUsing an internal environment variable is a potentially leaky\nabstraction because environment variables can come from sources besides\nthe parent process. A more robust solution would be to teach Git that\nthe repository is a submodule and to only read\n\"submodule.propagateBranches\" from the superproject config. There is WIP\nfor this on the ML [1].\n\nAnother alternative would be to pass \"-c submodule.propagateBranches\" to\nall child processes. This is error-prone because many different\nprocesses are invoked directly or indirectly by \"git submodule update\"\n(e.g. \"git submodule--helper clone\", \"git clone\", \"git checkout\"). With\nan environment variable, we can avoid this work because\nprepare_submodule_repo_env() is already called for submodule child\nprocesses.\n\n[1] https://lore.kernel.org/git/20220310004423.2627181-1-emilyshaffer@google.com/\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c | 11 +++--------\n cache.h          |  1 +\n repo-settings.c  | 13 +++++++++++++\n repository.h     |  1 +\n submodule.c      |  2 ++\n 5 files changed, 20 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e0e0af43202..8279d4eeb15 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -40,7 +40,6 @@ static const char * const builtin_branch_usage[] = {\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@@ -106,10 +105,6 @@ static int git_branch_config(const char *var, const char *value, void *cb)\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@@ -723,7 +718,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, builtin_branch_usage,\n \t\t\t     0);\n-\n+\tprepare_repo_settings(the_repository);\n \tif (!delete && !rename && !copy && !edit_description && !new_upstream &&\n \t    !show_current && !unset_upstream && argc == 0)\n \t\tlist = 1;\n@@ -739,7 +734,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_branch_usage, options);\n \n \tif (recurse_submodules_explicit) {\n-\t\tif (!submodule_propagate_branches)\n+\t\tif (!the_repository->settings.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@@ -747,7 +742,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \trecurse_submodules =\n \t\t(recurse_submodules || recurse_submodules_explicit) &&\n-\t\tsubmodule_propagate_branches;\n+\t\tthe_repository->settings.submodule_propagate_branches;\n \n \tif (filter.abbrev == -1)\n \t\tfilter.abbrev = DEFAULT_ABBREV;\ndiff --git a/cache.h b/cache.h\nindex 26ed03bd6de..151e1d49e77 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -505,6 +505,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define GIT_WORK_TREE_ENVIRONMENT \"GIT_WORK_TREE\"\n #define GIT_PREFIX_ENVIRONMENT \"GIT_PREFIX\"\n #define GIT_SUPER_PREFIX_ENVIRONMENT \"GIT_INTERNAL_SUPER_PREFIX\"\n+#define GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT \"GIT_INTERNAL_SUBMODULE_PROPAGATE_BRANCHES\"\n #define DEFAULT_GIT_DIR_ENVIRONMENT \".git\"\n #define DB_ENVIRONMENT \"GIT_OBJECT_DIRECTORY\"\n #define INDEX_ENVIRONMENT \"GIT_INDEX_FILE\"\ndiff --git a/repo-settings.c b/repo-settings.c\nindex e8b58151bc4..7ef2da9178d 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -71,6 +71,19 @@ void prepare_repo_settings(struct repository *r)\n \tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))\n \t\tr->settings.core_multi_pack_index = 1;\n \n+\t/*\n+\t * If the environment variable is set, assume that it was set by an\n+\t * invocation of git running in a superproject with\n+\t * submodule.propagateBranches set and that is recursing into this repo\n+\t * as a submodule. Therefore, we should ignore whatever is set in this\n+\t * repo's config.\n+\t */\n+\tr->settings.submodule_propagate_branches =\n+\t\tgit_env_bool(GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT, -1);\n+\tif (r->settings.submodule_propagate_branches == -1)\n+\t\trepo_cfg_bool(r, \"submodule.propagateBranches\",\n+\t\t\t      &r->settings.submodule_propagate_branches, 0);\n+\n \t/*\n \t * Non-boolean config\n \t */\ndiff --git a/repository.h b/repository.h\nindex 24316ac944e..3df6fdbdd5c 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -37,6 +37,7 @@ struct repo_settings {\n \tint fetch_write_commit_graph;\n \tint command_requires_full_index;\n \tint sparse_index;\n+\tint submodule_propagate_branches;\n \n \tstruct fsmonitor_settings *fsmonitor; /* lazily loaded */\n \ndiff --git a/submodule.c b/submodule.c\nindex bf7a2c79183..624404957fa 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -503,6 +503,8 @@ static void print_submodule_diff_summary(struct repository *r, struct rev_info *\n \n void prepare_submodule_repo_env(struct strvec *out)\n {\n+\tif (the_repository->settings.submodule_propagate_branches)\n+\t\tstrvec_pushf(out, \"%s=1\", GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT);\n \tprepare_other_repo_env(out, DEFAULT_GIT_DIR_ENVIRONMENT);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"465975","messageId":"5a24d7e9255de407e343ce8bd60edb63293505bb.1666988096.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"[PATCH v3 3/8] submodule--helper clone: create named branch","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-28T20:14:51Z","receivedAt":"2022-10-28T20:15:15Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nWhen submodule branching is enabled (i.e. \"submodule.propagateBranches =\ntrue\"), submodules are expected to have the same set of branches as the\nsuperproject. To support this behavior in newly cloned submodules, teach\n\"git submodule--helper clone\" to:\n\n- clone with the \"--detach\" flag (so that the submodule doesn't create a\n  branch corresponding to the remote's HEAD)\n- create a branch when using the --branch and --branch-oid flags\n\nThe --branch and --branch-oid flags are only allowed when submodule\nbranching is enabled, otherwise the named branch might conflict with the\nbranch from the submodule remote's HEAD.\n\nThis functionality will be tested in a later commit where \"git submodule\nupdate\" uses it to create and check out the correct branch when\nsubmodule branching is enabled. \"git submodule add\" (which also invokes\n\"git submodule--helper clone\") should also do something similar when\nsubmodule branching is enabled, but this is left for a later series.\n\nArguably, \"--detach\" should also be the default for\n\"submodule.propagateBranches=false\" since it doesn't make sense to\ncreate a submodule branch when the submodule is always expected to be in\ndetached HEAD. But, to be conservative, this commit does not change the\nbehavior of \"submodule.propagateBranches=false\".\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c | 33 +++++++++++++++++++++++++++++++++\n 1 file changed, 33 insertions(+)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 0b4acb442b2..c974206cad4 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1503,6 +1503,8 @@ struct module_clone_data {\n \tconst char *name;\n \tconst char *url;\n \tconst char *depth;\n+\tconst char *branch;\n+\tconst char *branch_oid;\n \tstruct list_objects_filter_options *filter_options;\n \tunsigned int quiet: 1;\n \tunsigned int progress: 1;\n@@ -1692,6 +1694,8 @@ static int clone_submodule(const struct module_clone_data *clone_data,\n \t\t\tstrvec_push(&cp.args, clone_data->single_branch ?\n \t\t\t\t    \"--single-branch\" :\n \t\t\t\t    \"--no-single-branch\");\n+\t\tif (the_repository->settings.submodule_propagate_branches)\n+\t\t\tstrvec_push(&cp.args, \"--detach\");\n \n \t\tstrvec_push(&cp.args, \"--\");\n \t\tstrvec_push(&cp.args, clone_data->url);\n@@ -1704,6 +1708,21 @@ static int clone_submodule(const struct module_clone_data *clone_data,\n \t\tif(run_command(&cp))\n \t\t\tdie(_(\"clone of '%s' into submodule path '%s' failed\"),\n \t\t\t    clone_data->url, clone_data_path);\n+\n+\t\tif (clone_data->branch) {\n+\t\t\tstruct child_process branch_cp = CHILD_PROCESS_INIT;\n+\n+\t\t\tbranch_cp.git_cmd = 1;\n+\t\t\tprepare_other_repo_env(&branch_cp.env, sm_gitdir);\n+\n+\t\t\tstrvec_pushl(&branch_cp.args, \"branch\",\n+\t\t\t\t     clone_data->branch, clone_data->branch_oid,\n+\t\t\t\t     NULL);\n+\n+\t\t\tif (run_command(&branch_cp))\n+\t\t\t\tdie(_(\"could not create branch '%s' in submodule path '%s'\"),\n+\t\t\t\t    clone_data->branch, clone_data_path);\n+\t\t}\n \t} else {\n \t\tchar *path;\n \n@@ -1778,6 +1797,12 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n \t\t\t   N_(\"disallow cloning into non-empty directory\")),\n \t\tOPT_BOOL(0, \"single-branch\", &clone_data.single_branch,\n \t\t\t N_(\"clone only one branch, HEAD or --branch\")),\n+\t\tOPT_STRING(0, \"branch\", &clone_data.branch,\n+\t\t\t   N_(\"string\"),\n+\t\t\t   N_(\"name of branch to be created\")),\n+\t\tOPT_STRING(0, \"branch-oid\", &clone_data.branch_oid,\n+\t\t\t   N_(\"object-id\"),\n+\t\t\t   N_(\"commit id for new branch\")),\n \t\tOPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),\n \t\tOPT_END()\n \t};\n@@ -1785,12 +1810,14 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n \t\tN_(\"git submodule--helper clone [--prefix=<path>] [--quiet] \"\n \t\t   \"[--reference <repository>] [--name <name>] [--depth <depth>] \"\n \t\t   \"[--single-branch] [--filter <filter-spec>] \"\n+\t\t   \"[--branch <branch> --branch-oid <oid>]\"\n \t\t   \"--url <url> --path <path>\"),\n \t\tNULL\n \t};\n \n \targc = parse_options(argc, argv, prefix, module_clone_options,\n \t\t\t     git_submodule_helper_usage, 0);\n+\tprepare_repo_settings(the_repository);\n \n \tclone_data.dissociate = !!dissociate;\n \tclone_data.quiet = !!quiet;\n@@ -1802,6 +1829,12 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_submodule_helper_usage,\n \t\t\t\t   module_clone_options);\n \n+\tif (!!clone_data.branch != !!clone_data.branch_oid)\n+\t\tBUG(\"--branch and --branch-oid must be set/unset together\");\n+\tif ((clone_data.branch &&\n+\t     !the_repository->settings.submodule_propagate_branches))\n+\t\tBUG(\"--branch is only expected with submodule.propagateBranches\");\n+\n \tclone_submodule(&clone_data, &reference);\n \tlist_objects_filter_release(&filter_options);\n \tstring_list_clear(&reference, 1);\n-- \ngitgitgadget\n\n"},{"id":"465976","messageId":"bd8ffd7cde2feb3853f837778d660c0f47db32ff.1666988096.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"[PATCH v3 5/8] submodule: return target of submodule symref","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-28T20:14:53Z","receivedAt":"2022-10-28T20:15:17Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nresolve_gitlink_ref() can tell us which oid the submodule ref is\npointing to, but in a future commit, we would also like to know the\nsymbolic ref target if we are checking a symbolic ref. Teach\nresolve_gitlink_ref() to \"return\" the symbolic ref's target via an \"out\"\nparameter.\n\nThis changes resolve_gitlink_ref()'s signature so that new callers\ntrying to use the old signature will be stopped by the compiler. If we\nreturned the target instead (just like refs_resolve_ref_unsafe()), we\nwould be more consistent with refs_resolve_ref_unsafe(), but callers\nexpecting the old signature will get the opposite return value from what\nthey expect (since exit code 0 means success, but NULL pointer means\nfailure). We should do this refactor once we think that nobody will try\nto use the old signature.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c |  8 +++++---\n builtin/update-index.c      |  4 ++--\n combine-diff.c              |  3 ++-\n diff-lib.c                  |  2 +-\n dir.c                       |  2 +-\n object-file.c               |  2 +-\n read-cache.c                |  4 ++--\n refs.c                      | 10 ++++++----\n refs.h                      |  5 ++++-\n unpack-trees.c              |  3 ++-\n 10 files changed, 26 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex c974206cad4..9ca138374b7 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2533,7 +2533,8 @@ static int update_submodule(struct update_data *update_data)\n \n \tif (update_data->just_cloned)\n \t\toidcpy(&update_data->suboid, null_oid());\n-\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &update_data->suboid))\n+\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n+\t\t\t\t     &update_data->suboid, NULL))\n \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n \t\t\t\t   update_data->displaypath);\n \n@@ -2560,7 +2561,8 @@ static int update_submodule(struct update_data *update_data)\n \t\t\t\t\t\t   update_data->sm_path);\n \t\t}\n \n-\t\tif (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))\n+\t\tif (resolve_gitlink_ref(update_data->sm_path, remote_ref,\n+\t\t\t\t\t&update_data->oid, NULL))\n \t\t\treturn die_message(_(\"Unable to find %s revision in submodule path '%s'\"),\n \t\t\t\t\t   remote_ref, update_data->sm_path);\n \n@@ -3305,7 +3307,7 @@ static void die_on_repo_without_commits(const char *path)\n \tstrbuf_addstr(&sb, path);\n \tif (is_nonbare_repository_dir(&sb)) {\n \t\tstruct object_id oid;\n-\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid) < 0)\n+\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid, NULL) < 0)\n \t\t\tdie(_(\"'%s' does not have a commit checked out\"), path);\n \t}\n \tstrbuf_release(&sb);\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex b62249905f1..19a21a4586c 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -339,7 +339,7 @@ static int process_directory(const char *path, int len, struct stat *st)\n \t\tif (S_ISGITLINK(ce->ce_mode)) {\n \n \t\t\t/* Do nothing to the index if there is no HEAD! */\n-\t\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid) < 0)\n+\t\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid, NULL) < 0)\n \t\t\t\treturn 0;\n \n \t\t\treturn add_one_path(ce, path, len, st);\n@@ -365,7 +365,7 @@ static int process_directory(const char *path, int len, struct stat *st)\n \t}\n \n \t/* No match - should we add it as a gitlink? */\n-\tif (!resolve_gitlink_ref(path, \"HEAD\", &oid))\n+\tif (!resolve_gitlink_ref(path, \"HEAD\", &oid, NULL))\n \t\treturn add_one_path(NULL, path, len, st);\n \n \t/* Error out. */\ndiff --git a/combine-diff.c b/combine-diff.c\nindex b0ece954808..88efcaeefa7 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -1060,7 +1060,8 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n \t\t\telem->mode = canon_mode(st.st_mode);\n \t\t} else if (S_ISDIR(st.st_mode)) {\n \t\t\tstruct object_id oid;\n-\t\t\tif (resolve_gitlink_ref(elem->path, \"HEAD\", &oid) < 0)\n+\t\t\tif (resolve_gitlink_ref(elem->path, \"HEAD\", &oid,\n+\t\t\t\t\t\tNULL) < 0)\n \t\t\t\tresult = grab_blob(opt->repo, &elem->oid,\n \t\t\t\t\t\t   elem->mode, &result_size,\n \t\t\t\t\t\t   NULL, NULL);\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 2edea41a234..ac94b6234ca 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -53,7 +53,7 @@ static int check_removed(const struct index_state *istate, const struct cache_en\n \t\t * a directory --- the blob was removed!\n \t\t */\n \t\tif (!S_ISGITLINK(ce->ce_mode) &&\n-\t\t    resolve_gitlink_ref(ce->name, \"HEAD\", &sub))\n+\t\t    resolve_gitlink_ref(ce->name, \"HEAD\", &sub, NULL))\n \t\t\treturn 1;\n \t}\n \treturn 0;\ndiff --git a/dir.c b/dir.c\nindex d604d1bab98..81d232424c1 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3251,7 +3251,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)\n \tstruct object_id submodule_head;\n \n \tif ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&\n-\t    !resolve_gitlink_ref(path->buf, \"HEAD\", &submodule_head)) {\n+\t    !resolve_gitlink_ref(path->buf, \"HEAD\", &submodule_head, NULL)) {\n \t\t/* Do not descend and nuke a nested git work tree. */\n \t\tif (kept_up)\n \t\t\t*kept_up = 1;\ndiff --git a/object-file.c b/object-file.c\nindex 5e309602346..a3c6580c16e 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -2522,7 +2522,7 @@ int index_path(struct index_state *istate, struct object_id *oid,\n \t\tstrbuf_release(&sb);\n \t\tbreak;\n \tcase S_IFDIR:\n-\t\treturn resolve_gitlink_ref(path, \"HEAD\", oid);\n+\t\treturn resolve_gitlink_ref(path, \"HEAD\", oid, NULL);\n \tdefault:\n \t\treturn error(_(\"%s: unsupported file type\"), path);\n \t}\ndiff --git a/read-cache.c b/read-cache.c\nindex 32024029274..4c1bf33ef48 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -285,7 +285,7 @@ static int ce_compare_gitlink(const struct cache_entry *ce)\n \t *\n \t * If so, we consider it always to match.\n \t */\n-\tif (resolve_gitlink_ref(ce->name, \"HEAD\", &oid) < 0)\n+\tif (resolve_gitlink_ref(ce->name, \"HEAD\", &oid, NULL) < 0)\n \t\treturn 0;\n \treturn !oideq(&oid, &ce->oid);\n }\n@@ -781,7 +781,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \n \tnamelen = strlen(path);\n \tif (S_ISDIR(st_mode)) {\n-\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid) < 0)\n+\t\tif (resolve_gitlink_ref(path, \"HEAD\", &oid, NULL) < 0)\n \t\t\treturn error(_(\"'%s' does not have a commit checked out\"), path);\n \t\twhile (namelen && path[namelen-1] == '/')\n \t\t\tnamelen--;\ndiff --git a/refs.c b/refs.c\nindex 1491ae937eb..a32a25ccb69 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1904,19 +1904,21 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,\n }\n \n int resolve_gitlink_ref(const char *submodule, const char *refname,\n-\t\t\tstruct object_id *oid)\n+\t\t\tstruct object_id *oid, const char **target_out)\n {\n \tstruct ref_store *refs;\n \tint flags;\n+\tconst char *target;\n \n \trefs = get_submodule_ref_store(submodule);\n \n \tif (!refs)\n \t\treturn -1;\n-\n-\tif (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||\n-\t    is_null_oid(oid))\n+\ttarget = refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags);\n+\tif (!target || is_null_oid(oid))\n \t\treturn -1;\n+\tif (target_out)\n+\t\t*target_out = target;\n \treturn 0;\n }\n \ndiff --git a/refs.h b/refs.h\nindex 8958717a17d..d5c32fac000 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -137,9 +137,12 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled);\n  * submodule (which must be non-NULL). If the resolution is\n  * successful, return 0 and set oid to the name of the object;\n  * otherwise, return a non-zero value.\n+ *\n+ * FIXME: Return \"target\" just like refs_resolve_ref_unsafe(). This will be\n+ * safe to do once we merge resolve_gitlink_ref() into master.\n  */\n int resolve_gitlink_ref(const char *submodule, const char *refname,\n-\t\t\tstruct object_id *oid);\n+\t\t\tstruct object_id *oid, const char **target);\n \n /*\n  * Return true iff abbrev_name is a possible abbreviation for\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex bae812156c4..db12bfcaffd 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -2288,7 +2288,8 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,\n \n \tif (S_ISGITLINK(ce->ce_mode)) {\n \t\tstruct object_id oid;\n-\t\tint sub_head = resolve_gitlink_ref(ce->name, \"HEAD\", &oid);\n+\t\tint sub_head =\n+\t\t\tresolve_gitlink_ref(ce->name, \"HEAD\", &oid, NULL);\n \t\t/*\n \t\t * If we are not going to update the submodule, then\n \t\t * we don't care.\n-- \ngitgitgadget\n\n"},{"id":"465977","messageId":"3a08f2ab776b037e00a47fb7ed63a3f50056350b.1666988096.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"[PATCH v3 4/8] t5617: drop references to remote-tracking branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-28T20:14:52Z","receivedAt":"2022-10-28T20:15:21Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nIt has included submodule cloning tests without remote-tracking branches\ntests since f05da2b48b (clone, submodule: pass partial clone filters to\nsubmodules, 2022-02-04) at least. Rename it accordingly so that we can\nput future submodule cloning tests there.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n ...617-clone-submodules-remote.sh => t5617-clone-submodules.sh} | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n rename t/{t5617-clone-submodules-remote.sh => t5617-clone-submodules.sh} (97%)\n\ndiff --git a/t/t5617-clone-submodules-remote.sh b/t/t5617-clone-submodules.sh\nsimilarity index 97%\nrename from t/t5617-clone-submodules-remote.sh\nrename to t/t5617-clone-submodules.sh\nindex 68843382493..c43a5b26fab 100755\n--- a/t/t5617-clone-submodules-remote.sh\n+++ b/t/t5617-clone-submodules.sh\n@@ -1,6 +1,6 @@\n #!/bin/sh\n \n-test_description='Test cloning repos with submodules using remote-tracking branches'\n+test_description='Test cloning repos with submodules'\n \n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n-- \ngitgitgadget\n\n"},{"id":"465978","messageId":"7cdd6c4184da2d3109498589167f10ecf972edc9.1666988096.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"[PATCH v3 8/8] clone, submodule update: create and check out branches","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-28T20:14:56Z","receivedAt":"2022-10-28T20:15:37Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nTeach \"git submodule update\" to:\n\n- create the branch with the same name as the current superproject\n  branch when cloning a submodule\n- check out that branch (instead of the commit OID) when updating\n  the submodule worktree\n\nwhen submodule branching is enabled (submodule.propagateBranches = true)\non the superproject and a branch is checked out. \"git clone\n--recurse-submodules\" also learns this trick because it is implemented\nwith \"git submodule update --recursive\".\n\nThis approach of checking out the branch will not result in a dirty\nworktree for freshly cloned submodules because we can ensure that the\nsubmodule branch points to the superproject gitlink. In other cases, it\ndoes not work as well, but we can handle them incrementally:\n\n- \"git pull --recurse-submodules\" merges the superproject tree,\n  (changing the gitlink without updating the submodule branches), and\n  runs \"git submodule update\" to update the worktrees, so it is almost\n  guaranteed to result in a dirty worktree.\n\n  The implementation of \"git pull --recurse-submodules\" is likely to\n  change drastically as submodule.propagateBranches work progresses\n  (e.g. \"git merge\" learns to recurse in to submodules), and we may be\n  able to replace the \"git submodule update\" invocation, or teach it new\n  tricks that make the update behave well.\n\n- The user might make changes to the submodule branch without committing\n  them back to superproject. This is primarily affects \"git checkout\n  --recurse-submodules\", since that is the primary way of switching away\n  from a branch and leaving behind WIP (as opposed to \"git submodule\n  update\", which is run post-checkout).\n\n  In a future series, \"git checkout --recurse-submodules\" will learn to\n  consider submodule branches. We can introduce appropriate guardrails\n  then, e.g. requiring that the superproject working tree is not dirty\n  before switching away.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c |  37 ++++++++-\n t/t5617-clone-submodules.sh |  38 +++++++++\n t/t7406-submodule-update.sh | 156 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 227 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex ef76a111c7f..767a0c81cde 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1917,6 +1917,7 @@ static void submodule_update_clone_release(struct submodule_update_clone *suc)\n struct update_data {\n \tconst char *prefix;\n \tchar *displaypath;\n+\tconst char *super_branch;\n \tenum submodule_update_type update_default;\n \tstruct string_list references;\n \tstruct submodule_update_strategy update_strategy;\n@@ -2091,6 +2092,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,\n \t\tstrvec_push(&child->args, suc->update_data->single_branch ?\n \t\t\t\t\t      \"--single-branch\" :\n \t\t\t\t\t      \"--no-single-branch\");\n+\tif (ud->super_branch) {\n+\t\tstrvec_pushf(&child->args, \"--branch=%s\", ud->super_branch);\n+\t\tstrvec_pushf(&child->args, \"--branch-oid=%s\",\n+\t\t\t     oid_to_hex(&ce->oid));\n+\t}\n \n cleanup:\n \tfree(displaypath);\n@@ -2254,9 +2260,14 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet,\n static int run_update_command(const struct update_data *ud, int subforce)\n {\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tconst char *update_target = oid_to_hex(&ud->oid);;\n+\tconst char *update_target;\n \tint ret;\n \n+\tif (ud->update_strategy.type == SM_UPDATE_CHECKOUT && ud->super_branch)\n+\t\tupdate_target = ud->super_branch;\n+\telse\n+\t\tupdate_target = oid_to_hex(&ud->oid);\n+\n \tswitch (ud->update_strategy.type) {\n \tcase SM_UPDATE_CHECKOUT:\n \t\tcp.git_cmd = 1;\n@@ -2523,6 +2534,7 @@ static int update_submodule(struct update_data *update_data)\n \tint submodule_up_to_date;\n \tint ret;\n \tstruct object_id suboid;\n+\tconst char *submodule_head = NULL;\n \n \tret = determine_submodule_update_strategy(the_repository,\n \t\t\t\t\t\t  update_data->just_cloned,\n@@ -2533,7 +2545,8 @@ static int update_submodule(struct update_data *update_data)\n \t\treturn ret;\n \n \tif (!update_data->just_cloned &&\n-\t    resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &suboid, NULL))\n+\t    resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &suboid,\n+\t\t\t\t&submodule_head))\n \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n \t\t\t\t   update_data->displaypath);\n \n@@ -2568,8 +2581,17 @@ static int update_submodule(struct update_data *update_data)\n \t\tfree(remote_ref);\n \t}\n \n-\tsubmodule_up_to_date = !update_data->just_cloned &&\n-\t\toideq(&update_data->oid, &suboid);\n+\tif (update_data->just_cloned)\n+\t\tsubmodule_up_to_date = 0;\n+\telse if (update_data->super_branch)\n+\t\t/* Check that the submodule's HEAD points to super_branch. */\n+\t\tsubmodule_up_to_date =\n+\t\t\tskip_prefix(submodule_head, \"refs/heads/\",\n+\t\t\t\t    &submodule_head) &&\n+\t\t\t!strcmp(update_data->super_branch, submodule_head);\n+\telse\n+\t\tsubmodule_up_to_date = oideq(&update_data->oid, &suboid);\n+\n \tif (!submodule_up_to_date || update_data->force) {\n \t\tret = run_update_procedure(update_data);\n \t\tif (ret)\n@@ -2603,6 +2625,12 @@ static int update_submodules(struct update_data *update_data)\n \tint i, ret = 0;\n \tstruct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;\n \n+\tif (the_repository->settings.submodule_propagate_branches) {\n+\t\tstruct branch *current_branch = branch_get(NULL);\n+\t\tif (current_branch)\n+\t\t\tupdate_data->super_branch = current_branch->name;\n+\t}\n+\n \tsuc.update_data = update_data;\n \trun_processes_parallel_tr2(suc.update_data->max_jobs, update_clone_get_next_task,\n \t\t\t\t   update_clone_start_failure,\n@@ -2718,6 +2746,7 @@ static int module_update(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, module_update_options,\n \t\t\t     git_submodule_helper_usage, 0);\n+\tprepare_repo_settings(the_repository);\n \n \tif (opt.require_init)\n \t\topt.init = 1;\ndiff --git a/t/t5617-clone-submodules.sh b/t/t5617-clone-submodules.sh\nindex c43a5b26fab..43f9b52bd44 100755\n--- a/t/t5617-clone-submodules.sh\n+++ b/t/t5617-clone-submodules.sh\n@@ -13,10 +13,17 @@ test_expect_success 'setup' '\n \tgit config --global protocol.file.allow always &&\n \tgit checkout -b main &&\n \ttest_commit commit1 &&\n+\tmkdir subsub &&\n+\t(\n+\t\tcd subsub &&\n+\t\tgit init &&\n+\t\ttest_commit subsubcommit1\n+\t) &&\n \tmkdir sub &&\n \t(\n \t\tcd sub &&\n \t\tgit init &&\n+\t\tgit submodule add \"file://$pwd/subsub\" subsub &&\n \t\ttest_commit subcommit1 &&\n \t\tgit tag sub_when_added_to_super &&\n \t\tgit branch other\n@@ -107,4 +114,35 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule\n \ttest_cmp_config -C super_clone3/sub false --default false remote.origin.promisor\n '\n \n+test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '\n+\ttest_when_finished \"git checkout main\" &&\n+\n+\tgit checkout -b checked-out &&\n+\tgit -C sub checkout -b not-in-clone &&\n+\tgit -C subsub checkout -b not-in-clone &&\n+\tgit clone --recurse-submodules \\\n+\t\t--branch checked-out \\\n+\t\t-c submodule.propagateBranches=true \\\n+\t\t\"file://$pwd/.\" super_clone4 &&\n+\n+\t# Assert that each repo is pointing to \"checked-out\"\n+\tfor REPO in \"super_clone4\" \"super_clone4/sub\" \"super_clone4/sub/subsub\"\n+\tdo\n+\t    HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&\n+\t    test $HEAD_BRANCH = \"refs/heads/checked-out\" || return 1\n+\tdone &&\n+\n+\t# Assert that the submodule branches are pointing to the right revs\n+\tEXPECT_SUB_OID=\"$(git -C super_clone4 rev-parse :sub)\" &&\n+\tACTUAL_SUB_OID=\"$(git -C super_clone4/sub rev-parse refs/heads/checked-out)\" &&\n+\ttest $EXPECT_SUB_OID = $ACTUAL_SUB_OID &&\n+\tEXPECT_SUBSUB_OID=\"$(git -C super_clone4/sub rev-parse :subsub)\" &&\n+\tACTUAL_SUBSUB_OID=\"$(git -C super_clone4/sub/subsub rev-parse refs/heads/checked-out)\" &&\n+\ttest $EXPECT_SUBSUB_OID = $ACTUAL_SUBSUB_OID &&\n+\n+\t# Assert that the submodules do not have branches from their upstream\n+\ttest_must_fail git -C super_clone4/sub rev-parse not-in-clone &&\n+\ttest_must_fail git -C super_clone4/sub/subsub rev-parse not-in-clone\n+'\n+\n test_done\ndiff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\nindex f094e3d7f36..b749d35f784 100755\n--- a/t/t7406-submodule-update.sh\n+++ b/t/t7406-submodule-update.sh\n@@ -1179,4 +1179,160 @@ test_expect_success 'submodule update --recursive skip submodules with strategy=\n \ttest_cmp expect.err actual.err\n '\n \n+test_expect_success 'setup superproject with submodule.propagateBranches' '\n+\tgit init sub1 &&\n+\ttest_commit -C sub1 \"sub1\" &&\n+\tgit init branch-super &&\n+\tgit -C branch-super submodule add ../sub1 sub1 &&\n+\tgit -C branch-super commit -m \"super\" &&\n+\n+\t# Clone into a clean repo that we can cp around\n+\tgit clone --recurse-submodules \\\n+\t\t-c submodule.propagateBranches=true \\\n+\t\tbranch-super branch-super-clean &&\n+\tgit -C branch-super-clean config submodule.propagateBranches true &&\n+\n+\t# sub2 will not be in the clone. We will fetch the containing\n+\t# superproject commit and clone sub2 with \"git submodule update\".\n+\tgit init sub2 &&\n+\ttest_commit -C sub2 \"sub2\" &&\n+\tgit -C branch-super submodule add ../sub2 sub2 &&\n+\tgit -C branch-super commit -m \"add sub2\"\n+'\n+\n+test_clean_submodule ()\n+{\n+\tlocal negate super_dir sub_dir expect_oid actual_oid &&\n+\tif test \"$1\" = \"!\"\n+\tthen\n+\t\tnegate=t\n+\t\tshift\n+\tfi\n+\tsuper_dir=\"$1\" &&\n+\tsub_dir=\"$2\" &&\n+\texpect_oid=\"$(git -C \"$super_dir\" rev-parse \":$sub_dir\")\" &&\n+\tactual_oid=\"$(git -C \"$super_dir/$sub_dir\" rev-parse HEAD)\" &&\n+\tif test -n \"$negate\"\n+\tthen\n+\t\t! test \"$expect_oid\" = \"$actual_oid\"\n+\telse\n+\t\ttest \"$expect_oid\" = \"$actual_oid\"\n+\tfi\n+}\n+\n+# Test the behavior of a newly cloned submodule\n+test_expect_success 'branches - newly-cloned submodule, detached HEAD' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned fetch origin main &&\n+\tgit -C branch-super-cloned checkout FETCH_HEAD &&\n+\tgit -C branch-super-cloned/sub1 checkout --detach &&\n+\tgit -C branch-super-cloned submodule update &&\n+\n+\t# sub1 and sub2 should be in detached HEAD\n+\tgit -C branch-super-cloned/sub1 rev-parse --verify HEAD &&\n+\ttest_must_fail git -C branch-super-cloned/sub1 symbolic-ref HEAD &&\n+\ttest_clean_submodule branch-super-cloned sub1 &&\n+\tgit -C branch-super-cloned/sub2 rev-parse --verify HEAD &&\n+\ttest_must_fail git -C branch-super-cloned/sub2 symbolic-ref HEAD &&\n+\ttest_clean_submodule branch-super-cloned sub2\n+'\n+\n+test_expect_success 'branches - newly-cloned submodule, branch checked out' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned fetch origin main &&\n+\tgit -C branch-super-cloned checkout FETCH_HEAD &&\n+\tgit -C branch-super-cloned branch new-branch &&\n+\tgit -C branch-super-cloned checkout new-branch &&\n+\tgit -C branch-super-cloned/sub1 branch new-branch &&\n+\tgit -C branch-super-cloned submodule update &&\n+\n+\t# Ignore sub1, we will test it later.\n+\t# sub2 should check out the branch\n+\tHEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&\n+\ttest $HEAD_BRANCH2 = \"refs/heads/new-branch\" &&\n+\ttest_clean_submodule branch-super-cloned sub2\n+'\n+\n+# Test the behavior of an already-cloned submodule.\n+# NEEDSWORK When updating with branches, we always use the branch instead of the\n+# gitlink's OID. This results in some imperfect behavior:\n+#\n+# - If the gitlink's OID disagrees with the branch OID, updating with branches\n+#   may result in a dirty worktree\n+# - If the branch does not exist, the update fails.\n+#\n+# We will reevaluate when \"git checkout --recurse-submodules\" supports branches\n+# For now, just test for this imperfect behavior.\n+test_expect_success 'branches - correct branch checked out, OIDs agree' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n+\tgit -C branch-super-cloned checkout new-branch &&\n+\tgit -C branch-super-cloned/sub1 checkout new-branch &&\n+\tgit -C branch-super-cloned submodule update &&\n+\n+\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n+\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n+\ttest_clean_submodule branch-super-cloned sub1\n+'\n+\n+test_expect_success 'branches - correct branch checked out, OIDs disagree' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n+\tgit -C branch-super-cloned checkout new-branch &&\n+\tgit -C branch-super-cloned/sub1 checkout new-branch &&\n+\ttest_commit -C branch-super-cloned/sub1 new-commit &&\n+\tgit -C branch-super-cloned submodule update &&\n+\n+\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n+\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n+\ttest_clean_submodule ! branch-super-cloned sub1\n+'\n+\n+test_expect_success 'branches - other branch checked out, correct branch exists, OIDs agree' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n+\tgit -C branch-super-cloned checkout new-branch &&\n+\tgit -C branch-super-cloned/sub1 checkout main &&\n+\tgit -C branch-super-cloned submodule update &&\n+\n+\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n+\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n+\ttest_clean_submodule branch-super-cloned sub1\n+'\n+\n+test_expect_success 'branches - other branch checked out, correct branch exists, OIDs disagree' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n+\tgit -C branch-super-cloned checkout new-branch &&\n+\tgit -C branch-super-cloned/sub1 checkout new-branch &&\n+\ttest_commit -C branch-super-cloned/sub1 new-commit &&\n+\tgit -C branch-super-cloned/sub1 checkout main &&\n+\tgit -C branch-super-cloned submodule update &&\n+\n+\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n+\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n+\ttest_clean_submodule ! branch-super-cloned sub1\n+'\n+\n+test_expect_success 'branches - other branch checked out, correct branch does not exist' '\n+\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n+\tcp -r branch-super-clean branch-super-cloned &&\n+\n+\tgit -C branch-super-cloned branch new-branch &&\n+\tgit -C branch-super-cloned checkout new-branch &&\n+\ttest_must_fail git -C branch-super-cloned submodule update\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"465979","messageId":"df1f7225f4909547c10c52e818eb5dfd65b3e24c.1666988096.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"[PATCH v3 6/8] submodule update: refactor update targets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-28T20:14:54Z","receivedAt":"2022-10-28T20:15:41Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nRefactor two \"git submodule update\" code locations so that they no\nlonger refer to oids directly. This shrinks a subsequent commit's diff,\nwhere this code will need to handle branches.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c | 24 +++++++++++++-----------\n 1 file changed, 13 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 9ca138374b7..894be133b3f 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2255,7 +2255,7 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet,\n static int run_update_command(const struct update_data *ud, int subforce)\n {\n \tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tchar *oid = oid_to_hex(&ud->oid);\n+\tconst char *update_target = oid_to_hex(&ud->oid);;\n \tint ret;\n \n \tswitch (ud->update_strategy.type) {\n@@ -2285,7 +2285,7 @@ static int run_update_command(const struct update_data *ud, int subforce)\n \t\tBUG(\"unexpected update strategy type: %d\",\n \t\t    ud->update_strategy.type);\n \t}\n-\tstrvec_push(&cp.args, oid);\n+\tstrvec_push(&cp.args, update_target);\n \n \tcp.dir = ud->sm_path;\n \tprepare_submodule_repo_env(&cp.env);\n@@ -2293,20 +2293,20 @@ static int run_update_command(const struct update_data *ud, int subforce)\n \t\tswitch (ud->update_strategy.type) {\n \t\tcase SM_UPDATE_CHECKOUT:\n \t\t\tdie_message(_(\"Unable to checkout '%s' in submodule path '%s'\"),\n-\t\t\t\t    oid, ud->displaypath);\n+\t\t\t\t    update_target, ud->displaypath);\n \t\t\t/* No \"ret\" assignment, use \"git checkout\"'s */\n \t\t\tbreak;\n \t\tcase SM_UPDATE_REBASE:\n \t\t\tret = die_message(_(\"Unable to rebase '%s' in submodule path '%s'\"),\n-\t\t\t\t\t  oid, ud->displaypath);\n+\t\t\t\t\t  update_target, ud->displaypath);\n \t\t\tbreak;\n \t\tcase SM_UPDATE_MERGE:\n \t\t\tret = die_message(_(\"Unable to merge '%s' in submodule path '%s'\"),\n-\t\t\t\t\t  oid, ud->displaypath);\n+\t\t\t\t\t  update_target, ud->displaypath);\n \t\t\tbreak;\n \t\tcase SM_UPDATE_COMMAND:\n \t\t\tret = die_message(_(\"Execution of '%s %s' failed in submodule path '%s'\"),\n-\t\t\t\t\t  ud->update_strategy.command, oid, ud->displaypath);\n+\t\t\t\t\t  ud->update_strategy.command, update_target, ud->displaypath);\n \t\t\tbreak;\n \t\tdefault:\n \t\t\tBUG(\"unexpected update strategy type: %d\",\n@@ -2322,19 +2322,19 @@ static int run_update_command(const struct update_data *ud, int subforce)\n \tswitch (ud->update_strategy.type) {\n \tcase SM_UPDATE_CHECKOUT:\n \t\tprintf(_(\"Submodule path '%s': checked out '%s'\\n\"),\n-\t\t       ud->displaypath, oid);\n+\t\t       ud->displaypath, update_target);\n \t\tbreak;\n \tcase SM_UPDATE_REBASE:\n \t\tprintf(_(\"Submodule path '%s': rebased into '%s'\\n\"),\n-\t\t       ud->displaypath, oid);\n+\t\t       ud->displaypath, update_target);\n \t\tbreak;\n \tcase SM_UPDATE_MERGE:\n \t\tprintf(_(\"Submodule path '%s': merged in '%s'\\n\"),\n-\t\t       ud->displaypath, oid);\n+\t\t       ud->displaypath, update_target);\n \t\tbreak;\n \tcase SM_UPDATE_COMMAND:\n \t\tprintf(_(\"Submodule path '%s': '%s %s'\\n\"),\n-\t\t       ud->displaypath, ud->update_strategy.command, oid);\n+\t\t       ud->displaypath, ud->update_strategy.command, update_target);\n \t\tbreak;\n \tdefault:\n \t\tBUG(\"unexpected update strategy type: %d\",\n@@ -2521,6 +2521,7 @@ static void update_data_to_args(const struct update_data *update_data,\n \n static int update_submodule(struct update_data *update_data)\n {\n+\tint submodule_up_to_date;\n \tint ret;\n \n \tret = determine_submodule_update_strategy(the_repository,\n@@ -2569,7 +2570,8 @@ static int update_submodule(struct update_data *update_data)\n \t\tfree(remote_ref);\n \t}\n \n-\tif (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) {\n+\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n+\tif (!submodule_up_to_date || update_data->force) {\n \t\tret = run_update_procedure(update_data);\n \t\tif (ret)\n \t\t\treturn ret;\n-- \ngitgitgadget\n\n"},{"id":"465980","messageId":"4e402b67145c6e33c13826f1daf1883a66cd9cd4.1666988096.git.gitgitgadget@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"[PATCH v3 7/8] submodule--helper: remove update_data.suboid","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-10-28T20:14:55Z","receivedAt":"2022-10-28T20:15:43Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nupdate_data.suboid's value is only used in update_submodule()'s call\nchain, where it represents the OID of the submodule's HEAD. If the\nsubmodule is newly cloned, it is set to null_oid().\n\nInstead of checking for the null OID, just check if the submodule is\nnewly cloned. This makes update_submodule() the only function where\nupdate_data.suboid is used, so replace it with a local variable.\n\nAs a result, the submodule_up_to_date check is more explicit, which\nmakes the next commit slightly easier to reason about.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/submodule--helper.c | 14 ++++++--------\n 1 file changed, 6 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 894be133b3f..ef76a111c7f 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1918,7 +1918,6 @@ struct update_data {\n \tconst char *prefix;\n \tchar *displaypath;\n \tenum submodule_update_type update_default;\n-\tstruct object_id suboid;\n \tstruct string_list references;\n \tstruct submodule_update_strategy update_strategy;\n \tstruct list_objects_filter_options *filter_options;\n@@ -2346,7 +2345,7 @@ static int run_update_command(const struct update_data *ud, int subforce)\n \n static int run_update_procedure(const struct update_data *ud)\n {\n-\tint subforce = is_null_oid(&ud->suboid) || ud->force;\n+\tint subforce = ud->just_cloned || ud->force;\n \n \tif (!ud->nofetch) {\n \t\t/*\n@@ -2523,6 +2522,7 @@ static int update_submodule(struct update_data *update_data)\n {\n \tint submodule_up_to_date;\n \tint ret;\n+\tstruct object_id suboid;\n \n \tret = determine_submodule_update_strategy(the_repository,\n \t\t\t\t\t\t  update_data->just_cloned,\n@@ -2532,10 +2532,8 @@ static int update_submodule(struct update_data *update_data)\n \tif (ret)\n \t\treturn ret;\n \n-\tif (update_data->just_cloned)\n-\t\toidcpy(&update_data->suboid, null_oid());\n-\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n-\t\t\t\t     &update_data->suboid, NULL))\n+\tif (!update_data->just_cloned &&\n+\t    resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &suboid, NULL))\n \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n \t\t\t\t   update_data->displaypath);\n \n@@ -2570,7 +2568,8 @@ static int update_submodule(struct update_data *update_data)\n \t\tfree(remote_ref);\n \t}\n \n-\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n+\tsubmodule_up_to_date = !update_data->just_cloned &&\n+\t\toideq(&update_data->oid, &suboid);\n \tif (!submodule_up_to_date || update_data->force) {\n \t\tret = run_update_procedure(update_data);\n \t\tif (ret)\n@@ -2583,7 +2582,6 @@ static int update_submodule(struct update_data *update_data)\n \n \t\tnext.prefix = NULL;\n \t\toidcpy(&next.oid, null_oid());\n-\t\toidcpy(&next.suboid, null_oid());\n \n \t\tcp.dir = update_data->sm_path;\n \t\tcp.git_cmd = 1;\n-- \ngitgitgadget\n\n"},{"id":"465984","messageId":"xmqqmt9fhbjt.fsf@gitster.g","threadId":"58375","inReplyTo":"432bc7cb3a42cf39d0033701c2cc677c9109b3dd.1666988096.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/8] clone: teach --detach option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-28T21:40:22Z","receivedAt":"2022-10-28T21:40:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> Teach \"git clone\" the \"--detach\" option, which leaves the cloned repo in\n> detached HEAD (like \"git checkout --detach\"). In addition, if the clone\n> is not bare, do not create the local branch pointed to by the remote's\n> HEAD symref (bare clones always copy all remote branches directly to\n> local branches, so the branch is still created in the bare case).\n>\n> This is especially useful in the \"submodule.propagateBranches\" workflow,\n> where local submodule branches are named after the superproject's\n> branches, so it makes no sense to create a local branch named after the\n> submodule's remote's branch.\n\nWouldn't it the same thing to do \"git clone -n && git checkout\n--detach\"?  If this is a pure implementation detail of another\ncommand and will never be used directly by end users, I am not\nsure if we should add a new option to do this.\n\nEspecially because it is probably not hard to perform internally an\nequivalent of \"checkout --detach\" without forking these days,\njudging from the fact that merges and rebases need to do so.\n"},{"id":"465986","messageId":"xmqqfsf7hb56.fsf@gitster.g","threadId":"58375","inReplyTo":"bd8ffd7cde2feb3853f837778d660c0f47db32ff.1666988096.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 5/8] submodule: return target of submodule symref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-28T21:49:09Z","receivedAt":"2022-10-28T21:49:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  int resolve_gitlink_ref(const char *submodule, const char *refname,\n> -\t\t\tstruct object_id *oid)\n> +\t\t\tstruct object_id *oid, const char **target_out)\n>  {\n>  \tstruct ref_store *refs;\n>  \tint flags;\n> +\tconst char *target;\n>  \n>  \trefs = get_submodule_ref_store(submodule);\n>  \n>  \tif (!refs)\n>  \t\treturn -1;\n> -\n> -\tif (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||\n> -\t    is_null_oid(oid))\n> +\ttarget = refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags);\n> +\tif (!target || is_null_oid(oid))\n>  \t\treturn -1;\n> +\tif (target_out)\n> +\t\t*target_out = target;\n>  \treturn 0;\n>  }\n\nPlease remind me why we call this underlying helper _unsafe()?\n\nIsn't it because we return a temporary buffer  (static strbuf),\nwhose contents is not permanent?\n\nI am wondering if we should force the callers who care enough to\npass the optional **target_out to xstrdup() the result.\n"},{"id":"465988","messageId":"xmqq7d0jhaw6.fsf@gitster.g","threadId":"58375","inReplyTo":"xmqqmt9fhbjt.fsf@gitster.g","subject":"Re: [PATCH v3 1/8] clone: teach --detach option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-10-28T21:54:33Z","receivedAt":"2022-10-28T21:54:51Z","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> Wouldn't it the same thing to do \"git clone -n && git checkout\n> --detach\"?\n\nNot exactly.  It still creates the initial branch and points HEAD at\nit.  It is so close, which is a bit disappointing, though.\n"},{"id":"465993","messageId":"kl6lbkpvd0de.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58375","inReplyTo":"xmqq7d0jhaw6.fsf@gitster.g","subject":"Re: [PATCH v3 1/8] clone: teach --detach option","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-10-28T22:55:25Z","receivedAt":"2022-10-28T22:55:51Z","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>> Wouldn't it the same thing to do \"git clone -n && git checkout\n>> --detach\"?\n>\n> Not exactly.  It still creates the initial branch and points HEAD at\n> it.  It is so close, which is a bit disappointing, though.\n\nYes. I was quite hopeful that \"git clone -n\" would just do the right\nthing too :/\n\nI'm guessing that changing the behavior of \"git clone -n\" is a\nnon-option, since there may be users relying on it.\n\nSo a better way forward is to add the new flag, which I imagine might be\nuseful to certain end users.\n"},{"id":"465994","messageId":"kl6l8rkzczlt.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58375","inReplyTo":"xmqqfsf7hb56.fsf@gitster.g","subject":"Re: [PATCH v3 5/8] submodule: return target of submodule symref","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-10-28T23:11:58Z","receivedAt":"2022-10-28T23:12:46Z","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 via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>>  int resolve_gitlink_ref(const char *submodule, const char *refname,\n>> -\t\t\tstruct object_id *oid)\n>> +\t\t\tstruct object_id *oid, const char **target_out)\n>>  {\n>>  \tstruct ref_store *refs;\n>>  \tint flags;\n>> +\tconst char *target;\n>>  \n>>  \trefs = get_submodule_ref_store(submodule);\n>>  \n>>  \tif (!refs)\n>>  \t\treturn -1;\n>> -\n>> -\tif (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||\n>> -\t    is_null_oid(oid))\n>> +\ttarget = refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags);\n>> +\tif (!target || is_null_oid(oid))\n>>  \t\treturn -1;\n>> +\tif (target_out)\n>> +\t\t*target_out = target;\n>>  \treturn 0;\n>>  }\n>\n> Please remind me why we call this underlying helper _unsafe()?\n>\n> Isn't it because we return a temporary buffer  (static strbuf),\n> whose contents is not permanent?\n\n8cad4744ee (Rename resolve_ref() to resolve_ref_unsafe(), 2011-12-12)\nseems to suggest so.\n\n(For some reason, I thought it was *_unsafe() because it would die(),\nbut it obviously doesn't.)\n\n> I am wondering if we should force the callers who care enough to\n> pass the optional **target_out to xstrdup() the result.\n\nYes, makes sense. We even have the *_refdup() helper for that.\n"},{"id":"466063","messageId":"Y16/AMeVYgERJuj6@nand.local","threadId":"58375","inReplyTo":"kl6lbkpvd0de.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v3 1/8] clone: teach --detach option","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-10-30T18:14:24Z","receivedAt":"2022-10-30T18:14:29Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 28, 2022 at 03:55:25PM -0700, Glen Choo wrote:\n> So a better way forward is to add the new flag, which I imagine might\n> be useful to certain end users.\n\nDisappointing, though I understand why such a new flag was needed. Do we\nreally care about whether or not the branch exists so long as we are\ndetached from it, though?\n\nThanks,\nTaylor\n"},{"id":"466064","messageId":"Y17ASoeq/truAQEs@nand.local","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/8] clone, submodule update: check out submodule branches","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-10-30T18:19:54Z","receivedAt":"2022-10-30T18:21:03Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 28, 2022 at 08:14:48PM +0000, Glen Choo via GitGitGadget wrote:\n> This version has relatively few changes, and should address all of\n> Jonathan's comments (thanks!).\n\nUpdated, thanks. I haven't read the topic closely, but am curious\nwhether other reviewers are happy with this round so far.\n\nThanks,\nTaylor\n"},{"id":"466102","messageId":"kl6l5yfzdirj.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58375","inReplyTo":"Y16/AMeVYgERJuj6@nand.local","subject":"Re: [PATCH v3 1/8] clone: teach --detach option","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-10-31T17:07:12Z","receivedAt":"2022-10-31T17:07:20Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n> On Fri, Oct 28, 2022 at 03:55:25PM -0700, Glen Choo wrote:\n>> So a better way forward is to add the new flag, which I imagine might\n>> be useful to certain end users.\n>\n> Disappointing, though I understand why such a new flag was needed. Do we\n> really care about whether or not the branch exists so long as we are\n> detached from it, though?\n\nYes.\n\n- With submodule branching, the \"main\" branch should correspond to the\n  gitlink of the superproject's \"main\" branch. So when we clone, we\n  can't _already_ have a \"main\" branch coming from the submodule's\n  remote.\n- Without submodule branching, submodules are always in detached HEAD\n  (e.g. when updating the worktree recursively) and no submodule\n  recursing functions create branches, _except_ \"git clone\n  --recurse-submodules\" (which as we've seen, may create the branch\n  corresponding to the submodule's remote). This just looks like an\n  oversight IMO, which is why I noted that even without branching, \"git\n  clone --recurse-submodules\" should probably also use \"--detach\" [1].\n- Outside of submodules, I can imagine there's at least one person who's\n  performed a clone and then \"git branch -D master\" (maybe followed by\n  \"git checkout -b main\"), and \"git clone --detach\" lets them skip the\n  branch deletion.\n\n[1] https://lore.kernel.org/git/5a24d7e9255de407e343ce8bd60edb63293505bb.1666988096.git.gitgitgadget@gmail.com\n\n>\n> Thanks,\n> Taylor\n"},{"id":"466819","messageId":"6b17b06e-8217-ce56-0733-5501d81fe433@gmail.com","threadId":"58375","inReplyTo":"432bc7cb3a42cf39d0033701c2cc677c9109b3dd.1666988096.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/8] clone: teach --detach option","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2022-11-08T13:32:07Z","receivedAt":"2022-11-08T13:32:15Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Glen,\n\nLe 2022-10-28 à 16:14, Glen Choo via GitGitGadget a écrit :\n> From: Glen Choo <chooglen@google.com>\n> \n> Teach \"git clone\" the \"--detach\" option, which leaves the cloned repo in\n> detached HEAD (like \"git checkout --detach\"). In addition, if the clone\n> is not bare, do not create the local branch pointed to by the remote's\n> HEAD symref (bare clones always copy all remote branches directly to\n> local branches, so the branch is still created in the bare case).\n> \n> This is especially useful in the \"submodule.propagateBranches\" workflow,\n> where local submodule branches are named after the superproject's\n> branches, so it makes no sense to create a local branch named after the\n> submodule's remote's branch.\n> \n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  Documentation/git-clone.txt |  8 +++++++-\n>  builtin/clone.c             | 12 +++++++++---\n>  t/t5601-clone.sh            | 22 ++++++++++++++++++++++\n>  3 files changed, 38 insertions(+), 4 deletions(-)\n> \n> diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\n> index d6434d262d6..6a4e5d31b46 100644\n> --- a/Documentation/git-clone.txt\n> +++ b/Documentation/git-clone.txt\n> @@ -16,7 +16,7 @@ SYNOPSIS\n>  \t  [--depth <depth>] [--[no-]single-branch] [--no-tags]\n>  \t  [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]\n>  \t  [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]\n> -\t  [--filter=<filter> [--also-filter-submodules]] [--] <repository>\n> +\t  [--filter=<filter> [--also-filter-submodules] [--detach]] [--] <repository>\n>  \t  [<directory>]\n>  \n>  DESCRIPTION\n> @@ -210,6 +210,12 @@ objects from the source repository into a pack in the cloned repository.\n>  \t`--branch` can also take tags and detaches the HEAD at that commit\n>  \tin the resulting repository.\n>  \n> +--detach::\n> +\tIf the cloned repository's HEAD points to a branch, point the newly\n> +\tcreated HEAD to the branch's commit instead of the branch itself.\n> +\tAdditionally, in a non-bare repository, the corresponding local branch\n> +\twill not be created.\n> +\n\n\n\"point the newly created HEAD to the branch's tip commit\"\nwould be slightly clearer, I think.\n"},{"id":"466821","messageId":"e6949ea0-8053-3487-81bb-6fe50fd34814@gmail.com","threadId":"58375","inReplyTo":"7cdd6c4184da2d3109498589167f10ecf972edc9.1666988096.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 8/8] clone, submodule update: create and check out branches","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2022-11-08T13:53:17Z","receivedAt":"2022-11-08T13:53:50Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Glen,\n\nLe 2022-10-28 à 16:14, Glen Choo via GitGitGadget a écrit :\n> From: Glen Choo <chooglen@google.com>\n> \n> Teach \"git submodule update\" to:\n> \n> - create the branch with the same name as the current superproject\n>   branch when cloning a submodule\n> - check out that branch (instead of the commit OID) when updating\n>   the submodule worktree\n> \n> when submodule branching is enabled (submodule.propagateBranches = true)\n> on the superproject and a branch is checked out. \"git clone\n> --recurse-submodules\" also learns this trick because it is implemented\n> with \"git submodule update --recursive\".\n> \n> This approach of checking out the branch will not result in a dirty\n> worktree for freshly cloned submodules because we can ensure that the\n> submodule branch points to the superproject gitlink. In other cases, it\n> does not work as well, but we can handle them incrementally:\n> \n> - \"git pull --recurse-submodules\" merges the superproject tree,\n>   (changing the gitlink without updating the submodule branches), and\n>   runs \"git submodule update\" to update the worktrees, so it is almost\n>   guaranteed to result in a dirty worktree.\n\nOK, here you mean that 'git -c submodule.propagateBranches=true submodule update'\nwould just checkout the submodule branch (or do nothing if it's already checked out),\nbut that branch would most likely not be up to date with the newly updated gitlink\nin the superproject, resulting in a dirty worktree.\n\n> \n>   The implementation of \"git pull --recurse-submodules\" is likely to\n>   change drastically as submodule.propagateBranches work progresses\n>   (e.g. \"git merge\" learns to recurse in to submodules), and we may be\n>   able to replace the \"git submodule update\" invocation, or teach it new\n>   tricks that make the update behave well.\n> \n> - The user might make changes to the submodule branch without committing\n>   them back to superproject. This is primarily affects \"git checkout\n>   --recurse-submodules\", since that is the primary way of switching away\n>   from a branch and leaving behind WIP (as opposed to \"git submodule\n>   update\", which is run post-checkout).\n> \n>   In a future series, \"git checkout --recurse-submodules\" will learn to\n>   consider submodule branches. We can introduce appropriate guardrails\n>   then, e.g. requiring that the superproject working tree is not dirty\n>   before switching away.\n> \n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  builtin/submodule--helper.c |  37 ++++++++-\n>  t/t5617-clone-submodules.sh |  38 +++++++++\n>  t/t7406-submodule-update.sh | 156 ++++++++++++++++++++++++++++++++++++\n>  3 files changed, 227 insertions(+), 4 deletions(-)\n> \n\n--8<--\n\n> diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\n> index f094e3d7f36..b749d35f784 100755\n> --- a/t/t7406-submodule-update.sh\n> +++ b/t/t7406-submodule-update.sh\n\n--8<--\n\n> +# Test the behavior of an already-cloned submodule.\n> +# NEEDSWORK When updating with branches, we always use the branch instead of the\n> +# gitlink's OID. This results in some imperfect behavior:\n> +#\n> +# - If the gitlink's OID disagrees with the branch OID, updating with branches\n> +#   may result in a dirty worktree\n> +# - If the branch does not exist, the update fails.\n> +#\n> +# We will reevaluate when \"git checkout --recurse-submodules\" supports branches\n> +# For now, just test for this imperfect behavior.\n\nOK. Maybe it would be a good idea to explicitely flag which tests below have\nan imperfect behaviour ? This way it's easier to spot where the NEEDSWORK applies...\n\n--8<--\n\nThis following test show the imperfect behaviour:\n\n> +test_expect_success 'branches - correct branch checked out, OIDs disagree' '\n> +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n> +\tcp -r branch-super-clean branch-super-cloned &&\n> +\n> +\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n> +\tgit -C branch-super-cloned checkout new-branch &&\n> +\tgit -C branch-super-cloned/sub1 checkout new-branch &&\n> +\ttest_commit -C branch-super-cloned/sub1 new-commit &&\n> +\tgit -C branch-super-cloned submodule update &&\n> +\n> +\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n> +\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n> +\ttest_clean_submodule ! branch-super-cloned sub1\n> +'\n\n--8<--\n\nThis one also:\n\n> +\n> +test_expect_success 'branches - other branch checked out, correct branch exists, OIDs disagree' '\n> +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n> +\tcp -r branch-super-clean branch-super-cloned &&\n> +\n> +\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n> +\tgit -C branch-super-cloned checkout new-branch &&\n> +\tgit -C branch-super-cloned/sub1 checkout new-branch &&\n> +\ttest_commit -C branch-super-cloned/sub1 new-commit &&\n> +\tgit -C branch-super-cloned/sub1 checkout main &&\n> +\tgit -C branch-super-cloned submodule update &&\n> +\n> +\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n> +\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n> +\ttest_clean_submodule ! branch-super-cloned sub1\n> +'\n> +\n\nThis one I'm not sure about: with this series 'git submodule update' does not create\nsubmodule branches at other times than at clone time, so I think this behviour would not change.\nI think this one is really an edge case where the user mixes submodule branching on/off.\n\n> +test_expect_success 'branches - other branch checked out, correct branch does not exist' '\n> +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n> +\tcp -r branch-super-clean branch-super-cloned &&\n> +\n> +\tgit -C branch-super-cloned branch new-branch &&\n> +\tgit -C branch-super-cloned checkout new-branch &&\n> +\ttest_must_fail git -C branch-super-cloned submodule update\n> +'\n> +\n>  test_done\n"},{"id":"466836","messageId":"b9e7e6b7-43f8-3b80-79ba-3ffc53bec7f8@gmail.com","threadId":"58375","inReplyTo":"pull.1321.v3.git.git.1666988096.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/8] clone, submodule update: check out submodule branches","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2022-11-08T14:23:35Z","receivedAt":"2022-11-08T14:25:27Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Glen,\n\nLe 2022-10-28 à 16:14, Glen Choo via GitGitGadget a écrit :\n> This version has relatively few changes, and should address all of\n> Jonathan's comments (thanks!).\n> \n\nI was not able to take a look before now, but I think the suggestions by Jonathan\non v2 make a lot of sense, especially adding more tests in the last patch.\nThanks for these additional tests.\n\nI have a few comments/questions on the overall design, which I'll write up \nat the end of this reply since they are more general.\n> = Description\n> \n> This series teaches \"git clone --recurse-submodules\" and \"git submodule\n> update\" to understand \"submodule.propagateBranches\" (see Further Reading for\n> context), i.e. if the superproject has a branch checked out and a submodule\n> is cloned, the submodule will have the same branch checked out.\n> \n> To do this, \"git submodule update\" checks if \"submodule.propagateBranches\"\n> is true. If so, and if the superproject has the branch 'topic' checked out,\n> then:\n> \n>  * Submodules are cloned without their upstream branches\n>  * The 'topic' branch is created in the submodule\n>  * The submodule is updated via \"git checkout topic\" instead of checking out\n>    the gitlink's OID.\n> \n\nCurrently, the description of submodule.propagateBranches is:\n\n    [EXPERIMENTAL] A boolean that enables branching support when \n    using --recurse-submodules or submodule.recurse=true. Enabling this \n    will allow certain commands to accept --recurse-submodules and certain \n    commands that already accept --recurse-submodules will now consider branches. \n    Defaults to false.\n\nI think with this series that description must be tweaked, because \"git submodule update\"\ndoes not qualify as a command that \"now accepts --recurse-submodules\", neither does\nit \"already accept --recurse-submodules\" but now changes behaviour to consider branches.\n\nIt does change behaviour to \"now consider branches\", but never had anything to do with\n\"--recurse-submodules\".\n\n--8<--\n\n> \n> = Future work\n> \n>  * Patch 5, which refactors resolve_gitlink_ref(), notes that a better\n>    interface would be to return the refname instead of using an \"out\"\n>    parameter, but we use an \"out\" parameter so that any new callers trying\n>    to use the old function signature will get stopped by the compiler. The\n>    refactor can be finished at a later time.\n> \n>  * Patch 5 uses the name \"target\" when we are talking about what a symref\n>    points to, instead of \"referent\" like the other functions. \"target\" is\n>    the better choice, since \"referent\" could also apply to non-symbolic\n>    refs, but that cleanup is quite big.\n> \n>  * Patch 8 notes that for already cloned submodules, the branch may not\n>    point to the same OID as the superproject gitlink, and it may not even\n>    exist. This will be addressed in a more comprehensive manner when we add\n>    support for checking out branches with \"git checkout\n>    --recurse-submodules\".\n\n\nA few points I thought about while reading this version:\n\n1. There is always a possibility that the branch name in the superproject already exists\nin the submodule remote, but is a completely different topic (think of a branch named \"refactor\"\nfor example). With this series (and propagateBranches=true), this would mean that \nthe initial submodule clone would create a local branch \"refactor\" that points to the gitlink\nin the superproject, and a remote-tracking branch \"origin/refactor\" that points to the unrelated\nsubmodule topic branch from the submodule remote. This could be confusing... but I don't\nreally know what Git could do about it !\n\nIn patch 3/8 'git branch' is used to create the submodule branch from an oid, and so \nit does not track any branch, and is not affected by 'branch.autoSetupMerge' as far as I \ncould test. But maybe this should be explicitely mentioned somewhere? \n\n2. The new \"git submodule update\" behaviour seems to only make sense with \"--checkout\", \nwhich is the default \"git submodue update\" mode. But what if one uses \"git submodule\nupdate --merge\", or \"--rebase\", or has submodule.<name>.update set to a custom command\nor \"none\" ? Is the idea that these modes are incompatible with submodule branching ?\nI can understand that this gets really complex and might change when 'git merge' and \n'git rebase' themselves are taught to update submodule worktrees (and probably also submodule\nbranches from what you refer to as future work), but in any case I think we should at \nleast test the new code when these options are used (if only to error out outright as\nincompatible)...\n\nThanks and cheers,\n\nPhilippe.\n"},{"id":"466896","messageId":"kl6lbkph41p4.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58375","inReplyTo":"b9e7e6b7-43f8-3b80-79ba-3ffc53bec7f8@gmail.com","subject":"Re: [PATCH v3 0/8] clone, submodule update: check out submodule branches","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-11-08T20:43:19Z","receivedAt":"2022-11-08T20:44:26Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Hi Philippe.\n\nThanks for spending time on the high level ideas (and not just the\ncode); it really helps to keep this logical and consistent.\n\nPhilippe Blain <levraiphilippeblain@gmail.com> writes:\n\n>> = Description\n>> \n>> This series teaches \"git clone --recurse-submodules\" and \"git submodule\n>> update\" to understand \"submodule.propagateBranches\" (see Further Reading for\n>> context), i.e. if the superproject has a branch checked out and a submodule\n>> is cloned, the submodule will have the same branch checked out.\n>> \n>> To do this, \"git submodule update\" checks if \"submodule.propagateBranches\"\n>> is true. If so, and if the superproject has the branch 'topic' checked out,\n>> then:\n>> \n>>  * Submodules are cloned without their upstream branches\n>>  * The 'topic' branch is created in the submodule\n>>  * The submodule is updated via \"git checkout topic\" instead of checking out\n>>    the gitlink's OID.\n>> \n>\n> Currently, the description of submodule.propagateBranches is:\n>\n>     [EXPERIMENTAL] A boolean that enables branching support when \n>     using --recurse-submodules or submodule.recurse=true. Enabling this \n>     will allow certain commands to accept --recurse-submodules and certain \n>     commands that already accept --recurse-submodules will now consider branches. \n>     Defaults to false.\n>\n> I think with this series that description must be tweaked, because \"git submodule update\"\n> does not qualify as a command that \"now accepts --recurse-submodules\", neither does\n> it \"already accept --recurse-submodules\" but now changes behaviour to consider branches.\n>\n> It does change behaviour to \"now consider branches\", but never had anything to do with\n> \"--recurse-submodules\".\n\nYes that's a good point, thanks.\n\n>\n> --8<--\n>\n>> \n>> = Future work\n>> \n>>  * Patch 5, which refactors resolve_gitlink_ref(), notes that a better\n>>    interface would be to return the refname instead of using an \"out\"\n>>    parameter, but we use an \"out\" parameter so that any new callers trying\n>>    to use the old function signature will get stopped by the compiler. The\n>>    refactor can be finished at a later time.\n>> \n>>  * Patch 5 uses the name \"target\" when we are talking about what a symref\n>>    points to, instead of \"referent\" like the other functions. \"target\" is\n>>    the better choice, since \"referent\" could also apply to non-symbolic\n>>    refs, but that cleanup is quite big.\n>> \n>>  * Patch 8 notes that for already cloned submodules, the branch may not\n>>    point to the same OID as the superproject gitlink, and it may not even\n>>    exist. This will be addressed in a more comprehensive manner when we add\n>>    support for checking out branches with \"git checkout\n>>    --recurse-submodules\".\n>\n>\n> A few points I thought about while reading this version:\n\nThis is a good reminder for me to check in that doc I promised that\ndescribes how branches in submodules would work. This is great feedback\nfor that work, though :)\n\n>\n> 1. There is always a possibility that the branch name in the superproject already exists\n> in the submodule remote, but is a completely different topic (think of a branch named \"refactor\"\n> for example). With this series (and propagateBranches=true), this would mean that \n> the initial submodule clone would create a local branch \"refactor\" that points to the gitlink\n> in the superproject, and a remote-tracking branch \"origin/refactor\" that points to the unrelated\n> submodule topic branch from the submodule remote. This could be confusing... but I don't\n> really know what Git could do about it !\n\nYes.. I think submodule.propagateBranches requires a change in mental\nmodel where submodule branch names have nothing to do with the\nsubmodule's remote's branch names. It _might_ not be so confusing once\nusers have a grasp on that, but I recognize that that's quite optimistic\nof me.\n\nThe biggest sources of confusion I see comes from remote-tracking,\neither implicitly (e.g. \"git checkout topic\" when \"topic\" doesn't exist,\nbut \"origin/topic\" does) or manually (e.g. \"git branch\n--set-upstream-to\"), because they'll see similarly-named remote and\nlocal branches that have nothing to do with each other.\n\nI can see some ways to address this, though they're not perfect:\n\n- Remove the need for users to set up manual remote-tracking and disable\n  implicit remote-tracking (possibly by using .gitmodules). \n- Block or warn on possibly confusing operations (e.g. don't allow users\n  to create branches in the submodule directly)\n\n> In patch 3/8 'git branch' is used to create the submodule branch from an oid, and so \n> it does not track any branch, and is not affected by 'branch.autoSetupMerge' as far as I \n> could test. But maybe this should be explicitely mentioned somewhere? \n\nI could mention it. For now, I don't see any reasonable way to respect\nbranch.autoSetupMerge, since there's no good way to map from 'submodule\nremote branch' -> 'local submodule branch that's part of a superproject\nbranch'.\n\nBetween this and the previous section, perhaps we should think of\nsubmodule branches as being fundamentally different from \"regular\"\nbranches..\n\n> 2. The new \"git submodule update\" behaviour seems to only make sense with \"--checkout\", \n> which is the default \"git submodue update\" mode. But what if one uses \"git submodule\n> update --merge\", or \"--rebase\", or has submodule.<name>.update set to a custom command\n> or \"none\" ? Is the idea that these modes are incompatible with submodule branching ?\n> I can understand that this gets really complex and might change when 'git merge' and \n> 'git rebase' themselves are taught to update submodule worktrees (and probably also submodule\n> branches from what you refer to as future work), but in any case I think we should at \n> least test the new code when these options are used (if only to error out outright as\n> incompatible)...\n\nAh, I should have been more explicit. Yes, I intend for the other modes\nto be incompatible, so this should error out outright.\n\n\"git submodule update\" is overdue for some refactoring IMO (at least for\ninternal use cases). It is quite badly overloaded - it peforms 3\ndifferent actions (clone missing submodules, fetch missing commits,\n\"update\" worktree) and supports 4 different update strategies. In this\nseries, I was concerned mainly with \"git clone --recurse-submodules\",\nand teaching \"git submodule update --checkout\" turned out to be a nice\nside-effect, but because the \"submodule update\" API surface is so large,\nwe end up with this extra noise of how this new setting will interact\nwith the rest of API.\n\nOne goal of the Submodule UX (branches aside) is to remove the need for\nmanual submodule lifecycle management via \"git submodule\". For \"git\nsubmodule update\", this would be:\n\n- Teach fetch to clone missing submodules\n- Teach fetch to fetch missing commits (I've done this already with my\n  work on fetching in out-of-tree submodules)\n- Teach checkout/reset to update working trees as we expect\n\nI'm still not sure what to think of the merge/rebase/custom command\nupdate strategies since they aren't simply \"updating\" the working tree.\nMy suspicion is that merge/rebase are more like partial implementations\nof merge/rebase --recurse-submodules. The custom command is perhaps more\nofa way for users to orchestrate nonnative Git workflows on top of\nsubmodules, so I don't feel bad about not supporting that at least.\n\n> Thanks and cheers,\n>\n> Philippe.\n"},{"id":"467290","messageId":"20221114234555.19060-1-jonathantanmy@google.com","threadId":"58375","inReplyTo":"4e402b67145c6e33c13826f1daf1883a66cd9cd4.1666988096.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 7/8] submodule--helper: remove update_data.suboid","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-11-14T23:45:54Z","receivedAt":"2022-11-14T23:46:02Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Rearranging the diffs...\n\n\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> @@ -2532,10 +2532,8 @@ static int update_submodule(struct update_data *update_data)\n>  \tif (ret)\n>  \t\treturn ret;\n>  \n> -\tif (update_data->just_cloned)\n> -\t\toidcpy(&update_data->suboid, null_oid());\n> -\telse if (resolve_gitlink_ref(update_data->sm_path, \"HEAD\",\n> -\t\t\t\t     &update_data->suboid, NULL))\n> +\tif (!update_data->just_cloned &&\n> +\t    resolve_gitlink_ref(update_data->sm_path, \"HEAD\", &suboid, NULL))\n>  \t\treturn die_message(_(\"Unable to find current revision in submodule path '%s'\"),\n>  \t\t\t\t   update_data->displaypath);\n>  \n\nHere, we only set suboid if !update_data->just_cloned...\n\n> @@ -2570,7 +2568,8 @@ static int update_submodule(struct update_data *update_data)\n>  \t\tfree(remote_ref);\n>  \t}\n>  \n> -\tsubmodule_up_to_date = oideq(&update_data->oid, &update_data->suboid);\n> +\tsubmodule_up_to_date = !update_data->just_cloned &&\n> +\t\toideq(&update_data->oid, &suboid);\n>  \tif (!submodule_up_to_date || update_data->force) {\n>  \t\tret = run_update_procedure(update_data);\n>  \t\tif (ret)\n\n...and here, we read suboid if !update_data->just_cloned.\n\n> @@ -2523,6 +2522,7 @@ static int update_submodule(struct update_data *update_data)\n>  {\n>  \tint submodule_up_to_date;\n>  \tint ret;\n> +\tstruct object_id suboid;\n\nAnd here we declare it with no initializer. This is safe for now since we only\nread it when !update_data->just_cloned, which is also the condition for setting\nit, but may be error-prone in the future. Probably best to initialize it to\nsomething.\n \nOther than that everything looks good up to and including this patch. This is\ndefinitely an improvement over using a null OID to signal something, so thanks.\n"},{"id":"467325","messageId":"20221115181524.164472-1-jonathantanmy@google.com","threadId":"58375","inReplyTo":"7cdd6c4184da2d3109498589167f10ecf972edc9.1666988096.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 8/8] clone, submodule update: create and check out branches","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-11-15T18:15:24Z","receivedAt":"2022-11-15T18:15:32Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> +# Test the behavior of an already-cloned submodule.\n> +# NEEDSWORK When updating with branches, we always use the branch instead of the\n> +# gitlink's OID. This results in some imperfect behavior:\n> +#\n> +# - If the gitlink's OID disagrees with the branch OID, updating with branches\n> +#   may result in a dirty worktree\n> +# - If the branch does not exist, the update fails.\n> +#\n> +# We will reevaluate when \"git checkout --recurse-submodules\" supports branches\n> +# For now, just test for this imperfect behavior.\n\nI think the rationale for this behavior is as follows:\n\nWe want a world in which submodules have branches and Git commands use them\nwherever possible. There are a few options for \"git submodule update\" when the\nsuperproject has a branch checked out:\n\n1. Checkout the branch, ignoring OID (as in this patch).\n2. Checkout the branch, erroring out if the OID is wrong.\n3. 1 + creating the branch if it does not exist.\n4. 2 + creating the branch if it does not exist.\n5. Always forcibly create the branch at the gitlink's OID and then checking\n   it out.\n\nAt this point in the discussion, for a low-level command like \"git submodule\nupdate\", doing as little as possible makes sense to me, which is 1.\n\nBut since we do not automatically create the branch if it does not exist, this\nmeans that we have to do it when we clone the submodule. Our options are:\n\nA. Create only the branch that is checked out in the superproject (as in this\n   patch).\nB. Create all branches that are present in the superproject.\nC. Go back on our previous decision, switching to 3.\n\nMy instinct is that we want to maintain, as much as possible, the invariant\nthat for each branch in the superproject, if the branch tip has a gitlink\npointing to a submodule, that submodule has a branch of the same name. And I\nthink that this invariant can only be maintained by \"git submodule update\" if\nwe use B or C.\n\n> +test_expect_success 'branches - other branch checked out, correct branch exists, OIDs disagree' '\n> +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n> +\tcp -r branch-super-clean branch-super-cloned &&\n> +\n> +\tgit -C branch-super-cloned branch --recurse-submodules new-branch &&\n> +\tgit -C branch-super-cloned checkout new-branch &&\n> +\tgit -C branch-super-cloned/sub1 checkout new-branch &&\n> +\ttest_commit -C branch-super-cloned/sub1 new-commit &&\n> +\tgit -C branch-super-cloned/sub1 checkout main &&\n> +\tgit -C branch-super-cloned submodule update &&\n> +\n> +\tHEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&\n> +\ttest $HEAD_BRANCH1 = \"refs/heads/new-branch\" &&\n> +\ttest_clean_submodule ! branch-super-cloned sub1\n> +'\n> +\n> +test_expect_success 'branches - other branch checked out, correct branch does not exist' '\n> +\ttest_when_finished \"rm -fr branch-super-cloned\" &&\n> +\tcp -r branch-super-clean branch-super-cloned &&\n> +\n> +\tgit -C branch-super-cloned branch new-branch &&\n> +\tgit -C branch-super-cloned checkout new-branch &&\n> +\ttest_must_fail git -C branch-super-cloned submodule update\n\nCan we also check what error message is being printed?\n \n"},{"id":"467797","messageId":"kl6lmt8iesmu.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"58375","inReplyTo":"20221115181524.164472-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 8/8] clone, submodule update: create and check out branches","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-11-22T18:44:25Z","receivedAt":"2022-11-22T18:44:31Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Thanks for the thoughtful response, Jonathan :)\n\nJonathan Tan <jonathantanmy@google.com> writes:\n\n> \"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> +# Test the behavior of an already-cloned submodule.\n>> +# NEEDSWORK When updating with branches, we always use the branch instead of the\n>> +# gitlink's OID. This results in some imperfect behavior:\n>> +#\n>> +# - If the gitlink's OID disagrees with the branch OID, updating with branches\n>> +#   may result in a dirty worktree\n>> +# - If the branch does not exist, the update fails.\n>> +#\n>> +# We will reevaluate when \"git checkout --recurse-submodules\" supports branches\n>> +# For now, just test for this imperfect behavior.\n>\n> I think the rationale for this behavior is as follows:\n>\n> We want a world in which submodules have branches and Git commands use them\n> wherever possible. There are a few options for \"git submodule update\" when the\n> superproject has a branch checked out:\n>\n> 1. Checkout the branch, ignoring OID (as in this patch).\n> 2. Checkout the branch, erroring out if the OID is wrong.\n> 3. 1 + creating the branch if it does not exist.\n> 4. 2 + creating the branch if it does not exist.\n> 5. Always forcibly create the branch at the gitlink's OID and then checking\n>    it out.\n>\n> At this point in the discussion, for a low-level command like \"git submodule\n> update\", doing as little as possible makes sense to me, which is 1.\n>\n> But since we do not automatically create the branch if it does not exist, this\n> means that we have to do it when we clone the submodule. Our options are:\n>\n> A. Create only the branch that is checked out in the superproject (as in this\n>    patch).\n> B. Create all branches that are present in the superproject.\n> C. Go back on our previous decision, switching to 3.\n>\n> My instinct is that we want to maintain, as much as possible, the invariant\n> that for each branch in the superproject, if the branch tip has a gitlink\n> pointing to a submodule, that submodule has a branch of the same name. And I\n> think that this invariant can only be maintained by \"git submodule update\" if\n> we use B or C.\n\nI think C is good to have in this series, though for slightly different\nreasons.\n\nI agree that the invariant should be preserved when we check out\nbranches both in the initial clone and in subsequent checkouts. However,\nI don't think that we necessarily need to have all superproject branches\nafter the initial clone. Even if the submodule only has a single\nsuperproject branch, that's enough to have an ephemeral clone for\nwriting small changes. We could defer the \"all superproject branches\"\nproblem til after we worry about subsequent checkouts (i.e. \"git\ncheckout\" with branches).\n\nWe can handle \"initial clone\" and \"subsequent checkout\" as smaller, more\ndigestible series as long as the work for \"initial clone\" doesn't get in\nthe way of \"subsequent checkout\". My plan (as of v2) was:\n\n- For the intial clone, create only the checked out superproject branch\n  at clone time and check it out (aka A)\n- For subsequent checkouts, check out the superproject branch, creating\n  it if it does not exist (aka C)\n\nBut it doesn't make sense to mix both A _and_ C, since C would already\ngive us the same result as A, so it probably makes sense to go straight\nto C in this series (i.e. only for the initial clone, not subsequent\ncheckouts). I'll do that in v3.\n\nI prefer C in the long run, since both A and B require that the list of\nsubmodule branches never get out of sync with the superproject, which is\nhard to enforce, e.g.:\n\n- The user could create a branch in the superproject without recursing\n  in to submodules.\n- The user could delete the branch in the submodule.\n- (Worst yet) The process that creates branches in the submodule _after_\n  creating the branch in the superproject could exit unexpectedly (e.g.\n  SIGINT). There is no atomic way to create branches in both repos.\n\nWe could create a command that would repair broken branch states (\"git\nsubmodule repair\"?), but C can self-repair, which avoids this problem\nentirely.\n"},{"id":"467842","messageId":"20221123013319.1597025-1-jonathantanmy@google.com","threadId":"58375","inReplyTo":"kl6lmt8iesmu.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v3 8/8] clone, submodule update: create and check out branches","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2022-11-23T01:33:19Z","receivedAt":"2022-11-23T01:33:30Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> > 1. Checkout the branch, ignoring OID (as in this patch).\n> > 2. Checkout the branch, erroring out if the OID is wrong.\n> > 3. 1 + creating the branch if it does not exist.\n> > 4. 2 + creating the branch if it does not exist.\n> > 5. Always forcibly create the branch at the gitlink's OID and then checking\n> >    it out.\n\n[snip]\n\n> > A. Create only the branch that is checked out in the superproject (as in this\n> >    patch).\n> > B. Create all branches that are present in the superproject.\n> > C. Go back on our previous decision, switching to 3.\n\n[snip]\n\n> But it doesn't make sense to mix both A _and_ C, since C would already\n> give us the same result as A, so it probably makes sense to go straight\n> to C in this series (i.e. only for the initial clone, not subsequent\n> checkouts). I'll do that in v3.\n> \n> I prefer C in the long run, since both A and B require that the list of\n> submodule branches never get out of sync with the superproject, which is\n> hard to enforce, e.g.:\n \nI discussed this with Glen in-office and Glen pointed out that A is actually\nnot necessarily redundant with respect to C, since a \"git submodule add\" may\nclone a submodule, but it would not run \"git submodule update\" (so 3 + C would\nmean that no branch is created in the submodule, which is not what we want). So\nwe still need A.\n\nAs for 1 vs. 3, we will still need 3 in the future for the reasons I described\nin my previous e-mail, but I think that that can be done incrementally. My\nconcern is to avoid doing something in a patch set that we will later need to\nundo; I think that we are indeed avoiding it here (we're doing A but we will\nstill need it in the future, so there is no undoing of A needed).\n\nSo overall, after this discussion, this patch set looks good to me, except for\nthe minor points that I have commented on in my previous emails.\n"},{"id":"467846","messageId":"xmqqbkoynwuv.fsf@gitster.g","threadId":"58375","inReplyTo":"20221123013319.1597025-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 8/8] clone, submodule update: create and check out branches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-11-23T04:00:40Z","receivedAt":"2022-11-23T04:00:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> ... My\n> concern is to avoid doing something in a patch set that we will later need to\n> undo; I think that we are indeed avoiding it here (we're doing A but we will\n> still need it in the future, so there is no undoing of A needed).\n>\n> So overall, after this discussion, this patch set looks good to me, except for\n> the minor points that I have commented on in my previous emails.\n\nThanks for a summary.\n"}]}