Merge branch 'sb/submodule-blanket-recursive'
authorJunio C Hamano <gitster@pobox.com>
Tue, 13 Jun 2017 20:47:07 +0000 (13:47 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 13 Jun 2017 20:47:07 +0000 (13:47 -0700)
Many commands learned to pay attention to submodule.recurse
configuration.

* sb/submodule-blanket-recursive:
builtin/fetch.c: respect 'submodule.recurse' option
builtin/push.c: respect 'submodule.recurse' option
builtin/grep.c: respect 'submodule.recurse' option
Introduce 'submodule.recurse' option for worktree manipulators
submodule loading: separate code path for .gitmodules and config overlay
reset/checkout/read-tree: unify config callback for submodule recursion
submodule test invocation: only pass additional arguments
submodule recursing: do not write a config variable twice

12 files changed:
1  2 
Documentation/config.txt
builtin/checkout.c
builtin/fetch.c
builtin/grep.c
builtin/read-tree.c
builtin/reset.c
submodule.c
submodule.h
t/lib-submodule-update.sh
t/t1013-read-tree-submodule.sh
t/t2013-checkout-submodule.sh
t/t5531-deep-submodule-push.sh
diff --combined Documentation/config.txt
index dd4beec39dbe0bb328448c2ff7ca918b97016aa5,f60c504e86dd1149b18a19b6040e0d547bd11191..f6278a5ae6a1f554ddb8b9861d9067181b928bb5
@@@ -79,20 -79,14 +79,20 @@@ escape sequences) are invalid
  Includes
  ~~~~~~~~
  
 +The `include` and `includeIf` sections allow you to include config
 +directives from another source. These sections behave identically to
 +each other with the exception that `includeIf` sections may be ignored
 +if their condition does not evaluate to true; see "Conditional includes"
 +below.
 +
  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. `include.path` can be given multiple times.
 +`include.path` (or `includeIf.*.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. These variables can be given multiple times.
  
 -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
 +The contents of the included file are inserted immediately, as if they
 +had been found at the location of the include directive. If the value of the
 +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.
  
@@@ -101,7 -95,8 +101,7 @@@ Conditional include
  
  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.
 +included.
  
  The condition starts with a keyword followed by a colon and some data
  whose format and meaning depends on the keyword. Supported keywords
@@@ -145,16 -140,6 +145,16 @@@ A few more notes on matching via `gitdi
  
   * Symlinks in `$GIT_DIR` are not resolved before matching.
  
 + * Both the symlink & realpath versions of paths will be matched
 +   outside of `$GIT_DIR`. E.g. if ~/git is a symlink to
 +   /mnt/storage/git, both `gitdir:~/git` and `gitdir:/mnt/storage/git`
 +   will match.
 ++
 +This was not the case in the initial release of this feature in
 +v2.13.0, which only matched the realpath version. Configuration that
 +wants to be compatible with the initial release of this feature needs
 +to either specify only the realpath version, or both versions.
 +
   * Note that "../" is not special and will match literally, which is
     unlikely what you want.
  
@@@ -182,8 -167,8 +182,8 @@@ Exampl
  
        [include]
                path = /path/to/foo.inc ; include by absolute path
 -              path = foo ; expand "foo" relative to the current file
 -              path = ~/foo ; expand "foo" in your `$HOME` directory
 +              path = foo.inc ; find "foo.inc" relative to the current file
 +              path = ~/foo.inc ; find "foo.inc" in your `$HOME` directory
  
        ; include if $GIT_DIR is /path/to/foo/.git
        [includeIf "gitdir:/path/to/foo/.git"]
        [includeIf "gitdir:~/to/group/"]
                path = /path/to/foo.inc
  
 +      ; relative paths are always relative to the including
 +      ; file (if the condition is true); their location is not
 +      ; affected by the condition
 +      [includeIf "gitdir:/path/to/group/"]
 +              path = foo.inc
 +
  Values
  ~~~~~~
  
@@@ -355,7 -334,7 +355,7 @@@ core.fileMode:
        is to be honored.
  +
  Some filesystems lose the executable bit when a file that is
 -marked as executable is checked out, or checks out an
 +marked as executable is checked out, or checks out a
  non-executable file with executable bit on.
  linkgit:git-clone[1] or linkgit:git-init[1] probe the filesystem
  to see if it handles the executable bit correctly
@@@ -883,7 -862,6 +883,7 @@@ core.abbrev:
        computed based on the approximate number of packed objects
        in your repository, which hopefully is enough for
        abbreviated object names to stay unique for some time.
 +      The minimum length is 4.
  
  add.ignoreErrors::
  add.ignore-errors (deprecated)::
@@@ -2165,10 -2143,6 +2165,10 @@@ log.showRoot:
        Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which
        normally hide the root commit will now show it. True by default.
  
 +log.showSignature::
 +      If true, makes linkgit:git-log[1], linkgit:git-show[1], and
 +      linkgit:git-whatchanged[1] assume `--show-signature`.
 +
  log.mailmap::
        If true, makes linkgit:git-log[1], linkgit:git-show[1], and
        linkgit:git-whatchanged[1] assume `--use-mailmap`.
@@@ -3091,6 -3065,11 +3091,11 @@@ submodule.active:
        submodule's path to determine if the submodule is of interest to git
        commands.
  
+ submodule.recurse::
+       Specifies if commands recurse into submodules by default. This
+       applies to all commands that have a `--recurse-submodules` option.
+       Defaults to false.
  submodule.fetchJobs::
        Specifies how many submodules are fetched/cloned at the same time.
        A positive integer allows up to that number of submodules fetched
@@@ -3236,13 -3215,6 +3241,13 @@@ url.<base>.insteadOf:
        the best alternative for the particular user, even for a
        never-before-seen repository on the site.  When more than one
        insteadOf strings match a given URL, the longest match is used.
 ++
 +Note that any protocol restrictions will be applied to the rewritten
 +URL. If the rewrite changes the URL to use a custom protocol or remote
 +helper, you may need to adjust the `protocol.*.allow` config to permit
 +the request.  In particular, protocols you expect to use for submodules
 +must be set to `always` rather than the default of `user`. See the
 +description of `protocol.allow` above.
  
  url.<base>.pushInsteadOf::
        Any URL that starts with this value will not be pushed to;
diff --combined builtin/checkout.c
index a6b2af39d3e881480193a38ff1ca3b65cdc55d94,e289b7d47751274a184ec879e414be6ad78c47a8..1624eed7e7625c18da7ffb58f1b9ae456c3dbd77
  #include "submodule-config.h"
  #include "submodule.h"
  
- static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
  static const char * const checkout_usage[] = {
        N_("git checkout [<options>] <branch>"),
        N_("git checkout [<options>] [<branch>] -- <file>..."),
        NULL,
  };
  
- static int option_parse_recurse_submodules(const struct option *opt,
-                                          const char *arg, int unset)
- {
-       if (unset) {
-               recurse_submodules = RECURSE_SUBMODULES_OFF;
-               return 0;
-       }
-       if (arg)
-               recurse_submodules =
-                       parse_update_recurse_submodules_arg(opt->long_name,
-                                                           arg);
-       else
-               recurse_submodules = RECURSE_SUBMODULES_ON;
-       return 0;
- }
  struct checkout_opts {
        int patch_mode;
        int quiet;
@@@ -235,24 -216,22 +216,24 @@@ static int checkout_merged(int pos, con
        /*
         * NEEDSWORK:
         * There is absolutely no reason to write this as a blob object
 -       * and create a phony cache entry just to leak.  This hack is
 -       * primarily to get to the write_entry() machinery that massages
 -       * the contents to work-tree format and writes out which only
 -       * allows it for a cache entry.  The code in write_entry() needs
 -       * to be refactored to allow us to feed a <buffer, size, mode>
 -       * instead of a cache entry.  Such a refactoring would help
 -       * merge_recursive as well (it also writes the merge result to the
 -       * object database even when it may contain conflicts).
 +       * and create a phony cache entry.  This hack is primarily to get
 +       * to the write_entry() machinery that massages the contents to
 +       * work-tree format and writes out which only allows it for a
 +       * cache entry.  The code in write_entry() needs to be refactored
 +       * to allow us to feed a <buffer, size, mode> instead of a cache
 +       * entry.  Such a refactoring would help merge_recursive as well
 +       * (it also writes the merge result to the object database even
 +       * when it may contain conflicts).
         */
        if (write_sha1_file(result_buf.ptr, result_buf.size,
                            blob_type, oid.hash))
                die(_("Unable to add merge result for '%s'"), path);
 +      free(result_buf.ptr);
        ce = make_cache_entry(mode, oid.hash, path, 2, 0);
        if (!ce)
                die(_("make_cache_entry failed for path '%s'"), path);
        status = checkout_entry(ce, state, NULL);
 +      free(ce);
        return status;
  }
  
@@@ -395,7 -374,7 +376,7 @@@ static int checkout_paths(const struct 
                die(_("unable to write new index file"));
  
        read_ref_full("HEAD", 0, rev.hash, NULL);
 -      head = lookup_commit_reference_gently(rev.hash, 1);
 +      head = lookup_commit_reference_gently(&rev, 1);
  
        errs |= post_checkout_hook(head, head, 0);
        return errs;
@@@ -529,10 -508,10 +510,10 @@@ static int merge_working_tree(const str
                        setup_standard_excludes(topts.dir);
                }
                tree = parse_tree_indirect(old->commit ?
 -                                         old->commit->object.oid.hash :
 -                                         EMPTY_TREE_SHA1_BIN);
 +                                         &old->commit->object.oid :
 +                                         &empty_tree_oid);
                init_tree_desc(&trees[0], tree->buffer, tree->size);
 -              tree = parse_tree_indirect(new->commit->object.oid.hash);
 +              tree = parse_tree_indirect(&new->commit->object.oid);
                init_tree_desc(&trees[1], tree->buffer, tree->size);
  
                ret = unpack_trees(2, trees, &topts);
@@@ -723,7 -702,7 +704,7 @@@ static int add_pending_uninteresting_re
                                         const struct object_id *oid,
                                         int flags, void *cb_data)
  {
 -      add_pending_sha1(cb_data, refname, oid->hash, UNINTERESTING);
 +      add_pending_oid(cb_data, refname, oid, UNINTERESTING);
        return 0;
  }
  
@@@ -809,7 -788,7 +790,7 @@@ static void orphaned_commit_warning(str
        add_pending_object(&revs, object, oid_to_hex(&object->oid));
  
        for_each_ref(add_pending_uninteresting_ref, &revs);
 -      add_pending_sha1(&revs, "HEAD", new->object.oid.hash, UNINTERESTING);
 +      add_pending_oid(&revs, "HEAD", &new->object.oid, UNINTERESTING);
  
        refs = revs.pending;
        revs.leak_pending = 1;
@@@ -836,7 -815,7 +817,7 @@@ static int switch_branches(const struc
        memset(&old, 0, sizeof(old));
        old.path = path_to_free = resolve_refdup("HEAD", 0, rev.hash, &flag);
        if (old.path)
 -              old.commit = lookup_commit_reference_gently(rev.hash, 1);
 +              old.commit = lookup_commit_reference_gently(&rev, 1);
        if (!(flag & REF_ISSYMREF))
                old.path = NULL;
  
@@@ -876,7 -855,7 +857,7 @@@ static int git_checkout_config(const ch
        }
  
        if (starts_with(var, "submodule."))
-               return parse_submodule_config_option(var, value);
+               return submodule_config(var, value, NULL);
  
        return git_xmerge_config(var, value, NULL);
  }
@@@ -1050,10 -1029,10 +1031,10 @@@ static int parse_branchname_arg(int arg
        else
                new->path = NULL; /* not an existing branch */
  
 -      new->commit = lookup_commit_reference_gently(rev->hash, 1);
 +      new->commit = lookup_commit_reference_gently(rev, 1);
        if (!new->commit) {
                /* not a commit */
 -              *source_tree = parse_tree_indirect(rev->hash);
 +              *source_tree = parse_tree_indirect(rev);
        } else {
                parse_commit_or_die(new->commit);
                *source_tree = new->commit->tree;
@@@ -1184,9 -1163,9 +1165,9 @@@ int cmd_checkout(int argc, const char *
                                N_("second guess 'git checkout <no-such-branch>'")),
                OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
                         N_("do not check if another worktree is holding the given ref")),
-               { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
+               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
                            "checkout", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
                OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
                OPT_END(),
        };
                git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
        }
  
-       if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
-               git_config(submodule_config, NULL);
-               if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT)
-                       set_config_update_recurse_submodules(recurse_submodules);
-       }
        if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
                die(_("-b, -B and --orphan are mutually exclusive"));
  
                 * new_branch && argc > 1 will be caught later.
                 */
                if (opts.new_branch && argc == 1)
 -                      die(_("Cannot update paths and switch to branch '%s' at the same time.\n"
 -                            "Did you intend to checkout '%s' which can not be resolved as commit?"),
 -                          opts.new_branch, argv[0]);
 +                      die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
 +                              argv[0], opts.new_branch);
  
                if (opts.force_detach)
                        die(_("git checkout: --detach does not take a path argument '%s'"),
diff --combined builtin/fetch.c
index 47708451bc5e124f9c6da4de60cc476a5d2c1f10,c1ec3b03c3829559b42932d7a875d7952223162f..100248c5afe3e1c16fa7fe79696200aeb5d1bde2
@@@ -73,6 -73,13 +73,13 @@@ static int git_fetch_config(const char 
                fetch_prune_config = git_config_bool(k, v);
                return 0;
        }
+       if (!strcmp(k, "submodule.recurse")) {
+               int r = git_config_bool(k, v) ?
+                       RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
+               recurse_submodules = r;
+       }
        return git_default_config(k, v, cb);
  }
  
