Merge branch 'sb/submodule-recursive-checkout-detach-head'
authorJunio C Hamano <gitster@pobox.com>
Wed, 6 Dec 2017 17:23:35 +0000 (09:23 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 6 Dec 2017 17:23:35 +0000 (09:23 -0800)
"git checkout --recursive" may overwrite and rewind the history of
the branch that happens to be checked out in submodule
repositories, which might not be desirable. Detach the HEAD but
still allow the recursive checkout to succeed in such a case.

* sb/submodule-recursive-checkout-detach-head:
Documentation/checkout: clarify submodule HEADs to be detached
recursive submodules: detach HEAD from new state

1  2 
Documentation/git-checkout.txt
submodule.c
t/lib-submodule-update.sh
index e108b0f74bb6dc4d9f3fc27e7c3fe571b19a9d2f,84bd323a0024ed46edc650a7ef2e621e4e7de0bc..bfa64ca5c98c54b9b9465d45669f45ab887605cf
@@@ -13,8 -13,7 +13,8 @@@ SYNOPSI
  'git checkout' [-q] [-f] [-m] [--detach] <commit>
  'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
  'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
 -'git checkout' [-p|--patch] [<tree-ish>] [--] [<paths>...]
 +'git checkout' [<tree-ish>] [--] <pathspec>...
 +'git checkout' (-p|--patch) [<tree-ish>] [--] [<paths>...]
  
  DESCRIPTION
  -----------
@@@ -39,7 -38,7 +39,7 @@@ $ git checkout -b <branch> --track <rem
  ------------
  +
  You could omit <branch>, in which case the command degenerates to
 -"check out the current branch", which is a glorified no-op with a
 +"check out the current branch", which is a glorified no-op with
  rather expensive side-effects to show only the tracking information,
  if exists, for the current branch.
  
@@@ -79,13 -78,20 +79,13 @@@ be used to detach HEAD at the tip of th
  +
  Omitting <branch> detaches HEAD at the tip of the current branch.
  
 -'git checkout' [-p|--patch] [<tree-ish>] [--] <pathspec>...::
 +'git checkout' [<tree-ish>] [--] <pathspec>...::
  
 -      When <paths> or `--patch` are given, 'git checkout' does *not*
 -      switch branches.  It updates the named paths in the working tree
 -      from the index file or from a named <tree-ish> (most often a
 -      commit).  In this case, the `-b` and `--track` options are
 -      meaningless and giving either of them results in an error.  The
 -      <tree-ish> argument can be used to specify a specific tree-ish
 -      (i.e.  commit, tag or tree) to update the index for the given
 -      paths before updating the working tree.
 -+
 -'git checkout' with <paths> or `--patch` is used to restore modified or
 -deleted paths to their original contents from the index or replace paths
 -with the contents from a named <tree-ish> (most often a commit-ish).
 +      Overwrite paths in the working tree by replacing with the
 +      contents in the index or in the <tree-ish> (most often a
 +      commit).  When a <tree-ish> is given, the paths that
 +      match the <pathspec> are updated both in the index and in
 +      the working tree.
  +
  The index may contain unmerged entries because of a previous failed merge.
  By default, if you try to check out such an entry from the index, the
@@@ -95,14 -101,6 +95,14 @@@ specific side of the merge can be check
  using `--ours` or `--theirs`.  With `-m`, changes made to the working tree
  file can be discarded to re-create the original conflicted merge result.
  
 +'git checkout' (-p|--patch) [<tree-ish>] [--] [<pathspec>...]::
 +      This is similar to the "check out paths to the working tree
 +      from either the index or from a tree-ish" mode described
 +      above, but lets you use the interactive interface to show
 +      the "diff" output and choose which hunks to use in the
 +      result.  See below for the description of `--patch` option.
 +
 +
  OPTIONS
  -------
  -q::
@@@ -264,6 -262,8 +264,8 @@@ section of linkgit:git-add[1] to learn 
        local modifications in a submodule would be overwritten the checkout
        will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
        is used, the work trees of submodules will not be updated.
+       Just like linkgit:git-submodule[1], this will detach the
+       submodules HEAD.
  
  <branch>::
        Branch to checkout; if it refers to a branch (i.e., a name that,
diff --combined submodule.c
index bb531e0e59ce7f8fa5cde622101b7ef1b324a603,37f4a9287284bbbb434f31170eaf276c0ee0175e..95e6aff2bb74e1374d22997d1918ed190c6edafc
  #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_OFF;
 -static int parallel_jobs = 1;
 -static struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP;
 +static struct string_list changed_submodule_names = 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;
  
  /*
 - * The following flag is set if the .gitmodules file is unmerged. We then
 - * disable recursion for all submodules where .git/config doesn't have a
 - * matching config entry because we can't guess what might be configured in
 - * .gitmodules unless the user resolves the conflict. When a command line
 - * option is given (which always overrides configuration) this flag will be
 - * ignored.
 + * Check if the .gitmodules file is unmerged. Parsing of the .gitmodules file
 + * will be disabled because we can't guess what might be configured in
 + * .gitmodules unless the user resolves the conflict.
   */
 -static int gitmodules_is_unmerged;
 +int is_gitmodules_unmerged(const struct index_state *istate)
 +{
 +      int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
 +      if (pos < 0) { /* .gitmodules not found or isn't merged */
 +              pos = -1 - pos;
 +              if (istate->cache_nr > pos) {  /* there is a .gitmodules */
 +                      const struct cache_entry *ce = istate->cache[pos];
 +                      if (ce_namelen(ce) == strlen(GITMODULES_FILE) &&
 +                          !strcmp(ce->name, GITMODULES_FILE))
 +                              return 1;
 +              }
 +      }
 +
 +      return 0;
 +}
  
  /*
 - * This flag is set if the .gitmodules file had unstaged modifications on
 - * startup. This must be checked before allowing modifications to the
 - * .gitmodules file with the intention to stage them later, because when
 - * continuing we would stage the modifications the user didn't stage herself
 - * too. That might change in a future version when we learn to stage the
 - * changes we do ourselves without staging any previous modifications.
 + * Check if the .gitmodules file has unstaged modifications.  This must be
 + * checked before allowing modifications to the .gitmodules file with the
 + * intention to stage them later, because when continuing we would stage the
 + * modifications the user didn't stage herself too. That might change in a
 + * future version when we learn to stage the changes we do ourselves without
 + * staging any previous modifications.
   */
 -static int gitmodules_is_modified;
 +int is_staging_gitmodules_ok(const struct index_state *istate)
 +{
 +      int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
 +
 +      if ((pos >= 0) && (pos < istate->cache_nr)) {
 +              struct stat st;
 +              if (lstat(GITMODULES_FILE, &st) == 0 &&
 +                  ce_match_stat(istate->cache[pos], &st, CE_MATCH_IGNORE_FSMONITOR) & DATA_CHANGED)
 +                      return 0;
 +      }
  
 -int is_staging_gitmodules_ok(void)
 +      return 1;
 +}
 +
 +static int for_each_remote_ref_submodule(const char *submodule,
 +                                       each_ref_fn fn, void *cb_data)
  {
 -      return !gitmodules_is_modified;
 +      return refs_for_each_remote_ref(get_submodule_ref_store(submodule),
 +                                      fn, cb_data);
  }
  
  /*
@@@ -86,13 -63,13 +86,13 @@@ int update_path_in_gitmodules(const cha
        struct strbuf entry = STRBUF_INIT;
        const struct submodule *submodule;
  
 -      if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
 +      if (!file_exists(GITMODULES_FILE)) /* Do nothing without .gitmodules */
                return -1;
  
 -      if (gitmodules_is_unmerged)
 +      if (is_gitmodules_unmerged(&the_index))
                die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
  
 -      submodule = submodule_from_path(null_sha1, oldpath);
 +      submodule = submodule_from_path(&null_oid, oldpath);
        if (!submodule || !submodule->name) {
                warning(_("Could not find section in .gitmodules where path=%s"), oldpath);
                return -1;
        strbuf_addstr(&entry, "submodule.");
        strbuf_addstr(&entry, submodule->name);
        strbuf_addstr(&entry, ".path");
 -      if (git_config_set_in_file_gently(".gitmodules", entry.buf, newpath) < 0) {
 +      if (git_config_set_in_file_gently(GITMODULES_FILE, entry.buf, newpath) < 0) {
                /* Maybe the user already did that, don't error out here */
                warning(_("Could not update .gitmodules entry %s"), entry.buf);
                strbuf_release(&entry);
@@@ -120,20 -97,20 +120,20 @@@ int remove_path_from_gitmodules(const c
        struct strbuf sect = STRBUF_INIT;
        const struct submodule *submodule;
  
 -      if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
 +      if (!file_exists(GITMODULES_FILE)) /* Do nothing without .gitmodules */
                return -1;
  
 -      if (gitmodules_is_unmerged)
 +      if (is_gitmodules_unmerged(&the_index))
                die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
  
 -      submodule = submodule_from_path(null_sha1, path);
 +      submodule = submodule_from_path(&null_oid, path);
        if (!submodule || !submodule->name) {
                warning(_("Could not find section in .gitmodules where path=%s"), path);
                return -1;
        }
        strbuf_addstr(&sect, "submodule.");
        strbuf_addstr(&sect, submodule->name);
 -      if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) {
 +      if (git_config_rename_section_in_file(GITMODULES_FILE, sect.buf, NULL) < 0) {
                /* Maybe the user already did that, don't error out here */
                warning(_("Could not remove .gitmodules entry for %s"), path);
                strbuf_release(&sect);
  
  void stage_updated_gitmodules(void)
  {
 -      if (add_file_to_cache(".gitmodules", 0))
 +      if (add_file_to_cache(GITMODULES_FILE, 0))
                die(_("staging updated .gitmodules failed"));
  }
  
@@@ -170,20 -147,42 +170,20 @@@ done
  void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
                                             const char *path)
  {
 -      const struct submodule *submodule = submodule_from_path(null_sha1, path);
 +      const struct submodule *submodule = submodule_from_path(&null_oid, path);
        if (submodule) {
 -              if (submodule->ignore)
 -                      handle_ignore_submodules_arg(diffopt, submodule->ignore);
 -              else if (gitmodules_is_unmerged)
 -                      DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
 -      }
 -}
 +              const char *ignore;
 +              char *key;
  
 -/* 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);
 -              if (parallel_jobs < 0)
 -                      die(_("negative values not allowed for submodule.fetchJobs"));
 -              return 0;
 -      } else if (starts_with(var, "submodule."))
 -              return parse_submodule_config_option(var, value);
 -      else if (!strcmp(var, "fetch.recursesubmodules")) {
 -              config_fetch_recurse_submodules = parse_fetch_recurse_submodules_arg(var, value);
 -              return 0;
 -      }
 -      return 0;
 -}
 +              key = xstrfmt("submodule.%s.ignore", submodule->name);
 +              if (repo_config_get_string_const(the_repository, key, &ignore))
 +                      ignore = submodule->ignore;
 +              free(key);
  
 -/* 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);
 +              if (ignore)
 +                      handle_ignore_submodules_arg(diffopt, ignore);
 +              else if (is_gitmodules_unmerged(&the_index))
 +                      diffopt->flags.ignore_submodules = 1;
        }
  }
  
@@@ -215,6 -214,74 +215,6 @@@ int option_parse_recurse_submodules_wor
        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 (work_tree) {
 -              struct strbuf gitmodules_path = STRBUF_INIT;
 -              int pos;
 -              strbuf_addstr(&gitmodules_path, work_tree);
 -              strbuf_addstr(&gitmodules_path, "/.gitmodules");
 -              if (read_cache() < 0)
 -                      die("index file corrupt");
 -              pos = cache_name_pos(".gitmodules", 11);
 -              if (pos < 0) { /* .gitmodules not found or isn't merged */
 -                      pos = -1 - pos;
 -                      if (active_nr > pos) {  /* there is a .gitmodules */
 -                              const struct cache_entry *ce = active_cache[pos];
 -                              if (ce_namelen(ce) == 11 &&
 -                                  !memcmp(ce->name, ".gitmodules", 11))
 -                                      gitmodules_is_unmerged = 1;
 -                      }
 -              } else if (pos < active_nr) {
 -                      struct stat st;
 -                      if (lstat(".gitmodules", &st) == 0 &&
 -                          ce_match_stat(active_cache[pos], &st, 0) & DATA_CHANGED)
 -                              gitmodules_is_modified = 1;
 -              }
 -
 -              if (!gitmodules_is_unmerged)
 -                      git_config_from_file(git_modules_config,
 -                              gitmodules_path.buf, NULL);
 -              strbuf_release(&gitmodules_path);
 -      }
 -}
 -
 -static int gitmodules_cb(const char *var, const char *value, void *data)
 -{
 -      struct repository *repo = data;
 -      return submodule_config_option(repo, var, value);
 -}
 -
 -void repo_read_gitmodules(struct repository *repo)
 -{
 -      char *gitmodules_path = repo_worktree_path(repo, ".gitmodules");
 -
 -      git_config_from_file(gitmodules_cb, gitmodules_path, repo);
 -      free(gitmodules_path);
 -}
 -
 -void gitmodules_config_sha1(const unsigned char *commit_sha1)
 -{
 -      struct strbuf rev = STRBUF_INIT;
 -      unsigned char sha1[20];
 -
 -      if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) {
 -              git_config_from_blob_sha1(git_modules_config, rev.buf,
 -                                        sha1, NULL);
 -      }
 -      strbuf_release(&rev);
 -}
 -
  /*
   * Determine if a submodule has been initialized at a given 'path'
   */
@@@ -226,7 -293,7 +226,7 @@@ int is_submodule_active(struct reposito
        const struct string_list *sl;
        const struct submodule *module;
  
 -      module = submodule_from_cache(repo, null_sha1, path);
 +      module = submodule_from_cache(repo, &null_oid, path);
  
        /* early return if there isn't a path->module mapping */
        if (!module)
@@@ -343,38 -410,24 +343,38 @@@ void die_path_inside_submodule(const st
        }
  }
  
 -int parse_submodule_update_strategy(const char *value,
 -              struct submodule_update_strategy *dst)
 +enum submodule_update_type parse_submodule_update_type(const char *value)
  {
 -      free((void*)dst->command);
 -      dst->command = NULL;
        if (!strcmp(value, "none"))
 -              dst->type = SM_UPDATE_NONE;
 +              return SM_UPDATE_NONE;
        else if (!strcmp(value, "checkout"))
 -              dst->type = SM_UPDATE_CHECKOUT;
 +              return SM_UPDATE_CHECKOUT;
        else if (!strcmp(value, "rebase"))
 -              dst->type = SM_UPDATE_REBASE;
 +              return SM_UPDATE_REBASE;
        else if (!strcmp(value, "merge"))
 -              dst->type = SM_UPDATE_MERGE;
 -      else if (skip_prefix(value, "!", &value)) {
 -              dst->type = SM_UPDATE_COMMAND;
 -              dst->command = xstrdup(value);
 -      } else
 +              return SM_UPDATE_MERGE;
 +      else if (*value == '!')
 +              return SM_UPDATE_COMMAND;
 +      else
 +              return SM_UPDATE_UNSPECIFIED;
 +}
 +
 +int parse_submodule_update_strategy(const char *value,
 +              struct submodule_update_strategy *dst)
 +{
 +      enum submodule_update_type type;
 +
 +      free((void*)dst->command);
 +      dst->command = NULL;
 +
 +      type = parse_submodule_update_type(value);
 +      if (type == SM_UPDATE_UNSPECIFIED)
                return -1;
 +
 +      dst->type = type;
 +      if (type == SM_UPDATE_COMMAND)
 +              dst->command = xstrdup(value + 1);
 +
        return 0;
  }
  
@@@ -402,16 -455,16 +402,16 @@@ const char *submodule_strategy_to_strin
  void handle_ignore_submodules_arg(struct diff_options *diffopt,
                                  const char *arg)
  {
 -      DIFF_OPT_CLR(diffopt, IGNORE_SUBMODULES);
 -      DIFF_OPT_CLR(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
 -      DIFF_OPT_CLR(diffopt, IGNORE_DIRTY_SUBMODULES);
 +      diffopt->flags.ignore_submodules = 0;
 +      diffopt->flags.ignore_untracked_in_submodules = 0;
 +      diffopt->flags.ignore_dirty_submodules = 0;
  
        if (!strcmp(arg, "all"))
 -              DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
 +              diffopt->flags.ignore_submodules = 1;
        else if (!strcmp(arg, "untracked"))
 -              DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
 +              diffopt->flags.ignore_untracked_in_submodules = 1;
        else if (!strcmp(arg, "dirty"))
 -              DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES);
 +              diffopt->flags.ignore_dirty_submodules = 1;
        else if (strcmp(arg, "none"))
                die("bad --ignore-submodules argument: %s", arg);
  }
@@@ -437,7 -490,9 +437,7 @@@ static int prepare_submodule_summary(st
        return prepare_revision_walk(rev);
  }
  
 -static void print_submodule_summary(struct rev_info *rev, FILE *f,
 -              const char *line_prefix,
 -              const char *del, const char *add, const char *reset)
 +static void print_submodule_summary(struct rev_info *rev, struct diff_options *o)
  {
        static const char format[] = "  %m %s";
        struct strbuf sb = STRBUF_INIT;
                ctx.date_mode = rev->date_mode;
                ctx.output_encoding = get_log_output_encoding();
                strbuf_setlen(&sb, 0);
 -              strbuf_addstr(&sb, line_prefix);
 -              if (commit->object.flags & SYMMETRIC_LEFT) {
 -                      if (del)
 -                              strbuf_addstr(&sb, del);
 -              }
 -              else if (add)
 -                      strbuf_addstr(&sb, add);
                format_commit_message(commit, format, &sb, &ctx);
 -              if (reset)
 -                      strbuf_addstr(&sb, reset);
                strbuf_addch(&sb, '\n');
 -              fprintf(f, "%s", sb.buf);
 +              if (commit->object.flags & SYMMETRIC_LEFT)
 +                      diff_emit_submodule_del(o, sb.buf);
 +              else
 +                      diff_emit_submodule_add(o, sb.buf);
        }
        strbuf_release(&sb);
  }
@@@ -480,9 -541,11 +480,9 @@@ void prepare_submodule_repo_env(struct 
   * attempt to lookup both the left and right commits and put them into the
   * left and right pointers.
   */
 -static void show_submodule_header(FILE *f, const char *path,
 -              const char *line_prefix,
 +static void show_submodule_header(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
 -              unsigned dirty_submodule, const char *meta,
 -              const char *reset,
 +              unsigned dirty_submodule,
                struct commit **left, struct commit **right,
                struct commit_list **merge_bases)
  {
        int fast_forward = 0, fast_backward = 0;
  
        if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
 -              fprintf(f, "%sSubmodule %s contains untracked content\n",
 -                      line_prefix, path);
 +              diff_emit_submodule_untracked(o, path);
 +
        if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
 -              fprintf(f, "%sSubmodule %s contains modified content\n",
 -                      line_prefix, path);
 +              diff_emit_submodule_modified(o, path);
  
        if (is_null_oid(one))
                message = "(new submodule)";
  
        if (add_submodule_odb(path)) {
                if (!message)
 -                      message = "(not initialized)";
 +                      message = "(commits not present)";
                goto output_header;
        }
  
        }
  
  output_header:
 -      strbuf_addf(&sb, "%s%sSubmodule %s ", line_prefix, meta, path);
 +      strbuf_addf(&sb, "Submodule %s ", path);
        strbuf_add_unique_abbrev(&sb, one->hash, DEFAULT_ABBREV);
        strbuf_addstr(&sb, (fast_backward || fast_forward) ? ".." : "...");
        strbuf_add_unique_abbrev(&sb, two->hash, DEFAULT_ABBREV);
        if (message)
 -              strbuf_addf(&sb, " %s%s\n", message, reset);
 +              strbuf_addf(&sb, " %s\n", message);
        else
 -              strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset);
 -      fwrite(sb.buf, sb.len, 1, f);
 +              strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : "");
 +      diff_emit_submodule_header(o, sb.buf);
  
        strbuf_release(&sb);
  }
  
 -void show_submodule_summary(FILE *f, const char *path,
 -              const char *line_prefix,
 +void show_submodule_summary(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
 -              unsigned dirty_submodule, const char *meta,
 -              const char *del, const char *add, const char *reset)
 +              unsigned dirty_submodule)
  {
        struct rev_info rev;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
  
 -      show_submodule_header(f, path, line_prefix, one, two, dirty_submodule,
 -                            meta, reset, &left, &right, &merge_bases);
 +      show_submodule_header(o, path, one, two, dirty_submodule,
 +                            &left, &right, &merge_bases);
  
        /*
         * If we don't have both a left and a right pointer, there is no
  
        /* Treat revision walker failure the same as missing commits */
        if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) {
 -              fprintf(f, "%s(revision walker failed)\n", line_prefix);
 +              diff_emit_submodule_error(o, "(revision walker failed)\n");
                goto out;
        }
  
 -      print_submodule_summary(&rev, f, line_prefix, del, add, reset);
 +      print_submodule_summary(&rev, o);
  
  out:
        if (merge_bases)
        clear_commit_marks(right, ~0);
  }
  
 -void show_submodule_inline_diff(FILE *f, const char *path,
 -              const char *line_prefix,
 +void show_submodule_inline_diff(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
 -              unsigned dirty_submodule, const char *meta,
 -              const char *del, const char *add, const char *reset,
 -              const struct diff_options *o)
 +              unsigned dirty_submodule)
  {
        const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
 -      struct strbuf submodule_dir = STRBUF_INIT;
        struct child_process cp = CHILD_PROCESS_INIT;
 +      struct strbuf sb = STRBUF_INIT;
  
 -      show_submodule_header(f, path, line_prefix, one, two, dirty_submodule,
 -                            meta, reset, &left, &right, &merge_bases);
 +      show_submodule_header(o, path, one, two, dirty_submodule,
 +                            &left, &right, &merge_bases);
  
        /* We need a valid left and right commit to display a difference */
        if (!(left || is_null_oid(one)) ||
        if (right)
                new = two;
  
 -      fflush(f);
        cp.git_cmd = 1;
        cp.dir = path;
 -      cp.out = dup(fileno(f));
 +      cp.out = -1;
        cp.no_stdin = 1;
  
        /* TODO: other options may need to be passed here. */
        argv_array_pushl(&cp.args, "diff", "--submodule=diff", NULL);
 +      argv_array_pushf(&cp.args, "--color=%s", want_color(o->use_color) ?
 +                       "always" : "never");
  
 -      argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix);
 -      if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
 +      if (o->flags.reverse_diff) {
                argv_array_pushf(&cp.args, "--src-prefix=%s%s/",
                                 o->b_prefix, path);
                argv_array_pushf(&cp.args, "--dst-prefix=%s%s/",
                argv_array_push(&cp.args, oid_to_hex(new));
  
        prepare_submodule_repo_env(&cp.env_array);
 -      if (run_command(&cp))
 -              fprintf(f, "(diff failed)\n");
 +      if (start_command(&cp))
 +              diff_emit_submodule_error(o, "(diff failed)\n");
 +
 +      while (strbuf_getwholeline_fd(&sb, cp.out, '\n') != EOF)
 +              diff_emit_submodule_pipethrough(o, sb.buf, sb.len);
 +
 +      if (finish_command(&cp))
 +              diff_emit_submodule_error(o, "(diff failed)\n");
  
  done:
 -      strbuf_release(&submodule_dir);
 +      strbuf_release(&sb);
        if (merge_bases)
                free_commit_list(merge_bases);
        if (left)
                clear_commit_marks(right, ~0);
  }
  
 -void set_config_fetch_recurse_submodules(int value)
 -{
 -      config_fetch_recurse_submodules = value;
 -}
 -
  int should_update_submodules(void)
  {
        return config_update_recurse_submodules == RECURSE_SUBMODULES_ON;
@@@ -670,15 -738,15 +670,15 @@@ const struct submodule *submodule_from_
        if (!should_update_submodules())
                return NULL;
  
 -      return submodule_from_path(null_sha1, ce->name);
 +      return submodule_from_path(&null_oid, ce->name);
  }
  
  static struct oid_array *submodule_commits(struct string_list *submodules,
 -                                         const char *path)
 +                                         const char *name)
  {
        struct string_list_item *item;
  
 -      item = string_list_insert(submodules, path);
 +      item = string_list_insert(submodules, name);
        if (item->util)
                return (struct oid_array *) item->util;
  
        return (struct oid_array *) item->util;
  }
  
 +struct collect_changed_submodules_cb_data {
 +      struct string_list *changed;
 +      const struct object_id *commit_oid;
 +};
 +
 +/*
 + * this would normally be two functions: default_name_from_path() and
 + * path_from_default_name(). Since the default name is the same as
 + * the submodule path we can get away with just one function which only
 + * checks whether there is a submodule in the working directory at that
 + * location.
 + */
 +static const char *default_name_or_path(const char *path_or_name)
 +{
 +      int error_code;
 +
 +      if (!is_submodule_populated_gently(path_or_name, &error_code))
 +              return NULL;
 +
 +      return path_or_name;
 +}
 +
  static void collect_changed_submodules_cb(struct diff_queue_struct *q,
                                          struct diff_options *options,
                                          void *data)
  {
 +      struct collect_changed_submodules_cb_data *me = data;
 +      struct string_list *changed = me->changed;
 +      const struct object_id *commit_oid = me->commit_oid;
        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;
 +              const struct submodule *submodule;
 +              const char *name;
 +
                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;
 +              submodule = submodule_from_path(commit_oid, p->two->path);
 +              if (submodule)
 +                      name = submodule->name;
 +              else {
 +                      name = default_name_or_path(p->two->path);
 +                      /* make sure name does not collide with existing one */
 +                      submodule = submodule_from_name(commit_oid, name);
 +                      if (submodule) {
 +                              warning("Submodule in commit %s at path: "
 +                                      "'%s' collides with a submodule named "
 +                                      "the same. Skipping it.",
 +                                      oid_to_hex(commit_oid), name);
 +                              name = NULL;
 +                      }
                }
 +
 +              if (!name)
 +                      continue;
 +
 +              commits = submodule_commits(changed, name);
 +              oid_array_append(commits, &p->two->oid);
        }
  }
  
