{"thread":{"id":"35575","subject":"[WIP/PATCH 0/5] git checkout --recurse-submodules","startedAt":"2013-12-26T15:58:57Z","lastAt":"2013-12-27T14:14:26Z","messageCount":11,"participants":["Jonathan Nieder","Junio C Hamano","Jens Lehmann"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"232400","messageId":"20131226155857.GL20443@google.com","threadId":"35575","inReplyTo":null,"subject":"[WIP/PATCH 0/5] git checkout --recurse-submodules","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-12-26T15:58:57Z","receivedAt":"2013-12-26T15:58:57Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nThis patch series comes from\nhttps://github.com/jlehmann/git-submod-enhancements branch\nrecursive_submodule_checkout.  It needed some tiny tweaks to apply to\ncurrent \"master\" and build without warnings, but nothing major, and I\nhaven't sanity checked it much beyond that and letting the kind folks\nthat use Debian experimental play with it.\n\nI'm sending it out now to get review and ideas for what needs to\nhappen next to get this series in shape to be included in git.git.\n\nThoughts of all kinds welcome.\n\nThanks,\nJonathan\n\nJens Lehmann (5):\n  submodule: prepare for recursive checkout of submodules\n  submodule: teach unpack_trees() to remove submodule contents\n  submodule: teach unpack_trees() to repopulate submodules\n  submodule: teach unpack_trees() to update submodules\n  Teach checkout to recursively checkout submodules\n\n Documentation/git-checkout.txt |   8 ++\n builtin/checkout.c             |  14 +++\n entry.c                        |  19 +++-\n submodule.c                    | 217 ++++++++++++++++++++++++++++++++++++++++-\n submodule.h                    |  11 +++\n t/t2013-checkout-submodule.sh  | 215 +++++++++++++++++++++++++++++++++++++++-\n unpack-trees.c                 |  94 ++++++++++++++----\n unpack-trees.h                 |   1 +\n wrapper.c                      |   3 +\n 9 files changed, 556 insertions(+), 26 deletions(-)\n"},{"id":"232401","messageId":"20131226160239.GM20443@google.com","threadId":"35575","inReplyTo":"20131226155857.GL20443@google.com","subject":"[PATCH 1/5] submodule: prepare for recursive checkout of submodules","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-12-26T16:02:40Z","receivedAt":"2013-12-26T16:02:40Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Jens Lehmann <Jens.Lehmann@web.de>\nDate: Mon, 18 Jun 2012 22:17:59 +0200\n\nThis commit adds the functions needed for configuration, for setting the\ndefault behavior and for determining if a submodule path should be updated\nautomatically.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nShould probably be squashed into a patch that uses and documents this\nconfiguration.\n\n submodule.c | 36 ++++++++++++++++++++++++++++++++++++\n submodule.h |  3 +++\n 2 files changed, 39 insertions(+)\n\ndiff --git a/submodule.c b/submodule.c\nindex 613857e..3f18d4d 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -16,6 +16,8 @@ static struct string_list config_name_for_path;\n static struct string_list config_fetch_recurse_submodules_for_name;\n static struct string_list config_ignore_for_name;\n static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;\n+static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;\n+static int option_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n static struct string_list changed_submodule_paths;\n static int initialized_fetch_ref_tips;\n static struct sha1_array ref_tips_before_fetch;\n@@ -382,6 +384,34 @@ int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg)\n \t}\n }\n \n+int parse_update_recurse_submodules_arg(const char *opt, const char *arg)\n+{\n+\tswitch (git_config_maybe_bool(opt, arg)) {\n+\tcase 1:\n+\t\treturn RECURSE_SUBMODULES_ON;\n+\tcase 0:\n+\t\treturn RECURSE_SUBMODULES_OFF;\n+\tdefault:\n+\t\tif (!strcmp(arg, \"checkout\"))\n+\t\t\treturn RECURSE_SUBMODULES_ON;\n+\t\tdie(\"bad %s argument: %s\", opt, arg);\n+\t}\n+}\n+\n+int submodule_needs_update(const char *path)\n+{\n+\tstruct string_list_item *path_option;\n+\tpath_option = unsorted_string_list_lookup(&config_name_for_path, path);\n+\tif (!path_option)\n+\t\treturn 0;\n+\n+\t/* update can't be \"none\", \"merge\" or \"rebase\" */\n+\n+\tif (option_update_recurse_submodules != RECURSE_SUBMODULES_DEFAULT)\n+\t\treturn 1;\n+\treturn config_update_recurse_submodules != RECURSE_SUBMODULES_OFF;\n+}\n+\n void show_submodule_summary(FILE *f, const char *path,\n \t\tconst char *line_prefix,\n \t\tunsigned char one[20], unsigned char two[20],\n@@ -589,6 +619,12 @@ int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam\n \treturn ret;\n }\n \n+void set_config_update_recurse_submodules(int default_value, int option_value)\n+{\n+\tconfig_update_recurse_submodules = default_value;\n+\toption_update_recurse_submodules = option_value;\n+}\n+\n static int is_submodule_commit_present(const char *path, unsigned char sha1[20])\n {\n \tint is_present = 0;\ndiff --git a/submodule.h b/submodule.h\nindex 7beec48..055918c 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -22,12 +22,15 @@ void gitmodules_config(void);\n int parse_submodule_config_option(const char *var, const char *value);\n void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);\n int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n+int parse_update_recurse_submodules_arg(const char *opt, const char *arg);\n+int submodule_needs_update(const char *path);\n void show_submodule_summary(FILE *f, const char *path,\n \t\tconst char *line_prefix,\n \t\tunsigned char one[20], unsigned char two[20],\n \t\tunsigned dirty_submodule, const char *meta,\n \t\tconst char *del, const char *add, const char *reset);\n void set_config_fetch_recurse_submodules(int value);\n+void set_config_update_recurse_submodules(int default_value, int option_value);\n void check_for_new_submodule_commits(unsigned char new_sha1[20]);\n int fetch_populated_submodules(const struct argv_array *options,\n \t\t\t       const char *prefix, int command_line_option,\n-- \n1.8.5.1\n"},{"id":"232402","messageId":"20131226161202.GN20443@google.com","threadId":"35575","inReplyTo":"20131226155857.GL20443@google.com","subject":"[PATCH 2/5] submodule: teach unpack_trees() to remove submodule contents","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-12-26T16:12:02Z","receivedAt":"2013-12-26T16:12:02Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Jens Lehmann <Jens.Lehmann@web.de>\nDate: Tue, 19 Jun 2012 20:55:45 +0200\n\nImplement the functionality needed to enable work tree manipulating\ncommands to that a deleted submodule should not only affect the index\n(leaving all the files of the submodule in the work tree) but also to\nremove the work tree of the superproject (including any untracked\nfiles).\n\nThat will only work properly when the submodule uses a gitfile instead of\na .git directory and no untracked files are present. Otherwise the removal\nwill fail with a warning (which is just what happened until now).\n\nExtend rmdir_or_warn() to remove the directories of those submodules which\nare scheduled for removal. Also teach verify_clean_submodule() to check\nthat a submodule configured to be removed is not modified before scheduling\nit for removal.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nShould this share some code (or just the error message) with the 'git\nrm' code that checks whether a submodule is safe to remove?\n\nrmdir_or_warn is a pretty low-level function --- it feels odd to be\nrelying on the git repository layout here.  On the other hand, that\nfunction only has two callers, so it is possible to check quickly\nwhether it is safe.\n\nI assume this is mostly for the sake of the caller in unpack-trees?\n\nIn builtin/apply.c, remove_file is used for deletion and rename\npatches.  Do we want this logic take effect there, too?\n\n submodule.c    | 37 +++++++++++++++++++++++++++++++++++++\n submodule.h    |  1 +\n unpack-trees.c |  6 +++---\n wrapper.c      |  3 +++\n 4 files changed, 44 insertions(+), 3 deletions(-)\n\ndiff --git a/submodule.c b/submodule.c\nindex 3f18d4d..a25db46 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -412,6 +412,43 @@ int submodule_needs_update(const char *path)\n \treturn config_update_recurse_submodules != RECURSE_SUBMODULES_OFF;\n }\n \n+int depopulate_submodule(const char *path)\n+{\n+\tstruct strbuf dot_git = STRBUF_INIT;\n+\tstruct child_process cp;\n+\tconst char *argv[] = {\"rm\", \"-rf\", path, NULL};\n+\n+\t/* Is it populated? */\n+\tstrbuf_addf(&dot_git, \"%s/.git\", path);\n+\tif (!resolve_gitdir(dot_git.buf)) {\n+\t\tstrbuf_release(&dot_git);\n+\t\treturn 0;\n+\t}\n+\tstrbuf_release(&dot_git);\n+\n+\t/* Does it have a .git directory? */\n+\tif (!submodule_uses_gitfile(path)) {\n+\t\twarning(_(\"cannot remove submodule '%s' because it (or one of \"\n+\t\t\t  \"its nested submodules) uses a .git directory\"),\n+\t\t\t  path);\n+\t\treturn -1;\n+\t}\n+\n+\t/* Remove the whole submodule directory */\n+\tmemset(&cp, 0, sizeof(cp));\n+\tcp.argv = argv;\n+\tcp.env = local_repo_env;\n+\tcp.git_cmd = 0;\n+\tcp.no_stdin = 1;\n+\tif (run_command(&cp)) {\n+\t\twarning(\"Could not remove submodule %s\", path);\n+\t\tstrbuf_release(&dot_git);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n void show_submodule_summary(FILE *f, const char *path,\n \t\tconst char *line_prefix,\n \t\tunsigned char one[20], unsigned char two[20],\ndiff --git a/submodule.h b/submodule.h\nindex 055918c..df291cf 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -24,6 +24,7 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);\n int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n int parse_update_recurse_submodules_arg(const char *opt, const char *arg);\n int submodule_needs_update(const char *path);\n+int depopulate_submodule(const char *path);\n void show_submodule_summary(FILE *f, const char *path,\n \t\tconst char *line_prefix,\n \t\tunsigned char one[20], unsigned char two[20],\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex ad3e9a0..89b506a 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -8,6 +8,7 @@\n #include \"progress.h\"\n #include \"refs.h\"\n #include \"attr.h\"\n+#include \"submodule.h\"\n \n /*\n  * Error messages expected by scripts out of plumbing commands such as\n@@ -1263,14 +1264,13 @@ static void invalidate_ce_path(const struct cache_entry *ce,\n /*\n  * Check that checking out ce->sha1 in subdir ce->name is not\n  * going to overwrite any working files.\n- *\n- * Currently, git does not checkout subprojects during a superproject\n- * checkout, so it is not going to overwrite anything.\n  */\n static int verify_clean_submodule(const struct cache_entry *ce,\n \t\t\t\t  enum unpack_trees_error_types error_type,\n \t\t\t\t  struct unpack_trees_options *o)\n {\n+\tif (submodule_needs_update(ce->name) && is_submodule_modified(ce->name, 0))\n+\t\treturn 1;\n \treturn 0;\n }\n \ndiff --git a/wrapper.c b/wrapper.c\nindex 0cc5636..425a3fd 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -2,6 +2,7 @@\n  * Various trivial helper wrappers around standard functions\n  */\n #include \"cache.h\"\n+#include \"submodule.h\"\n \n static void do_nothing(size_t size)\n {\n@@ -409,6 +410,8 @@ int unlink_or_warn(const char *file)\n \n int rmdir_or_warn(const char *file)\n {\n+\tif (submodule_needs_update(file) && depopulate_submodule(file))\n+\t\treturn -1;\n \treturn warn_if_unremovable(\"rmdir\", file, rmdir(file));\n }\n \n-- \n1.8.5.1\n"},{"id":"232403","messageId":"20131226161410.GO20443@google.com","threadId":"35575","inReplyTo":"20131226155857.GL20443@google.com","subject":"[PATCH 3/5] submodule: teach unpack_trees() to repopulate submodules","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-12-26T16:14:10Z","receivedAt":"2013-12-26T16:14:10Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Jens Lehmann <Jens.Lehmann@web.de>\nDate: Mon, 18 Jun 2012 22:11:45 +0200\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nNeat.  Would probably be clearer with some of the corresponding tests\nsquashed in to illustrate the intended behavior.\n\n entry.c        |  4 ++++\n submodule.c    | 44 +++++++++++++++++++++++++++++++++++++++++---\n submodule.h    |  1 +\n unpack-trees.c | 19 +++++++++++++++++++\n 4 files changed, 65 insertions(+), 3 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex 7b7aa81..d1bf6ec 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -2,6 +2,7 @@\n #include \"blob.h\"\n #include \"dir.h\"\n #include \"streaming.h\"\n+#include \"submodule.h\"\n \n static void create_directories(const char *path, int path_len,\n \t\t\t       const struct checkout *state)\n@@ -203,6 +204,9 @@ static int write_entry(struct cache_entry *ce,\n \t\t\treturn error(\"cannot create temporary submodule %s\", path);\n \t\tif (mkdir(path, 0777) < 0)\n \t\t\treturn error(\"cannot create submodule directory %s\", path);\n+\t\tif (submodule_needs_update(path) &&\n+\t\t    populate_submodule(path, ce->sha1, state->force))\n+\t\t\treturn error(\"cannot checkout submodule %s\", path);\n \t\tbreak;\n \tdefault:\n \t\treturn error(\"unknown file mode for %s in index\", path);\ndiff --git a/submodule.c b/submodule.c\nindex a25db46..06df5ae 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -412,6 +412,42 @@ int submodule_needs_update(const char *path)\n \treturn config_update_recurse_submodules != RECURSE_SUBMODULES_OFF;\n }\n \n+int populate_submodule(const char *path, unsigned char sha1[20], int force)\n+{\n+\tstruct string_list_item *path_option;\n+\tconst char *name, *real_git_dir;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct child_process cp;\n+\tconst char *argv[] = {\"read-tree\", force ? \"--reset\" : \"-m\", \"-u\", NULL, NULL};\n+\n+\tpath_option = unsorted_string_list_lookup(&config_name_for_path, path);\n+\tif (!path_option)\n+\t\treturn 0;\n+\n+\tname = path_option->util;\n+\n+\tstrbuf_addf(&buf, \"%s/modules/%s\", resolve_gitdir(get_git_dir()), name);\n+\treal_git_dir = resolve_gitdir(buf.buf);\n+\tif (!real_git_dir)\n+\t\tgoto out;\n+\tconnect_work_tree_and_git_dir(path, real_git_dir);\n+\n+\t/* Run read-tree --reset sha1 */\n+\tmemset(&cp, 0, sizeof(cp));\n+\tcp.argv = argv;\n+\tcp.env = local_repo_env;\n+\tcp.git_cmd = 1;\n+\tcp.no_stdin = 1;\n+\tcp.dir = path;\n+\targv[3] = sha1_to_hex(sha1);\n+\tif (run_command(&cp))\n+\t\twarning(_(\"Checking out submodule %s failed\"), path);\n+\n+out:\n+\tstrbuf_release(&buf);\n+\treturn 0;\n+}\n+\n int depopulate_submodule(const char *path)\n {\n \tstruct strbuf dot_git = STRBUF_INIT;\n@@ -1207,6 +1243,7 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)\n {\n \tstruct strbuf file_name = STRBUF_INIT;\n \tstruct strbuf rel_path = STRBUF_INIT;\n+\tconst char *real_git_dir = xstrdup(real_path(git_dir));\n \tconst char *real_work_tree = xstrdup(real_path(work_tree));\n \tFILE *fp;\n \n@@ -1215,15 +1252,15 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)\n \tfp = fopen(file_name.buf, \"w\");\n \tif (!fp)\n \t\tdie(_(\"Could not create git link %s\"), file_name.buf);\n-\tfprintf(fp, \"gitdir: %s\\n\", relative_path(git_dir, real_work_tree,\n+\tfprintf(fp, \"gitdir: %s\\n\", relative_path(real_git_dir, real_work_tree,\n \t\t\t\t\t\t  &rel_path));\n \tfclose(fp);\n \n \t/* Update core.worktree setting */\n \tstrbuf_reset(&file_name);\n-\tstrbuf_addf(&file_name, \"%s/config\", git_dir);\n+\tstrbuf_addf(&file_name, \"%s/config\", real_git_dir);\n \tif (git_config_set_in_file(file_name.buf, \"core.worktree\",\n-\t\t\t\t   relative_path(real_work_tree, git_dir,\n+\t\t\t\t   relative_path(real_work_tree, real_git_dir,\n \t\t\t\t\t\t &rel_path)))\n \t\tdie(_(\"Could not set core.worktree in %s\"),\n \t\t    file_name.buf);\n@@ -1231,4 +1268,5 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)\n \tstrbuf_release(&file_name);\n \tstrbuf_release(&rel_path);\n \tfree((void *)real_work_tree);\n+\tfree((void *)real_git_dir);\n }\ndiff --git a/submodule.h b/submodule.h\nindex df291cf..3657ca8 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -24,6 +24,7 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);\n int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n int parse_update_recurse_submodules_arg(const char *opt, const char *arg);\n int submodule_needs_update(const char *path);\n+int populate_submodule(const char *path, unsigned char sha1[20], int force);\n int depopulate_submodule(const char *path);\n void show_submodule_summary(FILE *f, const char *path,\n \t\tconst char *line_prefix,\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex 89b506a..ed48d41 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -1367,6 +1367,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype,\n \t\t\t      struct unpack_trees_options *o)\n {\n \tconst struct cache_entry *result;\n+\tchar *name_copy, *separator;\n \n \t/*\n \t * It may be that the 'lstat()' succeeded even though\n@@ -1409,6 +1410,24 @@ static int check_ok_to_remove(const char *name, int len, int dtype,\n \t\t\treturn 0;\n \t}\n \n+\t/*\n+\t * If the path lies inside a to be added submodule it\n+\t * is ok to remove it.\n+\t */\n+\tname_copy = xstrdup(name);\n+\twhile ((separator = strrchr(name_copy, '/'))) {\n+\t\tint i;\n+\t\t*separator = '\\0';\n+\t\tfor (i = 0; i < the_index.cache_nr; i++) {\n+\t\t\tstruct cache_entry *ce = the_index.cache[i];\n+\t\t\tif (!strcmp(ce->name, name_copy) && S_ISGITLINK(ce->ce_mode)) {\n+\t\t\t\tfree(name_copy);\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t}\n+\tfree(name_copy);\n+\n \treturn o->gently ? -1 :\n \t\tadd_rejected_path(o, error_type, name);\n }\n-- \n1.8.5.1\n"},{"id":"232404","messageId":"20131226161540.GP20443@google.com","threadId":"35575","inReplyTo":"20131226155857.GL20443@google.com","subject":"[PATCH 4/5] submodule: teach unpack_trees() to update submodules","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-12-26T16:15:40Z","receivedAt":"2013-12-26T16:15:40Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Jens Lehmann <Jens.Lehmann@web.de>\nDate: Fri, 5 Apr 2013 18:35:27 +0200\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nAlso neat, also would benefit from documentation or tests.\n\n entry.c        | 15 ++++++++--\n submodule.c    | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n submodule.h    |  3 ++\n unpack-trees.c | 69 ++++++++++++++++++++++++++++++++++++----------\n unpack-trees.h |  1 +\n 5 files changed, 157 insertions(+), 17 deletions(-)\n\ndiff --git a/entry.c b/entry.c\nindex d1bf6ec..61a2767 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -265,7 +265,7 @@ int checkout_entry(struct cache_entry *ce,\n \n \tif (!check_path(path, len, &st, state->base_dir_len)) {\n \t\tunsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);\n-\t\tif (!changed)\n+\t\tif (!changed && (!S_ISDIR(st.st_mode) || !S_ISGITLINK(ce->ce_mode)))\n \t\t\treturn 0;\n \t\tif (!state->force) {\n \t\t\tif (!state->quiet)\n@@ -280,9 +280,18 @@ int checkout_entry(struct cache_entry *ce,\n \t\t * just do the right thing)\n \t\t */\n \t\tif (S_ISDIR(st.st_mode)) {\n-\t\t\t/* If it is a gitlink, leave it alone! */\n-\t\t\tif (S_ISGITLINK(ce->ce_mode))\n+\t\t\tif (S_ISGITLINK(ce->ce_mode)) {\n+\t\t\t\tif (submodule_needs_update(ce->name)) {\n+\t\t\t\t\tif (is_submodule_populated(ce->name)) {\n+\t\t\t\t\t\tif (update_submodule(ce->name, ce->sha1, state->force))\n+\t\t\t\t\t\t\treturn error(\"cannot checkout submodule %s\", path);\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tif (populate_submodule(path, ce->sha1, state->force))\n+\t\t\t\t\t\t\treturn error(\"cannot populate submodule %s\", path);\n+\t\t\t\t\t}\n+\t\t\t\t}\n \t\t\t\treturn 0;\n+\t\t\t}\n \t\t\tif (!state->force)\n \t\t\t\treturn error(\"%s is a directory\", path);\n \t\t\tremove_subtree(path);\ndiff --git a/submodule.c b/submodule.c\nindex 06df5ae..3365987 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -485,6 +485,42 @@ int depopulate_submodule(const char *path)\n \treturn 0;\n }\n \n+int update_submodule(const char *path, const unsigned char sha1[20], int force)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct child_process cp;\n+\tconst char *hex_sha1 = sha1_to_hex(sha1);\n+\tconst char *argv[] = {\n+\t\t\"checkout\",\n+\t\tforce ? \"-fq\" : \"-q\",\n+\t\thex_sha1,\n+\t\tNULL,\n+\t};\n+\tconst char *git_dir;\n+\n+\tstrbuf_addf(&buf, \"%s/.git\", path);\n+\tgit_dir = read_gitfile(buf.buf);\n+\tif (!git_dir)\n+\t\tgit_dir = buf.buf;\n+\tif (!is_directory(git_dir)) {\n+\t\tstrbuf_release(&buf);\n+\t\t/* The submodule is not populated, so we can't check it out */\n+\t\treturn 0;\n+\t}\n+\tstrbuf_release(&buf);\n+\n+\tmemset(&cp, 0, sizeof(cp));\n+\tcp.argv = argv;\n+\tcp.env = local_repo_env;\n+\tcp.git_cmd = 1;\n+\tcp.no_stdin = 1;\n+\tcp.dir = path;   /* GIT_WORK_TREE doesn't work for git checkout */\n+\tif (run_command(&cp))\n+\t\treturn error(\"Could not checkout submodule %s\", path);\n+\n+\treturn 0;\n+}\n+\n void show_submodule_summary(FILE *f, const char *path,\n \t\tconst char *line_prefix,\n \t\tunsigned char one[20], unsigned char two[20],\n@@ -926,6 +962,17 @@ out:\n \treturn result;\n }\n \n+int is_submodule_populated(const char *path)\n+{\n+\tint retval = 0;\n+\tstruct strbuf gitdir = STRBUF_INIT;\n+\tstrbuf_addf(&gitdir, \"%s/.git\", path);\n+\tif (resolve_gitdir(gitdir.buf))\n+\t\tretval = 1;\n+\tstrbuf_release(&gitdir);\n+\treturn retval;\n+}\n+\n unsigned is_submodule_modified(const char *path, int ignore_untracked)\n {\n \tssize_t len;\n@@ -1075,6 +1122,45 @@ int ok_to_remove_submodule(const char *path)\n \treturn ok_to_remove;\n }\n \n+unsigned is_submodule_checkout_safe(const char *path, const unsigned char sha1[20])\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct child_process cp;\n+\tconst char *hex_sha1 = sha1_to_hex(sha1);\n+\tconst char *argv[] = {\n+\t\t\"read-tree\",\n+\t\t\"-n\",\n+\t\t\"-m\",\n+\t\t\"HEAD\",\n+\t\thex_sha1,\n+\t\tNULL,\n+\t};\n+\tconst char *git_dir;\n+\n+\tstrbuf_addf(&buf, \"%s/.git\", path);\n+\tgit_dir = read_gitfile(buf.buf);\n+\tif (!git_dir)\n+\t\tgit_dir = buf.buf;\n+\tif (!is_directory(git_dir)) {\n+\t\tstrbuf_release(&buf);\n+\t\t/* The submodule is not populated, it's safe to check it out */\n+\t\t/*\n+\t\t * TODO: When git learns to re-populate submodules, a check must be\n+\t\t * added here to assert that no local files will be overwritten.\n+\t\t */\n+\t\treturn 1;\n+\t}\n+\tstrbuf_release(&buf);\n+\n+\tmemset(&cp, 0, sizeof(cp));\n+\tcp.argv = argv;\n+\tcp.env = local_repo_env;\n+\tcp.git_cmd = 1;\n+\tcp.no_stdin = 1;\n+\tcp.dir = path;\n+\treturn run_command(&cp) == 0;\n+}\n+\n static int find_first_merges(struct object_array *result, const char *path,\n \t\tstruct commit *a, struct commit *b)\n {\ndiff --git a/submodule.h b/submodule.h\nindex 3657ca8..b42ae91 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -26,6 +26,7 @@ int parse_update_recurse_submodules_arg(const char *opt, const char *arg);\n int submodule_needs_update(const char *path);\n int populate_submodule(const char *path, unsigned char sha1[20], int force);\n int depopulate_submodule(const char *path);\n+int update_submodule(const char *path, const unsigned char sha1[20], int force);\n void show_submodule_summary(FILE *f, const char *path,\n \t\tconst char *line_prefix,\n \t\tunsigned char one[20], unsigned char two[20],\n@@ -37,9 +38,11 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20]);\n int fetch_populated_submodules(const struct argv_array *options,\n \t\t\t       const char *prefix, int command_line_option,\n \t\t\t       int quiet);\n+int is_submodule_populated(const char *path);\n unsigned is_submodule_modified(const char *path, int ignore_untracked);\n int submodule_uses_gitfile(const char *path);\n int ok_to_remove_submodule(const char *path);\n+unsigned is_submodule_checkout_safe(const char *path, const unsigned char sha1[20]);\n int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],\n \t\t    const unsigned char a[20], const unsigned char b[20], int search);\n int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,\ndiff --git a/unpack-trees.c b/unpack-trees.c\nindex ed48d41..fc8855e 100644\n--- a/unpack-trees.c\n+++ b/unpack-trees.c\n@@ -27,6 +27,9 @@ static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {\n \t/* ERROR_NOT_UPTODATE_DIR */\n \t\"Updating '%s' would lose untracked files in it\",\n \n+\t/* ERROR_NOT_UPTODATE_SUBMODULE */\n+\t\"Updating submodule '%s' would lose modifications in it\",\n+\n \t/* ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN */\n \t\"Untracked working tree file '%s' would be overwritten by merge.\",\n \n@@ -71,6 +74,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,\n \n \tmsgs[ERROR_NOT_UPTODATE_DIR] =\n \t\t\"Updating the following directories would lose untracked files in it:\\n%s\";\n+\tmsgs[ERROR_NOT_UPTODATE_SUBMODULE] =\n+\t\t\"Updating the following submodules would lose modifications in it:\\n%s\";\n \n \tif (advice_commit_before_merge)\n \t\tmsg = \"The following untracked working tree files would be %s by %s:\\n%%s\"\n@@ -1221,17 +1226,15 @@ static int verify_uptodate_1(const struct cache_entry *ce,\n \tif (!lstat(ce->name, &st)) {\n \t\tint flags = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE;\n \t\tunsigned changed = ie_match_stat(o->src_index, ce, &st, flags);\n-\t\tif (!changed)\n-\t\t\treturn 0;\n-\t\t/*\n-\t\t * NEEDSWORK: the current default policy is to allow\n-\t\t * submodule to be out of sync wrt the superproject\n-\t\t * index.  This needs to be tightened later for\n-\t\t * submodules that are marked to be automatically\n-\t\t * checked out.\n-\t\t */\n-\t\tif (S_ISGITLINK(ce->ce_mode))\n-\t\t\treturn 0;\n+\t\tif (!changed) {\n+\t\t\tif (!S_ISGITLINK(ce->ce_mode) || !submodule_needs_update(ce->name) ||\n+\t\t\t    (ce_stage(ce) ? is_submodule_checkout_safe(ce->name, ce->sha1)\n+\t\t\t    : !is_submodule_modified(ce->name, 1)))\n+\t\t\t\treturn 0;\n+\t\t} else\n+\t\t\tif (S_ISGITLINK(ce->ce_mode) && !submodule_needs_update(ce->name))\n+\t\t\t\treturn 0;\n+\n \t\terrno = 0;\n \t}\n \tif (errno == ENOENT)\n@@ -1254,6 +1257,36 @@ static int verify_uptodate_sparse(const struct cache_entry *ce,\n \treturn verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);\n }\n \n+/*\n+ * When a submodule gets turned into an unmerged entry, we want it to be\n+ * up-to-date regarding the merge changes.\n+ */\n+static int verify_uptodate_submodule(const struct cache_entry *old,\n+\t\t\t\t     const struct cache_entry *new,\n+\t\t\t\t     struct unpack_trees_options *o)\n+{\n+\tstruct stat st;\n+\n+\tif (o->index_only || (!((old->ce_flags & CE_VALID) || ce_skip_worktree(old)) && (o->reset || ce_uptodate(old))))\n+\t\treturn 0;\n+\tif (!lstat(old->name, &st)) {\n+\t\tunsigned changed = ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);\n+\t\tif (!changed) {\n+\t\t\tif (!S_ISGITLINK(old->ce_mode) ||\n+\t\t\t    !submodule_needs_update(new->name) ||\n+\t\t\t    is_submodule_checkout_safe(new->name, new->sha1))\n+\t\t\t\treturn 0;\n+\t\t} else\n+\t\t\tif (S_ISGITLINK(old->ce_mode) && !submodule_needs_update(new->name))\n+\t\t\t\treturn 0;\n+\t\terrno = 0;\n+\t}\n+\tif (errno == ENOENT)\n+\t\treturn 0;\n+\treturn o->gently ? -1 :\n+\t\tadd_rejected_path(o, ERROR_NOT_UPTODATE_SUBMODULE, old->name);\n+}\n+\n static void invalidate_ce_path(const struct cache_entry *ce,\n \t\t\t       struct unpack_trees_options *o)\n {\n@@ -1532,9 +1565,17 @@ static int merged_entry(const struct cache_entry *ce,\n \t\t\tcopy_cache_entry(merge, old);\n \t\t\tupdate = 0;\n \t\t} else {\n-\t\t\tif (verify_uptodate(old, o)) {\n-\t\t\t\tfree(merge);\n-\t\t\t\treturn -1;\n+\t\t\tif (S_ISGITLINK(old->ce_mode) ||\n+\t\t\t    S_ISGITLINK(merge->ce_mode)) {\n+\t\t\t\tif (verify_uptodate_submodule(old, merge, o)) {\n+\t\t\t\t\tfree(merge);\n+\t\t\t\t\treturn -1;\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\tif (verify_uptodate(old, o)) {\n+\t\t\t\t\tfree(merge);\n+\t\t\t\t\treturn -1;\n+\t\t\t\t}\n \t\t\t}\n \t\t\t/* Migrate old flags over */\n \t\t\tupdate |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);\ndiff --git a/unpack-trees.h b/unpack-trees.h\nindex 36a73a6..bee8740 100644\n--- a/unpack-trees.h\n+++ b/unpack-trees.h\n@@ -15,6 +15,7 @@ enum unpack_trees_error_types {\n \tERROR_WOULD_OVERWRITE = 0,\n \tERROR_NOT_UPTODATE_FILE,\n \tERROR_NOT_UPTODATE_DIR,\n+\tERROR_NOT_UPTODATE_SUBMODULE,\n \tERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN,\n \tERROR_WOULD_LOSE_UNTRACKED_REMOVED,\n \tERROR_BIND_OVERLAP,\n-- \n1.8.5.1\n"},{"id":"232405","messageId":"20131226162238.GQ20443@google.com","threadId":"35575","inReplyTo":"20131226155857.GL20443@google.com","subject":"[PATCH 5/5] Teach checkout to recursively checkout submodules","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-12-26T16:22:38Z","receivedAt":"2013-12-26T16:22:38Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"From: Jens Lehmann <Jens.Lehmann@web.de>\nDate: Wed, 13 Jun 2012 18:50:10 +0200\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nThis is the patch that actually introduces the --recurse-submodules\noption, which makes the rest work.\n\nThe tests indicate some future directions for improving this, but\nit works reasonably well already.  I think I'd be most comfortable\napplying these if they were rearranged a little to the following\norder:\n\n 1. First, introducing the --recurse-submodules option, perhaps\n    with no actual effect, with tests that it is parsed correctly,\n    the default works, etc.\n\n 2. Then, adding the desired behaviors one by one with corresponding\n    tests (handling submodule modifications, removals, additions).\n\n 3. Finally, any needed tweaks on top.\n\nThat way, it is easy to play around with early patches without\nworrying about the later ones at first, and the patches are easy\nto review to confirm that they at least don't break anything in\nthe --no-recurse-submodules case.\n\nThat said, Debian experimental has these applied as is already,\nand there haven't been any complaints yet.\n\nThoughts?\nJonathan\n\n Documentation/git-checkout.txt |   8 ++\n builtin/checkout.c             |  14 +++\n submodule.c                    |  14 +++\n submodule.h                    |   3 +\n t/t2013-checkout-submodule.sh  | 215 ++++++++++++++++++++++++++++++++++++++++-\n 5 files changed, 251 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 91294f8..aabcc65 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -225,6 +225,14 @@ This means that you can use `git checkout -p` to selectively discard\n edits from your current working tree. See the ``Interactive Mode''\n section of linkgit:git-add[1] to learn how to operate the `--patch` mode.\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 until `-f` is used. If nothing (or --no-recurse-submodules)\n+\tis used, the work trees of submodules will not be updated, only the\n+\thash recorded in the superproject will be changed.\n+\n <branch>::\n \tBranch to checkout; if it refers to a branch (i.e., a name that,\n \twhen prepended with \"refs/heads/\", is a valid ref), then that\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 5df3837..ac2f8d8 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -21,6 +21,9 @@\n #include \"submodule.h\"\n #include \"argv-array.h\"\n \n+static const char *recurse_submodules_default = \"off\";\n+static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n+\n static const char * const checkout_usage[] = {\n \tN_(\"git checkout [options] <branch>\"),\n \tN_(\"git checkout [options] [<branch>] -- <file>...\"),\n@@ -1111,6 +1114,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"do not limit pathspecs to sparse entries only\")),\n \t\tOPT_HIDDEN_BOOL(0, \"guess\", &dwim_new_local_branch,\n \t\t\t\tN_(\"second guess 'git checkout no-such-branch'\")),\n+\t\t{ OPTION_CALLBACK, 0, \"recurse-submodules\", &recurse_submodules,\n+\t\t\t    \"checkout\", \"control recursive updating of submodules\",\n+\t\t\t    PARSE_OPT_OPTARG, option_parse_update_submodules },\n+\t\t{ OPTION_STRING, 0, \"recurse-submodules-default\",\n+\t\t\t   &recurse_submodules_default, NULL,\n+\t\t\t   \"default mode for recursion\", PARSE_OPT_HIDDEN },\n \t\tOPT_END(),\n \t};\n \n@@ -1132,6 +1141,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tgit_xmerge_config(\"merge.conflictstyle\", conflict_style, NULL);\n \t}\n \n+\tset_config_update_recurse_submodules(\n+\t\tparse_fetch_recurse_submodules_arg(\"--recurse-submodules-default\",\n+\t\t\t\t\t\t   recurse_submodules_default),\n+\t\trecurse_submodules);\n+\n \tif ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)\n \t\tdie(_(\"-b, -B and --orphan are mutually exclusive\"));\n \ndiff --git a/submodule.c b/submodule.c\nindex 3365987..bdce1b2 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -398,6 +398,20 @@ int parse_update_recurse_submodules_arg(const char *opt, const char *arg)\n \t}\n }\n \n+int option_parse_update_submodules(const struct option *opt,\n+\t\t\t\t   const char *arg, int unset)\n+{\n+\tif (unset) {\n+\t\t*(int *)opt->value = RECURSE_SUBMODULES_OFF;\n+\t} else {\n+\t\tif (arg)\n+\t\t\t*(int *)opt->value = parse_update_recurse_submodules_arg(opt->long_name, arg);\n+\t\telse\n+\t\t\t*(int *)opt->value = RECURSE_SUBMODULES_ON;\n+\t}\n+\treturn 0;\n+}\n+\n int submodule_needs_update(const char *path)\n {\n \tstruct string_list_item *path_option;\ndiff --git a/submodule.h b/submodule.h\nindex b42ae91..9841da3 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -3,6 +3,7 @@\n \n struct diff_options;\n struct argv_array;\n+struct option;\n \n enum {\n \tRECURSE_SUBMODULES_ON_DEMAND = -1,\n@@ -23,6 +24,8 @@ int parse_submodule_config_option(const char *var, const char *value);\n void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);\n int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n int parse_update_recurse_submodules_arg(const char *opt, const char *arg);\n+int option_parse_update_submodules(const struct option *opt,\n+\t\tconst char *arg, int unset);\n int submodule_needs_update(const char *path);\n int populate_submodule(const char *path, unsigned char sha1[20], int force);\n int depopulate_submodule(const char *path);\ndiff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh\nindex 06b18f8..bc3e1ca 100755\n--- a/t/t2013-checkout-submodule.sh\n+++ b/t/t2013-checkout-submodule.sh\n@@ -4,17 +4,57 @@ test_description='checkout can handle submodules'\n \n . ./test-lib.sh\n \n+submodule_creation_must_succeed() {\n+\t# checkout base ($1)\n+\tgit checkout -f --recurse-submodules $1 &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached $1 &&\n+\n+\t# checkout target ($2)\n+\tif test -d submodule; then\n+\t\techo change>>submodule/first.t &&\n+\t\ttest_must_fail git checkout --recurse-submodules $2 &&\n+\t\tgit checkout -f --recurse-submodules $2\n+\telse\n+\t\tgit checkout --recurse-submodules $2\n+\tfi &&\n+\ttest -e submodule/.git &&\n+\ttest -f submodule/first.t &&\n+\ttest -f submodule/second.t &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached $2\n+}\n+\n+submodule_removal_must_succeed() {\n+\t# checkout base ($1)\n+\tgit checkout -f --recurse-submodules $1 &&\n+\tgit submodule update -f &&\n+\ttest -e submodule/.git &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached $1 &&\n+\n+\t# checkout target ($2)\n+\techo change>>submodule/first.t &&\n+\ttest_must_fail git checkout --recurse-submodules $2 &&\n+\tgit checkout -f --recurse-submodules $2 &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached $2 &&\n+\t! test -d submodule\n+}\n+\n test_expect_success 'setup' '\n \tmkdir submodule &&\n \t(cd submodule &&\n \t git init &&\n \t test_commit first) &&\n-\tgit add submodule &&\n+\techo first > file &&\n+\tgit add file submodule &&\n \ttest_tick &&\n \tgit commit -m superproject &&\n \t(cd submodule &&\n \t test_commit second) &&\n-\tgit add submodule &&\n+\techo second > file &&\n+\tgit add file submodule &&\n \ttest_tick &&\n \tgit commit -m updated.superproject\n '\n@@ -36,7 +76,8 @@ test_expect_success '\"checkout <submodule>\" updates the index only' '\n \tgit checkout HEAD^ submodule &&\n \ttest_must_fail git diff-files --quiet &&\n \tgit checkout HEAD submodule &&\n-\tgit diff-files --quiet\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached HEAD\n '\n \n test_expect_success '\"checkout <submodule>\" honors diff.ignoreSubmodules' '\n@@ -62,4 +103,172 @@ test_expect_success '\"checkout <submodule>\" honors submodule.*.ignore from .git/\n \t! test -s actual\n '\n \n+test_expect_success '\"checkout --recurse-submodules\" removes deleted submodule' '\n+\tgit config -f .gitmodules submodule.submodule.path submodule &&\n+\tgit config -f .gitmodules submodule.submodule.url submodule.bare &&\n+\t(cd submodule && git clone --bare . ../submodule.bare) &&\n+\techo submodule.bare >>.gitignore &&\n+\tgit config submodule.submodule.ignore none &&\n+\tgit add .gitignore .gitmodules submodule &&\n+\tgit submodule update --init &&\n+\tgit commit -m \"submodule registered\" &&\n+\tgit checkout -b base &&\n+\tgit checkout -b delete_submodule &&\n+\trm -rf submodule &&\n+\tgit rm submodule &&\n+\tgit commit -m \"submodule deleted\" &&\n+\tsubmodule_removal_must_succeed base delete_submodule\n+'\n+\n+test_expect_success '\"checkout --recurse-submodules\" repopulates submodule' '\n+\tsubmodule_creation_must_succeed delete_submodule base\n+'\n+\n+test_expect_success '\"checkout --recurse-submodules\" repopulates submodule in existing directory' '\n+\tgit checkout --recurse-submodules delete_submodule &&\n+\tmkdir submodule &&\n+\tsubmodule_creation_must_succeed delete_submodule base\n+'\n+\n+test_expect_success '\"checkout --recurse-submodules\" replaces submodule with files' '\n+\tgit checkout -f base &&\n+\tgit checkout -b replace_submodule_with_dir &&\n+\tgit update-index --force-remove submodule &&\n+\trm -rf submodule/.git .gitmodules &&\n+\tgit add .gitmodules submodule/* &&\n+\tgit commit -m \"submodule replaced\" &&\n+\tgit checkout -f base &&\n+\tgit submodule update -f &&\n+\tgit checkout --recurse-submodules replace_submodule_with_dir &&\n+\ttest -d submodule &&\n+\t! test -e submodule/.git &&\n+\ttest -f submodule/first.t &&\n+\ttest -f submodule/second.t\n+'\n+\n+test_expect_success '\"checkout --recurse-submodules\" removes files and repopulates submodule' '\n+\tsubmodule_creation_must_succeed replace_submodule_with_dir base\n+'\n+\n+test_expect_failure '\"checkout --recurse-submodules\" replaces submodule with a file' '\n+\tgit checkout -f base &&\n+\tgit checkout -b replace_submodule_with_file &&\n+\tgit update-index --force-remove submodule &&\n+\trm -rf submodule .gitmodules &&\n+\techo content >submodule &&\n+\tgit add .gitmodules submodule &&\n+\tgit commit -m \"submodule replaced with file\" &&\n+\tgit checkout -f base &&\n+\tgit submodule update -f &&\n+\tgit checkout --recurse-submodules replace_submodule_with_file &&\n+\ttest -d submodule &&\n+\t! test -e submodule/.git &&\n+\ttest -f submodule/first.t &&\n+\ttest -f submodule/second.t\n+'\n+\n+test_expect_success '\"checkout --recurse-submodules\" removes the file and repopulates submodule' '\n+\tsubmodule_creation_must_succeed replace_submodule_with_file base\n+'\n+\n+test_expect_failure '\"checkout --recurse-submodules\" replaces submodule with a link' '\n+\tgit checkout -f base &&\n+\tgit checkout -b replace_submodule_with_link &&\n+\tgit update-index --force-remove submodule &&\n+\trm -rf submodule .gitmodules &&\n+\tln -s submodule &&\n+\tgit add .gitmodules submodule &&\n+\tgit commit -m \"submodule replaced with link\" &&\n+\tgit checkout -f base &&\n+\tgit submodule update -f &&\n+\tgit checkout --recurse-submodules replace_submodule_with_link &&\n+\ttest -d submodule &&\n+\t! test -e submodule/.git &&\n+\ttest -f submodule/first.t &&\n+\ttest -f submodule/second.t\n+'\n+\n+test_expect_success '\"checkout --recurse-submodules\" removes the link and repopulates submodule' '\n+\tsubmodule_creation_must_succeed replace_submodule_with_link base\n+'\n+\n+test_expect_success '\"checkout --recurse-submodules\" updates recursively' '\n+\tgit checkout --recurse-submodules base &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached HEAD &&\n+\tgit checkout -b updated_submodule &&\n+\t(cd submodule &&\n+\t echo x >>first.t &&\n+\t git add first.t &&\n+\t test_commit third) &&\n+\tgit add submodule &&\n+\ttest_tick &&\n+\tgit commit -m updated.superproject &&\n+\tgit checkout --recurse-submodules base &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached HEAD\n+'\n+\n+test_expect_failure '\"checkout --recurse-submodules\" needs -f to update a modifed submodule commit' '\n+\t(\n+\t\tcd submodule &&\n+\t\tgit checkout --recurse-submodules HEAD^\n+\t) &&\n+\ttest_must_fail git checkout --recurse-submodules master &&\n+\ttest_must_fail git diff-files --quiet submodule &&\n+\tgit diff-files --quiet file &&\n+\tgit checkout --recurse-submodules -f master &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached HEAD\n+'\n+\n+test_expect_failure '\"checkout --recurse-submodules\" needs -f to update modifed submodule content' '\n+\techo modified >submodule/second.t &&\n+\ttest_must_fail git checkout --recurse-submodules HEAD^ &&\n+\ttest_must_fail git diff-files --quiet submodule &&\n+\tgit diff-files --quiet file &&\n+\tgit checkout --recurse-submodules -f HEAD^ &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached HEAD &&\n+\tgit checkout --recurse-submodules -f master &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached HEAD\n+'\n+\n+test_expect_failure '\"checkout --recurse-submodules\" ignores modified submodule content that would not be changed' '\n+\techo modified >expected &&\n+\tcp expected submodule/first.t &&\n+\tgit checkout --recurse-submodules HEAD^ &&\n+\ttest_cmp expected submodule/first.t &&\n+\ttest_must_fail git diff-files --quiet submodule &&\n+\tgit diff-index --quiet --cached HEAD &&\n+\tgit checkout --recurse-submodules -f master &&\n+\tgit diff-files --quiet &&\n+\tgit diff-index --quiet --cached HEAD\n+'\n+\n+test_expect_failure '\"checkout --recurse-submodules\" does not care about untracked submodule content' '\n+\techo untracked >submodule/untracked &&\n+\tgit checkout --recurse-submodules master &&\n+\tgit diff-files --quiet --ignore-submodules=untracked &&\n+\tgit diff-index --quiet --cached HEAD &&\n+\trm submodule/untracked\n+'\n+\n+test_expect_failure '\"checkout --recurse-submodules\" needs -f when submodule commit is not present (but does fail anyway)' '\n+\tgit checkout --recurse-submodules -b bogus_commit master &&\n+\tgit update-index --cacheinfo 160000 0123456789012345678901234567890123456789 submodule\n+\tBOGUS_TREE=$(git write-tree) &&\n+\tBOGUS_COMMIT=$(echo \"bogus submodule commit\" | git commit-tree $BOGUS_TREE) &&\n+\tgit commit -m \"bogus submodule commit\" &&\n+\tgit checkout --recurse-submodules -f master &&\n+\ttest_must_fail git checkout --recurse-submodules bogus_commit &&\n+\tgit diff-files --quiet &&\n+\ttest_must_fail git checkout --recurse-submodules -f bogus_commit &&\n+\ttest_must_fail git diff-files --quiet submodule &&\n+\tgit diff-files --quiet file &&\n+\tgit diff-index --quiet --cached HEAD &&\n+\tgit checkout --recurse-submodules -f master\n+'\n+\n test_done\n-- \n1.8.5.1\n"},{"id":"232415","messageId":"xmqq61qbcrkj.fsf@gitster.dls.corp.google.com","threadId":"35575","inReplyTo":"20131226155857.GL20443@google.com","subject":"Re: [WIP/PATCH 0/5] git checkout --recurse-submodules","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-26T19:58:36Z","receivedAt":"2013-12-26T19:58:36Z","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> Hi,\n>\n> This patch series comes from\n> https://github.com/jlehmann/git-submod-enhancements branch\n> recursive_submodule_checkout.  It needed some tiny tweaks to apply to\n> current \"master\" and build without warnings, but nothing major, and I\n> haven't sanity checked it much beyond that and letting the kind folks\n> that use Debian experimental play with it.\n>\n> I'm sending it out now to get review and ideas for what needs to\n> happen next to get this series in shape to be included in git.git.\n>\n> Thoughts of all kinds welcome.\n>\n> Thanks,\n> Jonathan\n>\n> Jens Lehmann (5):\n>   submodule: prepare for recursive checkout of submodules\n>   submodule: teach unpack_trees() to remove submodule contents\n>   submodule: teach unpack_trees() to repopulate submodules\n>   submodule: teach unpack_trees() to update submodules\n>   Teach checkout to recursively checkout submodules\n>\n>  Documentation/git-checkout.txt |   8 ++\n>  builtin/checkout.c             |  14 +++\n>  entry.c                        |  19 +++-\n>  submodule.c                    | 217 ++++++++++++++++++++++++++++++++++++++++-\n>  submodule.h                    |  11 +++\n>  t/t2013-checkout-submodule.sh  | 215 +++++++++++++++++++++++++++++++++++++++-\n>  unpack-trees.c                 |  94 ++++++++++++++----\n>  unpack-trees.h                 |   1 +\n>  wrapper.c                      |   3 +\n>  9 files changed, 556 insertions(+), 26 deletions(-)\n\nLooks reasonably clean from a cursory read. Thanks.\n"},{"id":"232430","messageId":"52BD81E7.40907@web.de","threadId":"35575","inReplyTo":"20131226155857.GL20443@google.com","subject":"Re: [WIP/PATCH 0/5] git checkout --recurse-submodules","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-12-27T13:34:31Z","receivedAt":"2013-12-27T13:34:31Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Am 26.12.2013 16:58, schrieb Jonathan Nieder:\n> This patch series comes from\n> https://github.com/jlehmann/git-submod-enhancements branch\n> recursive_submodule_checkout.  It needed some tiny tweaks to apply to\n> current \"master\" and build without warnings, but nothing major, and I\n> haven't sanity checked it much beyond that and letting the kind folks\n> that use Debian experimental play with it.\n\nCool! Thanks for rebasing this series and great to hear that more\npeople are using it.\n\n> I'm sending it out now to get review and ideas for what needs to\n> happen next to get this series in shape to be included in git.git.\n\nExcellent timing, Heiko and I wanted to work on this topic in the\ncoming days anyway.\n"},{"id":"232432","messageId":"52BD83DE.7070104@web.de","threadId":"35575","inReplyTo":"20131226160239.GM20443@google.com","subject":"Re: [PATCH 1/5] submodule: prepare for recursive checkout of submodules","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-12-27T13:42:54Z","receivedAt":"2013-12-27T13:42:54Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Am 26.12.2013 17:02, schrieb Jonathan Nieder:\n> From: Jens Lehmann <Jens.Lehmann@web.de>\n> Date: Mon, 18 Jun 2012 22:17:59 +0200\n> \n> This commit adds the functions needed for configuration, for setting the\n> default behavior and for determining if a submodule path should be updated\n> automatically.\n> \n> Signed-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n> ---\n> Should probably be squashed into a patch that uses and documents this\n> configuration.\n\nI'm not so sure. At the end of the day at least am, bisect, checkout,\ncheckout-index, cherry-pick, merge, pull, read-tree, rebase, reset\n& stash should have learned this option (and all porcelain must honor\nthe autoupdate setting by default, plumbing only when requested by\ngiving the \"--recurse-submodules=config\" option to make life easier\nfor scripting). And I think they should learn this one command per\ncommit, making this one - and the yet-to-be-programmed one introducing\nthe autoupdate flag - the base for them.\n\n>  submodule.c | 36 ++++++++++++++++++++++++++++++++++++\n>  submodule.h |  3 +++\n>  2 files changed, 39 insertions(+)\n> \n> diff --git a/submodule.c b/submodule.c\n> index 613857e..3f18d4d 100644\n> --- a/submodule.c\n> +++ b/submodule.c\n> @@ -16,6 +16,8 @@ static struct string_list config_name_for_path;\n>  static struct string_list config_fetch_recurse_submodules_for_name;\n>  static struct string_list config_ignore_for_name;\n>  static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;\n> +static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;\n> +static int option_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n>  static struct string_list changed_submodule_paths;\n>  static int initialized_fetch_ref_tips;\n>  static struct sha1_array ref_tips_before_fetch;\n> @@ -382,6 +384,34 @@ int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg)\n>  \t}\n>  }\n>  \n> +int parse_update_recurse_submodules_arg(const char *opt, const char *arg)\n> +{\n> +\tswitch (git_config_maybe_bool(opt, arg)) {\n> +\tcase 1:\n> +\t\treturn RECURSE_SUBMODULES_ON;\n> +\tcase 0:\n> +\t\treturn RECURSE_SUBMODULES_OFF;\n> +\tdefault:\n> +\t\tif (!strcmp(arg, \"checkout\"))\n> +\t\t\treturn RECURSE_SUBMODULES_ON;\n> +\t\tdie(\"bad %s argument: %s\", opt, arg);\n> +\t}\n> +}\n> +\n> +int submodule_needs_update(const char *path)\n> +{\n> +\tstruct string_list_item *path_option;\n> +\tpath_option = unsorted_string_list_lookup(&config_name_for_path, path);\n> +\tif (!path_option)\n> +\t\treturn 0;\n> +\n> +\t/* update can't be \"none\", \"merge\" or \"rebase\" */\n> +\n> +\tif (option_update_recurse_submodules != RECURSE_SUBMODULES_DEFAULT)\n> +\t\treturn 1;\n> +\treturn config_update_recurse_submodules != RECURSE_SUBMODULES_OFF;\n> +}\n> +\n>  void show_submodule_summary(FILE *f, const char *path,\n>  \t\tconst char *line_prefix,\n>  \t\tunsigned char one[20], unsigned char two[20],\n> @@ -589,6 +619,12 @@ int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam\n>  \treturn ret;\n>  }\n>  \n> +void set_config_update_recurse_submodules(int default_value, int option_value)\n> +{\n> +\tconfig_update_recurse_submodules = default_value;\n> +\toption_update_recurse_submodules = option_value;\n> +}\n> +\n>  static int is_submodule_commit_present(const char *path, unsigned char sha1[20])\n>  {\n>  \tint is_present = 0;\n> diff --git a/submodule.h b/submodule.h\n> index 7beec48..055918c 100644\n> --- a/submodule.h\n> +++ b/submodule.h\n> @@ -22,12 +22,15 @@ void gitmodules_config(void);\n>  int parse_submodule_config_option(const char *var, const char *value);\n>  void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);\n>  int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n> +int parse_update_recurse_submodules_arg(const char *opt, const char *arg);\n> +int submodule_needs_update(const char *path);\n>  void show_submodule_summary(FILE *f, const char *path,\n>  \t\tconst char *line_prefix,\n>  \t\tunsigned char one[20], unsigned char two[20],\n>  \t\tunsigned dirty_submodule, const char *meta,\n>  \t\tconst char *del, const char *add, const char *reset);\n>  void set_config_fetch_recurse_submodules(int value);\n> +void set_config_update_recurse_submodules(int default_value, int option_value);\n>  void check_for_new_submodule_commits(unsigned char new_sha1[20]);\n>  int fetch_populated_submodules(const struct argv_array *options,\n>  \t\t\t       const char *prefix, int command_line_option,\n> \n"},{"id":"232434","messageId":"52BD8786.1080900@web.de","threadId":"35575","inReplyTo":"20131226161202.GN20443@google.com","subject":"Re: [PATCH 2/5] submodule: teach unpack_trees() to remove submodule contents","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-12-27T13:58:30Z","receivedAt":"2013-12-27T13:58:30Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Am 26.12.2013 17:12, schrieb Jonathan Nieder:\n> From: Jens Lehmann <Jens.Lehmann@web.de>\n> Date: Tue, 19 Jun 2012 20:55:45 +0200\n> \n> Implement the functionality needed to enable work tree manipulating\n> commands to that a deleted submodule should not only affect the index\n> (leaving all the files of the submodule in the work tree) but also to\n> remove the work tree of the superproject (including any untracked\n> files).\n> \n> That will only work properly when the submodule uses a gitfile instead of\n> a .git directory and no untracked files are present. Otherwise the removal\n> will fail with a warning (which is just what happened until now).\n> \n> Extend rmdir_or_warn() to remove the directories of those submodules which\n> are scheduled for removal. Also teach verify_clean_submodule() to check\n> that a submodule configured to be removed is not modified before scheduling\n> it for removal.\n> \n> Signed-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n> ---\n> Should this share some code (or just the error message) with the 'git\n> rm' code that checks whether a submodule is safe to remove?\n\nYes, that would make sense.\n\n> rmdir_or_warn is a pretty low-level function --- it feels odd to be\n> relying on the git repository layout here.  On the other hand, that\n> function only has two callers, so it is possible to check quickly\n> whether it is safe.\n> \n> I assume this is mostly for the sake of the caller in unpack-trees?\n\nYup.\n\n> In builtin/apply.c, remove_file is used for deletion and rename\n> patches.  Do we want this logic take effect there, too?\n\nI think so. Recursive update should also affect apply and am when\nrequested via command line or configuration. But the apply\ndocumentation states that it also handles files outside a git\nrepository, so we would have to disable this logic in that case.\n\n>  submodule.c    | 37 +++++++++++++++++++++++++++++++++++++\n>  submodule.h    |  1 +\n>  unpack-trees.c |  6 +++---\n>  wrapper.c      |  3 +++\n>  4 files changed, 44 insertions(+), 3 deletions(-)\n> \n> diff --git a/submodule.c b/submodule.c\n> index 3f18d4d..a25db46 100644\n> --- a/submodule.c\n> +++ b/submodule.c\n> @@ -412,6 +412,43 @@ int submodule_needs_update(const char *path)\n>  \treturn config_update_recurse_submodules != RECURSE_SUBMODULES_OFF;\n>  }\n>  \n> +int depopulate_submodule(const char *path)\n> +{\n> +\tstruct strbuf dot_git = STRBUF_INIT;\n> +\tstruct child_process cp;\n> +\tconst char *argv[] = {\"rm\", \"-rf\", path, NULL};\n> +\n> +\t/* Is it populated? */\n> +\tstrbuf_addf(&dot_git, \"%s/.git\", path);\n> +\tif (!resolve_gitdir(dot_git.buf)) {\n> +\t\tstrbuf_release(&dot_git);\n> +\t\treturn 0;\n> +\t}\n> +\tstrbuf_release(&dot_git);\n> +\n> +\t/* Does it have a .git directory? */\n> +\tif (!submodule_uses_gitfile(path)) {\n> +\t\twarning(_(\"cannot remove submodule '%s' because it (or one of \"\n> +\t\t\t  \"its nested submodules) uses a .git directory\"),\n> +\t\t\t  path);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\t/* Remove the whole submodule directory */\n> +\tmemset(&cp, 0, sizeof(cp));\n> +\tcp.argv = argv;\n> +\tcp.env = local_repo_env;\n> +\tcp.git_cmd = 0;\n> +\tcp.no_stdin = 1;\n> +\tif (run_command(&cp)) {\n> +\t\twarning(\"Could not remove submodule %s\", path);\n> +\t\tstrbuf_release(&dot_git);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n>  void show_submodule_summary(FILE *f, const char *path,\n>  \t\tconst char *line_prefix,\n>  \t\tunsigned char one[20], unsigned char two[20],\n> diff --git a/submodule.h b/submodule.h\n> index 055918c..df291cf 100644\n> --- a/submodule.h\n> +++ b/submodule.h\n> @@ -24,6 +24,7 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);\n>  int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n>  int parse_update_recurse_submodules_arg(const char *opt, const char *arg);\n>  int submodule_needs_update(const char *path);\n> +int depopulate_submodule(const char *path);\n>  void show_submodule_summary(FILE *f, const char *path,\n>  \t\tconst char *line_prefix,\n>  \t\tunsigned char one[20], unsigned char two[20],\n> diff --git a/unpack-trees.c b/unpack-trees.c\n> index ad3e9a0..89b506a 100644\n> --- a/unpack-trees.c\n> +++ b/unpack-trees.c\n> @@ -8,6 +8,7 @@\n>  #include \"progress.h\"\n>  #include \"refs.h\"\n>  #include \"attr.h\"\n> +#include \"submodule.h\"\n>  \n>  /*\n>   * Error messages expected by scripts out of plumbing commands such as\n> @@ -1263,14 +1264,13 @@ static void invalidate_ce_path(const struct cache_entry *ce,\n>  /*\n>   * Check that checking out ce->sha1 in subdir ce->name is not\n>   * going to overwrite any working files.\n> - *\n> - * Currently, git does not checkout subprojects during a superproject\n> - * checkout, so it is not going to overwrite anything.\n>   */\n>  static int verify_clean_submodule(const struct cache_entry *ce,\n>  \t\t\t\t  enum unpack_trees_error_types error_type,\n>  \t\t\t\t  struct unpack_trees_options *o)\n>  {\n> +\tif (submodule_needs_update(ce->name) && is_submodule_modified(ce->name, 0))\n> +\t\treturn 1;\n>  \treturn 0;\n>  }\n>  \n> diff --git a/wrapper.c b/wrapper.c\n> index 0cc5636..425a3fd 100644\n> --- a/wrapper.c\n> +++ b/wrapper.c\n> @@ -2,6 +2,7 @@\n>   * Various trivial helper wrappers around standard functions\n>   */\n>  #include \"cache.h\"\n> +#include \"submodule.h\"\n>  \n>  static void do_nothing(size_t size)\n>  {\n> @@ -409,6 +410,8 @@ int unlink_or_warn(const char *file)\n>  \n>  int rmdir_or_warn(const char *file)\n>  {\n> +\tif (submodule_needs_update(file) && depopulate_submodule(file))\n> +\t\treturn -1;\n>  \treturn warn_if_unremovable(\"rmdir\", file, rmdir(file));\n>  }\n>  \n> \n"},{"id":"232437","messageId":"52BD8B42.2060507@web.de","threadId":"35575","inReplyTo":"20131226162238.GQ20443@google.com","subject":"Re: [PATCH 5/5] Teach checkout to recursively checkout submodules","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2013-12-27T14:14:26Z","receivedAt":"2013-12-27T14:14:26Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Am 26.12.2013 17:22, schrieb Jonathan Nieder:\n> From: Jens Lehmann <Jens.Lehmann@web.de>\n> Date: Wed, 13 Jun 2012 18:50:10 +0200\n> \n> Signed-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n> ---\n> This is the patch that actually introduces the --recurse-submodules\n> option, which makes the rest work.\n> \n> The tests indicate some future directions for improving this, but\n> it works reasonably well already.  I think I'd be most comfortable\n> applying these if they were rearranged a little to the following\n> order:\n> \n>  1. First, introducing the --recurse-submodules option, perhaps\n>     with no actual effect, with tests that it is parsed correctly,\n>     the default works, etc.\n> \n>  2. Then, adding the desired behaviors one by one with corresponding\n>     tests (handling submodule modifications, removals, additions).\n> \n>  3. Finally, any needed tweaks on top.\n> \n> That way, it is easy to play around with early patches without\n> worrying about the later ones at first, and the patches are easy\n> to review to confirm that they at least don't break anything in\n> the --no-recurse-submodules case.\n\nMakes tons of sense.\n\nThe only feature I'm aware of that is currently missing is to\nread the path <-> name mappings from the correct .gitmodules\nblob, which is needed to populate submodules that appear.\n\n> That said, Debian experimental has these applied as is already,\n> and there haven't been any complaints yet.\n\nGreat!\n\nI'm also using this code at $dayjob successfully for quite some\ntime now. Additionally I also enable unconditional recursive\ncheckout by putting a \"return 1\" in the first line of the\nsubmodule_needs_update() function (Which is a hack to emulate\n\"autoupdate=true\" while at the same time pretending to already\nhaving added \"--recurse-submodules=config\" to all call sites in\ngit porcelain scripts that call plumbing themselves). Only in a\nfew corner cases submodules aren't properly updated, but we'll\nweed those out while implementing the tests. And we need lots\nof those to cover all important combinations ...\n"}]}