Merge branch 'bw/submodule-is-active'
authorJunio C Hamano <gitster@pobox.com>
Thu, 30 Mar 2017 21:07:14 +0000 (14:07 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 30 Mar 2017 21:07:14 +0000 (14:07 -0700)
"what URL do we want to update this submodule?" and "are we
interested in this submodule?" are split into two distinct
concepts, and then the way used to express the latter got extended,
paving a way to make it easier to manage a project with many
submodules and make it possible to later extend use of multiple
worktrees for a project with submodules.

* bw/submodule-is-active:
submodule add: respect submodule.active and submodule.<name>.active
submodule--helper init: set submodule.<name>.active
clone: teach --recurse-submodules to optionally take a pathspec
submodule init: initialize active submodules
submodule: decouple url and submodule interest
submodule--helper clone: check for configured submodules using helper
submodule sync: use submodule--helper is-active
submodule sync: skip work for inactive submodules
submodule status: use submodule--helper is-active
submodule--helper: add is-active subcommand

1  2 
Documentation/config.txt
builtin/clone.c
builtin/submodule--helper.c
submodule.c
diff --combined Documentation/config.txt
index 1df1965457f18dba6b533bf00027650802d72479,d2d79b9d4fef1b8c3c9207efdba4175656878164..475e874d51550eba26a578c0ce3b17b61384fddc
@@@ -79,69 -79,18 +79,69 @@@ escape sequences) are invalid
  Includes
  ~~~~~~~~
  
 -You can include one config file from another by setting the special
 +You can include a config file from another by setting the special
  `include.path` variable to the name of the file to be included. The
  variable takes a pathname as its value, and is subject to tilde
 -expansion.
 +expansion. `include.path` can be given multiple times.
  
 -The
 -included file is expanded immediately, as if its contents had been
 +The included file is expanded immediately, as if its contents had been
  found at the location of the include directive. If the value of the
 -`include.path` variable is a relative path, the path is considered to be
 -relative to the configuration file in which the include directive was
 -found.  See below for examples.
 +`include.path` variable is a relative path, the path is considered to
 +be relative to the configuration file in which the include directive
 +was found.  See below for examples.
  
 +Conditional includes
 +~~~~~~~~~~~~~~~~~~~~
 +
 +You can include a config file from another conditionally by setting a
 +`includeIf.<condition>.path` variable to the name of the file to be
 +included. The variable's value is treated the same way as
 +`include.path`. `includeIf.<condition>.path` can be given multiple times.
 +
 +The condition starts with a keyword followed by a colon and some data
 +whose format and meaning depends on the keyword. Supported keywords
 +are:
 +
 +`gitdir`::
 +
 +      The data that follows the keyword `gitdir:` is used as a glob
 +      pattern. If the location of the .git directory matches the
 +      pattern, the include condition is met.
 ++
 +The .git location may be auto-discovered, or come from `$GIT_DIR`
 +environment variable. If the repository is auto discovered via a .git
 +file (e.g. from submodules, or a linked worktree), the .git location
 +would be the final location where the .git directory is, not where the
 +.git file is.
 ++
 +The pattern can contain standard globbing wildcards and two additional
 +ones, `**/` and `/**`, that can match multiple path components. Please
 +refer to linkgit:gitignore[5] for details. For convenience:
 +
 + * If the pattern starts with `~/`, `~` will be substituted with the
 +   content of the environment variable `HOME`.
 +
 + * If the pattern starts with `./`, it is replaced with the directory
 +   containing the current config file.
 +
 + * If the pattern does not start with either `~/`, `./` or `/`, `**/`
 +   will be automatically prepended. For example, the pattern `foo/bar`
 +   becomes `**/foo/bar` and would match `/any/path/to/foo/bar`.
 +
 + * If the pattern ends with `/`, `**` will be automatically added. For
 +   example, the pattern `foo/` becomes `foo/**`. In other words, it
 +   matches "foo" and everything inside, recursively.
 +
 +`gitdir/i`::
 +      This is the same as `gitdir` except that matching is done
 +      case-insensitively (e.g. on case-insensitive file sytems)
 +
 +A few more notes on matching via `gitdir` and `gitdir/i`:
 +
 + * Symlinks in `$GIT_DIR` are not resolved before matching.
 +
 + * Note that "../" is not special and will match literally, which is
 +   unlikely what you want.
  
  Example
  ~~~~~~~
                path = foo ; expand "foo" relative to the current file
                path = ~/foo ; expand "foo" in your `$HOME` directory
  
 +      ; include if $GIT_DIR is /path/to/foo/.git
 +      [includeIf "gitdir:/path/to/foo/.git"]
 +              path = /path/to/foo.inc
 +
 +      ; include for all repositories inside /path/to/group
 +      [includeIf "gitdir:/path/to/group/"]
 +              path = /path/to/foo.inc
 +
 +      ; include for all repositories inside $HOME/to/group
 +      [includeIf "gitdir:~/to/group/"]
 +              path = /path/to/foo.inc
  
  Values
  ~~~~~~