@@@ -636,8 -643,8 +643,8 @@@ static int update_local_ref(struct ref 
                return r;
        }
  
 -      current = lookup_commit_reference_gently(ref->old_oid.hash, 1);
 -      updated = lookup_commit_reference_gently(ref->new_oid.hash, 1);
 +      current = lookup_commit_reference_gently(&ref->old_oid, 1);
 +      updated = lookup_commit_reference_gently(&ref->new_oid, 1);
        if (!current || !updated) {
                const char *msg;
                const char *what;
@@@ -770,8 -777,7 +777,8 @@@ static int store_updated_refs(const cha
                                continue;
                        }
  
 -                      commit = lookup_commit_reference_gently(rm->old_oid.hash, 1);
 +                      commit = lookup_commit_reference_gently(&rm->old_oid,
 +                                                              1);
                        if (!commit)
                                rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
  
@@@ -941,7 -947,7 +948,7 @@@ static int prune_refs(struct refspec *r
                for (ref = stale_refs; ref; ref = ref->next)
                        string_list_append(&refnames, ref->name);
  
 -              result = delete_refs(&refnames, 0);
 +              result = delete_refs("fetch: prune", &refnames, 0);
                string_list_clear(&refnames, 0);
        }
  
diff --combined builtin/grep.c
index 7df9c253eebb3053693769d14efe0551393dfd4d,454e26382006f3ed872fa8150f37f595706010b6..d188871556d814b1532f7118f1d76741850a370d
@@@ -302,6 -302,9 +302,9 @@@ static int grep_cmd_config(const char *
  #endif
        }
  
+       if (!strcmp(var, "submodule.recurse"))
+               recurse_submodules = git_config_bool(var, value);
        return st;
  }
  
