Merge branch 'bw/pathspec-sans-the-index'
authorJunio C Hamano <gitster@pobox.com>
Tue, 30 May 2017 02:16:40 +0000 (11:16 +0900)
committerJunio C Hamano <gitster@pobox.com>
Tue, 30 May 2017 02:16:40 +0000 (11:16 +0900)
Simplify parse_pathspec() codepath and stop it from looking at the
default in-core index.

* bw/pathspec-sans-the-index:
pathspec: convert find_pathspecs_matching_against_index to take an index
pathspec: remove PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP
ls-files: prevent prune_cache from overeagerly pruning submodules
pathspec: remove PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE flag
submodule: add die_in_unpopulated_submodule function
pathspec: provide a more descriptive die message

1  2 
builtin/add.c
builtin/check-ignore.c
builtin/ls-files.c
builtin/reset.c
submodule.c
submodule.h
t/t6134-pathspec-in-submodule.sh
diff --combined builtin/add.c
index 36cad00ae6ddad2b181da0b51294e1fe86aef788,0365a520989047b5ecff4f5cc0253705b60ff45b..d9a2491e48f16d9ef7de5c05008f8c7e6a5e64b2
@@@ -17,6 -17,7 +17,7 @@@
  #include "revision.h"
  #include "bulk-checkin.h"
  #include "argv-array.h"
+ #include "submodule.h"
  
  static const char * const builtin_add_usage[] = {
        N_("git add [<options>] [--] <pathspec>..."),
@@@ -135,7 -136,7 +136,7 @@@ static char *prune_directory(struct dir
                        *dst++ = entry;
        }
        dir->nr = dst - dir->entries;
-       add_pathspec_matches_against_index(pathspec, seen);
+       add_pathspec_matches_against_index(pathspec, &the_index, seen);
        return seen;
  }
  
@@@ -379,16 -380,19 +380,19 @@@ int cmd_add(int argc, const char **argv
        if (read_cache() < 0)
                die(_("index file corrupt"));
  
+       die_in_unpopulated_submodule(&the_index, prefix);
        /*
         * Check the "pathspec '%s' did not match any files" block
         * below before enabling new magic.
         */
        parse_pathspec(&pathspec, 0,
                       PATHSPEC_PREFER_FULL |
-                      PATHSPEC_SYMLINK_LEADING_PATH |
-                      PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE,
+                      PATHSPEC_SYMLINK_LEADING_PATH,
                       prefix, argv);
  
+       die_path_inside_submodule(&the_index, &pathspec);
        if (add_new_files) {
                int baselen;
  
                }
  
                /* This picks up the paths that are not tracked */
 -              baselen = fill_directory(&dir, &pathspec);
 +              baselen = fill_directory(&dir, &the_index, &pathspec);
                if (pathspec.nr)
                        seen = prune_directory(&dir, &pathspec, baselen);
        }
                int i;
  
                if (!seen)
-                       seen = find_pathspecs_matching_against_index(&pathspec);
+                       seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
  
                /*
                 * file_exists() assumes exact match
                             !file_exists(path))) {
                                if (ignore_missing) {
                                        int dtype = DT_UNKNOWN;
 -                                      if (is_excluded(&dir, path, &dtype))
 -                                              dir_add_ignored(&dir, path, pathspec.items[i].len);
 +                                      if (is_excluded(&dir, &the_index, path, &dtype))
 +                                              dir_add_ignored(&dir, &the_index,
 +                                                              path, pathspec.items[i].len);
                                } else
                                        die(_("pathspec '%s' did not match any files"),
                                            pathspec.items[i].original);
diff --combined builtin/check-ignore.c
index d2293b22e1c294131e392fc17084ff4eaecdd02d,7629018a561eb07712dd8ae7fe14bf669159bbf6..c7b8c08897193e225d8da32f4d402def3f16fe50
@@@ -4,6 -4,7 +4,7 @@@
  #include "quote.h"
  #include "pathspec.h"
  #include "parse-options.h"
+ #include "submodule.h"
  
  static int quiet, verbose, stdin_paths, show_non_matching, no_index;
  static const char * const check_ignore_usage[] = {
@@@ -87,22 -88,22 +88,23 @@@ static int check_ignore(struct dir_stru
        parse_pathspec(&pathspec,
                       PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP,
                       PATHSPEC_SYMLINK_LEADING_PATH |
-                      PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE |
                       PATHSPEC_KEEP_ORDER,
                       prefix, argv);
  
+       die_path_inside_submodule(&the_index, &pathspec);
        /*
         * look for pathspecs matching entries in the index, since these
         * should not be ignored, in order to be consistent with
         * 'git status', 'git add' etc.
         */