@@@ -770,14 -810,11 +770,14 @@@ static void collect_changed_submodules(
  
        while ((commit = get_revision(&rev))) {
                struct rev_info diff_rev;
 +              struct collect_changed_submodules_cb_data data;
 +              data.changed = changed;
 +              data.commit_oid = &commit->object.oid;
  
                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_rev.diffopt.format_callback_data = &data;
                diff_tree_combined_merge(commit, 1, &diff_rev);
        }
  
@@@ -805,36 -842,19 +805,36 @@@ static int append_oid_to_argv(const str
        return 0;
  }
  
 +struct has_commit_data {
 +      int result;
 +      const char *path;
 +};
 +
  static int check_has_commit(const struct object_id *oid, void *data)
  {
 -      int *has_commit = data;
 +      struct has_commit_data *cb = data;
  
 -      if (!lookup_commit_reference(oid))
 -              *has_commit = 0;
 +      enum object_type type = sha1_object_info(oid->hash, NULL);
  
 -      return 0;
 +      switch (type) {
 +      case OBJ_COMMIT:
 +              return 0;
 +      case OBJ_BAD:
 +              /*
 +               * Object is missing or invalid. If invalid, an error message
 +               * has already been printed.
 +               */
 +              cb->result = 0;
 +              return 0;
 +      default:
 +              die(_("submodule entry '%s' (%s) is a %s, not a commit"),
 +                  cb->path, oid_to_hex(oid), typename(type));
 +      }
  }
  
  static int submodule_has_commits(const char *path, struct oid_array *commits)
  {
 -      int has_commit = 1;
 +      struct has_commit_data has_commit = { 1, path };
  
        /*
         * Perform a cheap, but incorrect check for the existence of 'commits'.
  
        oid_array_for_each_unique(commits, check_has_commit, &has_commit);
  
 -      if (has_commit) {
 +      if (has_commit.result) {
                /*
                 * Even if the submodule is checked out and the commit is
                 * present, make sure it exists in the submodule's object store
                cp.dir = path;
  
                if (capture_command(&cp, &out, GIT_MAX_HEXSZ + 1) || out.len)
 -                      has_commit = 0;
 +                      has_commit.result = 0;
  
                strbuf_release(&out);
        }
  
 -      return has_commit;
 +      return has_commit.result;
  }
  
  static int submodule_needs_pushing(const char *path, struct oid_array *commits)
@@@ -925,7 -945,7 +925,7 @@@ int find_unpushed_submodules(struct oid
                const char *remotes_name, struct string_list *needs_pushing)
  {
        struct string_list submodules = STRING_LIST_INIT_DUP;
 -      struct string_list_item *submodule;
 +      struct string_list_item *name;
        struct argv_array argv = ARGV_ARRAY_INIT;
  
        /* argv.argv[0] will be ignored by setup_revisions */
  
        collect_changed_submodules(&submodules, &argv);
  
 -      for_each_string_list_item(submodule, &submodules) {
 -              struct oid_array *commits = submodule->util;
 -              const char *path = submodule->string;
 +      for_each_string_list_item(name, &submodules) {
 +              struct oid_array *commits = name->util;
 +              const struct submodule *submodule;
 +              const char *path = NULL;
 +
 +              submodule = submodule_from_name(&null_oid, name->string);
 +              if (submodule)
 +                      path = submodule->path;
 +              else
 +                      path = default_name_or_path(name->string);
 +
 +              if (!path)
 +                      continue;
  
                if (submodule_needs_pushing(path, commits))
                        string_list_insert(needs_pushing, path);
@@@ -1005,8 -1015,7 +1005,8 @@@ static int push_submodule(const char *p
   * Perform a check in the submodule to see if the remote and refspec work.
   * Die if the submodule can't be pushed.
   */
 -static void submodule_push_check(const char *path, const struct remote *remote,
 +static void submodule_push_check(const char *path, const char *head,
 +                               const struct remote *remote,
                                 const char **refspec, int refspec_nr)
  {
        struct child_process cp = CHILD_PROCESS_INIT;
  
        argv_array_push(&cp.args, "submodule--helper");
        argv_array_push(&cp.args, "push-check");
 +      argv_array_push(&cp.args, head);
        argv_array_push(&cp.args, remote->name);
  
        for (i = 0; i < refspec_nr; i++)
@@@ -1053,20 -1061,10 +1053,20 @@@ int push_unpushed_submodules(struct oid
         * won't be propagated due to the remote being unconfigured (e.g. a URL
         * instead of a remote name).
         */
 -      if (remote->origin != REMOTE_UNCONFIGURED)
 +      if (remote->origin != REMOTE_UNCONFIGURED) {
 +              char *head;
 +              struct object_id head_oid;
 +
 +              head = resolve_refdup("HEAD", 0, &head_oid, NULL);
 +              if (!head)
 +                      die(_("Failed to resolve HEAD as a valid ref."));
 +
                for (i = 0; i < needs_pushing.nr; i++)
                        submodule_push_check(needs_pushing.items[i].string,
 -                                           remote, refspec, refspec_nr);
 +                                           head, remote,
 +                                           refspec, refspec_nr);
 +              free(head);
 +      }
  
        /* Actually push the submodules */
        for (i = 0; i < needs_pushing.nr; i++) {
@@@ -1106,7 -1104,7 +1106,7 @@@ static void calculate_changed_submodule
  {
        struct argv_array argv = ARGV_ARRAY_INIT;
        struct string_list changed_submodules = STRING_LIST_INIT_DUP;
 -      const struct string_list_item *item;
 +      const struct string_list_item *name;
  
        /* No need to check if there are no submodules configured */
        if (!submodule_from_path(NULL, NULL))
  
        /*
         * Collect all submodules (whether checked out or not) for which new
 -       * commits have been recorded upstream in "changed_submodule_paths".
 +       * commits have been recorded upstream in "changed_submodule_names".
         */
        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;
 +      for_each_string_list_item(name, &changed_submodules) {
 +              struct oid_array *commits = name->util;
 +              const struct submodule *submodule;
 +              const char *path = NULL;
 +
 +              submodule = submodule_from_name(&null_oid, name->string);
 +              if (submodule)
 +                      path = submodule->path;
 +              else
 +                      path = default_name_or_path(name->string);
 +
 +              if (!path)
 +                      continue;
  
                if (!submodule_has_commits(path, commits))
 -                      string_list_append(&changed_submodule_paths, path);
 +                      string_list_append(&changed_submodule_names, name->string);
        }
  
        free_submodules_oids(&changed_submodules);
@@@ -1157,6 -1145,7 +1157,6 @@@ int submodule_touches_in_range(struct o
        struct argv_array args = ARGV_ARRAY_INIT;
        int ret;
  
 -      gitmodules_config();
        /* No need to check if there are no submodules configured */
        if (!submodule_from_path(NULL, NULL))
                return 0;
@@@ -1181,36 -1170,10 +1181,36 @@@ struct submodule_parallel_fetch 
        const char *work_tree;
        const char *prefix;
        int command_line_option;
 +      int default_option;
        int quiet;
        int result;
  };
 -#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0}
 +#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0}
 +
 +static int get_fetch_recurse_config(const struct submodule *submodule,
 +                                  struct submodule_parallel_fetch *spf)
 +{
 +      if (spf->command_line_option != RECURSE_SUBMODULES_DEFAULT)
 +              return spf->command_line_option;
 +
 +      if (submodule) {
 +              char *key;
 +              const char *value;
 +
 +              int fetch_recurse = submodule->fetch_recurse;
 +              key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name);
 +              if (!repo_config_get_string_const(the_repository, key, &value)) {
 +                      fetch_recurse = parse_fetch_recurse_submodules_arg(key, value);
 +              }
 +              free(key);
 +
 +              if (fetch_recurse != RECURSE_SUBMODULES_NONE)
 +                      /* local config overrules everything except commandline */
 +                      return fetch_recurse;
 +      }
 +
 +      return spf->default_option;
 +}
  
  static int get_next_submodule(struct child_process *cp,
                              struct strbuf *err, void *data, void **task_cb)
                const struct cache_entry *ce = active_cache[spf->count];
                const char *git_dir, *default_argv;
                const struct submodule *submodule;
 +              struct submodule default_submodule = SUBMODULE_INIT;
  
                if (!S_ISGITLINK(ce->ce_mode))
                        continue;
  
 -              submodule = submodule_from_path(null_sha1, ce->name);
 -              if (!submodule)
 -                      submodule = submodule_from_name(null_sha1, ce->name);
 -
 -              default_argv = "yes";
 -              if (spf->command_line_option == RECURSE_SUBMODULES_DEFAULT) {
 -                      if (submodule &&
 -                          submodule->fetch_recurse !=
 -                                              RECURSE_SUBMODULES_NONE) {
 -                              if (submodule->fetch_recurse ==
 -                                              RECURSE_SUBMODULES_OFF)
 -                                      continue;
 -                              if (submodule->fetch_recurse ==
 -                                              RECURSE_SUBMODULES_ON_DEMAND) {
 -                                      if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
 -                                              continue;
 -                                      default_argv = "on-demand";
 -                              }
 -                      } else {
 -                              if ((config_fetch_recurse_submodules == RECURSE_SUBMODULES_OFF) ||
 -                                  gitmodules_is_unmerged)
 -                                      continue;
 -                              if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) {
 -                                      if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
 -                                              continue;
 -                                      default_argv = "on-demand";
 -                              }
 +              submodule = submodule_from_path(&null_oid, ce->name);
 +              if (!submodule) {
 +                      const char *name = default_name_or_path(ce->name);
 +                      if (name) {
 +                              default_submodule.path = default_submodule.name = name;
 +                              submodule = &default_submodule;
                        }
 -              } else if (spf->command_line_option == RECURSE_SUBMODULES_ON_DEMAND) {
 -                      if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
 +              }
 +
 +              switch (get_fetch_recurse_config(submodule, spf))
 +              {
 +              default:
 +              case RECURSE_SUBMODULES_DEFAULT:
 +              case RECURSE_SUBMODULES_ON_DEMAND:
 +                      if (!submodule || !unsorted_string_list_lookup(&changed_submodule_names,
 +                                                       submodule->name))
                                continue;
                        default_argv = "on-demand";
 +                      break;
 +              case RECURSE_SUBMODULES_ON:
 +                      default_argv = "yes";
 +                      break;
 +              case RECURSE_SUBMODULES_OFF:
 +                      continue;
                }
  
                strbuf_addf(&submodule_path, "%s/%s", spf->work_tree, ce->name);
@@@ -1311,7 -1281,6 +1311,7 @@@ static int fetch_finish(int retvalue, s
  
  int fetch_populated_submodules(const struct argv_array *options,
                               const char *prefix, int command_line_option,
 +                             int default_option,
                               int quiet, int max_parallel_jobs)
  {
        int i;
  
        spf.work_tree = get_git_work_tree();
        spf.command_line_option = command_line_option;
 +      spf.default_option = default_option;
        spf.quiet = quiet;
        spf.prefix = prefix;
  
        argv_array_push(&spf.args, "--recurse-submodules-default");
        /* default value, "--submodule-prefix" and its value are added later */
  
 -      if (max_parallel_jobs < 0)
 -              max_parallel_jobs = parallel_jobs;
 -
        calculate_changed_submodule_paths();
        run_processes_parallel(max_parallel_jobs,
                               get_next_submodule,
  
        argv_array_clear(&spf.args);
  out:
 -      string_list_clear(&changed_submodule_paths, 1);
 +      string_list_clear(&changed_submodule_names, 1);
        return spf.result;
  }
  
@@@ -1599,7 -1570,7 +1599,7 @@@ int submodule_move_head(const char *pat
        if (old && !is_submodule_populated_gently(path, error_code_ptr))
                return 0;
  
 -      sub = submodule_from_path(null_sha1, path);
 +      sub = submodule_from_path(&null_oid, path);
  
        if (!sub)
                die("BUG: could not get submodule information for '%s'", path);
                        cp.dir = path;
  
                        prepare_submodule_repo_env(&cp.env_array);
-                       argv_array_pushl(&cp.args, "update-ref", "HEAD", new, NULL);
+                       argv_array_pushl(&cp.args, "update-ref", "HEAD",
+                                        "--no-deref", new, NULL);
  
                        if (run_command(&cp)) {
                                ret = -1;
@@@ -1713,8 -1685,6 +1714,8 @@@ static int find_first_merges(struct obj
                        oid_to_hex(&a->object.oid));
        init_revisions(&revs, NULL);
        rev_opts.submodule = path;
 +      /* FIXME: can't handle linked worktrees in submodules yet */
 +      revs.single_worktree = path != NULL;
        setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
  
        /* save all revisions from the above list that contain b */
                        add_object_array(merges.objects[i].item, NULL, result);
        }
  
 -      free(merges.objects);
 +      object_array_clear(&merges);
        return result->nr;
  }
  
@@@ -1852,10 -1822,15 +1853,10 @@@ int merge_submodule(struct object_id *r
                        print_commit((struct commit *) merges.objects[i].item);
        }
  
 -      free(merges.objects);
 +      object_array_clear(&merges);
        return 0;
  }
  
 -int parallel_submodules(void)
 -{
 -      return parallel_jobs;
 -}
 -
  /*
   * Embeds a single submodules git directory into the superprojects git dir,
   * non recursively.
@@@ -1878,7 -1853,7 +1879,7 @@@ static void relocate_single_git_dir_int
  
        real_old_git_dir = real_pathdup(old_git_dir, 1);
  
 -      sub = submodule_from_path(null_sha1, path);
 +      sub = submodule_from_path(&null_oid, path);
        if (!sub)
                die(_("could not lookup name for submodule '%s'"), path);
  
@@@ -1934,7 -1909,7 +1935,7 @@@ void absorb_git_dir_into_superproject(c
                * superproject did not rewrite the git file links yet,
                * fix it now.
                */
 -              sub = submodule_from_path(null_sha1, path);
 +              sub = submodule_from_path(&null_oid, path);
                if (!sub)
                        die(_("could not lookup name for submodule '%s'"), path);
                connect_work_tree_and_git_dir(path,
@@@ -2059,10 -2034,6 +2060,10 @@@ const char *get_superproject_working_tr
        return ret;
  }
  
 +/*
 + * Put the gitdir for a submodule (given relative to the main
 + * repository worktree) into `buf`, or return -1 on error.
 + */
  int submodule_to_gitdir(struct strbuf *buf, const char *submodule)
  {
        const struct submodule *sub;
                strbuf_addstr(buf, git_dir);
        }
        if (!is_git_directory(buf->buf)) {
 -              gitmodules_config();
 -              sub = submodule_from_path(null_sha1, submodule);
 +              sub = submodule_from_path(&null_oid, submodule);
                if (!sub) {
                        ret = -1;
                        goto cleanup;
index bb94c2320958eb1d1a319899697ecefc881e3043,fc406b95d7f4c6041fdc2184ac10a2ddbc096851..9058bf978a005bbe24f9e5948ca5bf4f36eeb2a8
@@@ -306,9 -306,9 +306,9 @@@ test_submodule_content () 
  # to protect the history!
  #
  
 -# Test that submodule contents are currently not updated when switching
 -# between commits that change a submodule.
 -test_submodule_switch () {
 +# Internal function; use test_submodule_switch() or
 +# test_submodule_forced_switch() instead.
 +test_submodule_switch_common() {
        command="$1"
        ######################### Appearing submodule #########################
        # Switching to a commit letting a submodule appear creates empty dir ...
                        test_submodule_content sub1 origin/add_sub1
                )
        '
 -      # ... and doesn't care if it already exists ...
 +      # ... and doesn't care if it already exists.
        test_expect_$RESULT "$command: added submodule leaves existing empty directory alone" '
                prolog &&
                reset_work_tree_to no_submodule &&
                        test_submodule_content sub1 origin/add_sub1
                )
        '
 -      # ... unless there is an untracked file in its place.
 -      test_expect_success "$command: added submodule doesn't remove untracked unignored file with same name" '
 -              prolog &&
 -              reset_work_tree_to no_submodule &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t add_sub1 origin/add_sub1 &&
 -                      >sub1 &&
 -                      test_must_fail $command add_sub1 &&
 -                      test_superproject_content origin/no_submodule &&
 -                      test_must_be_empty sub1
 -              )
 -      '
        # Replacing a tracked file with a submodule produces an empty
        # directory ...
        test_expect_$RESULT "$command: replace tracked file with submodule creates empty directory" '
                # submodule files with the newly checked out ones in the
                # directory of the same name while it shouldn't.
                RESULT="failure"
 +      elif test "$KNOWN_FAILURE_FORCED_SWITCH_TESTS" = 1
 +      then
 +              # All existing tests that use test_submodule_forced_switch()
 +              # require this.
 +              RESULT="failure"
        else
                RESULT="success"
        fi
                        test_submodule_content sub1 origin/modify_sub1
                )
        '
 -
        # Updating a submodule to an invalid sha1 doesn't update the
        # submodule's work tree, subsequent update will fail
        test_expect_$RESULT "$command: modified submodule does not update submodule work tree to invalid commit" '
        '
  }
  
 -# Test that submodule contents are currently not updated when switching
 -# between commits that change a submodule, but throwing away local changes in
 -# the superproject is allowed.
 -test_submodule_forced_switch () {
 +# Declares and invokes several tests that, in various situations, checks that
 +# the provided transition function:
 +#  - succeeds in updating the worktree and index of a superproject to a target
 +#    commit, or fails atomically (depending on the test situation)
 +#  - if succeeds, the contents of submodule directories are unchanged
 +#  - if succeeds, once "git submodule update" is invoked, the contents of
 +#    submodule directories are updated
 +#
 +# Use as follows:
 +#
 +# my_func () {
 +#   target=$1
 +#   # Do something here that updates the worktree and index to match target,
 +#   # but not any submodule directories.
 +# }
 +# test_submodule_switch "my_func"
 +test_submodule_switch () {
        command="$1"
 -      ######################### Appearing submodule #########################
 -      # Switching to a commit letting a submodule appear creates empty dir ...
 -      test_expect_success "$command: added submodule creates empty directory" '
 -              prolog &&
 -              reset_work_tree_to no_submodule &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t add_sub1 origin/add_sub1 &&
 -                      $command add_sub1 &&
 -                      test_superproject_content origin/add_sub1 &&
 -                      test_dir_is_empty sub1 &&
 -                      git submodule update --init --recursive &&
 -                      test_submodule_content sub1 origin/add_sub1
 -              )
 -      '
 -      # ... and doesn't care if it already exists ...
 -      test_expect_success "$command: added submodule leaves existing empty directory alone" '
 +      test_submodule_switch_common "$command"
 +
 +      # An empty directory does not prevent the creation of a submodule of
 +      # the same name, but a file does.
 +      test_expect_success "$command: added submodule doesn't remove untracked unignored file with same name" '
                prolog &&
                reset_work_tree_to no_submodule &&
                (
                        cd submodule_update &&
                        git branch -t add_sub1 origin/add_sub1 &&
 -                      mkdir sub1 &&
 -                      $command add_sub1 &&
 -                      test_superproject_content origin/add_sub1 &&
 -                      test_dir_is_empty sub1 &&
 -                      git submodule update --init --recursive &&
 -                      test_submodule_content sub1 origin/add_sub1
 +                      >sub1 &&
 +                      test_must_fail $command add_sub1 &&
 +                      test_superproject_content origin/no_submodule &&
 +                      test_must_be_empty sub1
                )
        '
 -      # ... unless there is an untracked file in its place.
 +}
 +
 +# Same as test_submodule_switch(), except that throwing away local changes in
 +# the superproject is allowed.
 +test_submodule_forced_switch () {
 +      command="$1"
 +      KNOWN_FAILURE_FORCED_SWITCH_TESTS=1
 +      test_submodule_switch_common "$command"
 +
 +      # When forced, a file in the superproject does not prevent creating a
 +      # submodule of the same name.
        test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
                prolog &&
                reset_work_tree_to no_submodule &&
                        test_dir_is_empty sub1
                )
        '
 -      # Replacing a tracked file with a submodule produces an empty
 -      # directory ...
 -      test_expect_success "$command: replace tracked file with submodule creates empty directory" '
 -              prolog &&
 -              reset_work_tree_to replace_sub1_with_file &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 &&
 -                      $command replace_file_with_sub1 &&
 -                      test_superproject_content origin/replace_file_with_sub1 &&
 -                      test_dir_is_empty sub1 &&
 -                      git submodule update --init --recursive &&
 -                      test_submodule_content sub1 origin/replace_file_with_sub1
 -              )
 -      '
 -      # ... as does removing a directory with tracked files with a
 -      # submodule.
 -      test_expect_success "$command: replace directory with submodule" '
 -              prolog &&
 -              reset_work_tree_to replace_sub1_with_directory &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 &&
 -                      $command replace_directory_with_sub1 &&
 -                      test_superproject_content origin/replace_directory_with_sub1 &&
 -                      test_dir_is_empty sub1 &&
 -                      git submodule update --init --recursive &&
 -                      test_submodule_content sub1 origin/replace_directory_with_sub1
 -              )
 -      '
 -
 -      ######################## Disappearing submodule #######################
 -      # Removing a submodule doesn't remove its work tree ...
 -      test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" '
 -              prolog &&
 -              reset_work_tree_to add_sub1 &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t remove_sub1 origin/remove_sub1 &&
 -                      $command remove_sub1 &&
 -                      test_superproject_content origin/remove_sub1 &&
 -                      test_submodule_content sub1 origin/add_sub1
 -              )
 -      '
 -      # ... especially when it contains a .git directory.
 -      test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" '
 -              prolog &&
 -              reset_work_tree_to add_sub1 &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t remove_sub1 origin/remove_sub1 &&
 -                      replace_gitfile_with_git_dir sub1 &&
 -                      $command remove_sub1 &&
 -                      test_superproject_content origin/remove_sub1 &&
 -                      test_git_directory_is_unchanged sub1 &&
 -                      test_submodule_content sub1 origin/add_sub1
 -              )
 -      '
 -      # Replacing a submodule with files in a directory must fail as the
 -      # submodule work tree isn't removed ...
 -      test_expect_failure "$command: replace submodule with a directory must fail" '
 -              prolog &&
 -              reset_work_tree_to add_sub1 &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
 -                      test_must_fail $command replace_sub1_with_directory &&
 -                      test_superproject_content origin/add_sub1 &&
 -                      test_submodule_content sub1 origin/add_sub1
 -              )
 -      '
 -      # ... especially when it contains a .git directory.
 -      test_expect_failure "$command: replace submodule containing a .git directory with a directory must fail" '
 -              prolog &&
 -              reset_work_tree_to add_sub1 &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
 -                      replace_gitfile_with_git_dir sub1 &&
 -                      test_must_fail $command replace_sub1_with_directory &&
 -                      test_superproject_content origin/add_sub1 &&
 -                      test_git_directory_is_unchanged sub1 &&
 -                      test_submodule_content sub1 origin/add_sub1
 -              )
 -      '
 -      # Replacing it with a file must fail as it could throw away any local
 -      # work tree changes ...
 -      test_expect_failure "$command: replace submodule with a file must fail" '
 -              prolog &&
 -              reset_work_tree_to add_sub1 &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
 -                      test_must_fail $command replace_sub1_with_file &&
 -                      test_superproject_content origin/add_sub1 &&
 -                      test_submodule_content sub1 origin/add_sub1
 -              )
 -      '
 -      # ... or even destroy unpushed parts of submodule history if that
 -      # still uses a .git directory.
 -      test_expect_failure "$command: replace submodule containing a .git directory with a file must fail" '
 -              prolog &&
 -              reset_work_tree_to add_sub1 &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
 -                      replace_gitfile_with_git_dir sub1 &&
 -                      test_must_fail $command replace_sub1_with_file &&
 -                      test_superproject_content origin/add_sub1 &&
 -                      test_git_directory_is_unchanged sub1 &&
 -                      test_submodule_content sub1 origin/add_sub1
 -              )
 -      '
 -
 -      ########################## Modified submodule #########################
 -      # Updating a submodule sha1 doesn't update the submodule's work tree
 -      test_expect_success "$command: modified submodule does not update submodule work tree" '
 -              prolog &&
 -              reset_work_tree_to add_sub1 &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t modify_sub1 origin/modify_sub1 &&
 -                      $command modify_sub1 &&
 -                      test_superproject_content origin/modify_sub1 &&
 -                      test_submodule_content sub1 origin/add_sub1 &&
 -                      git submodule update &&
 -                      test_submodule_content sub1 origin/modify_sub1
 -              )
 -      '
 -      # Updating a submodule to an invalid sha1 doesn't update the
 -      # submodule's work tree, subsequent update will fail
 -      test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" '
 -              prolog &&
 -              reset_work_tree_to add_sub1 &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t invalid_sub1 origin/invalid_sub1 &&
 -                      $command invalid_sub1 &&
 -                      test_superproject_content origin/invalid_sub1 &&
 -                      test_submodule_content sub1 origin/add_sub1 &&
 -                      test_must_fail git submodule update &&
 -                      test_submodule_content sub1 origin/add_sub1
 -              )
 -      '
 -      # Updating a submodule from an invalid sha1 doesn't update the
 -      # submodule's work tree, subsequent update will succeed
 -      test_expect_success "$command: modified submodule does not update submodule work tree from invalid commit" '
 -              prolog &&
 -              reset_work_tree_to invalid_sub1 &&
 -              (
 -                      cd submodule_update &&
 -                      git branch -t valid_sub1 origin/valid_sub1 &&
 -                      $command valid_sub1 &&
 -                      test_superproject_content origin/valid_sub1 &&
 -                      test_dir_is_empty sub1 &&
 -                      git submodule update --init --recursive &&
 -                      test_submodule_content sub1 origin/valid_sub1
 -              )
 -      '
  }
  
  # Test that submodule contents are correctly updated when switching
@@@ -689,6 -848,23 +689,23 @@@ test_submodule_switch_recursing_with_ar
                        test_submodule_content sub1 origin/add_sub1
                )
        '
+       test_expect_success "$command: submodule branch is not changed, detach HEAD instead" '
+               prolog &&
+               reset_work_tree_to_interested add_sub1 &&
+               (
+                       cd submodule_update &&
+                       git -C sub1 checkout -b keep_branch &&
+                       git -C sub1 rev-parse HEAD >expect &&
+                       git branch -t check-keep origin/modify_sub1 &&
+                       $command check-keep &&
+                       test_superproject_content origin/modify_sub1 &&
+                       test_submodule_content sub1 origin/modify_sub1 &&
+                       git -C sub1 rev-parse keep_branch >actual &&
+                       test_cmp expect actual &&
+                       test_must_fail git -C sub1 symbolic-ref HEAD
+               )
+       '
        # Replacing a tracked file with a submodule produces a checked out submodule
        test_expect_success "$command: replace tracked file with submodule checks out submodule" '
                prolog &&