@@@ -879,7 -882,7 +882,7 @@@ static int grep_directory(struct grep_o
        if (exc_std)
                setup_standard_excludes(&dir);
  
 -      fill_directory(&dir, pathspec);
 +      fill_directory(&dir, &the_index, pathspec);
        for (i = 0; i < dir.nr; i++) {
                if (!dir_path_match(dir.entries[i], pathspec, 0, NULL))
                        continue;
@@@ -1203,18 -1206,16 +1206,18 @@@ int cmd_grep(int argc, const char **arg
                        break;
                }
  
 -              if (get_sha1_with_context(arg, 0, oid.hash, &oc)) {
 +              if (get_sha1_with_context(arg, GET_SHA1_RECORD_PATH,
 +                                        oid.hash, &oc)) {
                        if (seen_dashdash)
                                die(_("unable to resolve revision: %s"), arg);
                        break;
                }
  
 -              object = parse_object_or_die(oid.hash, arg);
 +              object = parse_object_or_die(&oid, arg);
                if (!seen_dashdash)
                        verify_non_filename(prefix, arg);
                add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
 +              free(oc.path);
        }
  
        /*
diff --combined builtin/read-tree.c
index 78d3193659e06b4969324153689f219f1cd1c1b3,7fd55140db6423f77237bed70b53a6acd4ef7064..5bfd4c9f76d84c177b72a8ed97d3418d111583e9
  static int nr_trees;
  static int read_empty;
  static struct tree *trees[MAX_UNPACK_TREES];
- static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
  
 -static int list_tree(unsigned char *sha1)
 +static int list_tree(struct object_id *oid)
  {
        struct tree *tree;
  
        if (nr_trees >= MAX_UNPACK_TREES)
                die("I cannot read more than %d trees", MAX_UNPACK_TREES);
 -      tree = parse_tree_indirect(sha1);
 +      tree = parse_tree_indirect(oid);
        if (!tree)
                return -1;
        trees[nr_trees++] = tree;
@@@ -99,21 -98,12 +98,12 @@@ static int debug_merge(const struct cac
        return 0;
  }
  
- static int option_parse_recurse_submodules(const struct option *opt,
-                                          const char *arg, int unset)
+ static int git_read_tree_config(const char *var, const char *value, void *cb)
  {
-       if (unset) {
-               recurse_submodules = RECURSE_SUBMODULES_OFF;
-               return 0;
-       }
-       if (arg)
-               recurse_submodules =
-                       parse_update_recurse_submodules_arg(opt->long_name,
-                                                           arg);
-       else
-               recurse_submodules = RECURSE_SUBMODULES_ON;
+       if (!strcmp(var, "submodule.recurse"))
+               return git_default_submodule_config(var, value, cb);
  
-       return 0;
+       return git_default_config(var, value, cb);
  }
  
  static struct lock_file lock_file;
  int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
  {
        int i, stage = 0;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct tree_desc t[MAX_UNPACK_TREES];
        struct unpack_trees_options opts;
        int prefix_set = 0;
                         N_("skip applying sparse checkout filter")),
                OPT_BOOL(0, "debug-unpack", &opts.debug_unpack,
                         N_("debug unpack-trees")),
-               { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
+               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
                            "checkout", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
                OPT_END()
        };
  
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
  
-       git_config(git_default_config, NULL);
+       git_config(git_read_tree_config, NULL);
  
        argc = parse_options(argc, argv, unused_prefix, read_tree_options,
                             read_tree_usage, 0);
  
-       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
+       load_submodule_cache();
  
-       if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) {
-               gitmodules_config();
-               git_config(submodule_config, NULL);
-               set_config_update_recurse_submodules(RECURSE_SUBMODULES_ON);
-       }
+       hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
  
        prefix_set = opts.prefix ? 1 : 0;
        if (1 < opts.merge + opts.reset + prefix_set)
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
  
 -              if (get_sha1(arg, sha1))
 +              if (get_oid(arg, &oid))
                        die("Not a valid object name %s", arg);
 -              if (list_tree(sha1) < 0)
 +              if (list_tree(&oid) < 0)
                        die("failed to unpack tree object %s", arg);
                stage++;
        }
 -      if (nr_trees == 0 && !read_empty)
 +      if (!nr_trees && !read_empty && !opts.merge)
                warning("read-tree: emptying the index with no arguments is deprecated; use --empty");
        else if (nr_trees > 0 && read_empty)
                die("passing trees as arguments contradicts --empty");
                setup_work_tree();
  
        if (opts.merge) {
 -              if (stage < 2)
 -                      die("just how do you expect me to merge %d trees?", stage-1);
                switch (stage - 1) {
 +              case 0:
 +                      die("you must specify at least one tree to merge");
 +                      break;
                case 1:
                        opts.fn = opts.prefix ? bind_merge : oneway_merge;
                        break;
diff --combined builtin/reset.c
index 430602d102133d125009e8c8068ce5547c24cab7,585cfe07451c28f1d8b5ca78a7f0054b0eee6def..45001e5200cb4c5aaf0ad316cf1622f9495d851d
  #include "submodule.h"
  #include "submodule-config.h"
  
- static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
- static int option_parse_recurse_submodules(const struct option *opt,
-                                          const char *arg, int unset)
- {
-       if (unset) {
-               recurse_submodules = RECURSE_SUBMODULES_OFF;
-               return 0;
-       }
-       if (arg)
-               recurse_submodules =
-                       parse_update_recurse_submodules_arg(opt->long_name,
-                                                           arg);
-       else
-               recurse_submodules = RECURSE_SUBMODULES_ON;
-       return 0;
- }
  static const char * const git_reset_usage[] = {
        N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
        N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
@@@ -105,7 -86,7 +86,7 @@@ static int reset_index(const struct obj
                return -1;
  
        if (reset_type == MIXED || reset_type == HARD) {
 -              tree = parse_tree_indirect(oid->hash);
 +              tree = parse_tree_indirect(oid);
                prime_cache_tree(&the_index, tree);
        }
  
@@@ -175,7 -156,7 +156,7 @@@ static int read_from_tree(const struct 
        opt.format_callback = update_index_from_diff;
        opt.format_callback_data = &intent_to_add;
  
 -      if (do_diff_cache(tree_oid->hash, &opt))
 +      if (do_diff_cache(tree_oid, &opt))
                return 1;
        diffcore_std(&opt);
        diff_flush(&opt);
@@@ -257,6 -238,7 +238,6 @@@ static void parse_args(struct pathspec 
  
        parse_pathspec(pathspec, 0,
                       PATHSPEC_PREFER_FULL |
 -                     PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP |
                       (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0),
                       prefix, argv);
  }
@@@ -284,6 -266,14 +265,14 @@@ static int reset_refs(const char *rev, 
        return update_ref_status;
  }
  
+ static int git_reset_config(const char *var, const char *value, void *cb)
+ {
+       if (!strcmp(var, "submodule.recurse"))
+               return git_default_submodule_config(var, value, cb);
+       return git_default_config(var, value, cb);
+ }
  int cmd_reset(int argc, const char **argv, const char *prefix)
  {
        int reset_type = NONE, update_ref_status = 0, quiet = 0;
                                N_("reset HEAD, index and working tree"), MERGE),
                OPT_SET_INT(0, "keep", &reset_type,
                                N_("reset HEAD but keep local changes"), KEEP),
-               { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
+               { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
                            "reset", "control recursive updating of submodules",
-                           PARSE_OPT_OPTARG, option_parse_recurse_submodules },
+                           PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
                OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
                OPT_BOOL('N', "intent-to-add", &intent_to_add,
                                N_("record only the fact that removed paths will be added later")),
                OPT_END()
        };
  
-       git_config(git_default_config, NULL);
+       git_config(git_reset_config, NULL);
  
        argc = parse_options(argc, argv, prefix, options, git_reset_usage,
                                                PARSE_OPT_KEEP_DASHDASH);
        parse_args(&pathspec, argv, prefix, patch_mode, &rev);
  
-       if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) {
-               gitmodules_config();
-               git_config(submodule_config, NULL);
-               set_config_update_recurse_submodules(RECURSE_SUBMODULES_ON);
-       }
+       load_submodule_cache();
  
        unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", oid.hash);
        if (unborn) {
                struct commit *commit;
                if (get_sha1_committish(rev, oid.hash))
                        die(_("Failed to resolve '%s' as a valid revision."), rev);
 -              commit = lookup_commit_reference(oid.hash);
 +              commit = lookup_commit_reference(&oid);
                if (!commit)
                        die(_("Could not parse object '%s'."), rev);
                oidcpy(&oid, &commit->object.oid);
                struct tree *tree;
                if (get_sha1_treeish(rev, oid.hash))
                        die(_("Failed to resolve '%s' as a valid tree."), rev);
 -              tree = parse_tree_indirect(oid.hash);
 +              tree = parse_tree_indirect(&oid);
                if (!tree)
                        die(_("Could not parse object '%s'."), rev);
                oidcpy(&oid, &tree->object.oid);
                update_ref_status = reset_refs(rev, &oid);
  
                if (reset_type == HARD && !update_ref_status && !quiet)
 -                      print_new_head_line(lookup_commit_reference(oid.hash));
 +                      print_new_head_line(lookup_commit_reference(&oid));
        }
        if (!pathspec.nr)
                remove_branch_state();
diff --combined submodule.c
index bf5a93d16fb71cdeb87f589732b6f95f4a83fbe0,2b157dc9952ee0ba49e28f9407b1dc56ecd9d81d..1b8a3b575db6c85a42e9c9e285617113ecac4a84
  #include "quote.h"
  #include "remote.h"
  #include "worktree.h"
+ #include "parse-options.h"
  
  static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
- static int config_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+ static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
  static int parallel_jobs = 1;
  static struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP;
  static int initialized_fetch_ref_tips;
@@@ -153,7 -154,8 +154,8 @@@ void set_diffopt_flags_from_submodule_c
        }
  }
  
- int submodule_config(const char *var, const char *value, void *cb)
+ /* For loading from the .gitmodules file. */
+ static int git_modules_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "submodule.fetchjobs")) {
                parallel_jobs = git_config_int(var, value);
        return 0;
  }
  