-       seen = find_pathspecs_matching_against_index(&pathspec);
+       seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
        for (i = 0; i < pathspec.nr; i++) {
                full_path = pathspec.items[i].match;
                exclude = NULL;
                if (!seen[i]) {
 -                      exclude = last_exclude_matching(dir, full_path, &dtype);
 +                      exclude = last_exclude_matching(dir, &the_index,
 +                                                      full_path, &dtype);
                }
                if (!quiet && (exclude || show_non_matching))
                        output_exclude(pathspec.items[i].original, exclude);
diff --combined builtin/ls-files.c
index 530e6ae7f78ff013b692b4a2a984cd5457783f3b,1f3d478443dc00df44dfc8dca571773b74eb7a06..b376afc3124c3240f4f249ccc206efa0b064675e
@@@ -97,7 -97,7 +97,7 @@@ static void show_dir_entry(const char *
  {
        int len = max_prefix_len;
  
-       if (len >= ent->len)
+       if (len > ent->len)
                die("git ls-files: internal error - directory entry not superset of prefix");
  
        if (!dir_path_match(ent, &pathspec, len, ps_matched))
@@@ -238,7 -238,7 +238,7 @@@ static void show_ce_entry(const char *t
                strbuf_addstr(&name, super_prefix);
        strbuf_addstr(&name, ce->name);
  
-       if (len >= ce_namelen(ce))
+       if (len > ce_namelen(ce))
                die("git ls-files: internal error - cache entry not superset of prefix");
  
        if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
@@@ -322,7 -322,7 +322,7 @@@ static void show_ru_info(void
  static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce)
  {
        int dtype = ce_to_dtype(ce);
 -      return is_excluded(dir, ce->name, &dtype);
 +      return is_excluded(dir, &the_index, ce->name, &dtype);
  }
  
  static void show_files(struct dir_struct *dir)
        if (show_others || show_killed) {
                if (!show_others)
                        dir->flags |= DIR_COLLECT_KILLED_ONLY;
 -              fill_directory(dir, &pathspec);
 +              fill_directory(dir, &the_index, &pathspec);
                if (show_others)
                        show_other_files(dir);
                if (show_killed)
@@@ -403,6 -403,25 +403,25 @@@ static void prune_cache(const char *pre
        active_nr = last - pos;
  }
  
+ static int get_common_prefix_len(const char *common_prefix)
+ {
+       int common_prefix_len;
+       if (!common_prefix)
+               return 0;
+       common_prefix_len = strlen(common_prefix);
+       /*
+        * If the prefix has a trailing slash, strip it so that submodules wont
+        * be pruned from the index.
+        */
+       if (common_prefix[common_prefix_len - 1] == '/')
+               common_prefix_len--;
+       return common_prefix_len;
+ }
  /*
   * Read the tree specified with --with-tree option
   * (typically, HEAD) into stage #1 and then
  void overlay_tree_on_cache(const char *tree_name, const char *prefix)
  {
        struct tree *tree;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct pathspec pathspec;
        struct cache_entry *last_stage0 = NULL;
        int i;
  
 -      if (get_sha1(tree_name, sha1))
 +      if (get_oid(tree_name, &oid))
                die("tree-ish %s not found.", tree_name);
 -      tree = parse_tree_indirect(sha1);
 +      tree = parse_tree_indirect(&oid);
        if (!tree)
                die("bad tree-ish %s", tree_name);
  
@@@ -624,8 -643,7 +643,7 @@@ int cmd_ls_files(int argc, const char *
                    "--error-unmatch");
  
        parse_pathspec(&pathspec, 0,
-                      PATHSPEC_PREFER_CWD |
-                      PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+                      PATHSPEC_PREFER_CWD,
                       prefix, argv);
  
        /*
                max_prefix = NULL;
        else
                max_prefix = common_prefix(&pathspec);
-       max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
+       max_prefix_len = get_common_prefix_len(max_prefix);
+       prune_cache(max_prefix, max_prefix_len);
  
        /* Treat unmatching pathspec elements as errors */
        if (pathspec.nr && error_unmatch)
              show_killed || show_modified || show_resolve_undo))
                show_cached = 1;
  
-       prune_cache(max_prefix, max_prefix_len);
        if (with_tree) {
                /*
                 * Basic sanity check; show-stages and show-unmerged
diff --combined builtin/reset.c
index 0afe1df250b2d4e416ad1273f75997d739fc94a7,5db2adc4c4839b5f79351358f3115bbf74f6cf53..430602d102133d125009e8c8068ce5547c24cab7
  #include "parse-options.h"
  #include "unpack-trees.h"
  #include "cache-tree.h"
 +#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>]"),
@@@ -105,7 -84,7 +105,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 -154,7 +175,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,7 -236,6 +257,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);
  }
@@@ -304,9 -282,6 +303,9 @@@ int cmd_reset(int argc, const char **ar
                                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,
 +                          "reset", "control recursive updating of submodules",
 +                          PARSE_OPT_OPTARG, option_parse_recurse_submodules },
                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")),
                                                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);
 +      }
 +
        unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", oid.hash);
        if (unborn) {
                /* reset on unborn branch: treat as reset to empty tree */
                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 9d5ec5f08454c5e1ef1dc4295795e7915c0959bf,80851d04471adfea10e8edbe75b4823d8810d7f7..bf5a93d16fb71cdeb87f589732b6f95f4a83fbe0
@@@ -20,7 -20,7 +20,7 @@@
  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 struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP;
  static int initialized_fetch_ref_tips;
  static struct oid_array ref_tips_before_fetch;
  static struct oid_array ref_tips_after_fetch;
@@@ -282,6 -282,69 +282,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)
  {
@@@ -447,8 -510,8 +510,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
@@@ -554,8 -617,7 +617,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/",
@@@ -618,94 -680,6 +681,94 @@@ const struct submodule *submodule_from_
        return submodule_from_path(null_sha1, ce->name);
  }
  
 +static struct oid_array *submodule_commits(struct string_list *submodules,
 +                                         const char *path)
 +{
 +      struct string_list_item *item;
 +
 +      item = string_list_insert(submodules, path);
 +      if (item->util)
 +              return (struct oid_array *) item->util;
 +
 +      /* NEEDSWORK: should we have oid_array_init()? */
 +      item->util = xcalloc(1, sizeof(struct oid_array));
 +      return (struct oid_array *) item->util;
 +}
 +
 +static void collect_changed_submodules_cb(struct diff_queue_struct *q,
 +                                        struct diff_options *options,
 +                                        void *data)
 +{
 +      int i;
 +      struct string_list *changed = data;
 +
 +      for (i = 0; i < q->nr; i++) {
 +              struct diff_filepair *p = q->queue[i];
 +              struct oid_array *commits;
 +              if (!S_ISGITLINK(p->two->mode))
 +                      continue;
 +
 +              if (S_ISGITLINK(p->one->mode)) {
 +                      /*
 +                       * NEEDSWORK: We should honor the name configured in
 +                       * the .gitmodules file of the commit we are examining
 +                       * here to be able to correctly follow submodules
 +                       * being moved around.
 +                       */
 +                      commits = submodule_commits(changed, p->two->path);
 +                      oid_array_append(commits, &p->two->oid);
 +              } else {
 +                      /* Submodule is new or was moved here */
 +                      /*
 +                       * NEEDSWORK: When the .git directories of submodules
 +                       * live inside the superprojects .git directory some
 +                       * day we should fetch new submodules directly into
 +                       * that location too when config or options request
 +                       * that so they can be checked out from there.
 +                       */
 +                      continue;
 +              }
 +      }
 +}
 +
 +/*
 + * Collect the paths of submodules in 'changed' which have changed based on
 + * the revisions as specified in 'argv'.  Each entry in 'changed' will also
 + * have a corresponding 'struct oid_array' (in the 'util' field) which lists
 + * what the submodule pointers were updated to during the change.
 + */
 +static void collect_changed_submodules(struct string_list *changed,
 +                                     struct argv_array *argv)
 +{
 +      struct rev_info rev;
 +      const struct commit *commit;
 +
 +      init_revisions(&rev, NULL);
 +      setup_revisions(argv->argc, argv->argv, &rev, NULL);
 +      if (prepare_revision_walk(&rev))
 +              die("revision walk setup failed");
 +
 +      while ((commit = get_revision(&rev))) {
 +              struct rev_info diff_rev;
 +
 +              init_revisions(&diff_rev, NULL);
 +              diff_rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 +              diff_rev.diffopt.format_callback = collect_changed_submodules_cb;
 +              diff_rev.diffopt.format_callback_data = changed;
 +              diff_tree_combined_merge(commit, 1, &diff_rev);
 +      }
 +
 +      reset_revision_walk();
 +}
 +
 +static void free_submodules_oids(struct string_list *submodules)
 +{
 +      struct string_list_item *item;
 +      for_each_string_list_item(item, submodules)
 +              oid_array_clear((struct oid_array *) item->util);
 +      string_list_clear(submodules, 1);
 +}
 +
  static int has_remote(const char *refname, const struct object_id *oid,
                      int flags, void *cb_data)
  {
@@@ -723,7 -697,7 +786,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;
@@@ -733,44 -707,10 +796,44 @@@ static int submodule_has_commits(const 
  {
        int has_commit = 1;
  
 +      /*
 +       * Perform a cheap, but incorrect check for the existance of 'commits'.
 +       * This is done by adding the submodule's object store to the in-core
 +       * object store, and then querying for each commit's existance.  If we
 +       * do not have the commit object anywhere, there is no chance we have
 +       * it in the object store of the correct submodule and have it
 +       * reachable from a ref, so we can fail early without spawning rev-list
 +       * which is expensive.
 +       */
        if (add_submodule_odb(path))
                return 0;
  
        oid_array_for_each_unique(commits, check_has_commit, &has_commit);
 +
 +      if (has_commit) {
 +              /*
 +               * Even if the submodule is checked out and the commit is
 +               * present, make sure it exists in the submodule's object store
 +               * and that it is reachable from a ref.
 +               */
 +              struct child_process cp = CHILD_PROCESS_INIT;
 +              struct strbuf out = STRBUF_INIT;
 +
 +              argv_array_pushl(&cp.args, "rev-list", "-n", "1", NULL);
 +              oid_array_for_each_unique(commits, append_oid_to_argv, &cp.args);
 +              argv_array_pushl(&cp.args, "--not", "--all", NULL);
 +
 +              prepare_submodule_repo_env(&cp.env_array);
 +              cp.git_cmd = 1;
 +              cp.no_stdin = 1;
 +              cp.dir = path;
 +
 +              if (capture_command(&cp, &out, GIT_MAX_HEXSZ + 1) || out.len)
 +                      has_commit = 0;
 +
 +              strbuf_release(&out);
 +      }
 +
        return has_commit;
  }
  
@@@ -818,31 -758,91 +881,31 @@@ static int submodule_needs_pushing(cons
        return 0;
  }
  
 -static struct oid_array *submodule_commits(struct string_list *submodules,
 -                                          const char *path)
 -{
 -      struct string_list_item *item;
 -
 -      item = string_list_insert(submodules, path);
 -      if (item->util)
 -              return (struct oid_array *) item->util;
 -
 -      /* NEEDSWORK: should we have oid_array_init()? */
 -      item->util = xcalloc(1, sizeof(struct oid_array));
 -      return (struct oid_array *) item->util;
 -}
 -
 -static void collect_submodules_from_diff(struct diff_queue_struct *q,
 -                                       struct diff_options *options,
 -                                       void *data)
 -{
 -      int i;
 -      struct string_list *submodules = data;
 -
 -      for (i = 0; i < q->nr; i++) {
 -              struct diff_filepair *p = q->queue[i];
 -              struct oid_array *commits;
 -              if (!S_ISGITLINK(p->two->mode))
 -                      continue;
 -              commits = submodule_commits(submodules, p->two->path);
 -              oid_array_append(commits, &p->two->oid);
 -      }
 -}
 -
 -static void find_unpushed_submodule_commits(struct commit *commit,
 -              struct string_list *needs_pushing)
 -{
 -      struct rev_info rev;
 -
 -      init_revisions(&rev, NULL);
 -      rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 -      rev.diffopt.format_callback = collect_submodules_from_diff;
 -      rev.diffopt.format_callback_data = needs_pushing;
 -      diff_tree_combined_merge(commit, 1, &rev);
 -}
 -
 -static void free_submodules_sha1s(struct string_list *submodules)
 -{
 -      struct string_list_item *item;
 -      for_each_string_list_item(item, submodules)
 -              oid_array_clear((struct oid_array *) item->util);
 -      string_list_clear(submodules, 1);
 -}
 -
  int find_unpushed_submodules(struct oid_array *commits,
                const char *remotes_name, struct string_list *needs_pushing)
  {
 -      struct rev_info rev;
 -      struct commit *commit;
        struct string_list submodules = STRING_LIST_INIT_DUP;
        struct string_list_item *submodule;
        struct argv_array argv = ARGV_ARRAY_INIT;
  
 -      init_revisions(&rev, NULL);
 -
        /* argv.argv[0] will be ignored by setup_revisions */
        argv_array_push(&argv, "find_unpushed_submodules");
        oid_array_for_each_unique(commits, append_oid_to_argv, &argv);
        argv_array_push(&argv, "--not");
        argv_array_pushf(&argv, "--remotes=%s", remotes_name);
  
 -      setup_revisions(argv.argc, argv.argv, &rev, NULL);
 -      if (prepare_revision_walk(&rev))
 -              die("revision walk setup failed");
 -
 -      while ((commit = get_revision(&rev)) != NULL)
 -              find_unpushed_submodule_commits(commit, &submodules);
 -
 -      reset_revision_walk();
 -      argv_array_clear(&argv);
 +      collect_changed_submodules(&submodules, &argv);
  
        for_each_string_list_item(submodule, &submodules) {
 -              struct oid_array *commits = (struct oid_array *) submodule->util;
 +              struct oid_array *commits = submodule->util;
 +              const char *path = submodule->string;
  
 -              if (submodule_needs_pushing(submodule->string, commits))
 -                      string_list_insert(needs_pushing, submodule->string);
 +              if (submodule_needs_pushing(path, commits))
 +                      string_list_insert(needs_pushing, path);
        }
 -      free_submodules_sha1s(&submodules);
 +
 +      free_submodules_oids(&submodules);
 +      argv_array_clear(&argv);
  
        return needs_pushing->nr;
  }
@@@ -959,56 -959,125 +1022,56 @@@ int push_unpushed_submodules(struct oid
        return ret;
  }
  
 -static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
 -{
 -      int is_present = 0;
 -      if (!add_submodule_odb(path) && lookup_commit_reference(sha1)) {
 -              /* Even if the submodule is checked out and the commit is
 -               * present, make sure it is reachable from a ref. */
 -              struct child_process cp = CHILD_PROCESS_INIT;
 -              const char *argv[] = {"rev-list", "-n", "1", NULL, "--not", "--all", NULL};
 -              struct strbuf buf = STRBUF_INIT;
 -
 -              argv[3] = sha1_to_hex(sha1);
 -              cp.argv = argv;
 -              prepare_submodule_repo_env(&cp.env_array);
 -              cp.git_cmd = 1;
 -              cp.no_stdin = 1;
 -              cp.dir = path;
 -              if (!capture_command(&cp, &buf, 1024) && !buf.len)
 -                      is_present = 1;
 -
 -              strbuf_release(&buf);
 -      }
 -      return is_present;
 -}
 -
 -static void submodule_collect_changed_cb(struct diff_queue_struct *q,
 -                                       struct diff_options *options,
 -                                       void *data)
 -{
 -      int i;
 -      for (i = 0; i < q->nr; i++) {
 -              struct diff_filepair *p = q->queue[i];
 -              if (!S_ISGITLINK(p->two->mode))
 -                      continue;
 -
 -              if (S_ISGITLINK(p->one->mode)) {
 -                      /* NEEDSWORK: We should honor the name configured in
 -                       * the .gitmodules file of the commit we are examining
 -                       * here to be able to correctly follow submodules
 -                       * being moved around. */
 -                      struct string_list_item *path;
 -                      path = unsorted_string_list_lookup(&changed_submodule_paths, p->two->path);
 -                      if (!path && !is_submodule_commit_present(p->two->path, p->two->oid.hash))
 -                              string_list_append(&changed_submodule_paths, xstrdup(p->two->path));
 -              } else {
 -                      /* Submodule is new or was moved here */
 -                      /* NEEDSWORK: When the .git directories of submodules
 -                       * live inside the superprojects .git directory some
 -                       * day we should fetch new submodules directly into
 -                       * that location too when config or options request
 -                       * that so they can be checked out from there. */
 -                      continue;
 -              }
 -      }
 -}
 -
 -static int add_sha1_to_array(const char *ref, const struct object_id *oid,
 -                           int flags, void *data)
 +static int append_oid_to_array(const char *ref, const struct object_id *oid,
 +                             int flags, void *data)
  {
 -      oid_array_append(data, oid);
 +      struct oid_array *array = data;
 +      oid_array_append(array, oid);
        return 0;
  }
  
  void check_for_new_submodule_commits(struct object_id *oid)
  {
        if (!initialized_fetch_ref_tips) {
 -              for_each_ref(add_sha1_to_array, &ref_tips_before_fetch);
 +              for_each_ref(append_oid_to_array, &ref_tips_before_fetch);
                initialized_fetch_ref_tips = 1;
        }
  
        oid_array_append(&ref_tips_after_fetch, oid);
  }
  
 -static int add_oid_to_argv(const struct object_id *oid, void *data)
 -{
 -      argv_array_push(data, oid_to_hex(oid));
 -      return 0;
 -}
 -
  static void calculate_changed_submodule_paths(void)
  {
 -      struct rev_info rev;
 -      struct commit *commit;
        struct argv_array argv = ARGV_ARRAY_INIT;
 +      struct string_list changed_submodules = STRING_LIST_INIT_DUP;
 +      const struct string_list_item *item;
  
        /* No need to check if there are no submodules configured */
        if (!submodule_from_path(NULL, NULL))
                return;
  
 -      init_revisions(&rev, NULL);
        argv_array_push(&argv, "--"); /* argv[0] program name */
        oid_array_for_each_unique(&ref_tips_after_fetch,
 -                                 add_oid_to_argv, &argv);
 +                                 append_oid_to_argv, &argv);
        argv_array_push(&argv, "--not");
        oid_array_for_each_unique(&ref_tips_before_fetch,
 -                                 add_oid_to_argv, &argv);
 -      setup_revisions(argv.argc, argv.argv, &rev, NULL);
 -      if (prepare_revision_walk(&rev))
 -              die("revision walk setup failed");
 +                                 append_oid_to_argv, &argv);
  
        /*
         * Collect all submodules (whether checked out or not) for which new
         * commits have been recorded upstream in "changed_submodule_paths".
         */
 -      while ((commit = get_revision(&rev))) {
 -              struct commit_list *parent = commit->parents;
 -              while (parent) {
 -                      struct diff_options diff_opts;
 -                      diff_setup(&diff_opts);
 -                      DIFF_OPT_SET(&diff_opts, RECURSIVE);
 -                      diff_opts.output_format |= DIFF_FORMAT_CALLBACK;
 -                      diff_opts.format_callback = submodule_collect_changed_cb;
 -                      diff_setup_done(&diff_opts);
 -                      diff_tree_sha1(parent->item->object.oid.hash, commit->object.oid.hash, "", &diff_opts);
 -                      diffcore_std(&diff_opts);
 -                      diff_flush(&diff_opts);
 -                      parent = parent->next;
 -              }
 +      collect_changed_submodules(&changed_submodules, &argv);
 +
 +      for_each_string_list_item(item, &changed_submodules) {
 +              struct oid_array *commits = item->util;
 +              const char *path = item->string;
 +
 +              if (!submodule_has_commits(path, commits))
 +                      string_list_append(&changed_submodule_paths, path);
        }
  
 +      free_submodules_oids(&changed_submodules);
        argv_array_clear(&argv);
        oid_array_clear(&ref_tips_before_fetch);
        oid_array_clear(&ref_tips_after_fetch);
@@@ -1357,7 -1426,7 +1420,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;
@@@ -1403,23 -1472,6 +1466,23 @@@ int submodule_move_head(const char *pat
        int ret = 0;
        struct child_process cp = CHILD_PROCESS_INIT;
        const struct submodule *sub;
 +      int *error_code_ptr, error_code;
 +
 +      if (!is_submodule_initialized(path))
 +              return 0;
 +
 +      if (flags & SUBMODULE_MOVE_HEAD_FORCE)
 +              /*
 +               * Pass non NULL pointer to is_submodule_populated_gently
 +               * to prevent die()-ing. We'll use connect_work_tree_and_git_dir
 +               * to fixup the submodule in the force case later.
 +               */
 +              error_code_ptr = &error_code;
 +      else
 +              error_code_ptr = NULL;
 +
 +      if (old && !is_submodule_populated_gently(path, error_code_ptr))
 +              return 0;
  
        sub = submodule_from_path(null_sha1, path);
  
                                absorb_git_dir_into_superproject("", path,
                                        ABSORB_GITDIR_RECURSE_SUBMODULES);
                } else {
 -                      struct strbuf sb = STRBUF_INIT;
 -                      strbuf_addf(&sb, "%s/modules/%s",
 +                      char *gitdir = xstrfmt("%s/modules/%s",
                                    get_git_common_dir(), sub->name);
 -                      connect_work_tree_and_git_dir(path, sb.buf);
 -                      strbuf_release(&sb);
 +                      connect_work_tree_and_git_dir(path, gitdir);
 +                      free(gitdir);
  
                        /* make sure the index is clean as well */
                        submodule_reset_index(path);
                }
 +
 +              if (old && (flags & SUBMODULE_MOVE_HEAD_FORCE)) {
 +                      char *gitdir = xstrfmt("%s/modules/%s",
 +                                  get_git_common_dir(), sub->name);
 +                      connect_work_tree_and_git_dir(path, gitdir);
 +                      free(gitdir);
 +              }
        }
  
 -      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;
                        }
@@@ -1584,9 -1629,9 +1647,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 89c2ed219177d07db5529bbac2d2a7996572f387,266d81f1c4e46b43e21e506a40762cec5bbd9de6..8fb0f25498d58344adb86fee53770bb11cf36cbc
@@@ -49,6 -49,10 +49,10 @@@ extern int is_submodule_initialized(con
   * 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);
@@@ -84,10 -88,10 +88,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 99a8982ab1554c6c2324402f78f46954445596fd,0f1cb49cedc645c5a9b0c56257d3aca4740d5cd3..c670668409817c6d1066de53dc294bade2f27e93
@@@ -21,16 -21,12 +21,12 @@@ EO
  test_expect_success 'error message for path inside submodule' '
        echo a >sub/a &&
        test_must_fail git add sub/a 2>actual &&
 -      test_cmp expect actual
 +      test_i18ncmp expect actual
  '
  
- cat <<EOF >expect
- fatal: Pathspec '.' is in submodule 'sub'
- EOF
  test_expect_success 'error message for path inside submodule from within submodule' '
        test_must_fail git -C sub add . 2>actual &&
-       test_i18ncmp expect actual
+       test_i18ngrep "in unpopulated submodule" actual
  '
  
  test_done