{"thread":{"id":"49444","subject":"[PATCH] FYI / RFC: submodules: introduce repo-like workflow","startedAt":"2018-09-27T22:16:43Z","lastAt":"2019-01-14T22:35:05Z","messageCount":6,"participants":["Stefan Beller","Jonathan Nieder","Junio C Hamano","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"359150","messageId":"20180927221603.148025-1-sbeller@google.com","threadId":"49444","inReplyTo":null,"subject":"[PATCH] FYI / RFC: submodules: introduce repo-like workflow","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-09-27T22:16:03Z","receivedAt":"2018-09-27T22:16:43Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Internally we have rolled out this as an experiment for\n\"submodules replacing the repo tool[1]\". The repo tool is described as:\n\n    Repo unifies Git repositories when necessary, performs uploads to the\n    Gerrit revision control system, and automates parts of the Android\n    development workflow. Repo is not meant to replace Git, only to make\n    it easier to work with Git in the context of Android. The repo command\n    is an executable Python script that you can put anywhere in your path.\n\n    In working with the Android source files, you use Repo for\n    across-network operations. For example, with a single Repo command you\n    can download files from multiple repositories into your local working\n    directory.\n\n    In most situations, you can use Git instead of Repo, or mix Repo and\n    Git commands to form complex commands.\n\n[1] https://source.android.com/setup/develop/\n\nSubmodules can also be understood as unifying Git repositories, even more,\nin the superproject the submodules have relationships between their\nversions, which the repo tool only provides in releases.\n\nThe repo tool does not provide these relationships between versions, but\nall the repositories (in case of Android think of ~1000 git repositories)\nare put in their place without depending on each other.\n\nThis comes with a couple of advantages and disadvantages:\n\n* Many users are familiar with Git, but not submodules. Each repository\n  can be used independently with Git and there is no need to update the\n  superproject or the repo manifest for a change in a repository.\n* It is easy to work with repositories with no version-control-dependencies\n  if there are dependencies in the code. In case of Android the\n  repositories are bound at natural boundaries. For example the linux\n  kernel is one repository, as then upstream work is made easy for this\n  repository. So it is desirable to keep an easy-as-repo workflow.\n* Fetching changes (\"repo sync\") needs to fetch all repositories, as there\n  is no central place that tracks what has changed. In a superproject\n  git fetch can determine which submodules need fetching.  In Androids\n  case the daily change is only in a few repositories (think 10s), so\n  migrating to a superproject would save an order of magnitude in fetch\n  traffic for daily updates of developers.\n* Sometimes when the dependencies are not on a clear repository boundary\n  one would like to have git-bisect available across the different\n  repositories, which repo cannot provide due to its design.\n\nInternally we have the Gerrit as a central point, where the source of\ntruth is found for a given repository.\n\nThis patch adds a new mode to submodule handling, where the superproject\ncontrols the existence of the submodule (just as current submodule\nhandling), but the submodule HEAD is not detached, but following the same\nbranch name as the superproject.\n\nCurrent situation visualized:\n\n  superproject\n  HEAD -> \"<branch name>\" -> OID\n                              |\n  submodule                   v\n  HEAD --------------------> OID\n\nThe OID in the submodule is controlled via the HEAD in the submodule that\nis set accordingly to the gitlink in the superproject. Confusion can arise\nwhen the (detached) HEAD in the submodule doesn't match the superprojects\ngitlink.\n\nThis patch visualized:\n\n  superproject\n  HEAD -> \"<branch name>\" -> OID\n                 |\n  submodule      v\n  HEAD -> \"<branch name>\" -> OID\n\nAs there is a central point of truth in our setup (our Gerrit installation)\nwhich keeps the superproject and the submodule branches in sync, this\nought to look the same for the user; removing the \"detached HEAD\" in the\nsubmodule. git-status will still notice if there is an OID mismatch between\nthe gitlink and the submodules branch, but that is a race condition and\nshould be caught by Gerrit.\n\nThis changes the following commands in the superproject:\n\n  checkout -B/-b create branches in subs, too\n  checkout (-f): update branch in submodule (create if needed) and check\n                 it out; Pass the argument down literally if it is a branch\n                 name (e.g. \"checkout -f master\" will run a\n                            \"checkout -f master\" in the submodule as well)\n  clone: see checkout\n  reset --hard: see checkout\n\nChange-Id: I69b361e5bd9d57226a0976fd36968cf9aeb52ae0\nSigned-off-by: Jonathan Nieder <jrn@google.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n Documentation/git-checkout.txt | 13 +++--\n Makefile                       |  1 +\n branch.c                       | 84 +++++++++++++++++++++++++++++++++\n builtin.h                      |  1 +\n builtin/branch.c               | 35 ++++++++++++++\n builtin/checkout.c             | 76 ++++++++++++++++++++++++------\n builtin/clone.c                | 12 ++++-\n builtin/reset.c                | 41 ++++++++++++++--\n entry.c                        | 43 +++++++++++++----\n git-submodule.sh               | 24 +++++++++-\n git.c                          |  1 +\n submodule-move-head.c          | 81 ++++++++++++++++++++++++++++++++\n submodule-move-head.h          | 22 +++++++++\n submodule.c                    | 14 ++++++\n submodule.h                    |  9 +++-\n t/lib-submodule-update.sh      | 86 +++++++++++++++++++++++++++++++++-\n t/t1013-read-tree-submodule.sh |  1 +\n t/t2013-checkout-submodule.sh  |  4 ++\n t/t7112-reset-submodule.sh     |  5 ++\n unpack-trees.c                 | 20 ++++++--\n unpack-trees.h                 | 10 ++++\n 21 files changed, 538 insertions(+), 45 deletions(-)\n create mode 100644 submodule-move-head.c\n create mode 100644 submodule-move-head.h\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 9db02928c4..19b0c2a272 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -268,13 +268,12 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\n \tworktree.\n \n --[no-]recurse-submodules::\n-\tUsing --recurse-submodules will update the content of all initialized\n-\tsubmodules according to the commit recorded in the superproject. If\n-\tlocal modifications in a submodule would be overwritten the checkout\n-\twill fail unless `-f` is used. If nothing (or --no-recurse-submodules)\n-\tis used, the work trees of submodules will not be updated.\n-\tJust like linkgit:git-submodule[1], this will detach the\n-\tsubmodules HEAD.\n+\tUsing --recurse-submodules will update the content and current\n+\tbranch of all initialized submodules in addition to the\n+\tsuperproject. If local modifications in a submodule would be\n+\toverwritten, the checkout will fail unless `-f` is used. If\n+\tnothing (or --no-recurse-submodules) is used, the work trees of\n+\tsubmodules will not be updated.\n \n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\ndiff --git a/Makefile b/Makefile\nindex 13e1c52478..6824dbcccf 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -975,6 +975,7 @@ LIB_OBJS += streaming.o\n LIB_OBJS += string-list.o\n LIB_OBJS += submodule.o\n LIB_OBJS += submodule-config.o\n+LIB_OBJS += submodule-move-head.o\n LIB_OBJS += sub-process.o\n LIB_OBJS += symlinks.o\n LIB_OBJS += tag.o\ndiff --git a/branch.c b/branch.c\nindex 776f55fc66..e4538f2b36 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -1,9 +1,13 @@\n #include \"git-compat-util.h\"\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"repository.h\"\n+#include \"submodule.h\"\n #include \"branch.h\"\n #include \"refs.h\"\n #include \"refspec.h\"\n+#include \"tree-walk.h\"\n+#include \"run-command.h\"\n #include \"remote.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n@@ -242,6 +246,76 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n+static void create_branch_in_submodule(const char *name, const char *start_name,\n+\t\t   const char *start_ref, const struct object_id *start_oid,\n+\t\t   int force, int reflog, int clobber_head,\n+\t\t   int quiet, enum branch_track track,\n+\t\t   int checking_out_branch,\n+\t\t   const char *sub_path, const struct object_id *entry_oid)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\ttrace_printf(\"create_branch_in_submodule %s\", sub_path);\n+\n+\tprepare_submodule_repo_env(&cp.env_array);\n+\tcp.git_cmd = 1;\n+\tcp.dir = sub_path;\n+\targv_array_push(&cp.args, \"branch--helper\");\n+\targv_array_pushf(&cp.args, \"--name=%s\", name);\n+\tif (checking_out_branch) {\n+\t\targv_array_pushf(&cp.args, \"--start_name=%s\",\n+\t\t\t\t start_ref);\n+\t} else {\n+\t\targv_array_pushf(&cp.args, \"--start_name=%s\",\n+\t\t\t\t oid_to_hex(entry_oid));\n+\t}\n+\targv_array_pushf(&cp.args, \"--force=%d\", force);\n+\targv_array_pushf(&cp.args, \"--reflog=%d\", reflog);\n+\targv_array_pushf(&cp.args, \"--clobber_head=%d\", clobber_head);\n+\targv_array_pushf(&cp.args, \"--quiet=%d\", quiet);\n+\targv_array_pushf(&cp.args, \"--track=%d\", track);\n+\n+\tif (run_command(&cp))\n+\t\tfprintf(stderr, \"process for submodule '%s' failed\", sub_path);\n+\tchild_process_clear(&cp);\n+}\n+\n+static void create_branch_in_submodules(const char *name, const char *start_name,\n+\t\t   const char *start_ref, const struct object_id *start_oid,\n+\t\t   int force, int reflog, int clobber_head,\n+\t\t   int quiet, enum branch_track track, struct strbuf *rec_path)\n+{\n+\n+\tint checking_out_branch = start_ref && starts_with(start_ref, \"refs/heads/\");\n+\tvoid *buf;\n+\tstruct tree_desc tree;\n+\tstruct name_entry entry;\n+\tint rec_path_len = rec_path->len;\n+\n+\tbuf = fill_tree_descriptor(&tree, start_oid);\n+\tif (!buf)\n+\t\tdie(\"could not read %s for checkout\", start_name);\n+\n+\twhile (tree_entry(&tree, &entry)) {\n+\n+\t\tif (rec_path->len > 0)\n+\t\t\tstrbuf_addch(rec_path, '/');\n+\t\tstrbuf_addstr(rec_path, entry.path);\n+\n+\t\ttrace_printf(\"create_branch_in_submodules %s %o\", rec_path->buf, entry.mode);\n+\n+\t\tif (S_ISGITLINK(entry.mode) && is_submodule_active(the_repository, rec_path->buf))\n+\t\t\tcreate_branch_in_submodule(name, start_name, start_ref,\n+\t\t\t\t\t\tstart_oid, force, reflog, clobber_head,\n+\t\t\t\t\t\tquiet, track, checking_out_branch, rec_path->buf, entry.oid);\n+\t\telse if (S_ISDIR(entry.mode)) {\n+\t\t\tcreate_branch_in_submodules(name, start_name, start_ref, entry.oid, force, reflog, clobber_head, quiet, track, rec_path);\n+\t\t}\n+\t\tstrbuf_setlen(rec_path, rec_path_len);\n+\t}\n+\tfree(buf);\n+}\n+\n void create_branch(const char *name, const char *start_name,\n \t\t   int force, int clobber_head_ok, int reflog,\n \t\t   int quiet, enum branch_track track)\n@@ -250,6 +324,7 @@ void create_branch(const char *name, const char *start_name,\n \tstruct object_id oid;\n \tchar *real_ref;\n \tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf sub_path = STRBUF_INIT;\n \tint forcing = 0;\n \tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n@@ -307,6 +382,14 @@ void create_branch(const char *name, const char *start_name,\n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n+\t/*\n+\t * NEEDSWORK: Doesn't handle errors part-way through very well.\n+\t */\n+\ttrace_printf(\"create_branch need to update subs: %d\", should_update_submodules());\n+\tif (behave_google_repo_like() && should_update_submodules())\n+\t\tcreate_branch_in_submodules(name, start_name, real_ref, &oid,\n+\t\t\t\t\t    force, reflog, clobber_head_ok, quiet, track, &sub_path);\n+\n \tif (!dont_change_ref) {\n \t\tstruct ref_transaction *transaction;\n \t\tstruct strbuf err = STRBUF_INIT;\n@@ -333,6 +416,7 @@ void create_branch(const char *name, const char *start_name,\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n \n \tstrbuf_release(&ref);\n+\tstrbuf_release(&sub_path);\n \tfree(real_ref);\n }\n \ndiff --git a/builtin.h b/builtin.h\nindex 962f0489ab..31030f248f 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);\n extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);\n extern int cmd_blame(int argc, const char **argv, const char *prefix);\n extern int cmd_branch(int argc, const char **argv, const char *prefix);\n+extern int cmd_branch_helper(int argc, const char **argv, const char *prefix);\n extern int cmd_bundle(int argc, const char **argv, const char *prefix);\n extern int cmd_cat_file(int argc, const char **argv, const char *prefix);\n extern int cmd_checkout(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex c396c41533..b6db7ec5bf 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -36,6 +36,11 @@ static const char * const builtin_branch_usage[] = {\n \tNULL\n };\n \n+static const char * const builtin_branch_helper_usage[] = {\n+\tN_(\"git branch--helper\"),\n+\tNULL\n+};\n+\n static const char *head;\n static struct object_id head_oid;\n \n@@ -578,6 +583,36 @@ static int edit_branch_description(const char *branch_name)\n \treturn 0;\n }\n \n+int cmd_branch_helper(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *name = NULL, *start_name = NULL;\n+\tint force = 0, reflog = 0, clobber_head = 0, quiet = 0;\n+\tenum branch_track track = BRANCH_TRACK_NEVER;\n+\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"name\", &name, N_(\"\"), N_(\"\")),\n+\t\tOPT_STRING(0, \"start_name\", &start_name, N_(\"\"), N_(\"\")),\n+\n+\t\tOPT_INTEGER(0, \"force\", &force, N_(\"\")),\n+\t\tOPT_INTEGER(0, \"reflog\", &reflog, N_(\"\")),\n+\t\tOPT_INTEGER(0, \"clobber_head\", &clobber_head, N_(\"\")),\n+\t\tOPT_INTEGER(0, \"quiet\", &quiet, N_(\"\")),\n+\n+\t\t/* implicit int -> enum conversion */\n+\t\tOPT_INTEGER(0, \"track\", &track, N_(\"\")),\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     builtin_branch_helper_usage, 0);\n+\tif (argc > 0)\n+\t\tdie (_(\"branchhelper doesn't know about %s\"), argv[0]);\n+\n+\tcreate_branch(name, start_name, force, clobber_head, reflog,\n+\t\t      quiet, track);\n+\treturn 0;\n+}\n+\n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n \tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b30b48767e..054d0ef60e 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -3,6 +3,7 @@\n #include \"checkout.h\"\n #include \"lockfile.h\"\n #include \"parse-options.h\"\n+#include \"repository.h\"\n #include \"refs.h\"\n #include \"object-store.h\"\n #include \"commit.h\"\n@@ -22,6 +23,7 @@\n #include \"ll-merge.h\"\n #include \"resolve-undo.h\"\n #include \"submodule-config.h\"\n+#include \"submodule-move-head.h\"\n #include \"submodule.h\"\n #include \"advice.h\"\n \n@@ -421,11 +423,24 @@ static void describe_detached_head(const char *msg, struct commit *commit)\n \tstrbuf_release(&sb);\n }\n \n-static int reset_tree(struct tree *tree, const struct checkout_opts *o,\n+struct branch_info {\n+\tconst char *name; /* The short name used */\n+\tconst char *path; /* The full name of a real branch */\n+\tstruct commit *commit; /* The named commit */\n+\t/*\n+\t * if not null the branch is detached because it's already\n+\t * checked out in this checkout\n+\t */\n+\tchar *checkout;\n+};\n+\n+static int reset_tree(struct branch_info *b, const struct checkout_opts *o,\n \t\t      int worktree, int *writeout_error)\n {\n \tstruct unpack_trees_options opts;\n+\tstruct submodule_move_head_options move_head_opts;\n \tstruct tree_desc tree_desc;\n+\tstruct tree *tree = get_commit_tree(b->commit);\n \n \tmemset(&opts, 0, sizeof(opts));\n \topts.head_idx = -1;\n@@ -437,6 +452,16 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,\n \topts.verbose_update = o->show_progress;\n \topts.src_index = &the_index;\n \topts.dst_index = &the_index;\n+\n+\tif (behave_google_repo_like()) {\n+\t\topts.move_head = unpack_trees_move_head;\n+\t\tmemset(&move_head_opts, 0, sizeof(move_head_opts));\n+\t\tmove_head_opts.force = 1;\n+\t\tmove_head_opts.new_ref = b->path;\n+\t\tmove_head_opts.target_ref = o->force_detach ? NULL : b->path;\n+\t\topts.unpack_data = &move_head_opts;\n+\t}\n+\n \tparse_tree(tree);\n \tinit_tree_desc(&tree_desc, tree->buffer, tree->size);\n \tswitch (unpack_trees(1, &tree_desc, &opts)) {\n@@ -456,17 +481,6 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,\n \t}\n }\n \n-struct branch_info {\n-\tconst char *name; /* The short name used */\n-\tconst char *path; /* The full name of a real branch */\n-\tstruct commit *commit; /* The named commit */\n-\t/*\n-\t * if not null the branch is detached because it's already\n-\t * checked out in this checkout\n-\t */\n-\tchar *checkout;\n-};\n-\n static void setup_branch_path(struct branch_info *branch)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -584,7 +598,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \n \tresolve_undo_clear();\n \tif (opts->force) {\n-\t\tret = reset_tree(get_commit_tree(new_branch_info->commit),\n+\t\tret = reset_tree(new_branch_info,\n \t\t\t\t opts, 1, writeout_error);\n \t\tif (ret)\n \t\t\treturn ret;\n@@ -592,6 +606,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n \t\tstruct unpack_trees_options topts;\n+\t\tstruct submodule_move_head_options mopts;\n \n \t\tmemset(&topts, 0, sizeof(topts));\n \t\ttopts.head_idx = -1;\n@@ -607,6 +622,16 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\treturn 1;\n \t\t}\n \n+\t\tif (behave_google_repo_like()) {\n+\t\t\ttopts.move_head = unpack_trees_move_head;\n+\t\t\tmemset(&mopts, 0, sizeof(mopts));\n+\t\t\tmopts.old_ref = old_branch_info->path;\n+\t\t\tmopts.new_ref = new_branch_info->path;\n+\t\t\tmopts.target_ref = opts->force_detach\n+\t\t\t\t\t? NULL : new_branch_info->path;\n+\t\t\ttopts.unpack_data = &mopts;\n+\t\t}\n+\n \t\t/* 2-way merge to the new branch */\n \t\ttopts.initial_checkout = is_cache_unborn();\n \t\ttopts.update = 1;\n@@ -672,7 +697,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\to.verbosity = 0;\n \t\t\twork = write_tree_from_memory(&o);\n \n-\t\t\tret = reset_tree(get_commit_tree(new_branch_info->commit),\n+\t\t\tret = reset_tree(new_branch_info,\n \t\t\t\t\t opts, 1,\n \t\t\t\t\t writeout_error);\n \t\t\tif (ret)\n@@ -687,7 +712,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t\t\t  &result);\n \t\t\tif (ret < 0)\n \t\t\t\texit(128);\n-\t\t\tret = reset_tree(get_commit_tree(new_branch_info->commit),\n+\t\t\tret = reset_tree(new_branch_info,\n \t\t\t\t\t opts, 0,\n \t\t\t\t\t writeout_error);\n \t\t\tstrbuf_release(&o.obuf);\n@@ -722,6 +747,18 @@ static void report_tracking(struct branch_info *new_branch_info)\n \tstrbuf_release(&sb);\n }\n \n+static void create_symref_in_submodules(const char *symref, const char *target, const char *logmsg)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < active_nr; i++) {\n+\t\tconst struct cache_entry *ce = active_cache[i];\n+\t\tif (!S_ISGITLINK(ce->ce_mode) || !is_submodule_active(the_repository, ce->name))\n+\t\t\tcontinue;\n+\n+\t\tcreate_symref_in_submodule(ce->name, symref, target, logmsg);\n+\t}\n+}\n+\n static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t   struct branch_info *old_branch_info,\n \t\t\t\t   struct branch_info *new_branch_info)\n@@ -732,6 +769,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\tif (opts->new_orphan_branch) {\n \t\t\tchar *refname;\n \n+\t\t\tif (should_update_submodules())\n+\t\t\t\tdie(\"--orphan --recurse-submodules is not implemented\");\n+\n \t\t\trefname = mkpathdup(\"refs/heads/%s\", opts->new_orphan_branch);\n \t\t\tif (opts->new_branch_log &&\n \t\t\t    !should_autocreate_reflog(refname)) {\n@@ -784,6 +824,12 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n+\t\t/*\n+\t\t * NEEDSWORK: We don't handle attachment on checkout <branch>\n+\t\t * yet.\n+\t\t */\n+\t\tif (opts->new_branch && should_update_submodules() && behave_google_repo_like())\n+\t\t\tcreate_symref_in_submodules(\"HEAD\", new_branch_info->path, msg.buf);\n \t\tif (create_symref(\"HEAD\", new_branch_info->path, msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 15b142d646..d5936ea1c7 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -28,6 +28,7 @@\n #include \"run-command.h\"\n #include \"connected.h\"\n #include \"packfile.h\"\n+#include \"submodule.h\"\n #include \"list-objects-filter-options.h\"\n #include \"object-store.h\"\n \n@@ -738,7 +739,6 @@ static int checkout(int submodule_progress)\n \t\tif (!starts_with(head, \"refs/heads/\"))\n \t\t\tdie(_(\"HEAD not found below refs/heads!\"));\n \t}\n-\tfree(head);\n \n \t/* We need to be in the new work tree for the checkout */\n \tsetup_work_tree();\n@@ -767,8 +767,17 @@ static int checkout(int submodule_progress)\n \t\t\t   oid_to_hex(&oid), \"1\", NULL);\n \n \tif (!err && (option_recurse_submodules.nr > 0)) {\n+\t\tconst char *branch;\n \t\tstruct argv_array args = ARGV_ARRAY_INIT;\n \t\targv_array_pushl(&args, \"submodule\", \"update\", \"--init\", \"--recursive\", NULL);\n+\t\tif (behave_google_repo_like()) {\n+\t\t\tif (!strcmp(head, \"HEAD\"))\n+\t\t\t\t; /* detach HEAD in submodules, too. */\n+\t\t\telse if (skip_prefix(head, \"refs/heads/\", &branch))\n+\t\t\t\targv_array_pushl(&args, \"--checkout-branch\", branch, NULL);\n+\t\t\telse\n+\t\t\t\tBUG(\"HEAD not found below refs/heads!\");\n+\t\t}\n \n \t\tif (option_shallow_submodules == 1)\n \t\t\targv_array_push(&args, \"--depth=1\");\n@@ -786,6 +795,7 @@ static int checkout(int submodule_progress)\n \t\targv_array_clear(&args);\n \t}\n \n+\tfree(head);\n \treturn err;\n }\n \ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 11cd0dcb8c..9cc8c4db52 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -24,6 +24,7 @@\n #include \"cache-tree.h\"\n #include \"submodule.h\"\n #include \"submodule-config.h\"\n+#include \"submodule-move-head.h\"\n \n static const char * const git_reset_usage[] = {\n \tN_(\"git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]\"),\n@@ -42,12 +43,16 @@ static inline int is_merge(void)\n \treturn !access(git_path_merge_head(the_repository), F_OK);\n }\n \n-static int reset_index(const struct object_id *oid, int reset_type, int quiet)\n+static int reset_index(const char *rev, const struct object_id *oid, int reset_type, int quiet)\n {\n-\tint i, nr = 0;\n+\tint i, nr = 0, flags = 0;\n \tstruct tree_desc desc[2];\n \tstruct tree *tree;\n \tstruct unpack_trees_options opts;\n+\tstruct submodule_move_head_options mopts;\n+\tchar *current_branch = NULL;\n+\tstruct object_id discard;\n+\tchar *new_ref = NULL;\n \tint ret = -1;\n \n \tmemset(&opts, 0, sizeof(opts));\n@@ -65,6 +70,31 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)\n \t\tbreak;\n \tcase HARD:\n \t\topts.update = 1;\n+\n+\t\tif (behave_google_repo_like()) {\n+\t\t\t/*\n+\t\t\t * Submodule handling:\n+\t\t\t * - unless we are detached, attach HEAD in submodules\n+\t\t\t * - if rev is a branch name, use that branch instead of oid in\n+\t\t\t *   submodules.\n+\t\t\t */\n+\t\t\tcurrent_branch = resolve_refdup(\"HEAD\", 0, NULL, &flags);\n+\t\t\tif (!(flags & REF_ISSYMREF))\n+\t\t\t\tcurrent_branch = NULL;\n+\t\t\tif (dwim_ref(rev, strlen(rev), &discard, &new_ref) != 1 ||\n+\t\t\t    !starts_with(new_ref, \"refs/heads/\")) {\n+\t\t\t\tfree(new_ref);\n+\t\t\t\tnew_ref = NULL;\n+\t\t\t}\n+\n+\t\t\topts.move_head = unpack_trees_move_head;\n+\n+\t\t\tmemset(&mopts, 0, sizeof(mopts));\n+\t\t\tmopts.force = 1;\n+\t\t\tmopts.new_ref = new_ref;\n+\t\t\tmopts.target_ref = current_branch;\n+\t\t\topts.unpack_data = &mopts;\n+\t\t}\n \t\t/* fallthrough */\n \tdefault:\n \t\topts.reset = 1;\n@@ -101,6 +131,8 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)\n out:\n \tfor (i = 0; i < nr; i++)\n \t\tfree((void *)desc[i].buffer);\n+\tfree(current_branch);\n+\tfree(new_ref);\n \treturn ret;\n }\n \n@@ -317,6 +349,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\toidcpy(&oid, the_hash_algo->empty_tree);\n \t} else if (!pathspec.nr) {\n \t\tstruct commit *commit;\n+\n \t\tif (get_oid_committish(rev, &oid))\n \t\t\tdie(_(\"Failed to resolve '%s' as a valid revision.\"), rev);\n \t\tcommit = lookup_commit_reference(the_repository, &oid);\n@@ -379,9 +412,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\t\t\trefresh_index(&the_index, flags, NULL, NULL,\n \t\t\t\t\t      _(\"Unstaged changes after reset:\"));\n \t\t} else {\n-\t\t\tint err = reset_index(&oid, reset_type, quiet);\n+\t\t\tint err = reset_index(rev, &oid, reset_type, quiet);\n \t\t\tif (reset_type == KEEP && !err)\n-\t\t\t\terr = reset_index(&oid, MIXED, quiet);\n+\t\t\t\terr = reset_index(rev, &oid, MIXED, quiet);\n \t\t\tif (err)\n \t\t\t\tdie(_(\"Could not reset index file to revision '%s'.\"), rev);\n \t\t}\ndiff --git a/entry.c b/entry.c\nindex 5d136c5d55..eee5bc498e 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -2,11 +2,23 @@\n #include \"blob.h\"\n #include \"object-store.h\"\n #include \"dir.h\"\n+#include \"unpack-trees.h\"\n #include \"streaming.h\"\n #include \"submodule.h\"\n #include \"progress.h\"\n #include \"fsmonitor.h\"\n \n+/* NEEDSWORK: share code with unpack-trees.c */\n+static int move_head(const struct unpack_trees_options *o, const char *path, const char *old, const char *new, unsigned flags)\n+{\n+\tif (behave_google_repo_like()) {\n+\t\tif (!o || !o->move_head)\n+\t\t\treturn submodule_move_head(path, old, new, flags);\n+\t\treturn o->move_head(o, path, old, new, flags);\n+\t} else\n+\t\treturn submodule_move_head(path, old, new, flags);\n+}\n+\n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n {\n@@ -251,7 +263,7 @@ int finish_delayed_checkout(struct checkout *state)\n \treturn errs;\n }\n \n-static int write_entry(struct cache_entry *ce,\n+static int write_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n \t\t       char *path, const struct checkout *state, int to_tempfile)\n {\n \tunsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;\n@@ -357,7 +369,7 @@ static int write_entry(struct cache_entry *ce,\n \t\t\treturn error(\"cannot create submodule directory %s\", path);\n \t\tsub = submodule_from_ce(ce);\n \t\tif (sub)\n-\t\t\treturn submodule_move_head(ce->name,\n+\t\t\treturn move_head(o, ce->name,\n \t\t\t\tNULL, oid_to_hex(&ce->oid),\n \t\t\t\tstate->force ? SUBMODULE_MOVE_HEAD_FORCE : 0);\n \t\tbreak;\n@@ -435,14 +447,15 @@ static void mark_colliding_entries(const struct checkout *state,\n  * its name is returned in topath[], which must be able to hold at\n  * least TEMPORARY_FILENAME_LENGTH bytes long.\n  */\n-int checkout_entry(struct cache_entry *ce,\n-\t\t   const struct checkout *state, char *topath)\n+int unpack_trees_checkout_entry(struct unpack_trees_options *o,\n+\t\t\t\tstruct cache_entry *ce,\n+\t\t\t\tconst struct checkout *state, char *topath)\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n \n \tif (topath)\n-\t\treturn write_entry(ce, topath, state, 1);\n+\t\treturn write_entry(o, ce, topath, state, 1);\n \n \tstrbuf_reset(&path);\n \tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n@@ -466,10 +479,10 @@ int checkout_entry(struct cache_entry *ce,\n \t\t\t\tif (!(st.st_mode & S_IFDIR))\n \t\t\t\t\tunlink_or_warn(ce->name);\n \n-\t\t\t\treturn submodule_move_head(ce->name,\n+\t\t\t\treturn move_head(o, ce->name,\n \t\t\t\t\tNULL, oid_to_hex(&ce->oid), 0);\n \t\t\t} else\n-\t\t\t\treturn submodule_move_head(ce->name,\n+\t\t\t\treturn move_head(o, ce->name,\n \t\t\t\t\t\"HEAD\", oid_to_hex(&ce->oid),\n \t\t\t\t\tstate->force ? SUBMODULE_MOVE_HEAD_FORCE : 0);\n \t\t}\n@@ -506,5 +519,19 @@ int checkout_entry(struct cache_entry *ce,\n \t\treturn 0;\n \n \tcreate_directories(path.buf, path.len, state);\n-\treturn write_entry(ce, path.buf, state, 0);\n+\treturn write_entry(o, ce, path.buf, state, 0);\n+}\n+\n+/*\n+ * Write the contents from ce out to the working tree.\n+ *\n+ * When topath[] is not NULL, instead of writing to the working tree\n+ * file named by ce, a temporary file is created by this function and\n+ * its name is returned in topath[], which must be able to hold at\n+ * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ */\n+int checkout_entry(struct cache_entry *ce,\n+\t\t   const struct checkout *state, char *topath)\n+{\n+\treturn unpack_trees_checkout_entry(NULL, ce, state, topath);\n }\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 1b568e29b9..5e6fcbe06e 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -424,6 +424,18 @@ fetch_in_submodule () (\n \tesac\n )\n \n+# usage: checkout_in_submoodule \"$sm_path\" \"$command\" \"${branch:-}\" \"$rev\"\n+checkout_in_submodule () (\n+\tsanitize_submodule_env &&\n+\tcd \"$1\" &&\n+\tif test -n \"$3\"\n+\tthen\n+\t\t$2 -B \"$3\" \"$4\"\n+\telse\n+\t\t$2 \"$4\"\n+\tfi\n+)\n+\n #\n # Update each submodule path to correct revision, using clone and checkout as needed\n #\n@@ -476,6 +488,11 @@ cmd_update()\n \t\t--recursive)\n \t\t\trecursive=1\n \t\t\t;;\n+\t\t--checkout-branch)\n+\t\t\tupdate=\"checkout\"\n+\t\t\tcheckout_dest=$2\n+\t\t\tshift\n+\t\t\t;;\n \t\t--checkout)\n \t\t\tupdate=\"checkout\"\n \t\t\t;;\n@@ -592,6 +609,11 @@ cmd_update()\n \t\t\t\tdie \"$(eval_gettext \"Fetched in submodule path '\\$displaypath', but it did not contain \\$sha1. Direct fetching of that commit failed.\")\"\n \t\t\tfi\n \n+\t\t\tif test -n \"$checkout_dest\" && test \"$update_module\" != checkout\n+\t\t\tthen\n+\t\t\t\tdie \"Cannot use --checkout-branch with update mode '$update_module'\"\n+\t\t\tfi\n+\n \t\t\tmust_die_on_failure=\n \t\t\tcase \"$update_module\" in\n \t\t\tcheckout)\n@@ -621,7 +643,7 @@ cmd_update()\n \t\t\t\tdie \"$(eval_gettext \"Invalid update mode '$update_module' for submodule path '$path'\")\"\n \t\t\tesac\n \n-\t\t\tif (sanitize_submodule_env; cd \"$sm_path\" && $command \"$sha1\")\n+\t\t\tif checkout_in_submodule \"$sm_path\" \"$command\" \"$checkout_dest\" \"$sha1\"\n \t\t\tthen\n \t\t\t\tsay \"$say_msg\"\n \t\t\telif test -n \"$must_die_on_failure\"\ndiff --git a/git.c b/git.c\nindex a6f4b44af5..3b9c112a36 100644\n--- a/git.c\n+++ b/git.c\n@@ -447,6 +447,7 @@ static struct cmd_struct commands[] = {\n \t{ \"bisect--helper\", cmd_bisect__helper, RUN_SETUP },\n \t{ \"blame\", cmd_blame, RUN_SETUP },\n \t{ \"branch\", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },\n+\t{ \"branch--helper\", cmd_branch_helper, RUN_SETUP },\n \t{ \"bundle\", cmd_bundle, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"cat-file\", cmd_cat_file, RUN_SETUP },\n \t{ \"check-attr\", cmd_check_attr, RUN_SETUP },\ndiff --git a/submodule-move-head.c b/submodule-move-head.c\nnew file mode 100644\nindex 0000000000..4c8f49066f\n--- /dev/null\n+++ b/submodule-move-head.c\n@@ -0,0 +1,81 @@\n+#include \"cache.h\"\n+#include \"submodule-move-head.h\"\n+#include \"repository.h\"\n+#include \"submodule.h\"\n+#include \"refs.h\"\n+#include \"unpack-trees.h\"\n+#include \"run-command.h\"\n+\n+void create_symref_in_submodule(const char *path, const char *symref, const char *target, const char *logmsg)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\t/* NEEDSWORK: What about sub-submodules? */\n+\tprepare_submodule_repo_env(&cp.env_array);\n+\tcp.git_cmd = 1;\n+\tcp.dir = path;\n+\targv_array_pushl(&cp.args, \"symbolic-ref\", \"-m\", logmsg, symref, target, NULL);\n+\n+\tif (run_command(&cp))\n+\t\tdie(\"process for submodule '%s' failed\", path);\n+}\n+\n+static void create_ref_in_submodule(const char *path, const char *ref, const char *value)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\t/* NEEDSWORK: set a reasonable reflog message. */\n+\tprepare_submodule_repo_env(&cp.env_array);\n+\tcp.git_cmd = 1;\n+\tcp.dir = path;\n+\targv_array_pushl(&cp.args, \"update-ref\", ref, value, sha1_to_hex(null_sha1), NULL);\n+\n+\tif (run_command(&cp))\n+\t\tdie(\"process for submodule '%s' failed\", path);\n+}\n+\n+static int ref_exists_in_submodule(const char *submodule_path, const char *refname)\n+{\n+\tstruct ref_store *refs = get_submodule_ref_store(submodule_path);\n+\tif (!refs)\n+\t\treturn 0;\n+\treturn refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, NULL, NULL) != NULL;\n+}\n+\n+int unpack_trees_move_head(const struct unpack_trees_options *opt, const char *path, const char *old, const char *new, unsigned flags)\n+{\n+\tstruct submodule_move_head_options *o = opt->unpack_data;\n+\tconst char *new_ref = o->new_ref;\n+\tconst char *target_ref = o->target_ref;\n+\tconst char *old_commit = old;\n+\tconst char *new_commit = new;\n+\n+\t/*\n+\t * NEEDSWORK:\n+\t * - set log message\n+\t * - what about sub-submodules?\n+\t */\n+\n+\tif (!is_submodule_active(the_repository, path))\n+\t\treturn 0;\n+\n+\tif (old) {\n+\t\tif (o->force)\n+\t\t\told_commit = \"HEAD\";\n+\t\telse if (o->old_ref && ref_exists_in_submodule(path, o->old_ref))\n+\t\t\told_commit = o->old_ref;\n+\t}\n+\tif (new_ref && new && ref_exists_in_submodule(path, new_ref))\n+\t\tnew_commit = new_ref;\n+\n+\tif (target_ref)\n+\t\tflags |= SUBMODULE_MOVE_HEAD_SKIP_REF_UPDATE;\n+\tif (submodule_move_head(path, old_commit, new_commit, flags) < 0)\n+\t\treturn -1;\n+\tif (new && target_ref && !(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {\n+\t\tif (!ref_exists_in_submodule(path, target_ref))\n+\t\t\tcreate_ref_in_submodule(path, target_ref, new);\n+\t\tcreate_symref_in_submodule(path, \"HEAD\", target_ref, \"msg\");\n+\t}\n+\treturn 0;\n+}\ndiff --git a/submodule-move-head.h b/submodule-move-head.h\nnew file mode 100644\nindex 0000000000..80e2679038\n--- /dev/null\n+++ b/submodule-move-head.h\n@@ -0,0 +1,22 @@\n+#ifndef SUBMODULE_MOVE_HEAD_H\n+#define SUBMODULE_MOVE_HEAD_H\n+\n+struct unpack_trees_options;\n+\n+/* NEEDSWORK: document */\n+struct submodule_move_head_options {\n+\tint force;\n+\tconst char *old_ref;\n+\tconst char *new_ref;\n+\tconst char *target_ref;\n+};\n+\n+/*\n+ * For use as unpack_trees_options.move_head. Parameters should be a\n+ * struct submodule_move_head_options * in unpack_trees_options.unpack_data.\n+ */\n+extern int unpack_trees_move_head(const struct unpack_trees_options *opt, const char *path, const char *old, const char *new, unsigned flags);\n+\n+extern void create_symref_in_submodule(const char *path, const char *symref, const char *target, const char *logmsg);\n+\n+#endif /* SUBMODULE_MOVE_HEAD_H */\ndiff --git a/submodule.c b/submodule.c\nindex b53cb6e9c4..81eace805f 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -30,6 +30,17 @@ static int initialized_fetch_ref_tips;\n static struct oid_array ref_tips_before_fetch;\n static struct oid_array ref_tips_after_fetch;\n \n+int behave_google_repo_like(void)\n+{\n+\tstatic int google_repo_like = -1;\n+\n+\tif (google_repo_like == -1)\n+\t\tgit_config_get_bool(\"submodule.repolike\", &google_repo_like);\n+\n+\treturn google_repo_like;\n+}\n+\n+\n /*\n  * Check if the .gitmodules file is unmerged. Parsing of the .gitmodules file\n  * will be disabled because we can't guess what might be configured in\n@@ -1676,6 +1687,9 @@ int submodule_move_head(const char *path,\n \n \tif (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {\n \t\tif (new_head) {\n+\t\t\tif (flags & SUBMODULE_MOVE_HEAD_SKIP_REF_UPDATE)\n+\t\t\t\tgoto out;\n+\n \t\t\tchild_process_init(&cp);\n \t\t\t/* also set the HEAD accordingly */\n \t\t\tcp.git_cmd = 1;\ndiff --git a/submodule.h b/submodule.h\nindex e452919aa4..3346b1a12e 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -120,8 +120,11 @@ int push_unpushed_submodules(struct oid_array *commits,\n  */\n int submodule_to_gitdir(struct strbuf *buf, const char *submodule);\n \n-#define SUBMODULE_MOVE_HEAD_DRY_RUN (1<<0)\n-#define SUBMODULE_MOVE_HEAD_FORCE   (1<<1)\n+#define SUBMODULE_MOVE_HEAD_DRY_RUN             (1<<0)\n+#define SUBMODULE_MOVE_HEAD_FORCE               (1<<1)\n+#define SUBMODULE_MOVE_HEAD_SKIP_REF_UPDATE     (1<<2)\n+\n+/* NULL values for old or new represent the \"missing\" state. */\n int submodule_move_head(const char *path,\n \t\t\tconst char *old,\n \t\t\tconst char *new_head,\n@@ -146,4 +149,6 @@ void absorb_git_dir_into_superproject(const char *prefix,\n  */\n const char *get_superproject_working_tree(void);\n \n+int behave_google_repo_like(void);\n+\n #endif\ndiff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh\nindex 016391723c..71d5b506de 100755\n--- a/t/lib-submodule-update.sh\n+++ b/t/lib-submodule-update.sh\n@@ -657,7 +657,54 @@ test_submodule_recursing_with_args_common() {\n \t\t\ttest_submodule_content sub1 origin/add_sub1\n \t\t)\n \t'\n-\ttest_expect_success \"$command: submodule branch is not changed, detach HEAD instead\" '\n+\n+\tif test \"$KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED\" = 1\n+\tthen\n+\t\tRESULT=failure\n+\telse\n+\t\tRESULT=success\n+\tfi\n+\tif test \"$KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED\" = \"read-tree\"\n+\tthen\n+\ttest_expect_$RESULT \"$command: submodule branch is not changed, detach HEAD\" '\n+\t\tprolog &&\n+\t\treset_work_tree_to_interested add_sub1 &&\n+\t\t(\n+\t\t\tcd submodule_update &&\n+\t\t\tgit -C sub1 checkout -b keep_branch &&\n+\t\t\tgit -C sub1 rev-parse HEAD >expect &&\n+\t\t\tgit branch -t modify_sub1 origin/modify_sub1 &&\n+\t\t\t$command modify_sub1 &&\n+\t\t\ttest_superproject_content origin/modify_sub1 &&\n+\t\t\ttest_submodule_content sub1 origin/modify_sub1 &&\n+\t\t\tgit -C sub1 rev-parse keep_branch >actual &&\n+\t\t\ttest_cmp expect actual &&\n+\t\t\ttest_must_fail git -C sub1 symbolic-ref HEAD >actual\n+\t\t)\n+\t'\n+\telif test \"$KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED\" = \"checkout -B current\"\n+\tthen\n+\ttest_expect_$RESULT \"$command: submodule branch is changed to 'current'\" '\n+\t\tprolog &&\n+\t\treset_work_tree_to_interested add_sub1 &&\n+\t\t(\n+\t\t\tcd submodule_update &&\n+\t\t\tgit -C sub1 checkout -b keep_branch &&\n+\t\t\tgit -C sub1 rev-parse HEAD >expect &&\n+\t\t\tgit branch -t modify_sub1 origin/modify_sub1 &&\n+\t\t\t$command modify_sub1 &&\n+\t\t\ttest_superproject_content origin/modify_sub1 &&\n+\t\t\ttest_submodule_content sub1 origin/modify_sub1 &&\n+\t\t\tgit -C sub1 rev-parse keep_branch >actual &&\n+\t\t\ttest_cmp expect actual &&\n+\t\t\techo refs/heads/current >expect &&\n+\t\t\tgit -C sub1 symbolic-ref HEAD >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\telif test \"$KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED\" = \"reset\"\n+\tthen\n+\ttest_expect_$RESULT \"$command: submodule branch is changed to superproject, resetting to target\" '\n \t\tprolog &&\n \t\treset_work_tree_to_interested add_sub1 &&\n \t\t(\n@@ -665,14 +712,49 @@ test_submodule_recursing_with_args_common() {\n \t\t\tgit -C sub1 checkout -b keep_branch &&\n \t\t\tgit -C sub1 rev-parse HEAD >expect &&\n \t\t\tgit branch -t modify_sub1 origin/modify_sub1 &&\n+\t\t\tgit checkout -b newbranch &&\n \t\t\t$command modify_sub1 &&\n+\n+\t\t\t# we modified the wt\n \t\t\ttest_superproject_content origin/modify_sub1 &&\n \t\t\ttest_submodule_content sub1 origin/modify_sub1 &&\n+\n+\t\t\t# keep_branch does not change\n \t\t\tgit -C sub1 rev-parse keep_branch >actual &&\n \t\t\ttest_cmp expect actual &&\n-\t\t\ttest_must_fail git -C sub1 symbolic-ref HEAD\n+\n+\t\t\t# the submodule is attached to the same branch as the superproject\n+\t\t\tgit -C sub1 symbolic-ref HEAD >actual &&\n+\t\t\techo refs/heads/newbranch >expect &&\n+\t\t\ttest_cmp expect actual\n \t\t)\n \t'\n+\telse\n+\ttest_expect_$RESULT \"$command: submodule branch is changed\" '\n+\t\tprolog &&\n+\t\treset_work_tree_to_interested add_sub1 &&\n+\t\t(\n+\t\t\tcd submodule_update &&\n+\t\t\tgit -C sub1 checkout -b keep_branch &&\n+\t\t\tgit -C sub1 rev-parse HEAD >expect &&\n+\t\t\tgit branch -t modify_sub1 origin/modify_sub1 &&\n+\t\t\t$command modify_sub1 &&\n+\n+\t\t\t# modified wt\n+\t\t\ttest_superproject_content origin/modify_sub1 &&\n+\t\t\ttest_submodule_content sub1 origin/modify_sub1 &&\n+\n+\t\t\t# unrelated keep_branch is fine\n+\t\t\tgit -C sub1 rev-parse keep_branch >actual &&\n+\t\t\ttest_cmp expect actual &&\n+\n+\t\t\t# submodule ref is checked out\n+\t\t\tgit -C sub1 symbolic-ref HEAD >actual &&\n+\t\t\techo refs/heads/modify_sub1 >expect &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\tfi\n \n \t# Replacing a tracked file with a submodule produces a checked out submodule\n \ttest_expect_success \"$command: replace tracked file with submodule checks out submodule\" '\ndiff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh\nindex 91a6fafcb4..de59ebd121 100755\n--- a/t/t1013-read-tree-submodule.sh\n+++ b/t/t1013-read-tree-submodule.sh\n@@ -7,6 +7,7 @@ test_description='read-tree can handle submodules'\n \n KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1\n KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1\n+KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED=\"read-tree\"\n \n test_submodule_switch_recursing_with_args \"read-tree -u -m\"\n \ndiff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh\nindex 8f86b5f4b2..940f0fca20 100755\n--- a/t/t2013-checkout-submodule.sh\n+++ b/t/t2013-checkout-submodule.sh\n@@ -66,6 +66,10 @@ test_expect_success '\"checkout <submodule>\" honors submodule.*.ignore from .git/\n KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1\n test_submodule_switch_recursing_with_args \"checkout\"\n \n+KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED=\"checkout -B current\"\n+test_submodule_switch_recursing_with_args \"checkout -B current\"\n+unset KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED\n+\n test_submodule_forced_switch_recursing_with_args \"checkout -f\"\n \n test_submodule_switch \"git checkout\"\ndiff --git a/t/t7112-reset-submodule.sh b/t/t7112-reset-submodule.sh\nindex a1cb9ff858..c31acc6578 100755\n--- a/t/t7112-reset-submodule.sh\n+++ b/t/t7112-reset-submodule.sh\n@@ -8,13 +8,18 @@ test_description='reset can handle submodules'\n KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1\n KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1\n KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1\n+KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED=\"reset\"\n \n+KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED=1\n test_submodule_switch_recursing_with_args \"reset --keep\"\n \n+unset KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED\n test_submodule_forced_switch_recursing_with_args \"reset --hard\"\n \n+KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED=1\n test_submodule_switch \"git reset --keep\"\n \n+unset KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED\n test_submodule_switch \"git reset --merge\"\n \n test_submodule_forced_switch \"git reset --hard\"\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 51bfac6aa0..4d4a85226e 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -254,6 +254,16 @@ static void display_error_msgs(struct unpack_trees_options *o)\n \t\tfprintf(stderr, _(\"Aborting\\n\"));\n }\n \n+static int move_head(const struct unpack_trees_options *o, const char *path, const char *old, const char *new, unsigned flags)\n+{\n+\tif (behave_google_repo_like()) {\n+\t\tif (!o->move_head)\n+\t\t\treturn submodule_move_head(path, old, new, flags);\n+\t\treturn o->move_head(o, path, old, new, flags);\n+\t} else\n+\t\treturn submodule_move_head(path, old, new, flags);\n+}\n+\n static int check_submodule_move_head(const struct cache_entry *ce,\n \t\t\t\t     const char *old_id,\n \t\t\t\t     const char *new_id,\n@@ -268,7 +278,7 @@ static int check_submodule_move_head(const struct cache_entry *ce,\n \tif (o->reset)\n \t\tflags |= SUBMODULE_MOVE_HEAD_FORCE;\n \n-\tif (submodule_move_head(ce->name, old_id, new_id, flags))\n+\tif (move_head(o, ce->name, old_id, new_id, flags))\n \t\treturn o->gently ? -1 :\n \t\t\t\t   add_rejected_path(o, ERROR_WOULD_LOSE_SUBMODULE, ce->name);\n \treturn 0;\n@@ -304,12 +314,12 @@ static void load_gitmodules_file(struct index_state *index,\n  * Unlink the last component and schedule the leading directories for\n  * removal, such that empty directories get removed.\n  */\n-static void unlink_entry(const struct cache_entry *ce)\n+static void unlink_entry(const struct unpack_trees_options *o, const struct cache_entry *ce)\n {\n \tconst struct submodule *sub = submodule_from_ce(ce);\n \tif (sub) {\n \t\t/* state.force is set at the caller. */\n-\t\tsubmodule_move_head(ce->name, \"HEAD\", NULL,\n+\t\tmove_head(o, ce->name, \"HEAD\", NULL,\n \t\t\t\t    SUBMODULE_MOVE_HEAD_FORCE);\n \t}\n \tif (!check_leading_path(ce->name, ce_namelen(ce)))\n@@ -408,7 +418,7 @@ static int check_updates(struct unpack_trees_options *o)\n \t\tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\t\tdisplay_progress(progress, ++cnt);\n \t\t\tif (o->update && !o->dry_run)\n-\t\t\t\tunlink_entry(ce);\n+\t\t\t\tunlink_entry(o, ce);\n \t\t}\n \t}\n \tremove_marked_cache_entries(index);\n@@ -450,7 +460,7 @@ static int check_updates(struct unpack_trees_options *o)\n \t\t\tdisplay_progress(progress, ++cnt);\n \t\t\tce->ce_flags &= ~CE_UPDATE;\n \t\t\tif (o->update && !o->dry_run) {\n-\t\t\t\terrs |= checkout_entry(ce, &state, NULL);\n+\t\t\t\terrs |= unpack_trees_checkout_entry(o, ce, &state, NULL);\n \t\t\t}\n \t\t}\n \t}\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 0135080a7b..3f1a205ad0 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -9,12 +9,15 @@\n #define MAX_UNPACK_TREES 8\n \n struct cache_entry;\n+struct tree_desc;\n struct unpack_trees_options;\n struct exclude_list;\n \n typedef int (*merge_fn_t)(const struct cache_entry * const *src,\n \t\tstruct unpack_trees_options *options);\n \n+typedef int (*submodule_move_head_fn)(const struct unpack_trees_options *o, const char *path, const char *old, const char *new, unsigned flags);\n+\n enum unpack_trees_error_types {\n \tERROR_WOULD_OVERWRITE = 0,\n \tERROR_NOT_UPTODATE_FILE,\n@@ -65,6 +68,7 @@ struct unpack_trees_options {\n \tstruct dir_struct *dir;\n \tstruct pathspec *pathspec;\n \tmerge_fn_t fn;\n+\tsubmodule_move_head_fn move_head;\n \tconst char *msgs[NB_UNPACK_TREES_ERROR_TYPES];\n \tstruct argv_array msgs_to_free;\n \t/*\n@@ -86,6 +90,12 @@ struct unpack_trees_options {\n \tstruct exclude_list *el; /* for internal use */\n };\n \n+/* defined in entry.c, for internal use */\n+int unpack_trees_checkout_entry(struct unpack_trees_options *o,\n+\t\t\t\tstruct cache_entry *ce,\n+\t\t\t\tconst struct checkout *state,\n+\t\t\t\tchar *topath);\n+\n int unpack_trees(unsigned n, struct tree_desc *t,\n \t\t struct unpack_trees_options *options);\n \n-- \n2.19.0\n\n"},{"id":"359154","messageId":"20180927222728.GD193497@aiede.svl.corp.google.com","threadId":"49444","inReplyTo":"20180927221603.148025-1-sbeller@google.com","subject":"Re: [PATCH] FYI / RFC: submodules: introduce repo-like workflow","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-09-27T22:27:28Z","receivedAt":"2018-09-27T22:27:35Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"(dropping cc-s to my internal address that I don't use on this list\n and to git-core@google.com which bounces)\nHi,\n\nStefan Beller wrote:\n\n> Internally we have rolled out this as an experiment for\n> \"submodules replacing the repo tool[1]\". The repo tool is described as:\n>\n>     Repo unifies Git repositories when necessary, performs uploads to the\n>     Gerrit revision control system, and automates parts of the Android\n>     development workflow. Repo is not meant to replace Git, only to make\n>     it easier to work with Git in the context of Android. The repo command\n>     is an executable Python script that you can put anywhere in your path.\n[...]\n> Submodules can also be understood as unifying Git repositories, even more,\n> in the superproject the submodules have relationships between their\n> versions, which the repo tool only provides in releases.\n>\n> The repo tool does not provide these relationships between versions, but\n> all the repositories (in case of Android think of ~1000 git repositories)\n> are put in their place without depending on each other.\n>\n> This comes with a couple of advantages and disadvantages:\n\nThanks for describing this background.\n\n[...]\n> This changes the following commands in the superproject:\n>\n>   checkout -B/-b create branches in subs, too\n>   checkout (-f): update branch in submodule (create if needed) and check\n>                  it out; Pass the argument down literally if it is a branch\n>                  name (e.g. \"checkout -f master\" will run a\n>                             \"checkout -f master\" in the submodule as well)\n>   clone: see checkout\n>   reset --hard: see checkout\n\nAs you mentioned, I've been using this submodule.repoLike=true mode\nfor my own use for a while.  You did a nice job of explaining on how\nit fits into a Gerrit-driven workflow; I'd like to add that I find it\npleasant in non-Gerrit-driven contexts as well.\n\nThe primary difference from repoLike=false is that this makes the\nnormal state to have branches checked out in submodules.  For example,\nif I run\n\n\tgit checkout --recurse-submodules -B master origin/master\n\nthen this will create and check out a \"master\" branch in all\nsubmodules instead of only in the superproject.  This helps avoid some\nissues in Git's submodule handling where submodule commits can be\npruned if they have not been checked out in a while because there is\nno ref pointing to them.\n\nSome next steps:\n\n- now that we have a repository object, some of the implementation can\n  be simplified and made more robust.  I expect that will also make\n  these patches easier to review\n\n- also in the direction of reviewability, at that point we may want to\n  split this into multiple patches\n\n- gitsubmodules.txt and config.txt should describe the new option, to\n  help new users understand what this new repoLike workflow does\n\n- there are some edge cases in the UX that get... messy that I should\n  describe in another message\n\nAll that said, thanks for sending this out, and I'd be happy to hear\nfrom any interested people --- feedback from anyone adventurous enough\nto try this out would be very welcome.\n\nHappy hacking,\nJonathan\n"},{"id":"359228","messageId":"xmqqbm8hcx6j.fsf@gitster-ct.c.googlers.com","threadId":"49444","inReplyTo":"20180927222728.GD193497@aiede.svl.corp.google.com","subject":"Re: [PATCH] FYI / RFC: submodules: introduce repo-like workflow","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-09-28T18:08:04Z","receivedAt":"2018-09-28T18:08:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> (dropping cc-s to my internal address that I don't use on this list\n>  and to git-core@google.com which bounces)\n> Hi,\n>\n> Stefan Beller wrote:\n>\n>> Internally we have rolled out this as an experiment for\n>> \"submodules replacing the repo tool[1]\". The repo tool is described as:\n>>\n>>     Repo unifies Git repositories when necessary, performs uploads to the\n>>     Gerrit revision control system, and automates parts of the Android\n>>     development workflow. Repo is not meant to replace Git, only to make\n>>     it easier to work with Git in the context of Android. The repo command\n>>     is an executable Python script that you can put anywhere in your path.\n> [...]\n>> Submodules can also be understood as unifying Git repositories, even more,\n>> in the superproject the submodules have relationships between their\n>> versions, which the repo tool only provides in releases.\n>>\n>> The repo tool does not provide these relationships between versions, but\n>> all the repositories (in case of Android think of ~1000 git repositories)\n>> are put in their place without depending on each other.\n>>\n>> This comes with a couple of advantages and disadvantages:\n>\n> Thanks for describing this background.\n\nThanks for this.  I probably won't be reading this before other\ntopics, but I've often found that changes from google were lacking\nthe backstory to make sense of them as a coherent whole.  \n\nFor example, a patch that says \"Instead of detaching at the commit\nrecorded in the superproject, check out the branch with the same\nname, even if it points at a different commit.  Here is the write up\nof what it does in the Documentation/ and code\" is hard to judge\nbeyond checking if the code does what it claims to do and if the\ndocs describe what the code does---without backstory like this that\ntalks about how individual small pieces fit within the plan for the\nwhole thing, judging if that \"what it claims to do\" is even sensible\nis impossible.\n"},{"id":"359238","messageId":"87h8i9ift4.fsf@evledraar.gmail.com","threadId":"49444","inReplyTo":"20180927221603.148025-1-sbeller@google.com","subject":"Re: [PATCH] FYI / RFC: submodules: introduce repo-like workflow","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-09-28T19:26:47Z","receivedAt":"2018-09-28T19:26:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Sep 27 2018, Stefan Beller wrote:\n\n> Internally we have rolled out this as an experiment for\n> \"submodules replacing the repo tool[1]\". The repo tool is described as:\n>\n>     Repo unifies Git repositories when necessary, performs uploads to the\n>     Gerrit revision control system, and automates parts of the Android\n>     development workflow. Repo is not meant to replace Git, only to make\n>     it easier to work with Git in the context of Android. The repo command\n>     is an executable Python script that you can put anywhere in your path.\n>\n>     In working with the Android source files, you use Repo for\n>     across-network operations. For example, with a single Repo command you\n>     can download files from multiple repositories into your local working\n>     directory.\n>\n>     In most situations, you can use Git instead of Repo, or mix Repo and\n>     Git commands to form complex commands.\n>\n> [1] https://source.android.com/setup/develop/\n\nSome questions just out of curiosity, not for this patch in particular:\n\nThose docs seem to describe the situation without this patch, with this\npatch is the repo tool fully replaced?\n\nHow are you planning to migrate from repo to this on a repository-data\nbasis, does gerrit also populate .gitmodules files appropriately, which\nyour clone --recurse-submodules will pick up, but repo will just ignore,\nso you can use the two in parallel?\n\nNow \"repo init -u\" takes a URL to a manifest of repositories to stitch\ntogether, I've understood from past conversations (but am not sure) that\nthis is used e.g. by downstream Android vendors so they can use what\nGoogle's using + whatever they have in-house, i.e. make the manifest the\nset of open source repos plus some (e.g. drivers specific to their\ndevice). How is that sort of workflow going to work where you\n(presumably) have do that via .gitmodules + commit entries in trees?\nThey run their own Gerrit install with some magic to sync back & forth?\n\nI assume that now the recursive \"checkout\" relies on all the origin/HEAD\nsymbolic refs pointing to \"master\", but how is this going to deal with\nincorporating a repo whose main branch has a different name?\nE.g. \"trunk\" or \"blead\"? Perhaps some interaction with\ncheckout.defaultRemote + submodule.<name>.branch=<NAME> could make \"git\ncheckout :mainbranch\" DWYM.\n\n> * Fetching changes (\"repo sync\") needs to fetch all repositories, as there\n>   is no central place that tracks what has changed. In a superproject\n>   git fetch can determine which submodules need fetching.  In Androids\n>   case the daily change is only in a few repositories (think 10s), so\n>   migrating to a superproject would save an order of magnitude in fetch\n>   traffic for daily updates of developers.\n\nInteresting that in all this time with the reliance on a central server\nrepo wasn't already asking some custom API \"what repos changed since xyz\ntime\" to narrow that down, but hey, .gitmodules + commit entries in\ntrees will do it for you.\n\n> * Sometimes when the dependencies are not on a clear repository boundary\n>   one would like to have git-bisect available across the different\n>   repositories, which repo cannot provide due to its design.\n\nI assume that you're not upgrading independently to e.g. every single\nlinux commit, just stable releases, so does bisecting deal with knowing\nthat e.g. a breakage occurred when linux.git was updated from v4.10 to\nv4.12, and then to go within the repo itself and bisect from there, or\nis that done manually?\n"},{"id":"359246","messageId":"CAGZ79kbe=AabtXjVbzAa=HpEZwO=TjGLj6rrmE+-ng4JgQVWeA@mail.gmail.com","threadId":"49444","inReplyTo":"87h8i9ift4.fsf@evledraar.gmail.com","subject":"Re: [PATCH] FYI / RFC: submodules: introduce repo-like workflow","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-09-28T20:23:18Z","receivedAt":"2018-09-28T20:23:33Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Sep 28, 2018 at 12:26 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n>\n> On Thu, Sep 27 2018, Stefan Beller wrote:\n>\n> > Internally we have rolled out this as an experiment for\n> > \"submodules replacing the repo tool[1]\". The repo tool is described as:\n> >\n> >     Repo unifies Git repositories when necessary, performs uploads to the\n> >     Gerrit revision control system, and automates parts of the Android\n> >     development workflow. Repo is not meant to replace Git, only to make\n> >     it easier to work with Git in the context of Android. The repo command\n> >     is an executable Python script that you can put anywhere in your path.\n> >\n> >     In working with the Android source files, you use Repo for\n> >     across-network operations. For example, with a single Repo command you\n> >     can download files from multiple repositories into your local working\n> >     directory.\n> >\n> >     In most situations, you can use Git instead of Repo, or mix Repo and\n> >     Git commands to form complex commands.\n> >\n> > [1] https://source.android.com/setup/develop/\n>\n> Some questions just out of curiosity, not for this patch in particular:\n>\n> Those docs seem to describe the situation without this patch, with this\n> patch is the repo tool fully replaced?\n\nYeah I started by describing the status quo. Some points to add:\n\n* The repo UX looks very similar to perforce, as that was used internally\n   at Google at the time a lot. Git wasn't around for long in the days of\n   early Android. Now that Git is well known, new grads joining are more likely\n   to know Git than perforce.\n* repo has no tests because it started as a small script\n* maintenance/releases of repo are not ideal.\n\nSo yes, one of our long term goals is to replace repo with Git-only\n(on the client side) workflow.\n\n> How are you planning to migrate from repo to this on a repository-data\n> basis\n\nWe don't plan to migrate the existing clients. You'd have to re-clone.\n\n>, does gerrit also populate .gitmodules files appropriately, which\n> your clone --recurse-submodules will pick up, but repo will just ignore,\n> so you can use the two in parallel?\n\nGerrit has native support to update submodules in a superproject.\nSo if you submit code to a project that is a submodule of a superproject\nthe superproject updates its gitlink. (and if you use a topic based workflow\nto submit to two projects, it will show up as an atomic update in the\nsuperproject by having just one commit updating two gitlinks)\n\nAlso Gerrit has a plugin \"supermanifest\" [1], which tracks repo\nmanifests and mirrors changes from the manifest into the\nsuperproject, e.g. adding a new project to the manifest will\nadd a new submodule to the superproject.\n\n[1] https://gerrit.googlesource.com/plugins/supermanifest/\n\nWith both Gerrits internal superproject subscription and that plugin\nit should be possible to use repo or git-submodule in parallel (on an\norganisational level, i.e. you choose one of them, and your coworker\nchooses the other one)\n\n> Now \"repo init -u\" takes a URL to a manifest of repositories to stitch\n> together, I've understood from past conversations (but am not sure) that\n> this is used e.g. by downstream Android vendors so they can use what\n> Google's using + whatever they have in-house, i.e. make the manifest the\n> set of open source repos plus some (e.g. drivers specific to their\n> device). How is that sort of workflow going to work where you\n> (presumably) have do that via .gitmodules + commit entries in trees?\n\nThe manifest is tracked in its own manifest repo, so today they fork\nthat project, with the superproject you'd fork the superproject and modify\nthe .gitmodules file as needed.\n\n> They run their own Gerrit install with some magic to sync back & forth?\n>\n> I assume that now the recursive \"checkout\" relies on all the origin/HEAD\n> symbolic refs pointing to \"master\", but how is this going to deal with\n> incorporating a repo whose main branch has a different name?\n\nChange the name? Or pin it via sha1.\n\n> E.g. \"trunk\" or \"blead\"? Perhaps some interaction with\n> checkout.defaultRemote + submodule.<name>.branch=<NAME> could make \"git\n> checkout :mainbranch\" DWYM.\n>\n> > * Fetching changes (\"repo sync\") needs to fetch all repositories, as there\n> >   is no central place that tracks what has changed. In a superproject\n> >   git fetch can determine which submodules need fetching.  In Androids\n> >   case the daily change is only in a few repositories (think 10s), so\n> >   migrating to a superproject would save an order of magnitude in fetch\n> >   traffic for daily updates of developers.\n>\n> Interesting that in all this time with the reliance on a central server\n> repo wasn't already asking some custom API \"what repos changed since xyz\n> time\" to narrow that down, but hey, .gitmodules + commit entries in\n> trees will do it for you.\n\nIt's complicated. Shawn really grew to dislike the repo tool and wanted\nto have it gone as quickly as possible, so no hacks that extend its life. ;-)\nI would prefer to keep that stance as any hack in there would grow into\na nightmare down the road.\n\n> > * Sometimes when the dependencies are not on a clear repository boundary\n> >   one would like to have git-bisect available across the different\n> >   repositories, which repo cannot provide due to its design.\n>\n> I assume that you're not upgrading independently to e.g. every single\n> linux commit, just stable releases, so does bisecting deal with knowing\n> that e.g. a breakage occurred when linux.git was updated from v4.10 to\n> v4.12, and then to go within the repo itself and bisect from there, or\n> is that done manually?\n\nWe currently have no submodule support in bisect, but that is open to\nfuture contribution. But with relations between submodules via the\nsuperproject it is at least possible to bisect precisely, whereas in repo\nthe projects are unrelated to each other and it becomes a political\nissue if A broke B or B broke A.\n\nThanks for taking time to read and think through the concept here,\n\nStefan\n"},{"id":"366684","messageId":"20190114223458.GF162110@google.com","threadId":"49444","inReplyTo":"20180927221603.148025-1-sbeller@google.com","subject":"Re: [PATCH] FYI / RFC: submodules: introduce repo-like workflow","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2019-01-14T22:34:58Z","receivedAt":"2019-01-14T22:35:05Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nStefan Beller wrote:\n\n> Internally we have rolled out this as an experiment for\n> \"submodules replacing the repo tool[1]\".\n\nThanks again for writing and explaining it.  Here's an updated\nversion, basically identical (just rebased).  \"git range-diff\" shows\nmostly context lines affected, so this really is a minor update.\n\nI suspect the *next* time this hits the list will be more interesting,\nsince we can start making use of the repository API.\n\nThoughts of all kinds welcome, as always.\n\n-- >8 --\nFrom: Stefan Beller <sbeller@google.com>\nDate: Mon, 4 Dec 2017 15:47:40 -0800\n\nInternally we have rolled out this as an experiment for\n\"submodules replacing the repo tool[1]\". The repo tool is described as:\n\n    Repo unifies Git repositories when necessary, performs uploads to the\n    Gerrit revision control system, and automates parts of the Android\n    development workflow. Repo is not meant to replace Git, only to make\n    it easier to work with Git in the context of Android. The repo command\n    is an executable Python script that you can put anywhere in your path.\n\n    In working with the Android source files, you use Repo for\n    across-network operations. For example, with a single Repo command you\n    can download files from multiple repositories into your local working\n    directory.\n\n    In most situations, you can use Git instead of Repo, or mix Repo and\n    Git commands to form complex commands.\n\n[1] https://source.android.com/setup/develop/\n\nSubmodules can also be understood as unifying Git repositories, even more,\nin the superproject the submodules have relationships between their\nversions, which the repo tool only provides in releases.\n\nThe repo tool does not provide these relationships between versions, but\nall the repositories (in case of Android think of ~1000 git repositories)\nare put in their place without depending on each other.\n\nThis comes with a couple of advantages and disadvantages:\n\n* Many users are familiar with Git, but not submodules. Each repository\n  can be used independently with Git and there is no need to update the\n  superproject or the repo manifest for a change in a repository.\n* It is easy to work with repositories with no version-control-dependencies\n  if there are dependencies in the code. In case of Android the\n  repositories are bound at natural boundaries. For example the linux\n  kernel is one repository, as then upstream work is made easy for this\n  repository. So it is desirable to keep an easy-as-repo workflow.\n* Fetching changes (\"repo sync\") needs to fetch all repositories, as there\n  is no central place that tracks what has changed. In a superproject\n  git fetch can determine which submodules need fetching.  In Androids\n  case the daily change is only in a few repositories (think 10s), so\n  migrating to a superproject would save an order of magnitude in fetch\n  traffic for daily updates of developers.\n* Sometimes when the dependencies are not on a clear repository boundary\n  one would like to have git-bisect available across the different\n  repositories, which repo cannot provide due to its design.\n\nInternally we have the Gerrit as a central point, where the source of\ntruth is found for a given repository.\n\nThis patch adds a new mode to submodule handling, where the superproject\ncontrols the existence of the submodule (just as current submodule\nhandling), but the submodule HEAD is not detached, but following the same\nbranch name as the superproject.\n\nCurrent situation visualized:\n\n  superproject\n  HEAD -> \"<branch name>\" -> OID\n                              |\n  submodule                   v\n  HEAD --------------------> OID\n\nThe OID in the submodule is controlled via the HEAD in the submodule that\nis set accordingly to the gitlink in the superproject. Confusion can arise\nwhen the (detached) HEAD in the submodule doesn't match the superprojects\ngitlink.\n\nThis patch visualized:\n\n  superproject\n  HEAD -> \"<branch name>\" -> OID\n                 |\n  submodule      v\n  HEAD -> \"<branch name>\" -> OID\n\nAs there is a central point of truth in our setup (our Gerrit installation)\nwhich keeps the superproject and the submodule branches in sync, this\nought to look the same for the user; removing the \"detached HEAD\" in the\nsubmodule. git-status will still notice if there is an OID mismatch between\nthe gitlink and the submodules branch, but that is a race condition and\nshould be caught by Gerrit.\n\nThis changes the following commands in the superproject:\n\n  checkout -B/-b create branches in subs, too\n  checkout (-f): update branch in submodule (create if needed) and check\n                 it out; Pass the argument down literally if it is a branch\n                 name (e.g. \"checkout -f master\" will run a\n                            \"checkout -f master\" in the submodule as well)\n  clone: see checkout\n  reset --hard: see checkout\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n Documentation/git-checkout.txt | 13 +++--\n Makefile                       |  1 +\n branch.c                       | 84 +++++++++++++++++++++++++++++++++\n builtin.h                      |  1 +\n builtin/branch.c               | 35 ++++++++++++++\n builtin/checkout.c             | 76 ++++++++++++++++++++++++------\n builtin/clone.c                | 12 ++++-\n builtin/reset.c                | 41 ++++++++++++++--\n entry.c                        | 51 +++++++++++++-------\n git-submodule.sh               | 24 +++++++++-\n git.c                          |  1 +\n submodule-move-head.c          | 81 ++++++++++++++++++++++++++++++++\n submodule-move-head.h          | 22 +++++++++\n submodule.c                    | 14 ++++++\n submodule.h                    |  8 +++-\n t/lib-submodule-update.sh      | 86 +++++++++++++++++++++++++++++++++-\n t/t1013-read-tree-submodule.sh |  1 +\n t/t2013-checkout-submodule.sh  |  4 ++\n t/t7112-reset-submodule.sh     |  5 ++\n unpack-trees.c                 | 20 ++++++--\n unpack-trees.h                 |  9 ++++\n 21 files changed, 536 insertions(+), 53 deletions(-)\n create mode 100644 submodule-move-head.c\n create mode 100644 submodule-move-head.h\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 6acc3d98e7..d9ca9e9e24 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -268,13 +268,12 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\n \tworktree.\n \n --[no-]recurse-submodules::\n-\tUsing --recurse-submodules will update the content of all initialized\n-\tsubmodules according to the commit recorded in the superproject. If\n-\tlocal modifications in a submodule would be overwritten the checkout\n-\twill fail unless `-f` is used. If nothing (or --no-recurse-submodules)\n-\tis used, the work trees of submodules will not be updated.\n-\tJust like linkgit:git-submodule[1], this will detach the\n-\tsubmodules HEAD.\n+\tUsing --recurse-submodules will update the content and current\n+\tbranch of all initialized submodules in addition to the\n+\tsuperproject. If local modifications in a submodule would be\n+\toverwritten, the checkout will fail unless `-f` is used. If\n+\tnothing (or --no-recurse-submodules) is used, the work trees of\n+\tsubmodules will not be updated.\n \n --no-guess::\n \tDo not attempt to create a branch if a remote tracking branch\ndiff --git a/Makefile b/Makefile\nindex 1a44c811aa..132c336bbf 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -989,6 +989,7 @@ LIB_OBJS += streaming.o\n LIB_OBJS += string-list.o\n LIB_OBJS += submodule.o\n LIB_OBJS += submodule-config.o\n+LIB_OBJS += submodule-move-head.o\n LIB_OBJS += sub-process.o\n LIB_OBJS += symlinks.o\n LIB_OBJS += tag.o\ndiff --git a/branch.c b/branch.c\nindex 28b81a7e02..9212a0a256 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -1,9 +1,13 @@\n #include \"git-compat-util.h\"\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"repository.h\"\n+#include \"submodule.h\"\n #include \"branch.h\"\n #include \"refs.h\"\n #include \"refspec.h\"\n+#include \"tree-walk.h\"\n+#include \"run-command.h\"\n #include \"remote.h\"\n #include \"commit.h\"\n #include \"worktree.h\"\n@@ -242,6 +246,76 @@ N_(\"\\n\"\n \"will track its remote counterpart, you may want to use\\n\"\n \"\\\"git push -u\\\" to set the upstream config as you push.\");\n \n+static void create_branch_in_submodule(const char *name, const char *start_name,\n+\t\t   const char *start_ref, const struct object_id *start_oid,\n+\t\t   int force, int reflog, int clobber_head,\n+\t\t   int quiet, enum branch_track track,\n+\t\t   int checking_out_branch,\n+\t\t   const char *sub_path, const struct object_id *entry_oid)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\ttrace_printf(\"create_branch_in_submodule %s\", sub_path);\n+\n+\tprepare_submodule_repo_env(&cp.env_array);\n+\tcp.git_cmd = 1;\n+\tcp.dir = sub_path;\n+\targv_array_push(&cp.args, \"branch--helper\");\n+\targv_array_pushf(&cp.args, \"--name=%s\", name);\n+\tif (checking_out_branch) {\n+\t\targv_array_pushf(&cp.args, \"--start_name=%s\",\n+\t\t\t\t start_ref);\n+\t} else {\n+\t\targv_array_pushf(&cp.args, \"--start_name=%s\",\n+\t\t\t\t oid_to_hex(entry_oid));\n+\t}\n+\targv_array_pushf(&cp.args, \"--force=%d\", force);\n+\targv_array_pushf(&cp.args, \"--reflog=%d\", reflog);\n+\targv_array_pushf(&cp.args, \"--clobber_head=%d\", clobber_head);\n+\targv_array_pushf(&cp.args, \"--quiet=%d\", quiet);\n+\targv_array_pushf(&cp.args, \"--track=%d\", track);\n+\n+\tif (run_command(&cp))\n+\t\tfprintf(stderr, \"process for submodule '%s' failed\", sub_path);\n+\tchild_process_clear(&cp);\n+}\n+\n+static void create_branch_in_submodules(const char *name, const char *start_name,\n+\t\t   const char *start_ref, const struct object_id *start_oid,\n+\t\t   int force, int reflog, int clobber_head,\n+\t\t   int quiet, enum branch_track track, struct strbuf *rec_path)\n+{\n+\n+\tint checking_out_branch = start_ref && starts_with(start_ref, \"refs/heads/\");\n+\tvoid *buf;\n+\tstruct tree_desc tree;\n+\tstruct name_entry entry;\n+\tint rec_path_len = rec_path->len;\n+\n+\tbuf = fill_tree_descriptor(&tree, start_oid);\n+\tif (!buf)\n+\t\tdie(\"could not read %s for checkout\", start_name);\n+\n+\twhile (tree_entry(&tree, &entry)) {\n+\n+\t\tif (rec_path->len > 0)\n+\t\t\tstrbuf_addch(rec_path, '/');\n+\t\tstrbuf_addstr(rec_path, entry.path);\n+\n+\t\ttrace_printf(\"create_branch_in_submodules %s %o\", rec_path->buf, entry.mode);\n+\n+\t\tif (S_ISGITLINK(entry.mode) && is_submodule_active(the_repository, rec_path->buf))\n+\t\t\tcreate_branch_in_submodule(name, start_name, start_ref,\n+\t\t\t\t\t\tstart_oid, force, reflog, clobber_head,\n+\t\t\t\t\t\tquiet, track, checking_out_branch, rec_path->buf, entry.oid);\n+\t\telse if (S_ISDIR(entry.mode)) {\n+\t\t\tcreate_branch_in_submodules(name, start_name, start_ref, entry.oid, force, reflog, clobber_head, quiet, track, rec_path);\n+\t\t}\n+\t\tstrbuf_setlen(rec_path, rec_path_len);\n+\t}\n+\tfree(buf);\n+}\n+\n void create_branch(struct repository *r,\n \t\t   const char *name, const char *start_name,\n \t\t   int force, int clobber_head_ok, int reflog,\n@@ -251,6 +325,7 @@ void create_branch(struct repository *r,\n \tstruct object_id oid;\n \tchar *real_ref;\n \tstruct strbuf ref = STRBUF_INIT;\n+\tstruct strbuf sub_path = STRBUF_INIT;\n \tint forcing = 0;\n \tint dont_change_ref = 0;\n \tint explicit_tracking = 0;\n@@ -308,6 +383,14 @@ void create_branch(struct repository *r,\n \tif (reflog)\n \t\tlog_all_ref_updates = LOG_REFS_NORMAL;\n \n+\t/*\n+\t * NEEDSWORK: Doesn't handle errors part-way through very well.\n+\t */\n+\ttrace_printf(\"create_branch need to update subs: %d\", should_update_submodules());\n+\tif (behave_google_repo_like() && should_update_submodules())\n+\t\tcreate_branch_in_submodules(name, start_name, real_ref, &oid,\n+\t\t\t\t\t    force, reflog, clobber_head_ok, quiet, track, &sub_path);\n+\n \tif (!dont_change_ref) {\n \t\tstruct ref_transaction *transaction;\n \t\tstruct strbuf err = STRBUF_INIT;\n@@ -334,6 +417,7 @@ void create_branch(struct repository *r,\n \t\tsetup_tracking(ref.buf + 11, real_ref, track, quiet);\n \n \tstrbuf_release(&ref);\n+\tstrbuf_release(&sub_path);\n \tfree(real_ref);\n }\n \ndiff --git a/builtin.h b/builtin.h\nindex 6538932e99..68dc715392 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -135,6 +135,7 @@ extern int cmd_archive(int argc, const char **argv, const char *prefix);\n extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);\n extern int cmd_blame(int argc, const char **argv, const char *prefix);\n extern int cmd_branch(int argc, const char **argv, const char *prefix);\n+extern int cmd_branch_helper(int argc, const char **argv, const char *prefix);\n extern int cmd_bundle(int argc, const char **argv, const char *prefix);\n extern int cmd_cat_file(int argc, const char **argv, const char *prefix);\n extern int cmd_checkout(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 1be727209b..6ee45cbbaa 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -36,6 +36,11 @@ static const char * const builtin_branch_usage[] = {\n \tNULL\n };\n \n+static const char * const builtin_branch_helper_usage[] = {\n+\tN_(\"git branch--helper\"),\n+\tNULL\n+};\n+\n static const char *head;\n static struct object_id head_oid;\n \n@@ -578,6 +583,36 @@ static int edit_branch_description(const char *branch_name)\n \treturn 0;\n }\n \n+int cmd_branch_helper(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *name = NULL, *start_name = NULL;\n+\tint force = 0, reflog = 0, clobber_head = 0, quiet = 0;\n+\tenum branch_track track = BRANCH_TRACK_NEVER;\n+\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"name\", &name, N_(\"\"), N_(\"\")),\n+\t\tOPT_STRING(0, \"start_name\", &start_name, N_(\"\"), N_(\"\")),\n+\n+\t\tOPT_INTEGER(0, \"force\", &force, N_(\"\")),\n+\t\tOPT_INTEGER(0, \"reflog\", &reflog, N_(\"\")),\n+\t\tOPT_INTEGER(0, \"clobber_head\", &clobber_head, N_(\"\")),\n+\t\tOPT_INTEGER(0, \"quiet\", &quiet, N_(\"\")),\n+\n+\t\t/* implicit int -> enum conversion */\n+\t\tOPT_INTEGER(0, \"track\", &track, N_(\"\")),\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     builtin_branch_helper_usage, 0);\n+\tif (argc > 0)\n+\t\tdie (_(\"branchhelper doesn't know about %s\"), argv[0]);\n+\n+\tcreate_branch(the_repository, name, start_name, force, clobber_head,\n+\t\t      reflog, quiet, track);\n+\treturn 0;\n+}\n+\n int cmd_branch(int argc, const char **argv, const char *prefix)\n {\n \tint delete = 0, rename = 0, copy = 0, force = 0, list = 0;\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 08b0ac48f3..5a8a4e200f 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -3,6 +3,7 @@\n #include \"checkout.h\"\n #include \"lockfile.h\"\n #include \"parse-options.h\"\n+#include \"repository.h\"\n #include \"refs.h\"\n #include \"object-store.h\"\n #include \"commit.h\"\n@@ -22,6 +23,7 @@\n #include \"ll-merge.h\"\n #include \"resolve-undo.h\"\n #include \"submodule-config.h\"\n+#include \"submodule-move-head.h\"\n #include \"submodule.h\"\n #include \"advice.h\"\n \n@@ -422,11 +424,24 @@ static void describe_detached_head(const char *msg, struct commit *commit)\n \tstrbuf_release(&sb);\n }\n \n-static int reset_tree(struct tree *tree, const struct checkout_opts *o,\n+struct branch_info {\n+\tconst char *name; /* The short name used */\n+\tconst char *path; /* The full name of a real branch */\n+\tstruct commit *commit; /* The named commit */\n+\t/*\n+\t * if not null the branch is detached because it's already\n+\t * checked out in this checkout\n+\t */\n+\tchar *checkout;\n+};\n+\n+static int reset_tree(struct branch_info *b, const struct checkout_opts *o,\n \t\t      int worktree, int *writeout_error)\n {\n \tstruct unpack_trees_options opts;\n+\tstruct submodule_move_head_options move_head_opts;\n \tstruct tree_desc tree_desc;\n+\tstruct tree *tree = get_commit_tree(b->commit);\n \n \tmemset(&opts, 0, sizeof(opts));\n \topts.head_idx = -1;\n@@ -438,6 +453,16 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,\n \topts.verbose_update = o->show_progress;\n \topts.src_index = &the_index;\n \topts.dst_index = &the_index;\n+\n+\tif (behave_google_repo_like()) {\n+\t\topts.move_head = unpack_trees_move_head;\n+\t\tmemset(&move_head_opts, 0, sizeof(move_head_opts));\n+\t\tmove_head_opts.force = 1;\n+\t\tmove_head_opts.new_ref = b->path;\n+\t\tmove_head_opts.target_ref = o->force_detach ? NULL : b->path;\n+\t\topts.unpack_data = &move_head_opts;\n+\t}\n+\n \tparse_tree(tree);\n \tinit_tree_desc(&tree_desc, tree->buffer, tree->size);\n \tswitch (unpack_trees(1, &tree_desc, &opts)) {\n@@ -457,17 +482,6 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,\n \t}\n }\n \n-struct branch_info {\n-\tconst char *name; /* The short name used */\n-\tconst char *path; /* The full name of a real branch */\n-\tstruct commit *commit; /* The named commit */\n-\t/*\n-\t * if not null the branch is detached because it's already\n-\t * checked out in this checkout\n-\t */\n-\tchar *checkout;\n-};\n-\n static void setup_branch_path(struct branch_info *branch)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -586,7 +600,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \n \tresolve_undo_clear();\n \tif (opts->force) {\n-\t\tret = reset_tree(get_commit_tree(new_branch_info->commit),\n+\t\tret = reset_tree(new_branch_info,\n \t\t\t\t opts, 1, writeout_error);\n \t\tif (ret)\n \t\t\treturn ret;\n@@ -594,6 +608,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n \t\tstruct unpack_trees_options topts;\n+\t\tstruct submodule_move_head_options mopts;\n \n \t\tmemset(&topts, 0, sizeof(topts));\n \t\ttopts.head_idx = -1;\n@@ -609,6 +624,16 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\treturn 1;\n \t\t}\n \n+\t\tif (behave_google_repo_like()) {\n+\t\t\ttopts.move_head = unpack_trees_move_head;\n+\t\t\tmemset(&mopts, 0, sizeof(mopts));\n+\t\t\tmopts.old_ref = old_branch_info->path;\n+\t\t\tmopts.new_ref = new_branch_info->path;\n+\t\t\tmopts.target_ref = opts->force_detach\n+\t\t\t\t\t? NULL : new_branch_info->path;\n+\t\t\ttopts.unpack_data = &mopts;\n+\t\t}\n+\n \t\t/* 2-way merge to the new branch */\n \t\ttopts.initial_checkout = is_cache_unborn();\n \t\ttopts.update = 1;\n@@ -674,7 +699,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\to.verbosity = 0;\n \t\t\twork = write_tree_from_memory(&o);\n \n-\t\t\tret = reset_tree(get_commit_tree(new_branch_info->commit),\n+\t\t\tret = reset_tree(new_branch_info,\n \t\t\t\t\t opts, 1,\n \t\t\t\t\t writeout_error);\n \t\t\tif (ret)\n@@ -689,7 +714,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t\t\t  &result);\n \t\t\tif (ret < 0)\n \t\t\t\texit(128);\n-\t\t\tret = reset_tree(get_commit_tree(new_branch_info->commit),\n+\t\t\tret = reset_tree(new_branch_info,\n \t\t\t\t\t opts, 0,\n \t\t\t\t\t writeout_error);\n \t\t\tstrbuf_release(&o.obuf);\n@@ -724,6 +749,18 @@ static void report_tracking(struct branch_info *new_branch_info)\n \tstrbuf_release(&sb);\n }\n \n+static void create_symref_in_submodules(const char *symref, const char *target, const char *logmsg)\n+{\n+\tint i = 0;\n+\tfor (i = 0; i < active_nr; i++) {\n+\t\tconst struct cache_entry *ce = active_cache[i];\n+\t\tif (!S_ISGITLINK(ce->ce_mode) || !is_submodule_active(the_repository, ce->name))\n+\t\t\tcontinue;\n+\n+\t\tcreate_symref_in_submodule(ce->name, symref, target, logmsg);\n+\t}\n+}\n+\n static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\t\t   struct branch_info *old_branch_info,\n \t\t\t\t   struct branch_info *new_branch_info)\n@@ -734,6 +771,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\tif (opts->new_orphan_branch) {\n \t\t\tchar *refname;\n \n+\t\t\tif (should_update_submodules())\n+\t\t\t\tdie(\"--orphan --recurse-submodules is not implemented\");\n+\n \t\t\trefname = mkpathdup(\"refs/heads/%s\", opts->new_orphan_branch);\n \t\t\tif (opts->new_branch_log &&\n \t\t\t    !should_autocreate_reflog(refname)) {\n@@ -787,6 +827,12 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n+\t\t/*\n+\t\t * NEEDSWORK: We don't handle attachment on checkout <branch>\n+\t\t * yet.\n+\t\t */\n+\t\tif (opts->new_branch && should_update_submodules() && behave_google_repo_like())\n+\t\t\tcreate_symref_in_submodules(\"HEAD\", new_branch_info->path, msg.buf);\n \t\tif (create_symref(\"HEAD\", new_branch_info->path, msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 7c7f98c72c..c91ec0f5f0 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -28,6 +28,7 @@\n #include \"run-command.h\"\n #include \"connected.h\"\n #include \"packfile.h\"\n+#include \"submodule.h\"\n #include \"list-objects-filter-options.h\"\n #include \"object-store.h\"\n \n@@ -744,7 +745,6 @@ static int checkout(int submodule_progress)\n \t\tif (!starts_with(head, \"refs/heads/\"))\n \t\t\tdie(_(\"HEAD not found below refs/heads!\"));\n \t}\n-\tfree(head);\n \n \t/* We need to be in the new work tree for the checkout */\n \tsetup_work_tree();\n@@ -773,8 +773,17 @@ static int checkout(int submodule_progress)\n \t\t\t   oid_to_hex(&oid), \"1\", NULL);\n \n \tif (!err && (option_recurse_submodules.nr > 0)) {\n+\t\tconst char *branch;\n \t\tstruct argv_array args = ARGV_ARRAY_INIT;\n \t\targv_array_pushl(&args, \"submodule\", \"update\", \"--init\", \"--recursive\", NULL);\n+\t\tif (behave_google_repo_like()) {\n+\t\t\tif (!strcmp(head, \"HEAD\"))\n+\t\t\t\t; /* detach HEAD in submodules, too. */\n+\t\t\telse if (skip_prefix(head, \"refs/heads/\", &branch))\n+\t\t\t\targv_array_pushl(&args, \"--checkout-branch\", branch, NULL);\n+\t\t\telse\n+\t\t\t\tBUG(\"HEAD not found below refs/heads!\");\n+\t\t}\n \n \t\tif (option_shallow_submodules == 1)\n \t\t\targv_array_push(&args, \"--depth=1\");\n@@ -792,6 +801,7 @@ static int checkout(int submodule_progress)\n \t\targv_array_clear(&args);\n \t}\n \n+\tfree(head);\n \treturn err;\n }\n \ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 59898c972e..108b2b97c7 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -24,6 +24,7 @@\n #include \"cache-tree.h\"\n #include \"submodule.h\"\n #include \"submodule-config.h\"\n+#include \"submodule-move-head.h\"\n \n #define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000)\n \n@@ -44,12 +45,16 @@ static inline int is_merge(void)\n \treturn !access(git_path_merge_head(the_repository), F_OK);\n }\n \n-static int reset_index(const struct object_id *oid, int reset_type, int quiet)\n+static int reset_index(const char *rev, const struct object_id *oid, int reset_type, int quiet)\n {\n-\tint i, nr = 0;\n+\tint i, nr = 0, flags = 0;\n \tstruct tree_desc desc[2];\n \tstruct tree *tree;\n \tstruct unpack_trees_options opts;\n+\tstruct submodule_move_head_options mopts;\n+\tchar *current_branch = NULL;\n+\tstruct object_id discard;\n+\tchar *new_ref = NULL;\n \tint ret = -1;\n \n \tmemset(&opts, 0, sizeof(opts));\n@@ -67,6 +72,31 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)\n \t\tbreak;\n \tcase HARD:\n \t\topts.update = 1;\n+\n+\t\tif (behave_google_repo_like()) {\n+\t\t\t/*\n+\t\t\t * Submodule handling:\n+\t\t\t * - unless we are detached, attach HEAD in submodules\n+\t\t\t * - if rev is a branch name, use that branch instead of oid in\n+\t\t\t *   submodules.\n+\t\t\t */\n+\t\t\tcurrent_branch = resolve_refdup(\"HEAD\", 0, NULL, &flags);\n+\t\t\tif (!(flags & REF_ISSYMREF))\n+\t\t\t\tcurrent_branch = NULL;\n+\t\t\tif (dwim_ref(rev, strlen(rev), &discard, &new_ref) != 1 ||\n+\t\t\t    !starts_with(new_ref, \"refs/heads/\")) {\n+\t\t\t\tfree(new_ref);\n+\t\t\t\tnew_ref = NULL;\n+\t\t\t}\n+\n+\t\t\topts.move_head = unpack_trees_move_head;\n+\n+\t\t\tmemset(&mopts, 0, sizeof(mopts));\n+\t\t\tmopts.force = 1;\n+\t\t\tmopts.new_ref = new_ref;\n+\t\t\tmopts.target_ref = current_branch;\n+\t\t\topts.unpack_data = &mopts;\n+\t\t}\n \t\t/* fallthrough */\n \tdefault:\n \t\topts.reset = 1;\n@@ -103,6 +133,8 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)\n out:\n \tfor (i = 0; i < nr; i++)\n \t\tfree((void *)desc[i].buffer);\n+\tfree(current_branch);\n+\tfree(new_ref);\n \treturn ret;\n }\n \n@@ -321,6 +353,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\toidcpy(&oid, the_hash_algo->empty_tree);\n \t} else if (!pathspec.nr) {\n \t\tstruct commit *commit;\n+\n \t\tif (get_oid_committish(rev, &oid))\n \t\t\tdie(_(\"Failed to resolve '%s' as a valid revision.\"), rev);\n \t\tcommit = lookup_commit_reference(the_repository, &oid);\n@@ -393,9 +426,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \t\t\t\t}\n \t\t\t}\n \t\t} else {\n-\t\t\tint err = reset_index(&oid, reset_type, quiet);\n+\t\t\tint err = reset_index(rev, &oid, reset_type, quiet);\n \t\t\tif (reset_type == KEEP && !err)\n-\t\t\t\terr = reset_index(&oid, MIXED, quiet);\n+\t\t\t\terr = reset_index(rev, &oid, MIXED, quiet);\n \t\t\tif (err)\n \t\t\t\tdie(_(\"Could not reset index file to revision '%s'.\"), rev);\n \t\t}\ndiff --git a/entry.c b/entry.c\nindex 0a3c451f5f..7943602f13 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -2,11 +2,23 @@\n #include \"blob.h\"\n #include \"object-store.h\"\n #include \"dir.h\"\n+#include \"unpack-trees.h\"\n #include \"streaming.h\"\n #include \"submodule.h\"\n #include \"progress.h\"\n #include \"fsmonitor.h\"\n \n+/* NEEDSWORK: share code with unpack-trees.c */\n+static int move_head(const struct unpack_trees_options *o, const char *path, const char *old, const char *new, unsigned flags)\n+{\n+\tif (behave_google_repo_like()) {\n+\t\tif (!o || !o->move_head)\n+\t\t\treturn submodule_move_head(path, old, new, flags);\n+\t\treturn o->move_head(o, path, old, new, flags);\n+\t} else\n+\t\treturn submodule_move_head(path, old, new, flags);\n+}\n+\n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n {\n@@ -251,7 +263,7 @@ int finish_delayed_checkout(struct checkout *state)\n \treturn errs;\n }\n \n-static int write_entry(struct cache_entry *ce,\n+static int write_entry(struct unpack_trees_options *o, struct cache_entry *ce,\n \t\t       char *path, const struct checkout *state, int to_tempfile)\n {\n \tunsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;\n@@ -357,7 +369,7 @@ static int write_entry(struct cache_entry *ce,\n \t\t\treturn error(\"cannot create submodule directory %s\", path);\n \t\tsub = submodule_from_ce(ce);\n \t\tif (sub)\n-\t\t\treturn submodule_move_head(ce->name,\n+\t\t\treturn move_head(o, ce->name,\n \t\t\t\tNULL, oid_to_hex(&ce->oid),\n \t\t\t\tstate->force ? SUBMODULE_MOVE_HEAD_FORCE : 0);\n \t\tbreak;\n@@ -427,22 +439,15 @@ static void mark_colliding_entries(const struct checkout *state,\n \t}\n }\n \n-/*\n- * Write the contents from ce out to the working tree.\n- *\n- * When topath[] is not NULL, instead of writing to the working tree\n- * file named by ce, a temporary file is created by this function and\n- * its name is returned in topath[], which must be able to hold at\n- * least TEMPORARY_FILENAME_LENGTH bytes long.\n- */\n-int checkout_entry(struct cache_entry *ce,\n-\t\t   const struct checkout *state, char *topath)\n+int unpack_trees_checkout_entry(struct unpack_trees_options *o,\n+\t\t\t\tstruct cache_entry *ce,\n+\t\t\t\tconst struct checkout *state, char *topath)\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \tstruct stat st;\n \n \tif (topath)\n-\t\treturn write_entry(ce, topath, state, 1);\n+\t\treturn write_entry(o, ce, topath, state, 1);\n \n \tstrbuf_reset(&path);\n \tstrbuf_add(&path, state->base_dir, state->base_dir_len);\n@@ -466,10 +471,10 @@ int checkout_entry(struct cache_entry *ce,\n \t\t\t\tif (!(st.st_mode & S_IFDIR))\n \t\t\t\t\tunlink_or_warn(ce->name);\n \n-\t\t\t\treturn submodule_move_head(ce->name,\n+\t\t\t\treturn move_head(o, ce->name,\n \t\t\t\t\tNULL, oid_to_hex(&ce->oid), 0);\n \t\t\t} else\n-\t\t\t\treturn submodule_move_head(ce->name,\n+\t\t\t\treturn move_head(o, ce->name,\n \t\t\t\t\t\"HEAD\", oid_to_hex(&ce->oid),\n \t\t\t\t\tstate->force ? SUBMODULE_MOVE_HEAD_FORCE : 0);\n \t\t}\n@@ -506,5 +511,19 @@ int checkout_entry(struct cache_entry *ce,\n \t\treturn 0;\n \n \tcreate_directories(path.buf, path.len, state);\n-\treturn write_entry(ce, path.buf, state, 0);\n+\treturn write_entry(o, ce, path.buf, state, 0);\n+}\n+\n+/*\n+ * Write the contents from ce out to the working tree.\n+ *\n+ * When topath[] is not NULL, instead of writing to the working tree\n+ * file named by ce, a temporary file is created by this function and\n+ * its name is returned in topath[], which must be able to hold at\n+ * least TEMPORARY_FILENAME_LENGTH bytes long.\n+ */\n+int checkout_entry(struct cache_entry *ce,\n+\t\t   const struct checkout *state, char *topath)\n+{\n+\treturn unpack_trees_checkout_entry(NULL, ce, state, topath);\n }\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 5e608f8bad..1d228c9df7 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -434,6 +434,18 @@ fetch_in_submodule () (\n \tesac\n )\n \n+# usage: checkout_in_submoodule \"$sm_path\" \"$command\" \"${branch:-}\" \"$rev\"\n+checkout_in_submodule () (\n+\tsanitize_submodule_env &&\n+\tcd \"$1\" &&\n+\tif test -n \"$3\"\n+\tthen\n+\t\t$2 -B \"$3\" \"$4\"\n+\telse\n+\t\t$2 \"$4\"\n+\tfi\n+)\n+\n #\n # Update each submodule path to correct revision, using clone and checkout as needed\n #\n@@ -486,6 +498,11 @@ cmd_update()\n \t\t--recursive)\n \t\t\trecursive=1\n \t\t\t;;\n+\t\t--checkout-branch)\n+\t\t\tupdate=\"checkout\"\n+\t\t\tcheckout_dest=$2\n+\t\t\tshift\n+\t\t\t;;\n \t\t--checkout)\n \t\t\tupdate=\"checkout\"\n \t\t\t;;\n@@ -602,6 +619,11 @@ cmd_update()\n \t\t\t\tdie \"$(eval_gettext \"Fetched in submodule path '\\$displaypath', but it did not contain \\$sha1. Direct fetching of that commit failed.\")\"\n \t\t\tfi\n \n+\t\t\tif test -n \"$checkout_dest\" && test \"$update_module\" != checkout\n+\t\t\tthen\n+\t\t\t\tdie \"Cannot use --checkout-branch with update mode '$update_module'\"\n+\t\t\tfi\n+\n \t\t\tmust_die_on_failure=\n \t\t\tcase \"$update_module\" in\n \t\t\tcheckout)\n@@ -631,7 +653,7 @@ cmd_update()\n \t\t\t\tdie \"$(eval_gettext \"Invalid update mode '$update_module' for submodule path '$path'\")\"\n \t\t\tesac\n \n-\t\t\tif (sanitize_submodule_env; cd \"$sm_path\" && $command \"$sha1\")\n+\t\t\tif checkout_in_submodule \"$sm_path\" \"$command\" \"$checkout_dest\" \"$sha1\"\n \t\t\tthen\n \t\t\t\tsay \"$say_msg\"\n \t\t\telif test -n \"$must_die_on_failure\"\ndiff --git a/git.c b/git.c\nindex 4d53a3d50d..3f13b4e7cf 100644\n--- a/git.c\n+++ b/git.c\n@@ -450,6 +450,7 @@ static struct cmd_struct commands[] = {\n \t{ \"bisect--helper\", cmd_bisect__helper, RUN_SETUP },\n \t{ \"blame\", cmd_blame, RUN_SETUP },\n \t{ \"branch\", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },\n+\t{ \"branch--helper\", cmd_branch_helper, RUN_SETUP },\n \t{ \"bundle\", cmd_bundle, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"cat-file\", cmd_cat_file, RUN_SETUP },\n \t{ \"check-attr\", cmd_check_attr, RUN_SETUP },\ndiff --git a/submodule-move-head.c b/submodule-move-head.c\nnew file mode 100644\nindex 0000000000..4c8f49066f\n--- /dev/null\n+++ b/submodule-move-head.c\n@@ -0,0 +1,81 @@\n+#include \"cache.h\"\n+#include \"submodule-move-head.h\"\n+#include \"repository.h\"\n+#include \"submodule.h\"\n+#include \"refs.h\"\n+#include \"unpack-trees.h\"\n+#include \"run-command.h\"\n+\n+void create_symref_in_submodule(const char *path, const char *symref, const char *target, const char *logmsg)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\t/* NEEDSWORK: What about sub-submodules? */\n+\tprepare_submodule_repo_env(&cp.env_array);\n+\tcp.git_cmd = 1;\n+\tcp.dir = path;\n+\targv_array_pushl(&cp.args, \"symbolic-ref\", \"-m\", logmsg, symref, target, NULL);\n+\n+\tif (run_command(&cp))\n+\t\tdie(\"process for submodule '%s' failed\", path);\n+}\n+\n+static void create_ref_in_submodule(const char *path, const char *ref, const char *value)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\t/* NEEDSWORK: set a reasonable reflog message. */\n+\tprepare_submodule_repo_env(&cp.env_array);\n+\tcp.git_cmd = 1;\n+\tcp.dir = path;\n+\targv_array_pushl(&cp.args, \"update-ref\", ref, value, sha1_to_hex(null_sha1), NULL);\n+\n+\tif (run_command(&cp))\n+\t\tdie(\"process for submodule '%s' failed\", path);\n+}\n+\n+static int ref_exists_in_submodule(const char *submodule_path, const char *refname)\n+{\n+\tstruct ref_store *refs = get_submodule_ref_store(submodule_path);\n+\tif (!refs)\n+\t\treturn 0;\n+\treturn refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, NULL, NULL) != NULL;\n+}\n+\n+int unpack_trees_move_head(const struct unpack_trees_options *opt, const char *path, const char *old, const char *new, unsigned flags)\n+{\n+\tstruct submodule_move_head_options *o = opt->unpack_data;\n+\tconst char *new_ref = o->new_ref;\n+\tconst char *target_ref = o->target_ref;\n+\tconst char *old_commit = old;\n+\tconst char *new_commit = new;\n+\n+\t/*\n+\t * NEEDSWORK:\n+\t * - set log message\n+\t * - what about sub-submodules?\n+\t */\n+\n+\tif (!is_submodule_active(the_repository, path))\n+\t\treturn 0;\n+\n+\tif (old) {\n+\t\tif (o->force)\n+\t\t\told_commit = \"HEAD\";\n+\t\telse if (o->old_ref && ref_exists_in_submodule(path, o->old_ref))\n+\t\t\told_commit = o->old_ref;\n+\t}\n+\tif (new_ref && new && ref_exists_in_submodule(path, new_ref))\n+\t\tnew_commit = new_ref;\n+\n+\tif (target_ref)\n+\t\tflags |= SUBMODULE_MOVE_HEAD_SKIP_REF_UPDATE;\n+\tif (submodule_move_head(path, old_commit, new_commit, flags) < 0)\n+\t\treturn -1;\n+\tif (new && target_ref && !(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {\n+\t\tif (!ref_exists_in_submodule(path, target_ref))\n+\t\t\tcreate_ref_in_submodule(path, target_ref, new);\n+\t\tcreate_symref_in_submodule(path, \"HEAD\", target_ref, \"msg\");\n+\t}\n+\treturn 0;\n+}\ndiff --git a/submodule-move-head.h b/submodule-move-head.h\nnew file mode 100644\nindex 0000000000..80e2679038\n--- /dev/null\n+++ b/submodule-move-head.h\n@@ -0,0 +1,22 @@\n+#ifndef SUBMODULE_MOVE_HEAD_H\n+#define SUBMODULE_MOVE_HEAD_H\n+\n+struct unpack_trees_options;\n+\n+/* NEEDSWORK: document */\n+struct submodule_move_head_options {\n+\tint force;\n+\tconst char *old_ref;\n+\tconst char *new_ref;\n+\tconst char *target_ref;\n+};\n+\n+/*\n+ * For use as unpack_trees_options.move_head. Parameters should be a\n+ * struct submodule_move_head_options * in unpack_trees_options.unpack_data.\n+ */\n+extern int unpack_trees_move_head(const struct unpack_trees_options *opt, const char *path, const char *old, const char *new, unsigned flags);\n+\n+extern void create_symref_in_submodule(const char *path, const char *symref, const char *target, const char *logmsg);\n+\n+#endif /* SUBMODULE_MOVE_HEAD_H */\ndiff --git a/submodule.c b/submodule.c\nindex 6415cc5580..001f5eb364 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -30,6 +30,17 @@ static int initialized_fetch_ref_tips;\n static struct oid_array ref_tips_before_fetch;\n static struct oid_array ref_tips_after_fetch;\n \n+int behave_google_repo_like(void)\n+{\n+\tstatic int google_repo_like = -1;\n+\n+\tif (google_repo_like == -1)\n+\t\tgit_config_get_bool(\"submodule.repolike\", &google_repo_like);\n+\n+\treturn google_repo_like;\n+}\n+\n+\n /*\n  * Check if the .gitmodules file is unmerged. Parsing of the .gitmodules file\n  * will be disabled because we can't guess what might be configured in\n@@ -1703,6 +1714,9 @@ int submodule_move_head(const char *path,\n \n \tif (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {\n \t\tif (new_head) {\n+\t\t\tif (flags & SUBMODULE_MOVE_HEAD_SKIP_REF_UPDATE)\n+\t\t\t\tgoto out;\n+\n \t\t\tchild_process_init(&cp);\n \t\t\t/* also set the HEAD accordingly */\n \t\t\tcp.git_cmd = 1;\ndiff --git a/submodule.h b/submodule.h\nindex a680214c01..91ff94b0cb 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -124,8 +124,10 @@ int push_unpushed_submodules(struct repository *r,\n  */\n int submodule_to_gitdir(struct strbuf *buf, const char *submodule);\n \n-#define SUBMODULE_MOVE_HEAD_DRY_RUN (1<<0)\n-#define SUBMODULE_MOVE_HEAD_FORCE   (1<<1)\n+#define SUBMODULE_MOVE_HEAD_DRY_RUN             (1<<0)\n+#define SUBMODULE_MOVE_HEAD_FORCE               (1<<1)\n+#define SUBMODULE_MOVE_HEAD_SKIP_REF_UPDATE     (1<<2)\n+\n int submodule_move_head(const char *path,\n \t\t\tconst char *old,\n \t\t\tconst char *new_head,\n@@ -150,4 +152,6 @@ void absorb_git_dir_into_superproject(const char *prefix,\n  */\n const char *get_superproject_working_tree(void);\n \n+int behave_google_repo_like(void);\n+\n #endif\ndiff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh\nindex 016391723c..71d5b506de 100755\n--- a/t/lib-submodule-update.sh\n+++ b/t/lib-submodule-update.sh\n@@ -657,7 +657,16 @@ test_submodule_recursing_with_args_common() {\n \t\t\ttest_submodule_content sub1 origin/add_sub1\n \t\t)\n \t'\n-\ttest_expect_success \"$command: submodule branch is not changed, detach HEAD instead\" '\n+\n+\tif test \"$KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED\" = 1\n+\tthen\n+\t\tRESULT=failure\n+\telse\n+\t\tRESULT=success\n+\tfi\n+\tif test \"$KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED\" = \"read-tree\"\n+\tthen\n+\ttest_expect_$RESULT \"$command: submodule branch is not changed, detach HEAD\" '\n \t\tprolog &&\n \t\treset_work_tree_to_interested add_sub1 &&\n \t\t(\n@@ -670,9 +679,82 @@ test_submodule_recursing_with_args_common() {\n \t\t\ttest_submodule_content sub1 origin/modify_sub1 &&\n \t\t\tgit -C sub1 rev-parse keep_branch >actual &&\n \t\t\ttest_cmp expect actual &&\n-\t\t\ttest_must_fail git -C sub1 symbolic-ref HEAD\n+\t\t\ttest_must_fail git -C sub1 symbolic-ref HEAD >actual\n \t\t)\n \t'\n+\telif test \"$KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED\" = \"checkout -B current\"\n+\tthen\n+\ttest_expect_$RESULT \"$command: submodule branch is changed to 'current'\" '\n+\t\tprolog &&\n+\t\treset_work_tree_to_interested add_sub1 &&\n+\t\t(\n+\t\t\tcd submodule_update &&\n+\t\t\tgit -C sub1 checkout -b keep_branch &&\n+\t\t\tgit -C sub1 rev-parse HEAD >expect &&\n+\t\t\tgit branch -t modify_sub1 origin/modify_sub1 &&\n+\t\t\t$command modify_sub1 &&\n+\t\t\ttest_superproject_content origin/modify_sub1 &&\n+\t\t\ttest_submodule_content sub1 origin/modify_sub1 &&\n+\t\t\tgit -C sub1 rev-parse keep_branch >actual &&\n+\t\t\ttest_cmp expect actual &&\n+\t\t\techo refs/heads/current >expect &&\n+\t\t\tgit -C sub1 symbolic-ref HEAD >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\telif test \"$KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED\" = \"reset\"\n+\tthen\n+\ttest_expect_$RESULT \"$command: submodule branch is changed to superproject, resetting to target\" '\n+\t\tprolog &&\n+\t\treset_work_tree_to_interested add_sub1 &&\n+\t\t(\n+\t\t\tcd submodule_update &&\n+\t\t\tgit -C sub1 checkout -b keep_branch &&\n+\t\t\tgit -C sub1 rev-parse HEAD >expect &&\n+\t\t\tgit branch -t modify_sub1 origin/modify_sub1 &&\n+\t\t\tgit checkout -b newbranch &&\n+\t\t\t$command modify_sub1 &&\n+\n+\t\t\t# we modified the wt\n+\t\t\ttest_superproject_content origin/modify_sub1 &&\n+\t\t\ttest_submodule_content sub1 origin/modify_sub1 &&\n+\n+\t\t\t# keep_branch does not change\n+\t\t\tgit -C sub1 rev-parse keep_branch >actual &&\n+\t\t\ttest_cmp expect actual &&\n+\n+\t\t\t# the submodule is attached to the same branch as the superproject\n+\t\t\tgit -C sub1 symbolic-ref HEAD >actual &&\n+\t\t\techo refs/heads/newbranch >expect &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\telse\n+\ttest_expect_$RESULT \"$command: submodule branch is changed\" '\n+\t\tprolog &&\n+\t\treset_work_tree_to_interested add_sub1 &&\n+\t\t(\n+\t\t\tcd submodule_update &&\n+\t\t\tgit -C sub1 checkout -b keep_branch &&\n+\t\t\tgit -C sub1 rev-parse HEAD >expect &&\n+\t\t\tgit branch -t modify_sub1 origin/modify_sub1 &&\n+\t\t\t$command modify_sub1 &&\n+\n+\t\t\t# modified wt\n+\t\t\ttest_superproject_content origin/modify_sub1 &&\n+\t\t\ttest_submodule_content sub1 origin/modify_sub1 &&\n+\n+\t\t\t# unrelated keep_branch is fine\n+\t\t\tgit -C sub1 rev-parse keep_branch >actual &&\n+\t\t\ttest_cmp expect actual &&\n+\n+\t\t\t# submodule ref is checked out\n+\t\t\tgit -C sub1 symbolic-ref HEAD >actual &&\n+\t\t\techo refs/heads/modify_sub1 >expect &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\tfi\n \n \t# Replacing a tracked file with a submodule produces a checked out submodule\n \ttest_expect_success \"$command: replace tracked file with submodule checks out submodule\" '\ndiff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh\nindex 91a6fafcb4..de59ebd121 100755\n--- a/t/t1013-read-tree-submodule.sh\n+++ b/t/t1013-read-tree-submodule.sh\n@@ -7,6 +7,7 @@ test_description='read-tree can handle submodules'\n \n KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1\n KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1\n+KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED=\"read-tree\"\n \n test_submodule_switch_recursing_with_args \"read-tree -u -m\"\n \ndiff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh\nindex 8f86b5f4b2..940f0fca20 100755\n--- a/t/t2013-checkout-submodule.sh\n+++ b/t/t2013-checkout-submodule.sh\n@@ -66,6 +66,10 @@ test_expect_success '\"checkout <submodule>\" honors submodule.*.ignore from .git/\n KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1\n test_submodule_switch_recursing_with_args \"checkout\"\n \n+KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED=\"checkout -B current\"\n+test_submodule_switch_recursing_with_args \"checkout -B current\"\n+unset KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED\n+\n test_submodule_forced_switch_recursing_with_args \"checkout -f\"\n \n test_submodule_switch \"git checkout\"\ndiff --git a/t/t7112-reset-submodule.sh b/t/t7112-reset-submodule.sh\nindex a1cb9ff858..c31acc6578 100755\n--- a/t/t7112-reset-submodule.sh\n+++ b/t/t7112-reset-submodule.sh\n@@ -8,13 +8,18 @@ test_description='reset can handle submodules'\n KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1\n KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1\n KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1\n+KNOWN_DIFFERENCE_SUBMODULE_REFS_NOT_UPDATED=\"reset\"\n \n+KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED=1\n test_submodule_switch_recursing_with_args \"reset --keep\"\n \n+unset KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED\n test_submodule_forced_switch_recursing_with_args \"reset --hard\"\n \n+KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED=1\n test_submodule_switch \"git reset --keep\"\n \n+unset KNOWN_FAILURE_SUBMODULE_REFS_NOT_UPDATED\n test_submodule_switch \"git reset --merge\"\n \n test_submodule_forced_switch \"git reset --hard\"\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 6d53cbfc86..f721d48f38 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -254,6 +254,16 @@ static void display_error_msgs(struct unpack_trees_options *o)\n \t\tfprintf(stderr, _(\"Aborting\\n\"));\n }\n \n+static int move_head(const struct unpack_trees_options *o, const char *path, const char *old, const char *new, unsigned flags)\n+{\n+\tif (behave_google_repo_like()) {\n+\t\tif (!o->move_head)\n+\t\t\treturn submodule_move_head(path, old, new, flags);\n+\t\treturn o->move_head(o, path, old, new, flags);\n+\t} else\n+\t\treturn submodule_move_head(path, old, new, flags);\n+}\n+\n static int check_submodule_move_head(const struct cache_entry *ce,\n \t\t\t\t     const char *old_id,\n \t\t\t\t     const char *new_id,\n@@ -268,7 +278,7 @@ static int check_submodule_move_head(const struct cache_entry *ce,\n \tif (o->reset)\n \t\tflags |= SUBMODULE_MOVE_HEAD_FORCE;\n \n-\tif (submodule_move_head(ce->name, old_id, new_id, flags))\n+\tif (move_head(o, ce->name, old_id, new_id, flags))\n \t\treturn o->gently ? -1 :\n \t\t\t\t   add_rejected_path(o, ERROR_WOULD_LOSE_SUBMODULE, ce->name);\n \treturn 0;\n@@ -304,12 +314,12 @@ static void load_gitmodules_file(struct index_state *index,\n  * Unlink the last component and schedule the leading directories for\n  * removal, such that empty directories get removed.\n  */\n-static void unlink_entry(const struct cache_entry *ce)\n+static void unlink_entry(const struct unpack_trees_options *o, const struct cache_entry *ce)\n {\n \tconst struct submodule *sub = submodule_from_ce(ce);\n \tif (sub) {\n \t\t/* state.force is set at the caller. */\n-\t\tsubmodule_move_head(ce->name, \"HEAD\", NULL,\n+\t\tmove_head(o, ce->name, \"HEAD\", NULL,\n \t\t\t\t    SUBMODULE_MOVE_HEAD_FORCE);\n \t}\n \tif (!check_leading_path(ce->name, ce_namelen(ce)))\n@@ -408,7 +418,7 @@ static int check_updates(struct unpack_trees_options *o)\n \t\tif (ce->ce_flags & CE_WT_REMOVE) {\n \t\t\tdisplay_progress(progress, ++cnt);\n \t\t\tif (o->update && !o->dry_run)\n-\t\t\t\tunlink_entry(ce);\n+\t\t\t\tunlink_entry(o, ce);\n \t\t}\n \t}\n \tremove_marked_cache_entries(index);\n@@ -450,7 +460,7 @@ static int check_updates(struct unpack_trees_options *o)\n \t\t\tdisplay_progress(progress, ++cnt);\n \t\t\tce->ce_flags &= ~CE_UPDATE;\n \t\t\tif (o->update && !o->dry_run) {\n-\t\t\t\terrs |= checkout_entry(ce, &state, NULL);\n+\t\t\t\terrs |= unpack_trees_checkout_entry(o, ce, &state, NULL);\n \t\t\t}\n \t\t}\n \t}\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 0135080a7b..ad98157d6e 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -9,12 +9,15 @@\n #define MAX_UNPACK_TREES 8\n \n struct cache_entry;\n+struct tree_desc;\n struct unpack_trees_options;\n struct exclude_list;\n \n typedef int (*merge_fn_t)(const struct cache_entry * const *src,\n \t\tstruct unpack_trees_options *options);\n \n+typedef int (*submodule_move_head_fn)(const struct unpack_trees_options *o, const char *path, const char *old, const char *new, unsigned flags);\n+\n enum unpack_trees_error_types {\n \tERROR_WOULD_OVERWRITE = 0,\n \tERROR_NOT_UPTODATE_FILE,\n@@ -65,6 +68,7 @@ struct unpack_trees_options {\n \tstruct dir_struct *dir;\n \tstruct pathspec *pathspec;\n \tmerge_fn_t fn;\n+\tsubmodule_move_head_fn move_head;\n \tconst char *msgs[NB_UNPACK_TREES_ERROR_TYPES];\n \tstruct argv_array msgs_to_free;\n \t/*\n@@ -86,6 +90,11 @@ struct unpack_trees_options {\n \tstruct exclude_list *el; /* for internal use */\n };\n \n+/* defined in entry.c, for internal use */\n+int unpack_trees_checkout_entry(struct unpack_trees_options *o,\n+\t\t\t\tstruct cache_entry *ce,\n+\t\t\t\tconst struct checkout *state, char *topath);\n+\n int unpack_trees(unsigned n, struct tree_desc *t,\n \t\t struct unpack_trees_options *options);\n \n-- \n2.20.1.97.g81188d93c3\n\n"}]}