@@@ -396,10 -334,6 +396,10 @@@ core.trustctime:
        crawlers and some backup systems).
        See linkgit:git-update-index[1]. True by default.
  
 +core.splitIndex::
 +      If true, the split-index feature of the index will be used.
 +      See linkgit:git-update-index[1]. False by default.
 +
  core.untrackedCache::
        Determines what to do about the untracked cache feature of the
        index. It will be kept, if this variable is unset or set to
@@@ -737,13 -671,13 +737,13 @@@ alternative to having an `init.template
  default hooks.
  
  core.editor::
 -      Commands such as `commit` and `tag` that lets you edit
 -      messages by launching an editor uses the value of this
 +      Commands such as `commit` and `tag` that let you edit
 +      messages by launching an editor use the value of this
        variable when it is set, and the environment variable
        `GIT_EDITOR` is not set.  See linkgit:git-var[1].
  
  core.commentChar::
 -      Commands such as `commit` and `tag` that lets you edit
 +      Commands such as `commit` and `tag` that let you edit
        messages consider a line that begins with this character
        commented, and removes them after the editor returns
        (default '#').
@@@ -2521,8 -2455,6 +2521,8 @@@ push.default:
    pushing to the same repository you would normally pull from
    (i.e. central workflow).
  
 +* `tracking` - This is a deprecated synonym for `upstream`.
 +
  * `simple` - in centralized workflow, work like `upstream` with an
    added safety to refuse to push if the upstream branch's name is
    different from the local one.
@@@ -2918,31 -2850,6 +2918,31 @@@ showbranch.default:
        The default set of branches for linkgit:git-show-branch[1].
        See linkgit:git-show-branch[1].
  
 +splitIndex.maxPercentChange::
 +      When the split index feature is used, this specifies the
 +      percent of entries the split index can contain compared to the
 +      total number of entries in both the split index and the shared
 +      index before a new shared index is written.
 +      The value should be between 0 and 100. If the value is 0 then
 +      a new shared index is always written, if it is 100 a new
 +      shared index is never written.
 +      By default the value is 20, so a new shared index is written
 +      if the number of entries in the split index would be greater
 +      than 20 percent of the total number of entries.
 +      See linkgit:git-update-index[1].
 +
 +splitIndex.sharedIndexExpire::
 +      When the split index feature is used, shared index files that
 +      were not modified since the time this variable specifies will
 +      be removed when a new shared index file is created. The value
 +      "now" expires all entries immediately, and "never" suppresses
 +      expiration altogether.
 +      The default value is "2.weeks.ago".
 +      Note that a shared index file is considered modified (for the
 +      purpose of expiration) each time a new split-index file is
 +      either created based on it or read from it.
 +      See linkgit:git-update-index[1].
 +
  status.relativePaths::
        By default, linkgit:git-status[1] shows paths relative to the
        current directory. Setting this variable to `false` shows paths
@@@ -3013,8 -2920,9 +3013,9 @@@ submodule.<name>.url:
        The URL for a submodule. This variable is copied from the .gitmodules
        file to the git config via 'git submodule init'. The user can change
        the configured URL before obtaining the submodule via 'git submodule
-       update'. After obtaining the submodule, the presence of this variable
-       is used as a sign whether the submodule is of interest to git commands.
+       update'. If neither submodule.<name>.active or submodule.active are
+       set, the presence of this variable is used as a fallback to indicate
+       whether the submodule is of interest to git commands.
        See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
  
  submodule.<name>.update::
@@@ -3052,6 -2960,16 +3053,16 @@@ submodule.<name>.ignore:
        "--ignore-submodules" option. The 'git submodule' commands are not
        affected by this setting.
  
+ submodule.<name>.active::
+       Boolean value indicating if the submodule is of interest to git
+       commands.  This config option takes precedence over the
+       submodule.active config option.
+ submodule.active::
+       A repeated field which contains a pathspec used to match against a
+       submodule's path to determine if the submodule is of interest to git
+       commands.
  submodule.fetchJobs::
        Specifies how many submodules are fetched/cloned at the same time.
        A positive integer allows up to that number of submodules fetched
diff --combined builtin/clone.c
index b4c929bb8afbf94967949279a1cc3b9b21dae100,a7be61d6b7035666538d863c4b08de2567698bd7..de85b85254e49ba0211ea6476179fc6d4c774ca9
@@@ -39,7 -39,7 +39,7 @@@ static const char * const builtin_clone
  };
  
  static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
- static int option_local = -1, option_no_hardlinks, option_shared, option_recursive;
+ static int option_local = -1, option_no_hardlinks, option_shared;
  static int option_shallow_submodules;
  static int deepen;
  static char *option_template, *option_depth, *option_since;
@@@ -56,6 -56,21 +56,21 @@@ static struct string_list option_requir
  static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
  static int option_dissociate;
  static int max_jobs = -1;
+ static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
+ static int recurse_submodules_cb(const struct option *opt,
+                                const char *arg, int unset)
+ {
+       if (unset)
+               string_list_clear((struct string_list *)opt->value, 0);
+       else if (arg)
+               string_list_append((struct string_list *)opt->value, arg);
+       else
+               string_list_append((struct string_list *)opt->value,
+                                  (const char *)opt->defval);
+       return 0;
+ }
  
  static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
                    N_("don't use local hardlinks, always copy")),
        OPT_BOOL('s', "shared", &option_shared,
                    N_("setup as shared repository")),
-       OPT_BOOL(0, "recursive", &option_recursive,
-                   N_("initialize submodules in the clone")),
-       OPT_BOOL(0, "recurse-submodules", &option_recursive,
-                   N_("initialize submodules in the clone")),
+       { OPTION_CALLBACK, 0, "recursive", &option_recurse_submodules,
+         N_("pathspec"), N_("initialize submodules in the clone"),
+         PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, recurse_submodules_cb,
+         (intptr_t)"." },
+       { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules,
+         N_("pathspec"), N_("initialize submodules in the clone"),
+         PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." },
        OPT_INTEGER('j', "jobs", &max_jobs,
                    N_("number of submodules cloned in parallel")),
        OPT_STRING(0, "template", &option_template, N_("template-directory"),
@@@ -681,7 -699,7 +699,7 @@@ static void update_head(const struct re
  
  static int checkout(int submodule_progress)
  {
 -      unsigned char sha1[20];
 +      struct object_id oid;
        char *head;
        struct lock_file *lock_file;
        struct unpack_trees_options opts;
        if (option_no_checkout)
                return 0;
  
 -      head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL);
 +      head = resolve_refdup("HEAD", RESOLVE_REF_READING, oid.hash, NULL);
        if (!head) {
                warning(_("remote HEAD refers to nonexistent ref, "
                          "unable to checkout.\n"));
        }
        if (!strcmp(head, "HEAD")) {
                if (advice_detached_head)
 -                      detach_advice(sha1_to_hex(sha1));
 +                      detach_advice(oid_to_hex(&oid));
        } else {
                if (!starts_with(head, "refs/heads/"))
                        die(_("HEAD not found below refs/heads!"));
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
  
 -      tree = parse_tree_indirect(sha1);
 +      tree = parse_tree_indirect(oid.hash);
        parse_tree(tree);
        init_tree_desc(&t, tree->buffer, tree->size);
        if (unpack_trees(1, &t, &opts) < 0)
                die(_("unable to write new index file"));
  
        err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
 -                         sha1_to_hex(sha1), "1", NULL);
 +                         oid_to_hex(&oid), "1", NULL);
  
-       if (!err && option_recursive) {
+       if (!err && (option_recurse_submodules.nr > 0)) {
                struct argv_array args = ARGV_ARRAY_INIT;
                argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
  
@@@ -957,7 -975,25 +975,25 @@@ int cmd_clone(int argc, const char **ar
                        fprintf(stderr, _("Cloning into '%s'...\n"), dir);
        }
  
-       if (option_recursive) {
+       if (option_recurse_submodules.nr > 0) {
+               struct string_list_item *item;
+               struct strbuf sb = STRBUF_INIT;
+               /* remove duplicates */
+               string_list_sort(&option_recurse_submodules);
+               string_list_remove_duplicates(&option_recurse_submodules, 0);
+               /*
+                * NEEDSWORK: In a multi-working-tree world, this needs to be
+                * set in the per-worktree config.
+                */
+               for_each_string_list_item(item, &option_recurse_submodules) {
+                       strbuf_addf(&sb, "submodule.active=%s",
+                                   item->string);
+                       string_list_append(&option_config,
+                                          strbuf_detach(&sb, NULL));
+               }
                if (option_required_reference.nr &&
                    option_optional_reference.nr)
                        die(_("clone --recursive is not compatible with "
index be316bbbc898c9311c6ec82aa0cfbc808f04e8c9,7700d89488ed971a4e7cdfcafcbc4c0880836ee3..85aafe46a463c651eb09d540ea9ffa0cfcd4a796
@@@ -270,6 -270,29 +270,29 @@@ static int module_list_compute(int argc
        return result;
  }
  
+ static void module_list_active(struct module_list *list)
+ {
+       int i;
+       struct module_list active_modules = MODULE_LIST_INIT;
+       gitmodules_config();
+       for (i = 0; i < list->nr; i++) {
+               const struct cache_entry *ce = list->entries[i];
+               if (!is_submodule_initialized(ce->name))
+                       continue;
+               ALLOC_GROW(active_modules.entries,
+                          active_modules.nr + 1,
+                          active_modules.alloc);
+               active_modules.entries[active_modules.nr++] = ce;
+       }
+       free(list->entries);
+       *list = active_modules;
+ }
  static int module_list(int argc, const char **argv, const char *prefix)
  {
        int i;
@@@ -333,6 -356,18 +356,18 @@@ static void init_submodule(const char *
                die(_("No url found for submodule path '%s' in .gitmodules"),
                        displaypath);
  
+       /*
+        * NEEDSWORK: In a multi-working-tree world, this needs to be
+        * set in the per-worktree config.
+        *
+        * Set active flag for the submodule being initialized
+        */
+       if (!is_submodule_initialized(path)) {
+               strbuf_reset(&sb);
+               strbuf_addf(&sb, "submodule.%s.active", sub->name);
+               git_config_set_gently(sb.buf, "true");
+       }
        /*
         * Copy url setting when it is not set yet.
         * To look up the url in .git/config, we must not fall back to
@@@ -420,6 -455,13 +455,13 @@@ static int module_init(int argc, const 
        if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
                return 1;
  
+       /*
+        * If there are no path args and submodule.active is set then,
+        * by default, only initialize 'active' modules.
+        */
+       if (!argc && git_config_get_value_multi("submodule.active"))
+               module_list_active(&list);
        for (i = 0; i < list.nr; i++)
                init_submodule(list.entries[i]->name, prefix, quiet);
  
@@@ -577,7 -619,9 +619,7 @@@ static int module_clone(int argc, cons
        const char *name = NULL, *url = NULL, *depth = NULL;
        int quiet = 0;
        int progress = 0;
 -      FILE *submodule_dot_git;
        char *p, *path = NULL, *sm_gitdir;
 -      struct strbuf rel_path = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
        struct string_list reference = STRING_LIST_INIT_NODUP;
        char *sm_alternate = NULL, *error_strategy = NULL;
                strbuf_reset(&sb);
        }
  
 -      /* Write a .git file in the submodule to redirect to the superproject. */
 -      strbuf_addf(&sb, "%s/.git", path);
 -      if (safe_create_leading_directories_const(sb.buf) < 0)
 -              die(_("could not create leading directories of '%s'"), sb.buf);
 -      submodule_dot_git = fopen(sb.buf, "w");
 -      if (!submodule_dot_git)
 -              die_errno(_("cannot open file '%s'"), sb.buf);
 -
 -      fprintf_or_die(submodule_dot_git, "gitdir: %s\n",
 -                     relative_path(sm_gitdir, path, &rel_path));
 -      if (fclose(submodule_dot_git))
 -              die(_("could not close file %s"), sb.buf);
 -      strbuf_reset(&sb);
 -      strbuf_reset(&rel_path);
 +      /* Connect module worktree and git dir */
 +      connect_work_tree_and_git_dir(path, sm_gitdir);
  
 -      /* Redirect the worktree of the submodule in the superproject's config */
        p = git_pathdup_submodule(path, "config");
        if (!p)
                die(_("could not get submodule directory for '%s'"), path);
 -      git_config_set_in_file(p, "core.worktree",
 -                             relative_path(path, sm_gitdir, &rel_path));
  
        /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */
        git_config_get_string("submodule.alternateLocation", &sm_alternate);
        free(error_strategy);
  
        strbuf_release(&sb);
 -      strbuf_release(&rel_path);
        free(sm_gitdir);
        free(path);
        free(p);
@@@ -741,7 -801,6 +783,6 @@@ static int prepare_to_clone_next_submod
        struct strbuf displaypath_sb = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
        const char *displaypath = NULL;
-       char *url = NULL;
        int needs_cloning = 0;
  
        if (ce_stage(ce)) {
                goto cleanup;
        }
  
-       /*
-        * Looking up the url in .git/config.
-        * We must not fall back to .gitmodules as we only want
-        * to process configured submodules.
-        */
-       strbuf_reset(&sb);
-       strbuf_addf(&sb, "submodule.%s.url", sub->name);
-       git_config_get_string(sb.buf, &url);
-       if (!url) {
+       /* Check if the submodule has been initialized. */
+       if (!is_submodule_initialized(ce->name)) {
                next_submodule_warn_missing(suc, out, displaypath);
                goto cleanup;
        }
                argv_array_push(&child->args, "--depth=1");
        argv_array_pushl(&child->args, "--path", sub->path, NULL);
        argv_array_pushl(&child->args, "--name", sub->name, NULL);
-       argv_array_pushl(&child->args, "--url", url, NULL);
+       argv_array_pushl(&child->args, "--url", sub->url, NULL);
        if (suc->references.nr) {
                struct string_list_item *item;
                for_each_string_list_item(item, &suc->references)
                argv_array_push(&child->args, suc->depth);
  
  cleanup:
-       free(url);
        strbuf_reset(&displaypath_sb);
        strbuf_reset(&sb);
  
@@@ -1109,6 -1160,16 +1142,16 @@@ static int absorb_git_dirs(int argc, co
        return 0;
  }
  
+ static int is_active(int argc, const char **argv, const char *prefix)
+ {
+       if (argc != 2)
+               die("submodule--helper is-active takes exactly 1 arguments");
+       gitmodules_config();
+       return !is_submodule_initialized(argv[1]);
+ }
  #define SUPPORT_SUPER_PREFIX (1<<0)
  
  struct cmd_struct {
@@@ -1128,6 -1189,7 +1171,7 @@@ static struct cmd_struct commands[] = 
        {"init", module_init, SUPPORT_SUPER_PREFIX},
        {"remote-branch", resolve_remote_submodule_branch, 0},
        {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
+       {"is-active", is_active, 0},
  };
  
  int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
diff --combined submodule.c
index 040f4c2287190cca706f9cd007c5942ae5c2b117,ad2779ee76c80ddc534f5d2982605dc54e474e2a..c52d6634c528d79ddedf069b7e09e955d283ed64
@@@ -17,7 -17,6 +17,7 @@@
  #include "worktree.h"
  
  static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
 +static int config_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
  static int parallel_jobs = 1;
  static struct string_list changed_submodule_paths = STRING_LIST_INIT_NODUP;
  static int initialized_fetch_ref_tips;
@@@ -213,34 -212,71 +213,68 @@@ void gitmodules_config_sha1(const unsig
  }
  
  /*
+  * NEEDSWORK: With the addition of different configuration options to determine
+  * if a submodule is of interests, the validity of this function's name comes
+  * into question.  Once the dust has settled and more concrete terminology is
+  * decided upon, come up with a more proper name for this function.  One
+  * potential candidate could be 'is_submodule_active()'.
+  *
   * Determine if a submodule has been initialized at a given 'path'
   */
  int is_submodule_initialized(const char *path)
  {
        int ret = 0;
-       const struct submodule *module = NULL;
+       char *key = NULL;
+       char *value = NULL;
+       const struct string_list *sl;
+       const struct submodule *module = submodule_from_path(null_sha1, path);
  
-       module = submodule_from_path(null_sha1, path);
+       /* early return if there isn't a path->module mapping */
+       if (!module)
+               return 0;
  
-       if (module) {
-               char *key = xstrfmt("submodule.%s.url", module->name);
-               char *value = NULL;
+       /* submodule.<name>.active is set */
+       key = xstrfmt("submodule.%s.active", module->name);
+       if (!git_config_get_bool(key, &ret)) {
+               free(key);
+               return ret;
+       }
+       free(key);
  
-               ret = !git_config_get_string(key, &value);
+       /* submodule.active is set */
+       sl = git_config_get_value_multi("submodule.active");
+       if (sl) {
+               struct pathspec ps;
+               struct argv_array args = ARGV_ARRAY_INIT;
+               const struct string_list_item *item;
  
-               free(value);
-               free(key);
+               for_each_string_list_item(item, sl) {
+                       argv_array_push(&args, item->string);
+               }
+               parse_pathspec(&ps, 0, 0, NULL, args.argv);
+               ret = match_pathspec(&ps, path, strlen(path), 0, NULL, 1);
+               argv_array_clear(&args);
+               clear_pathspec(&ps);
+               return ret;
        }
  
+       /* fallback to checking if the URL is set */
+       key = xstrfmt("submodule.%s.url", module->name);
+       ret = !git_config_get_string(key, &value);
+       free(value);
+       free(key);
        return ret;
  }
  
 -/*
 - * Determine if a submodule has been populated at a given 'path'
 - */
 -int is_submodule_populated(const char *path)
 +int is_submodule_populated_gently(const char *path, int *return_error_code)
  {
        int ret = 0;
        char *gitdir = xstrfmt("%s/.git", path);
  
 -      if (resolve_gitdir(gitdir))
 +      if (resolve_gitdir_gently(gitdir, return_error_code))
                ret = 1;
  
        free(gitdir);
@@@ -356,23 -392,6 +390,23 @@@ static void print_submodule_summary(str
        strbuf_release(&sb);
  }
  
 +static void prepare_submodule_repo_env_no_git_dir(struct argv_array *out)
 +{
 +      const char * const *var;
 +
 +      for (var = local_repo_env; *var; var++) {
 +              if (strcmp(*var, CONFIG_DATA_ENVIRONMENT))
 +                      argv_array_push(out, *var);
 +      }
 +}
 +
 +void prepare_submodule_repo_env(struct argv_array *out)
 +{
 +      prepare_submodule_repo_env_no_git_dir(out);
 +      argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT,
 +                       DEFAULT_GIT_DIR_ENVIRONMENT);
 +}
 +
  /* Helper function to display the submodule header line prior to the full
   * summary output. If it can locate the submodule objects directory it will
   * attempt to lookup both the left and right commits and put them into the
@@@ -560,27 -579,6 +594,27 @@@ void set_config_fetch_recurse_submodule
        config_fetch_recurse_submodules = value;
  }
  
 +void set_config_update_recurse_submodules(int value)
 +{
 +      config_update_recurse_submodules = value;
 +}
 +
 +int should_update_submodules(void)
 +{
 +      return config_update_recurse_submodules == RECURSE_SUBMODULES_ON;
 +}
 +
 +const struct submodule *submodule_from_ce(const struct cache_entry *ce)
 +{
 +      if (!S_ISGITLINK(ce->ce_mode))
 +              return NULL;
 +
 +      if (!should_update_submodules())
 +              return NULL;
 +
 +      return submodule_from_path(null_sha1, ce->name);
 +}
 +
  static int has_remote(const char *refname, const struct object_id *oid,
                      int flags, void *cb_data)
  {
        return ret;
  }
  
 +static const char *get_super_prefix_or_empty(void)
 +{
 +      const char *s = get_super_prefix();
 +      if (!s)
 +              s = "";
 +      return s;
 +}
 +
 +static int submodule_has_dirty_index(const struct submodule *sub)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +
 +      prepare_submodule_repo_env_no_git_dir(&cp.env_array);
 +
 +      cp.git_cmd = 1;
 +      argv_array_pushl(&cp.args, "diff-index", "--quiet",
 +                                 "--cached", "HEAD", NULL);
 +      cp.no_stdin = 1;
 +      cp.no_stdout = 1;
 +      cp.dir = sub->path;
 +      if (start_command(&cp))
 +              die("could not recurse into submodule '%s'", sub->path);
 +
 +      return finish_command(&cp);
 +}
 +
 +static void submodule_reset_index(const char *path)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      prepare_submodule_repo_env_no_git_dir(&cp.env_array);
 +
 +      cp.git_cmd = 1;
 +      cp.no_stdin = 1;
 +      cp.dir = path;
 +
 +      argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
 +                                 get_super_prefix_or_empty(), path);
 +      argv_array_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
 +
 +      argv_array_push(&cp.args, EMPTY_TREE_SHA1_HEX);
 +
 +      if (run_command(&cp))
 +              die("could not reset submodule index");
 +}
 +
 +/**
 + * Moves a submodule at a given path from a given head to another new head.
 + * For edge cases (a submodule coming into existence or removing a submodule)
 + * pass NULL for old or new respectively.
 + */
 +int submodule_move_head(const char *path,
 +                       const char *old,
 +                       const char *new,
 +                       unsigned flags)
 +{
 +      int ret = 0;
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      const struct submodule *sub;
 +
 +      sub = submodule_from_path(null_sha1, path);
 +
 +      if (!sub)
 +              die("BUG: could not get submodule information for '%s'", path);
 +
 +      if (old && !(flags & SUBMODULE_MOVE_HEAD_FORCE)) {
 +              /* Check if the submodule has a dirty index. */
 +              if (submodule_has_dirty_index(sub))
 +                      return error(_("submodule '%s' has dirty index"), path);
 +      }
 +
 +      if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {
 +              if (old) {
 +                      if (!submodule_uses_gitfile(path))
 +                              absorb_git_dir_into_superproject("", path,
 +                                      ABSORB_GITDIR_RECURSE_SUBMODULES);
 +              } else {
 +                      struct strbuf sb = STRBUF_INIT;
 +                      strbuf_addf(&sb, "%s/modules/%s",
 +                                  get_git_common_dir(), sub->name);
 +                      connect_work_tree_and_git_dir(path, sb.buf);
 +                      strbuf_release(&sb);
 +
 +                      /* make sure the index is clean as well */
 +                      submodule_reset_index(path);
 +              }
 +      }
 +
 +      prepare_submodule_repo_env_no_git_dir(&cp.env_array);
 +
 +      cp.git_cmd = 1;
 +      cp.no_stdin = 1;
 +      cp.dir = path;
 +
 +      argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
 +                      get_super_prefix_or_empty(), path);
 +      argv_array_pushl(&cp.args, "read-tree", NULL);
 +
 +      if (flags & SUBMODULE_MOVE_HEAD_DRY_RUN)
 +              argv_array_push(&cp.args, "-n");
 +      else
 +              argv_array_push(&cp.args, "-u");
 +
 +      if (flags & SUBMODULE_MOVE_HEAD_FORCE)
 +              argv_array_push(&cp.args, "--reset");
 +      else
 +              argv_array_push(&cp.args, "-m");
 +
 +      argv_array_push(&cp.args, old ? old : EMPTY_TREE_SHA1_HEX);
 +      argv_array_push(&cp.args, new ? new : EMPTY_TREE_SHA1_HEX);
 +
 +      if (run_command(&cp)) {
 +              ret = -1;
 +              goto out;
 +      }
 +
 +      if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {
 +              if (new) {
 +                      struct child_process cp1 = CHILD_PROCESS_INIT;
 +                      /* also set the HEAD accordingly */
 +                      cp1.git_cmd = 1;
 +                      cp1.no_stdin = 1;
 +                      cp1.dir = path;
 +
 +                      argv_array_pushl(&cp1.args, "update-ref", "HEAD",
 +                                       new ? new : EMPTY_TREE_SHA1_HEX, NULL);
 +
 +                      if (run_command(&cp1)) {
 +                              ret = -1;
 +                              goto out;
 +                      }
 +              } else {
 +                      struct strbuf sb = STRBUF_INIT;
 +
 +                      strbuf_addf(&sb, "%s/.git", path);
 +                      unlink_or_warn(sb.buf);
 +                      strbuf_release(&sb);
 +
 +                      if (is_empty_dir(path))
 +                              rmdir_or_warn(path);
 +              }
 +      }
 +out:
 +      return ret;
 +}
 +
  static int find_first_merges(struct object_array *result, const char *path,
                struct commit *a, struct commit *b)
  {
@@@ -1552,6 -1405,18 +1586,6 @@@ int parallel_submodules(void
        return parallel_jobs;
  }
  
 -void prepare_submodule_repo_env(struct argv_array *out)
 -{
 -      const char * const *var;
 -
 -      for (var = local_repo_env; *var; var++) {
 -              if (strcmp(*var, CONFIG_DATA_ENVIRONMENT))
 -                      argv_array_push(out, *var);
 -      }
 -      argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT,
 -                       DEFAULT_GIT_DIR_ENVIRONMENT);
 -}
 -
  /*
   * Embeds a single submodules git directory into the superprojects git dir,
   * non recursively.
@@@ -1583,8 -1448,11 +1617,8 @@@ static void relocate_single_git_dir_int
                die(_("could not create directory '%s'"), new_git_dir);
        real_new_git_dir = real_pathdup(new_git_dir, 1);
  
 -      if (!prefix)
 -              prefix = get_super_prefix();
 -
        fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
 -              prefix ? prefix : "", path,
 +              get_super_prefix_or_empty(), path,
                real_old_git_dir, real_new_git_dir);
  
        relocate_gitdir(path, real_old_git_dir, real_new_git_dir);
@@@ -1611,6 -1479,8 +1645,6 @@@ void absorb_git_dir_into_superproject(c
  
        /* Not populated? */
        if (!sub_git_dir) {
 -              char *real_new_git_dir;
 -              const char *new_git_dir;
                const struct submodule *sub;
  
                if (err_code == READ_GITFILE_ERR_STAT_FAILED) {
                sub = submodule_from_path(null_sha1, path);
                if (!sub)
                        die(_("could not lookup name for submodule '%s'"), path);
 -              new_git_dir = git_path("modules/%s", sub->name);
 -              if (safe_create_leading_directories_const(new_git_dir) < 0)
 -                      die(_("could not create directory '%s'"), new_git_dir);
 -              real_new_git_dir = real_pathdup(new_git_dir, 1);
 -              connect_work_tree_and_git_dir(path, real_new_git_dir);
 -
 -              free(real_new_git_dir);
 +              connect_work_tree_and_git_dir(path,
 +                      git_path("modules/%s", sub->name));
        } else {
                /* Is it already absorbed into the superprojects git dir? */
                char *real_sub_git_dir = real_pathdup(sub_git_dir, 1);
                if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES)
                        die("BUG: we don't know how to pass the flags down?");
  
 -              if (get_super_prefix())
 -                      strbuf_addstr(&sb, get_super_prefix());
 +              strbuf_addstr(&sb, get_super_prefix_or_empty());
                strbuf_addstr(&sb, path);
                strbuf_addch(&sb, '/');
  
                strbuf_release(&sb);
        }
  }
 +
 +const char *get_superproject_working_tree(void)
 +{
 +      struct child_process cp = CHILD_PROCESS_INIT;
 +      struct strbuf sb = STRBUF_INIT;
 +      const char *one_up = real_path_if_valid("../");
 +      const char *cwd = xgetcwd();
 +      const char *ret = NULL;
 +      const char *subpath;
 +      int code;
 +      ssize_t len;
 +
 +      if (!is_inside_work_tree())
 +              /*
 +               * FIXME:
 +               * We might have a superproject, but it is harder
 +               * to determine.
 +               */
 +              return NULL;
 +
 +      if (!one_up)
 +              return NULL;
 +
 +      subpath = relative_path(cwd, one_up, &sb);
 +
 +      prepare_submodule_repo_env(&cp.env_array);
 +      argv_array_pop(&cp.env_array);
 +
 +      argv_array_pushl(&cp.args, "--literal-pathspecs", "-C", "..",
 +                      "ls-files", "-z", "--stage", "--full-name", "--",
 +                      subpath, NULL);
 +      strbuf_reset(&sb);
 +
 +      cp.no_stdin = 1;
 +      cp.no_stderr = 1;
 +      cp.out = -1;
 +      cp.git_cmd = 1;
 +
 +      if (start_command(&cp))
 +              die(_("could not start ls-files in .."));
 +
 +      len = strbuf_read(&sb, cp.out, PATH_MAX);
 +      close(cp.out);
 +
 +      if (starts_with(sb.buf, "160000")) {
 +              int super_sub_len;
 +              int cwd_len = strlen(cwd);
 +              char *super_sub, *super_wt;
 +
 +              /*
 +               * There is a superproject having this repo as a submodule.
 +               * The format is <mode> SP <hash> SP <stage> TAB <full name> \0,
 +               * We're only interested in the name after the tab.
 +               */
 +              super_sub = strchr(sb.buf, '\t') + 1;
 +              super_sub_len = sb.buf + sb.len - super_sub - 1;
 +
 +              if (super_sub_len > cwd_len ||
 +                  strcmp(&cwd[cwd_len - super_sub_len], super_sub))
 +                      die (_("BUG: returned path string doesn't match cwd?"));
 +
 +              super_wt = xstrdup(cwd);
 +              super_wt[cwd_len - super_sub_len] = '\0';
 +
 +              ret = real_path(super_wt);
 +              free(super_wt);
 +      }
 +      strbuf_release(&sb);
 +
 +      code = finish_command(&cp);
 +
 +      if (code == 128)
 +              /* '../' is not a git repository */
 +              return NULL;
 +      if (code == 0 && len == 0)
 +              /* There is an unrelated git repository at '../' */
 +              return NULL;
 +      if (code)
 +              die(_("ls-tree returned unexpected return code %d"), code);
 +
 +      return ret;
 +}