+ /* Loads all submodule settings from the config. */
+ int submodule_config(const char *var, const char *value, void *cb)
+ {
+       if (!strcmp(var, "submodule.recurse")) {
+               int v = git_config_bool(var, value) ?
+                       RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
+               config_update_recurse_submodules = v;
+               return 0;
+       } else {
+               return git_modules_config(var, value, cb);
+       }
+ }
+ /* Cheap function that only determines if we're interested in submodules at all */
+ int git_default_submodule_config(const char *var, const char *value, void *cb)
+ {
+       if (!strcmp(var, "submodule.recurse")) {
+               int v = git_config_bool(var, value) ?
+                       RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
+               config_update_recurse_submodules = v;
+       }
+       return 0;
+ }
+ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
+                                                    const char *arg, int unset)
+ {
+       if (unset) {
+               config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
+               return 0;
+       }
+       if (arg)
+               config_update_recurse_submodules =
+                       parse_update_recurse_submodules_arg(opt->long_name,
+                                                           arg);
+       else
+               config_update_recurse_submodules = RECURSE_SUBMODULES_ON;
+       return 0;
+ }
+ void load_submodule_cache(void)
+ {
+       if (config_update_recurse_submodules == RECURSE_SUBMODULES_OFF)
+               return;
+       gitmodules_config();
+       git_config(submodule_config, NULL);
+ }
  void gitmodules_config(void)
  {
        const char *work_tree = get_git_work_tree();
                }
  
                if (!gitmodules_is_unmerged)
-                       git_config_from_file(submodule_config, gitmodules_path.buf, NULL);
+                       git_config_from_file(git_modules_config,
+                               gitmodules_path.buf, NULL);
                strbuf_release(&gitmodules_path);
        }
  }
@@@ -207,7 -260,7 +260,7 @@@ void gitmodules_config_sha1(const unsig
        unsigned char sha1[20];
  
        if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) {
-               git_config_from_blob_sha1(submodule_config, rev.buf,
+               git_config_from_blob_sha1(git_modules_config, rev.buf,
                                          sha1, NULL);
        }
        strbuf_release(&rev);
@@@ -282,69 -335,6 +335,69 @@@ int is_submodule_populated_gently(cons
        return ret;
  }
  
 +/*
 + * Dies if the provided 'prefix' corresponds to an unpopulated submodule
 + */
 +void die_in_unpopulated_submodule(const struct index_state *istate,
 +                                const char *prefix)
 +{
 +      int i, prefixlen;
 +
 +      if (!prefix)
 +              return;
 +
 +      prefixlen = strlen(prefix);
 +
 +      for (i = 0; i < istate->cache_nr; i++) {
 +              struct cache_entry *ce = istate->cache[i];
 +              int ce_len = ce_namelen(ce);
 +
 +              if (!S_ISGITLINK(ce->ce_mode))
 +                      continue;
 +              if (prefixlen <= ce_len)
 +                      continue;
 +              if (strncmp(ce->name, prefix, ce_len))
 +                      continue;
 +              if (prefix[ce_len] != '/')
 +                      continue;
 +
 +              die(_("in unpopulated submodule '%s'"), ce->name);
 +      }
 +}
 +
 +/*
 + * Dies if any paths in the provided pathspec descends into a submodule
 + */
 +void die_path_inside_submodule(const struct index_state *istate,
 +                             const struct pathspec *ps)
 +{
 +      int i, j;
 +
 +      for (i = 0; i < istate->cache_nr; i++) {
 +              struct cache_entry *ce = istate->cache[i];
 +              int ce_len = ce_namelen(ce);
 +
 +              if (!S_ISGITLINK(ce->ce_mode))
 +                      continue;
 +
 +              for (j = 0; j < ps->nr ; j++) {
 +                      const struct pathspec_item *item = &ps->items[j];
 +
 +                      if (item->len <= ce_len)
 +                              continue;
 +                      if (item->match[ce_len] != '/')
 +                              continue;
 +                      if (strncmp(ce->name, item->match, ce_len))
 +                              continue;
 +                      if (item->len == ce_len + 1)
 +                              continue;
 +
 +                      die(_("Pathspec '%s' is in submodule '%.*s'"),
 +                          item->original, ce_len, ce->name);
 +              }
 +      }
 +}
 +
  int parse_submodule_update_strategy(const char *value,
                struct submodule_update_strategy *dst)
  {
@@@ -510,8 -500,8 +563,8 @@@ static void show_submodule_header(FILE 
         * Attempt to lookup the commit references, and determine if this is
         * a fast forward or fast backwards update.
         */
 -      *left = lookup_commit_reference(one->hash);
 -      *right = lookup_commit_reference(two->hash);
 +      *left = lookup_commit_reference(one);
 +      *right = lookup_commit_reference(two);
  
        /*
         * Warn about missing commits in the submodule project, but only if
@@@ -617,8 -607,7 +670,8 @@@ void show_submodule_inline_diff(FILE *f
        cp.no_stdin = 1;
  
        /* TODO: other options may need to be passed here. */
 -      argv_array_push(&cp.args, "diff");
 +      argv_array_pushl(&cp.args, "diff", "--submodule=diff", NULL);
 +
        argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix);
        if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
                argv_array_pushf(&cp.args, "--src-prefix=%s%s/",
@@@ -660,11 -649,6 +713,6 @@@ 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;
@@@ -786,7 -770,7 +834,7 @@@ static int check_has_commit(const struc
  {
        int *has_commit = data;
  
 -      if (!lookup_commit_reference(oid->hash))
 +      if (!lookup_commit_reference(oid))
                *has_commit = 0;
  
        return 0;
@@@ -1420,7 -1404,7 +1468,7 @@@ static int submodule_has_dirty_index(co
  {
        struct child_process cp = CHILD_PROCESS_INIT;
  
 -      prepare_submodule_repo_env_no_git_dir(&cp.env_array);
 +      prepare_submodule_repo_env(&cp.env_array);
  
        cp.git_cmd = 1;
        argv_array_pushl(&cp.args, "diff-index", "--quiet",
  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);
 +      prepare_submodule_repo_env(&cp.env_array);
  
        cp.git_cmd = 1;
        cp.no_stdin = 1;
@@@ -1518,7 -1502,7 +1566,7 @@@ int submodule_move_head(const char *pat
                }
        }
  
 -      prepare_submodule_repo_env_no_git_dir(&cp.env_array);
 +      prepare_submodule_repo_env(&cp.env_array);
  
        cp.git_cmd = 1;
        cp.no_stdin = 1;
  
        argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
                        get_super_prefix_or_empty(), path);
 -      argv_array_pushl(&cp.args, "read-tree", NULL);
 +      argv_array_pushl(&cp.args, "read-tree", "--recurse-submodules", NULL);
  
        if (flags & SUBMODULE_MOVE_HEAD_DRY_RUN)
                argv_array_push(&cp.args, "-n");
  
        if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {
                if (new) {
 -                      struct child_process cp1 = CHILD_PROCESS_INIT;
 +                      child_process_init(&cp);
                        /* also set the HEAD accordingly */
 -                      cp1.git_cmd = 1;
 -                      cp1.no_stdin = 1;
 -                      cp1.dir = path;
 +                      cp.git_cmd = 1;
 +                      cp.no_stdin = 1;
 +                      cp.dir = path;
  
 -                      argv_array_pushl(&cp1.args, "update-ref", "HEAD", new, NULL);
 +                      prepare_submodule_repo_env(&cp.env_array);
 +                      argv_array_pushl(&cp.args, "update-ref", "HEAD", new, NULL);
  
 -                      if (run_command(&cp1)) {
 +                      if (run_command(&cp)) {
                                ret = -1;
                                goto out;
                        }
@@@ -1647,9 -1630,9 +1695,9 @@@ static void print_commit(struct commit 
  #define MERGE_WARNING(path, msg) \
        warning("Failed to merge submodule %s (%s)", path, msg);
  
 -int merge_submodule(unsigned char result[20], const char *path,
 -                  const unsigned char base[20], const unsigned char a[20],
 -                  const unsigned char b[20], int search)
 +int merge_submodule(struct object_id *result, const char *path,
 +                  const struct object_id *base, const struct object_id *a,
 +                  const struct object_id *b, int search)
  {
        struct commit *commit_base, *commit_a, *commit_b;
        int parent_count;
        int i;
  
        /* store a in result in case we fail */
 -      hashcpy(result, a);
 +      oidcpy(result, a);
  
        /* we can not handle deletion conflicts */
 -      if (is_null_sha1(base))
 +      if (is_null_oid(base))
                return 0;
 -      if (is_null_sha1(a))
 +      if (is_null_oid(a))
                return 0;
 -      if (is_null_sha1(b))
 +      if (is_null_oid(b))
                return 0;
  
        if (add_submodule_odb(path)) {
  
        /* Case #1: a is contained in b or vice versa */
        if (in_merge_bases(commit_a, commit_b)) {
 -              hashcpy(result, b);
 +              oidcpy(result, b);
                return 1;
        }
        if (in_merge_bases(commit_b, commit_a)) {
 -              hashcpy(result, a);
 +              oidcpy(result, a);
                return 1;
        }
  
diff --combined submodule.h
index 8fb0f25498d58344adb86fee53770bb11cf36cbc,d920ca1d5a6953edbe05178cb0fb28786892bc78..cbe5c1726f9adab2b032d872d4bd84425e6ce518
@@@ -39,6 -39,12 +39,12 @@@ extern void stage_updated_gitmodules(vo
  extern void set_diffopt_flags_from_submodule_config(struct diff_options *,
                const char *path);
  extern int submodule_config(const char *var, const char *value, void *cb);
+ extern int git_default_submodule_config(const char *var, const char *value, void *cb);
+ struct option;
+ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
+                                                    const char *arg, int unset);
+ void load_submodule_cache(void);
  extern void gitmodules_config(void);
  extern void gitmodules_config_sha1(const unsigned char *commit_sha1);
  extern int is_submodule_initialized(const char *path);
   * Otherwise the return error code is the same as of resolve_gitdir_gently.
   */
  extern int is_submodule_populated_gently(const char *path, int *return_error_code);
 +extern void die_in_unpopulated_submodule(const struct index_state *istate,
 +                                       const char *prefix);
 +extern void die_path_inside_submodule(const struct index_state *istate,
 +                                    const struct pathspec *ps);
  extern int parse_submodule_update_strategy(const char *value,
                struct submodule_update_strategy *dst);
  extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);
@@@ -69,7 -71,6 +75,6 @@@ extern void show_submodule_inline_diff(
                const char *del, const char *add, const char *reset,
                const struct diff_options *opt);
  extern void set_config_fetch_recurse_submodules(int value);
- extern void set_config_update_recurse_submodules(int value);
  /* Check if we want to update any submodule.*/
  extern int should_update_submodules(void);
  /*
@@@ -88,10 -89,10 +93,10 @@@ extern int submodule_uses_gitfile(cons
  #define SUBMODULE_REMOVAL_IGNORE_UNTRACKED (1<<1)
  #define SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED (1<<2)
  extern int bad_to_remove_submodule(const char *path, unsigned flags);
 -extern int merge_submodule(unsigned char result[20], const char *path,
 -                         const unsigned char base[20],
 -                         const unsigned char a[20],
 -                         const unsigned char b[20], int search);
 +extern int merge_submodule(struct object_id *result, const char *path,
 +                         const struct object_id *base,
 +                         const struct object_id *a,
 +                         const struct object_id *b, int search);
  extern int find_unpushed_submodules(struct oid_array *commits,
                                    const char *remotes_name,
                                    struct string_list *needs_pushing);
index 58bd4aeb2c52c67831a1dced2ef6b09755d417d3,52beadad964bb6ab157b4a0f2659ca936b728b9b..2d26f86800906ab1783edf10e7196adadd5f4af7
@@@ -781,13 -781,19 +781,14 @@@ test_submodule_forced_switch () 
  # - Removing a submodule with a git directory absorbs the submodules
  #   git directory first into the superproject.
  
- test_submodule_switch_recursing () {
-       command="$1"
+ test_submodule_switch_recursing_with_args () {
+       cmd_args="$1"
+       command="git $cmd_args --recurse-submodules"
        RESULTDS=success
        if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
        then
                RESULTDS=failure
        fi
 -      RESULTR=success
 -      if test "$KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED" = 1
 -      then
 -              RESULTR=failure
 -      fi
        RESULTOI=success
        if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1
        then
                )
        '
  
+       test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" '
+               prolog &&
+               reset_work_tree_to_interested add_sub1 &&
+               (
+                       cd submodule_update &&
+                       git branch -t modify_sub1 origin/modify_sub1 &&
+                       git -c submodule.recurse=true $cmd_args modify_sub1 &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1
+               )
+       '
        # Updating a submodule to an invalid sha1 doesn't update the
        # superproject nor the submodule's work tree.
        test_expect_success "$command: updating to a missing submodule commit fails" '
        '
  
        # recursing deeper than one level doesn't work yet.
 -      test_expect_$RESULTR "$command: modified submodule updates submodule recursively" '
 +      test_expect_success "$command: modified submodule updates submodule recursively" '
                prolog &&
                reset_work_tree_to_interested add_nested_sub &&
                (
  # Test that submodule contents are updated when switching between commits
  # that change a submodule, but throwing away local changes in
  # the superproject as well as the submodule is allowed.
- test_submodule_forced_switch_recursing () {
-       command="$1"
+ test_submodule_forced_switch_recursing_with_args () {
+       cmd_args="$1"
+       command="git $cmd_args --recurse-submodules"
        RESULT=success
        if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
        then
index 7019d0a04feb7714f0ed8aad1d093c3a3878c78a,2c8d6203240114f6d47f4099d6a2df224c9c9286..91a6fafcb425f367192c7eb4e4cdf0ae4d19ac37
@@@ -5,12 -5,13 +5,12 @@@ test_description='read-tree can handle 
  . ./test-lib.sh
  . "$TEST_DIRECTORY"/lib-submodule-update.sh
  
 -KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1
  KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
  KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
  
- test_submodule_switch_recursing "git read-tree --recurse-submodules -u -m"
+ test_submodule_switch_recursing_with_args "read-tree -u -m"
  
- test_submodule_forced_switch_recursing "git read-tree --recurse-submodules -u --reset"
+ test_submodule_forced_switch_recursing_with_args "read-tree -u --reset"
  
  test_submodule_switch "git read-tree -u -m"
  
index aa3522336966749fbea6e8dbe6d8f32cf711a021,c962a02277943256254278ede8f03e623595de6e..6ef15738e44ed8ad82960c042b3212fce266ec93
@@@ -64,9 -64,10 +64,9 @@@ test_expect_success '"checkout <submodu
  '
  
  KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
- test_submodule_switch_recursing "git checkout --recurse-submodules"
 -KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1
+ test_submodule_switch_recursing_with_args "checkout"
  
- test_submodule_forced_switch_recursing "git checkout -f --recurse-submodules"
+ test_submodule_forced_switch_recursing_with_args "checkout -f"
  
  test_submodule_switch "git checkout"
  
index 23c533e82e71e3b6ed26fc69313c4b51c36c3faa,712c595fd83a32df11ef92cf86684dd14e2e6e5a..beff65b8ace505d78c3997a737711ee7fc6383b5
@@@ -1,6 -1,6 +1,6 @@@
  #!/bin/sh
  
 -test_description='unpack-objects'
 +test_description='test push with submodules'
  
  . ./test-lib.sh
  
@@@ -27,7 -27,7 +27,7 @@@ test_expect_success setup 
        )
  '
  
 -test_expect_success push '
 +test_expect_success 'push works with recorded gitlink' '
        (
                cd work &&
                git push ../pub.git master
@@@ -126,6 -126,27 +126,27 @@@ test_expect_success 'push succeeds if s
        )
  '
  
+ test_expect_success 'push succeeds if submodule commit not on remote but using auto-on-demand via submodule.recurse config' '
+       (
+               cd work/gar/bage &&
+               >recurse-on-demand-from-submodule-recurse-config &&
+               git add recurse-on-demand-from-submodule-recurse-config &&
+               git commit -m "Recurse submodule.recurse from config junk"
+       ) &&
+       (
+               cd work &&
+               git add gar/bage &&
+               git commit -m "Recurse submodule.recurse from config for gar/bage" &&
+               git -c submodule.recurse push ../pub.git master &&
+               # Check that the supermodule commit got there
+               git fetch ../pub.git &&
+               git diff --quiet FETCH_HEAD master &&
+               # Check that the submodule commit got there too
+               cd gar/bage &&
+               git diff --quiet origin/master master
+       )
+ '
  test_expect_success 'push recurse-submodules on command line overrides config' '
        (
                cd work/gar/bage &&