]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Sync with 2.20.2
authorJohannes Schindelin <johannes.schindelin@gmx.de>
Wed, 4 Dec 2019 21:46:37 +0000 (22:46 +0100)
committerJohannes Schindelin <johannes.schindelin@gmx.de>
Fri, 6 Dec 2019 15:31:12 +0000 (16:31 +0100)
* maint-2.20: (36 commits)
  Git 2.20.2
  t7415: adjust test for dubiously-nested submodule gitdirs for v2.20.x
  Git 2.19.3
  Git 2.18.2
  Git 2.17.3
  Git 2.16.6
  test-drop-caches: use `has_dos_drive_prefix()`
  Git 2.15.4
  Git 2.14.6
  mingw: handle `subst`-ed "DOS drives"
  mingw: refuse to access paths with trailing spaces or periods
  mingw: refuse to access paths with illegal characters
  unpack-trees: let merged_entry() pass through do_add_entry()'s errors
  quote-stress-test: offer to test quoting arguments for MSYS2 sh
  t6130/t9350: prepare for stringent Win32 path validation
  quote-stress-test: allow skipping some trials
  quote-stress-test: accept arguments to test via the command-line
  tests: add a helper to stress test argument quoting
  mingw: fix quoting of arguments
  Disallow dubiously-nested submodule git directories
  ...

25 files changed:
1  2 
Documentation/git-fast-import.txt
builtin/clone.c
builtin/submodule--helper.c
compat/mingw.c
compat/mingw.h
compat/win32/path-utils.c
compat/win32/path-utils.h
config.mak.uname
connect.c
environment.c
fast-import.c
fsck.c
git-compat-util.h
git-submodule.sh
path.c
read-cache.c
submodule.c
submodule.h
t/helper/test-path-utils.c
t/t1450-fsck.sh
t/t7415-submodule-names.sh
t/t9350-fast-export.sh
transport-helper.c
tree-walk.c
unpack-trees.c

index 43ab3b1637b50462ff62aa9c33c87184ef8ebc47,9e3b9581d76d7e7146423d857f27f1067710b580..89747e0bbfb6d1a6fbcf49fb0b171074c155f8ac
@@@ -40,10 -40,9 +40,10 @@@ OPTION
        not contain the old commit).
  
  --quiet::
 -      Disable all non-fatal output, making fast-import silent when it
 -      is successful.  This option disables the output shown by
 -      --stats.
 +      Disable the output shown by --stats, making fast-import usually
 +      be silent when it is successful.  However, if the import stream
 +      has directives intended to show user output (e.g. `progress`
 +      directives), the corresponding messages will still be shown.
  
  --stats::
        Display some basic statistics about the objects fast-import has
        memory used by fast-import during this run.  Showing this output
        is currently the default, but can be disabled with --quiet.
  
+ --allow-unsafe-features::
+       Many command-line options can be provided as part of the
+       fast-import stream itself by using the `feature` or `option`
+       commands. However, some of these options are unsafe (e.g.,
+       allowing fast-import to access the filesystem outside of the
+       repository). These options are disabled by default, but can be
+       allowed by providing this option on the command line.  This
+       currently impacts only the `export-marks`, `import-marks`, and
+       `import-marks-if-exists` feature commands.
+ +
+       Only enable this option if you trust the program generating the
+       fast-import stream! This option is enabled automatically for
+       remote-helpers that use the `import` capability, as they are
+       already trusted to run their own code.
  Options for Frontends
  ~~~~~~~~~~~~~~~~~~~~~
  
@@@ -385,7 -399,6 +400,7 @@@ change to the project
  ....
        'commit' SP <ref> LF
        mark?
 +      original-oid?
        ('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
        'committer' (SP <name>)? SP LT <email> GT SP <when> LF
        data
@@@ -742,19 -755,6 +757,19 @@@ New marks are created automatically.  E
  to another object simply by reusing the same `<idnum>` in another
  `mark` command.
  
 +`original-oid`
 +~~~~~~~~~~~~~~
 +Provides the name of the object in the original source control system.
 +fast-import will simply ignore this directive, but filter processes
 +which operate on and modify the stream before feeding to fast-import
 +may have uses for this information
 +
 +....
 +      'original-oid' SP <object-identifier> LF
 +....
 +
 +where `<object-identifer>` is any string not containing LF.
 +
  `tag`
  ~~~~~
  Creates an annotated tag referring to a specific commit.  To create
@@@ -763,7 -763,6 +778,7 @@@ lightweight (non-annotated) tags see th
  ....
        'tag' SP <name> LF
        'from' SP <commit-ish> LF
 +      original-oid?
        'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
        data
  ....
@@@ -838,7 -837,6 +853,7 @@@ assigned mark
  ....
        'blob' LF
        mark?
 +      original-oid?
        data
  ....
  
diff --combined builtin/clone.c
index 50bde9961809b1d55c74fb6d3cbfbf5a86ddf5c7,5d31bea55e0e0f22012c96757eb9106c9d1e5133..0aeed9af3ae18f8fb32a69d4073a116b307a85e9
@@@ -8,7 -8,6 +8,7 @@@
   * Clone a repository into a different directory that does not yet exist.
   */
  
 +#define USE_THE_INDEX_COMPATIBILITY_MACROS
  #include "builtin.h"
  #include "config.h"
  #include "lockfile.h"
@@@ -549,7 -548,7 +549,7 @@@ static struct ref *find_remote_branch(c
  }
  
  static struct ref *wanted_peer_refs(const struct ref *refs,
 -              struct refspec_item *refspec)
 +              struct refspec *refspec)
  {
        struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
        struct ref *local_refs = head;
                        warning(_("Could not find remote branch %s to clone."),
                                option_branch);
                else {
 -                      get_fetch_map(remote_head, refspec, &tail, 0);
 +                      int i;
 +                      for (i = 0; i < refspec->nr; i++)
 +                              get_fetch_map(remote_head, &refspec->items[i],
 +                                            &tail, 0);
  
                        /* if --branch=tag, pull the requested tag explicitly */
                        get_fetch_map(remote_head, tag_refspec, &tail, 0);
                }
 -      } else
 -              get_fetch_map(refs, refspec, &tail, 0);
 +      } else {
 +              int i;
 +              for (i = 0; i < refspec->nr; i++)
 +                      get_fetch_map(refs, &refspec->items[i], &tail, 0);
 +      }
  
        if (!option_mirror && !option_single_branch && !option_no_tags)
                get_fetch_map(refs, tag_refspec, &tail, 0);
@@@ -775,7 -768,7 +775,7 @@@ static int checkout(int submodule_progr
  
        if (!err && (option_recurse_submodules.nr > 0)) {
                struct argv_array args = ARGV_ARRAY_INIT;
-               argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
+               argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL);
  
                if (option_shallow_submodules == 1)
                        argv_array_push(&args, "--depth=1");
@@@ -897,8 -890,7 +897,8 @@@ int cmd_clone(int argc, const char **ar
        const struct ref *our_head_points_at;
        struct ref *mapped_refs;
        const struct ref *ref;
 -      struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
 +      struct strbuf key = STRBUF_INIT;
 +      struct strbuf default_refspec = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
        const char *src_ref_prefix = "refs/heads/";
        int err = 0, complete_refs_before_fetch = 1;
        int submodule_progress;
  
 -      struct refspec rs = REFSPEC_INIT_FETCH;
        struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
  
        fetch_if_missing = 0;
                strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
        }
  
 -      strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
        strbuf_addf(&key, "remote.%s.url", option_origin);
        git_config_set(key.buf, repo);
        strbuf_reset(&key);
        if (option_required_reference.nr || option_optional_reference.nr)
                setup_reference();
  
 -      refspec_append(&rs, value.buf);
 +      remote = remote_get(option_origin);
  
 -      strbuf_reset(&value);
 +      strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix,
 +                  branch_top.buf);
 +      refspec_append(&remote->fetch, default_refspec.buf);
  
 -      remote = remote_get(option_origin);
        transport = transport_get(remote, remote->url[0]);
        transport_set_verbosity(transport, option_verbosity, option_progress);
        transport->family = family;
                                     option_upload_pack);
  
        if (filter_options.choice) {
 +              struct strbuf expanded_filter_spec = STRBUF_INIT;
 +              expand_list_objects_filter_spec(&filter_options,
 +                                              &expanded_filter_spec);
                transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
 -                                   filter_options.filter_spec);
 +                                   expanded_filter_spec.buf);
                transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
 +              strbuf_release(&expanded_filter_spec);
        }
  
        if (transport->smart_options && !deepen && !filter_options.choice)
  
  
        argv_array_push(&ref_prefixes, "HEAD");
 -      refspec_ref_prefixes(&rs, &ref_prefixes);
 +      refspec_ref_prefixes(&remote->fetch, &ref_prefixes);
        if (option_branch)
                expand_ref_prefix(&ref_prefixes, option_branch);
        if (!option_no_tags)
        refs = transport_get_remote_refs(transport, &ref_prefixes);
  
        if (refs) {
 -              mapped_refs = wanted_peer_refs(refs, &rs.items[0]);
 +              mapped_refs = wanted_peer_refs(refs, &remote->fetch);
                /*
                 * transport_get_remote_refs() may return refs with null sha-1
                 * in mapped_refs (see struct transport->get_refs_list
        strbuf_release(&reflog_msg);
        strbuf_release(&branch_top);
        strbuf_release(&key);
 -      strbuf_release(&value);
 +      strbuf_release(&default_refspec);
        junk_mode = JUNK_LEAVE_ALL;
  
 -      refspec_clear(&rs);
        argv_array_clear(&ref_prefixes);
        return err;
  }
index b80fc4ba3d88b56436fa3f6b9a132bfe9e82b79b,f26bdace0ba9c185ddf196e7e79206cc2d25e17d..24dbd5e65c195ec4ef55fe9439c78024dd6c3d26
@@@ -1,4 -1,3 +1,4 @@@
 +#define USE_THE_INDEX_COMPATIBILITY_MACROS
  #include "builtin.h"
  #include "repository.h"
  #include "cache.h"
@@@ -19,6 -18,7 +19,7 @@@
  #include "diffcore.h"
  #include "diff.h"
  #include "object-store.h"
+ #include "dir.h"
  
  #define OPT_QUIET (1 << 0)
  #define OPT_CACHED (1 << 1)
@@@ -1132,8 -1132,6 +1133,8 @@@ static void deinit_submodule(const cha
                if (!(flags & OPT_QUIET))
                        printf(format, displaypath);
  
 +              submodule_unset_core_worktree(sub);
 +
                strbuf_release(&sb_rm);
        }
  
@@@ -1268,20 -1266,19 +1269,20 @@@ struct submodule_alternate_setup 
        SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL }
  
  static int add_possible_reference_from_superproject(
 -              struct alternate_object_database *alt, void *sas_cb)
 +              struct object_directory *odb, void *sas_cb)
  {
        struct submodule_alternate_setup *sas = sas_cb;
 +      size_t len;
  
        /*
         * If the alternate object store is another repository, try the
         * standard layout with .git/(modules/<name>)+/objects
         */
 -      if (ends_with(alt->path, "/objects")) {
 +      if (strip_suffix(odb->path, "/objects", &len)) {
                char *sm_alternate;
                struct strbuf sb = STRBUF_INIT;
                struct strbuf err = STRBUF_INIT;
 -              strbuf_add(&sb, alt->path, strlen(alt->path) - strlen("objects"));
 +              strbuf_add(&sb, odb->path, len);
  
                /*
                 * We need to end the new path with '/' to mark it as a dir,
                 * as the last part of a missing submodule reference would
                 * be taken as a file name.
                 */
 -              strbuf_addf(&sb, "modules/%s/", sas->submodule_name);
 +              strbuf_addf(&sb, "/modules/%s/", sas->submodule_name);
  
                sm_alternate = compute_alternate_path(sb.buf, &err);
                if (sm_alternate) {
@@@ -1358,7 -1355,7 +1359,7 @@@ static int module_clone(int argc, cons
        char *p, *path = NULL, *sm_gitdir;
        struct strbuf sb = STRBUF_INIT;
        struct string_list reference = STRING_LIST_INIT_NODUP;
-       int dissociate = 0;
+       int dissociate = 0, require_init = 0;
        char *sm_alternate = NULL, *error_strategy = NULL;
  
        struct option module_clone_options[] = {
                OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
                OPT_BOOL(0, "progress", &progress,
                           N_("force cloning progress")),
+               OPT_BOOL(0, "require-init", &require_init,
+                          N_("disallow cloning into non-empty directory")),
                OPT_END()
        };
  
        } else
                path = xstrdup(path);
  
+       if (validate_submodule_git_dir(sm_gitdir, name) < 0)
+               die(_("refusing to create/use '%s' in another submodule's "
+                       "git dir"), sm_gitdir);
        if (!file_exists(sm_gitdir)) {
                if (safe_create_leading_directories_const(sm_gitdir) < 0)
                        die(_("could not create directory '%s'"), sm_gitdir);
                        die(_("clone of '%s' into submodule path '%s' failed"),
                            url, path);
        } else {
+               if (require_init && !access(path, X_OK) && !is_empty_dir(path))
+                       die(_("directory not empty: '%s'"), path);
                if (safe_create_leading_directories_const(path) < 0)
                        die(_("could not create directory '%s'"), path);
                strbuf_addf(&sb, "%s/index", sm_gitdir);
@@@ -1477,6 -1482,8 +1486,8 @@@ static void determine_submodule_update_
                        die(_("Invalid update mode '%s' configured for submodule path '%s'"),
                                val, path);
        } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) {
+               if (sub->update_strategy.type == SM_UPDATE_COMMAND)
+                       BUG("how did we read update = !command from .gitmodules?");
                out->type = sub->update_strategy.type;
                out->command = sub->update_strategy.command;
        } else
@@@ -1535,6 -1542,7 +1546,7 @@@ struct submodule_update_clone 
        int recommend_shallow;
        struct string_list references;
        int dissociate;
+       unsigned require_init;
        const char *depth;
        const char *recursive_prefix;
        const char *prefix;
        int max_jobs;
  };
  #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
-       SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
+       SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, 0, \
        NULL, NULL, NULL, \
 -      NULL, 0, 0, 0, NULL, 0, 0, 0}
 +      NULL, 0, 0, 0, NULL, 0, 0, 1}
  
  
  static void next_submodule_warn_missing(struct submodule_update_clone *suc,
@@@ -1680,6 -1688,8 +1692,8 @@@ static int prepare_to_clone_next_submod
                argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
        if (suc->recommend_shallow && sub->recommend_shallow == 1)
                argv_array_push(&child->args, "--depth=1");
+       if (suc->require_init)
+               argv_array_push(&child->args, "--require-init");
        argv_array_pushl(&child->args, "--path", sub->path, NULL);
        argv_array_pushl(&child->args, "--name", sub->name, NULL);
        argv_array_pushl(&child->args, "--url", url, NULL);
@@@ -1870,6 -1880,8 +1884,8 @@@ static int update_clone(int argc, cons
                OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
                OPT_BOOL(0, "progress", &suc.progress,
                            N_("force cloning progress")),
+               OPT_BOOL(0, "require-init", &suc.require_init,
+                          N_("disallow cloning into non-empty directory")),
                OPT_END()
        };
  
@@@ -2049,7 -2061,7 +2065,7 @@@ static int ensure_core_worktree(int arg
        struct repository subrepo;
  
        if (argc != 2)
 -              BUG("submodule--helper connect-gitdir-workingtree <name> <path>");
 +              BUG("submodule--helper ensure-core-worktree <path>");
  
        path = argv[1];
  
        if (!sub)
                BUG("We could get the submodule handle before?");
  
 -      if (repo_submodule_init(&subrepo, the_repository, path))
 +      if (repo_submodule_init(&subrepo, the_repository, sub))
                die(_("could not get a repository handle for submodule '%s'"), path);
  
        if (!repo_config_get_string(&subrepo, "core.worktree", &cw)) {
diff --combined compat/mingw.c
index 8141f77189c66b07a1e1cd5ab92418d0de4617e5,a010e0b69d36f51787c07a55ef35096adb81bf6f..0e14cab0126559217b07a049ba11d22621c83877
@@@ -7,7 -7,6 +7,7 @@@
  #include "../cache.h"
  #include "win32/lazyload.h"
  #include "../config.h"
 +#include "dir.h"
  
  #define HCAST(type, handle) ((type)(intptr_t)handle)
  
@@@ -351,7 -350,7 +351,7 @@@ static inline int needs_hiding(const ch
                return 0;
  
        /* We cannot use basename(), as it would remove trailing slashes */
 -      mingw_skip_dos_drive_prefix((char **)&path);
 +      win32_skip_dos_drive_prefix((char **)&path);
        if (!*path)
                return 0;
  
@@@ -390,6 -389,12 +390,12 @@@ int mingw_mkdir(const char *path, int m
  {
        int ret;
        wchar_t wpath[MAX_PATH];
+       if (!is_valid_win32_path(path)) {
+               errno = EINVAL;
+               return -1;
+       }
        if (xutftowcs_path(wpath, path) < 0)
                return -1;
        ret = _wmkdir(wpath);
@@@ -463,7 -468,7 +469,7 @@@ int mingw_open (const char *filename, i
        typedef int (*open_fn_t)(wchar_t const *wfilename, int oflags, ...);
        va_list args;
        unsigned mode;
-       int fd;
+       int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL);
        wchar_t wfilename[MAX_PATH];
        open_fn_t open_fn;
  
        mode = va_arg(args, int);
        va_end(args);
  
+       if (!is_valid_win32_path(filename)) {
+               errno = create ? EINVAL : ENOENT;
+               return -1;
+       }
        if (filename && !strcmp(filename, "/dev/null"))
                filename = "nul";
  
@@@ -537,6 -547,11 +548,11 @@@ FILE *mingw_fopen (const char *filename
        int hide = needs_hiding(filename);
        FILE *file;
        wchar_t wfilename[MAX_PATH], wotype[4];
+       if (!is_valid_win32_path(filename)) {
+               int create = otype && strchr(otype, 'w');
+               errno = create ? EINVAL : ENOENT;
+               return NULL;
+       }
        if (filename && !strcmp(filename, "/dev/null"))
                filename = "nul";
        if (xutftowcs_path(wfilename, filename) < 0 ||
@@@ -559,6 -574,11 +575,11 @@@ FILE *mingw_freopen (const char *filena
        int hide = needs_hiding(filename);
        FILE *file;
        wchar_t wfilename[MAX_PATH], wotype[4];
+       if (!is_valid_win32_path(filename)) {
+               int create = otype && strchr(otype, 'w');
+               errno = create ? EINVAL : ENOENT;
+               return NULL;
+       }
        if (filename && !strcmp(filename, "/dev/null"))
                filename = "nul";
        if (xutftowcs_path(wfilename, filename) < 0 ||
@@@ -1032,7 -1052,7 +1053,7 @@@ char *mingw_getcwd(char *pointer, int l
   * See "Parsing C++ Command-Line Arguments" at Microsoft's Docs:
   * https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
   */
 -static const char *quote_arg(const char *arg)
 +static const char *quote_arg_msvc(const char *arg)
  {
        /* count chars to quote */
        int len = 0, n = 0;
                                p++;
                                len++;
                        }
-                       if (*p == '"')
+                       if (*p == '"' || !*p)
                                n += count*2 + 1;
                        continue;
                }
                                count++;
                                *d++ = *arg++;
                        }
-                       if (*arg == '"') {
+                       if (*arg == '"' || !*arg) {
                                while (count-- > 0)
                                        *d++ = '\\';
+                               /* don't escape the surrounding end quote */
+                               if (!*arg)
+                                       break;
                                *d++ = '\\';
                        }
                }
                *d++ = *arg++;
        }
        *d++ = '"';
-       *d++ = 0;
+       *d++ = '\0';
        return q;
  }
  
 +#include "quote.h"
 +
 +static const char *quote_arg_msys2(const char *arg)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      const char *p2 = arg, *p;
 +
 +      for (p = arg; *p; p++) {
 +              int ws = isspace(*p);
 +              if (!ws && *p != '\\' && *p != '"' && *p != '{')
 +                      continue;
 +              if (!buf.len)
 +                      strbuf_addch(&buf, '"');
 +              if (p != p2)
 +                      strbuf_add(&buf, p2, p - p2);
 +              if (!ws && *p != '{')
 +                      strbuf_addch(&buf, '\\');
 +              p2 = p;
 +      }
 +
 +      if (p == arg)
 +              strbuf_addch(&buf, '"');
 +      else if (!buf.len)
 +              return arg;
 +      else
 +              strbuf_add(&buf, p2, p - p2),
 +
 +      strbuf_addch(&buf, '"');
 +      return strbuf_detach(&buf, 0);
 +}
 +
  static const char *parse_interpreter(const char *cmd)
  {
        static char buf[100];
@@@ -1349,47 -1341,6 +1373,47 @@@ struct pinfo_t 
  static struct pinfo_t *pinfo = NULL;
  CRITICAL_SECTION pinfo_cs;
  
 +/* Used to match and chomp off path components */
 +static inline int match_last_path_component(const char *path, size_t *len,
 +                                          const char *component)
 +{
 +      size_t component_len = strlen(component);
 +      if (*len < component_len + 1 ||
 +          !is_dir_sep(path[*len - component_len - 1]) ||
 +          fspathncmp(path + *len - component_len, component, component_len))
 +              return 0;
 +      *len -= component_len + 1;
 +      /* chomp off repeated dir separators */
 +      while (*len > 0 && is_dir_sep(path[*len - 1]))
 +              (*len)--;
 +      return 1;
 +}
 +
 +static int is_msys2_sh(const char *cmd)
 +{
 +      if (cmd && !strcmp(cmd, "sh")) {
 +              static int ret = -1;
 +              char *p;
 +
 +              if (ret >= 0)
 +                      return ret;
 +
 +              p = path_lookup(cmd, 0);
 +              if (!p)
 +                      ret = 0;
 +              else {
 +                      size_t len = strlen(p);
 +
 +                      ret = match_last_path_component(p, &len, "sh.exe") &&
 +                              match_last_path_component(p, &len, "bin") &&
 +                              match_last_path_component(p, &len, "usr");
 +                      free(p);
 +              }
 +              return ret;
 +      }
 +      return 0;
 +}
 +
  static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaenv,
                              const char *dir,
                              int prepend_cmd, int fhin, int fhout, int fherr)
        unsigned flags = CREATE_UNICODE_ENVIRONMENT;
        BOOL ret;
        HANDLE cons;
 +      const char *(*quote_arg)(const char *arg) =
 +              is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc;
  
        do_unset_environment_variables();
  
@@@ -1632,7 -1581,7 +1656,7 @@@ int mingw_kill(pid_t pid, int sig
   */
  char *mingw_getenv(const char *name)
  {
 -#define GETENV_MAX_RETAIN 30
 +#define GETENV_MAX_RETAIN 64
        static char *values[GETENV_MAX_RETAIN];
        static int value_counter;
        int len_key, len_value;
@@@ -2175,7 -2124,7 +2199,7 @@@ static void stop_timer_thread(void
        if (timer_event)
                SetEvent(timer_event);  /* tell thread to terminate */
        if (timer_thread) {
 -              int rc = WaitForSingleObject(timer_thread, 1000);
 +              int rc = WaitForSingleObject(timer_thread, 10000);
                if (rc == WAIT_TIMEOUT)
                        error("timer thread did not terminate timely");
                else if (rc != WAIT_OBJECT_0)
@@@ -2350,6 -2299,57 +2374,6 @@@ pid_t waitpid(pid_t pid, int *status, i
        return -1;
  }
  
 -int mingw_has_dos_drive_prefix(const char *path)
 -{
 -      int i;
 -
 -      /*
 -       * Does it start with an ASCII letter (i.e. highest bit not set),
 -       * followed by a colon?
 -       */
 -      if (!(0x80 & (unsigned char)*path))
 -              return *path && path[1] == ':' ? 2 : 0;
 -
 -      /*
 -       * While drive letters must be letters of the English alphabet, it is
 -       * possible to assign virtually _any_ Unicode character via `subst` as
 -       * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
 -       * like this:
 -       *
 -       *      subst ֍: %USERPROFILE%\Desktop
 -       */
 -      for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
 -              ; /* skip first UTF-8 character */
 -      return path[i] == ':' ? i + 1 : 0;
 -}
 -
 -int mingw_skip_dos_drive_prefix(char **path)
 -{
 -      int ret = has_dos_drive_prefix(*path);
 -      *path += ret;
 -      return ret;
 -}
 -
 -int mingw_offset_1st_component(const char *path)
 -{
 -      char *pos = (char *)path;
 -
 -      /* unc paths */
 -      if (!skip_dos_drive_prefix(&pos) &&
 -                      is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
 -              /* skip server name */
 -              pos = strpbrk(pos + 2, "\\/");
 -              if (!pos)
 -                      return 0; /* Error: malformed unc path */
 -
 -              do {
 -                      pos++;
 -              } while (*pos && !is_dir_sep(*pos));
 -      }
 -
 -      return pos + is_dir_sep(*pos) - path;
 -}
 -
  int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
  {
        int upos = 0, wpos = 0;
@@@ -2464,6 -2464,50 +2488,50 @@@ static void setup_windows_environment(v
                setenv("TERM", "cygwin", 1);
  }
  
+ int is_valid_win32_path(const char *path)
+ {
+       int preceding_space_or_period = 0, i = 0, periods = 0;
+       if (!protect_ntfs)
+               return 1;
+       skip_dos_drive_prefix((char **)&path);
+       for (;;) {
+               char c = *(path++);
+               switch (c) {
+               case '\0':
+               case '/': case '\\':
+                       /* cannot end in ` ` or `.`, except for `.` and `..` */
+                       if (preceding_space_or_period &&
+                           (i != periods || periods > 2))
+                               return 0;
+                       if (!c)
+                               return 1;
+                       i = periods = preceding_space_or_period = 0;
+                       continue;
+               case '.':
+                       periods++;
+                       /* fallthru */
+               case ' ':
+                       preceding_space_or_period = 1;
+                       i++;
+                       continue;
+               case ':': /* DOS drive prefix was already skipped */
+               case '<': case '>': case '"': case '|': case '?': case '*':
+                       /* illegal character */
+                       return 0;
+               default:
+                       if (c > '\0' && c < '\x20')
+                               /* illegal character */
+                               return 0;
+               }
+               preceding_space_or_period = 0;
+               i++;
+       }
+ }
  /*
   * Disable MSVCRT command line wildcard expansion (__getmainargs called from
   * mingw startup code, see init.c in mingw runtime).
diff --combined compat/mingw.h
index 30d9fb3e36274657e5d2a63ef2f5eb3e1c55ce61,163ae1b59e2a1d444daa0087174f9a347b47e6b0..ec9fc1ca80387020122a4a9dc0816092bdae4e04
@@@ -443,12 -443,32 +443,12 @@@ HANDLE winansi_get_osfhandle(int fd)
   * git specific compatibility
   */
  
 -int mingw_has_dos_drive_prefix(const char *path);
 -#define has_dos_drive_prefix mingw_has_dos_drive_prefix
 -int mingw_skip_dos_drive_prefix(char **path);
 -#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 -static inline int mingw_is_dir_sep(int c)
 -{
 -      return c == '/' || c == '\\';
 -}
 -#define is_dir_sep mingw_is_dir_sep
 -static inline char *mingw_find_last_dir_sep(const char *path)
 -{
 -      char *ret = NULL;
 -      for (; *path; ++path)
 -              if (is_dir_sep(*path))
 -                      ret = (char *)path;
 -      return ret;
 -}
  static inline void convert_slashes(char *path)
  {
        for (; *path; path++)
                if (*path == '\\')
                        *path = '/';
  }
 -#define find_last_dir_sep mingw_find_last_dir_sep
 -int mingw_offset_1st_component(const char *path);
 -#define offset_1st_component mingw_offset_1st_component
  #define PATH_SEP ';'
  extern char *mingw_query_user_email(void);
  #define query_user_email mingw_query_user_email
  #include <inttypes.h>
  #endif
  
+ /**
+  * Verifies that the given path is a valid one on Windows.
+  *
+  * In particular, path segments are disallowed which
+  *
+  * - end in a period or a space (except the special directories `.` and `..`).
+  *
+  * - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
+  *
+  * Returns 1 upon success, otherwise 0.
+  */
+ int is_valid_win32_path(const char *path);
+ #define is_valid_path(path) is_valid_win32_path(path)
  /**
   * Converts UTF-8 encoded string to UTF-16LE.
   *
index d9d3641de8574506ca1aa9fc00cf5012631a03b6,0000000000000000000000000000000000000000..ebf2f12eb666910eede5a7e04ee2ab5b0dddb0f8
mode 100644,000000..100644
--- /dev/null
@@@ -1,28 -1,0 +1,52 @@@
 +#include "../../git-compat-util.h"
 +
++int win32_has_dos_drive_prefix(const char *path)
++{
++      int i;
++
++      /*
++       * Does it start with an ASCII letter (i.e. highest bit not set),
++       * followed by a colon?
++       */
++      if (!(0x80 & (unsigned char)*path))
++              return *path && path[1] == ':' ? 2 : 0;
++
++      /*
++       * While drive letters must be letters of the English alphabet, it is
++       * possible to assign virtually _any_ Unicode character via `subst` as
++       * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
++       * like this:
++       *
++       *      subst ֍: %USERPROFILE%\Desktop
++       */
++      for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
++              ; /* skip first UTF-8 character */
++      return path[i] == ':' ? i + 1 : 0;
++}
++
 +int win32_skip_dos_drive_prefix(char **path)
 +{
 +      int ret = has_dos_drive_prefix(*path);
 +      *path += ret;
 +      return ret;
 +}
 +
 +int win32_offset_1st_component(const char *path)
 +{
 +      char *pos = (char *)path;
 +
 +      /* unc paths */
 +      if (!skip_dos_drive_prefix(&pos) &&
 +                      is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
 +              /* skip server name */
 +              pos = strpbrk(pos + 2, "\\/");
 +              if (!pos)
 +                      return 0; /* Error: malformed unc path */
 +
 +              do {
 +                      pos++;
 +              } while (*pos && !is_dir_sep(*pos));
 +      }
 +
 +      return pos + is_dir_sep(*pos) - path;
 +}
index 0f70d439204fbe1336dcea51c43a38498e69de96,0000000000000000000000000000000000000000..3403681458b9db1631a962021cb5ad57845192d4
mode 100644,000000..100644
--- /dev/null
@@@ -1,20 -1,0 +1,21 @@@
- #define has_dos_drive_prefix(path) \
-       (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
++int win32_has_dos_drive_prefix(const char *path);
++#define has_dos_drive_prefix win32_has_dos_drive_prefix
++
 +int win32_skip_dos_drive_prefix(char **path);
 +#define skip_dos_drive_prefix win32_skip_dos_drive_prefix
 +static inline int win32_is_dir_sep(int c)
 +{
 +      return c == '/' || c == '\\';
 +}
 +#define is_dir_sep win32_is_dir_sep
 +static inline char *win32_find_last_dir_sep(const char *path)
 +{
 +      char *ret = NULL;
 +      for (; *path; ++path)
 +              if (is_dir_sep(*path))
 +                      ret = (char *)path;
 +      return ret;
 +}
 +#define find_last_dir_sep win32_find_last_dir_sep
 +int win32_offset_1st_component(const char *path);
 +#define offset_1st_component win32_offset_1st_component
diff --combined config.mak.uname
index b37fa8424c88cae23abf02f8276e8b4f6ffef19a,85af9f9cf1081335b786ddab7907cebb37ceb66d..76d6c0043a4853fa3300f32f3fe0da4fe7a9024d
@@@ -187,7 -187,7 +187,7 @@@ ifeq ($(uname_O),Cygwin
        UNRELIABLE_FSTAT = UnfortunatelyYes
        OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
        MMAP_PREVENTS_DELETE = UnfortunatelyYes
 -      COMPAT_OBJS += compat/cygwin.o
 +      COMPAT_OBJS += compat/win32/path-utils.o
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
  endif
  ifeq ($(uname_S),FreeBSD)
        HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
        PAGER_ENV = LESS=FRX LV=-c MORE=FRX
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
 +      FILENO_IS_A_MACRO = UnfortunatelyYes
  endif
  ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
        HAVE_BSD_SYSCTL = YesPlease
        HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
        PROCFS_EXECUTABLE_PATH = /proc/curproc/file
 +      FREAD_READS_DIRECTORIES = UnfortunatelyYes
 +      FILENO_IS_A_MACRO = UnfortunatelyYes
  endif
  ifeq ($(uname_S),MirBSD)
        NO_STRCASESTR = YesPlease
@@@ -399,7 -396,6 +399,6 @@@ ifeq ($(uname_S),Windows
        EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj
        PTHREAD_LIBS =
        lib =
-       BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
  ifndef DEBUG
        BASIC_CFLAGS += -GL -Os -MD
        BASIC_LDFLAGS += -LTCG
@@@ -444,43 -440,26 +443,43 @@@ ifeq ($(uname_S),NONSTOP_KERNEL
        # INLINE='' would just replace one set of warnings with another and
        # still not compile in c89 mode, due to non-const array initializations.
        CC = cc -c99
 +      # Build down-rev compatible objects that don't use our new getopt_long.
 +      ifeq ($(uname_R).$(uname_V),J06.21)
 +              CC += -WRVU=J06.20
 +      endif
 +      ifeq ($(uname_R).$(uname_V),L17.02)
 +              CC += -WRVU=L16.05
 +      endif
        # Disable all optimization, seems to result in bad code, with -O or -O2
        # or even -O1 (default), /usr/local/libexec/git-core/git-pack-objects
        # abends on "git push". Needs more investigation.
 -      CFLAGS = -g -O0
 +      CFLAGS = -g -O0 -Winline
        # We'd want it to be here.
        prefix = /usr/local
 -      # Our's are in ${prefix}/bin (perl might also be in /usr/bin/perl).
 -      PERL_PATH = ${prefix}/bin/perl
 -      PYTHON_PATH = ${prefix}/bin/python
 -
 +      # perl and python must be in /usr/bin on NonStop - supplied by HPE
 +      # with operating system in that managed directory.
 +      PERL_PATH = /usr/bin/perl
 +      PYTHON_PATH = /usr/bin/python
 +      # The current /usr/coreutils/rm at lowest support level does not work
 +      # with the git test structure. Long paths as in
 +      # 'trash directory...' cause rm to terminate prematurely without fully
 +      # removing the directory at OS releases J06.21 and L17.02.
 +      # Default to the older rm until those two releases are deprecated.
 +      RM = /bin/rm -f
        # As detected by './configure'.
        # Missdetected, hence commented out, see below.
        #NO_CURL = YesPlease
        # Added manually, see above.
 +      NEEDS_SSL_WITH_CURL = YesPlease
 +      NEEDS_CRYPTO_WITH_SSL = YesPlease
 +      HAVE_DEV_TTY = YesPlease
        HAVE_LIBCHARSET_H = YesPlease
        HAVE_STRINGS_H = YesPlease
        NEEDS_LIBICONV = YesPlease
        NEEDS_LIBINTL_BEFORE_LIBICONV = YesPlease
        NO_SYS_SELECT_H = UnfortunatelyYes
        NO_D_TYPE_IN_DIRENT = YesPlease
 +      NO_GETTEXT = YesPlease
        NO_HSTRERROR = YesPlease
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
        NO_MKDTEMP = YesPlease
        # Currently libiconv-1.9.1.
        OLD_ICONV = UnfortunatelyYes
 -      NO_REGEX = YesPlease
 +      NO_REGEX = NeedsStartEnd
        NO_PTHREADS = UnfortunatelyYes
 +      FREAD_READS_DIRECTORIES = UnfortunatelyYes
  
        # Not detected (nor checked for) by './configure'.
        # We don't have SA_RESTART on NonStop, unfortunalety.
        # RFE 10-120912-4693 submitted to HP NonStop development.
        NO_SETITIMER = UnfortunatelyYes
        SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin
 -      SHELL_PATH = /usr/local/bin/bash
 -      # as of H06.25/J06.14, we might better use this
 -      #SHELL_PATH = /usr/coreutils/bin/bash
 +      SHELL_PATH = /usr/coreutils/bin/bash
  endif
  ifneq (,$(findstring MINGW,$(uname_S)))
        pathsep = ;
        COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
 +              compat/win32/path-utils.o \
                compat/win32/pthread.o compat/win32/syslog.o \
                compat/win32/dirent.o
-       BASIC_CFLAGS += -DWIN32 -DPROTECT_NTFS_DEFAULT=1
+       BASIC_CFLAGS += -DWIN32
        EXTLIBS += -lws2_32
        GITLIBS += git.res
        PTHREAD_LIBS =
        RC = windres -O coff
        NATIVE_CRLF = YesPlease
        X = .exe
 -      SPARSE_FLAGS = -Wno-one-bit-signed-bitfield
  ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
        htmldir = doc/git/html/
        prefix =
@@@ -572,11 -552,9 +571,11 @@@ els
                prefix = /usr/
                ifeq (MINGW32,$(MSYSTEM))
                        prefix = /mingw32
 +                      HOST_CPU = i686
                endif
                ifeq (MINGW64,$(MSYSTEM))
                        prefix = /mingw64
 +                      HOST_CPU = x86_64
                else
                        COMPAT_CFLAGS += -D_USE_32BIT_TIME_T
                        BASIC_LDFLAGS += -Wl,--large-address-aware
diff --combined connect.c
index 4813f005ab05279a72ef2894cfae8887965d9640,79f1b3b24257a100cfd338eebdb465e0069e0953..0a905d5fecb7d93b724369c3e88b02f21de73d2a
+++ b/connect.c
@@@ -296,6 -296,7 +296,6 @@@ struct ref **get_remote_heads(struct pa
        struct ref **orig_list = list;
        int len = 0;
        enum get_remote_heads_state state = EXPECTING_FIRST_REF;
 -      const char *arg;
  
        *list = NULL;
  
                        die_initial_contact(1);
                case PACKET_READ_NORMAL:
                        len = reader->pktlen;
 -                      if (len > 4 && skip_prefix(reader->line, "ERR ", &arg))
 -                              die(_("remote error: %s"), arg);
                        break;
                case PACKET_READ_FLUSH:
                        state = EXPECTING_DONE;
@@@ -511,7 -514,7 +511,7 @@@ int url_is_local_not_ssh(const char *ur
        const char *colon = strchr(url, ':');
        const char *slash = strchr(url, '/');
        return !colon || (slash && slash < colon) ||
-               has_dos_drive_prefix(url);
+               (has_dos_drive_prefix(url) && is_valid_path(url));
  }
  
  static const char *prot_name(enum protocol protocol)
diff --combined environment.c
index 89af47cb8504903b90460d5faa91be9f61bd2fc3,c7b76d3bbb1143f870685ac13ea70a0d333c6121..9f5f3811dbeefe64864f54779903376c52857fef
@@@ -80,7 -80,7 +80,7 @@@ enum log_refs_config log_all_ref_update
  int protect_hfs = PROTECT_HFS_DEFAULT;
  
  #ifndef PROTECT_NTFS_DEFAULT
- #define PROTECT_NTFS_DEFAULT 0
+ #define PROTECT_NTFS_DEFAULT 1
  #endif
  int protect_ntfs = PROTECT_NTFS_DEFAULT;
  const char *core_fsmonitor;
@@@ -107,7 -107,7 +107,7 @@@ char *git_work_tree_cfg
  
  static char *git_namespace;
  
 -static const char *super_prefix;
 +static char *super_prefix;
  
  /*
   * Repository-local GIT_* environment variables; see cache.h for details.
@@@ -240,7 -240,7 +240,7 @@@ const char *get_super_prefix(void
  {
        static int initialized;
        if (!initialized) {
 -              super_prefix = getenv(GIT_SUPER_PREFIX_ENVIRONMENT);
 +              super_prefix = xstrdup_or_null(getenv(GIT_SUPER_PREFIX_ENVIRONMENT));
                initialized = 1;
        }
        return super_prefix;
@@@ -274,9 -274,9 +274,9 @@@ const char *get_git_work_tree(void
  
  char *get_object_directory(void)
  {
 -      if (!the_repository->objects->objectdir)
 +      if (!the_repository->objects->odb)
                BUG("git environment hasn't been setup");
 -      return the_repository->objects->objectdir;
 +      return the_repository->objects->odb->path;
  }
  
  int odb_mkstemp(struct strbuf *temp_filename, const char *pattern)
diff --combined fast-import.c
index b7ba755c2b88df35d5e50272d7b9341134625c56,c8b372bc4abbfdd6b3b3e31cc12bfe2ac0d9e838..a84354524c1690b73302dff3e30bfe85a7e7ebc3
@@@ -1,3 -1,157 +1,3 @@@
 -/*
 -(See Documentation/git-fast-import.txt for maintained documentation.)
 -Format of STDIN stream:
 -
 -  stream ::= cmd*;
 -
 -  cmd ::= new_blob
 -        | new_commit
 -        | new_tag
 -        | reset_branch
 -        | checkpoint
 -        | progress
 -        ;
 -
 -  new_blob ::= 'blob' lf
 -    mark?
 -    file_content;
 -  file_content ::= data;
 -
 -  new_commit ::= 'commit' sp ref_str lf
 -    mark?
 -    ('author' (sp name)? sp '<' email '>' sp when lf)?
 -    'committer' (sp name)? sp '<' email '>' sp when lf
 -    commit_msg
 -    ('from' sp commit-ish lf)?
 -    ('merge' sp commit-ish lf)*
 -    (file_change | ls)*
 -    lf?;
 -  commit_msg ::= data;
 -
 -  ls ::= 'ls' sp '"' quoted(path) '"' lf;
 -
 -  file_change ::= file_clr
 -    | file_del
 -    | file_rnm
 -    | file_cpy
 -    | file_obm
 -    | file_inm;
 -  file_clr ::= 'deleteall' lf;
 -  file_del ::= 'D' sp path_str lf;
 -  file_rnm ::= 'R' sp path_str sp path_str lf;
 -  file_cpy ::= 'C' sp path_str sp path_str lf;
 -  file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
 -  file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
 -    data;
 -  note_obm ::= 'N' sp (hexsha1 | idnum) sp commit-ish lf;
 -  note_inm ::= 'N' sp 'inline' sp commit-ish lf
 -    data;
 -
 -  new_tag ::= 'tag' sp tag_str lf
 -    'from' sp commit-ish lf
 -    ('tagger' (sp name)? sp '<' email '>' sp when lf)?
 -    tag_msg;
 -  tag_msg ::= data;
 -
 -  reset_branch ::= 'reset' sp ref_str lf
 -    ('from' sp commit-ish lf)?
 -    lf?;
 -
 -  checkpoint ::= 'checkpoint' lf
 -    lf?;
 -
 -  progress ::= 'progress' sp not_lf* lf
 -    lf?;
 -
 -     # note: the first idnum in a stream should be 1 and subsequent
 -     # idnums should not have gaps between values as this will cause
 -     # the stream parser to reserve space for the gapped values.  An
 -     # idnum can be updated in the future to a new object by issuing
 -     # a new mark directive with the old idnum.
 -     #
 -  mark ::= 'mark' sp idnum lf;
 -  data ::= (delimited_data | exact_data)
 -    lf?;
 -
 -    # note: delim may be any string but must not contain lf.
 -    # data_line may contain any data but must not be exactly
 -    # delim.
 -  delimited_data ::= 'data' sp '<<' delim lf
 -    (data_line lf)*
 -    delim lf;
 -
 -     # note: declen indicates the length of binary_data in bytes.
 -     # declen does not include the lf preceding the binary data.
 -     #
 -  exact_data ::= 'data' sp declen lf
 -    binary_data;
 -
 -     # note: quoted strings are C-style quoting supporting \c for
 -     # common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn
 -     # is the signed byte value in octal.  Note that the only
 -     # characters which must actually be escaped to protect the
 -     # stream formatting is: \, " and LF.  Otherwise these values
 -     # are UTF8.
 -     #
 -  commit-ish  ::= (ref_str | hexsha1 | sha1exp_str | idnum);
 -  ref_str     ::= ref;
 -  sha1exp_str ::= sha1exp;
 -  tag_str     ::= tag;
 -  path_str    ::= path    | '"' quoted(path)    '"' ;
 -  mode        ::= '100644' | '644'
 -                | '100755' | '755'
 -                | '120000'
 -                ;
 -
 -  declen ::= # unsigned 32 bit value, ascii base10 notation;
 -  bigint ::= # unsigned integer value, ascii base10 notation;
 -  binary_data ::= # file content, not interpreted;
 -
 -  when         ::= raw_when | rfc2822_when;
 -  raw_when     ::= ts sp tz;
 -  rfc2822_when ::= # Valid RFC 2822 date and time;
 -
 -  sp ::= # ASCII space character;
 -  lf ::= # ASCII newline (LF) character;
 -
 -     # note: a colon (':') must precede the numerical value assigned to
 -     # an idnum.  This is to distinguish it from a ref or tag name as
 -     # GIT does not permit ':' in ref or tag strings.
 -     #
 -  idnum   ::= ':' bigint;
 -  path    ::= # GIT style file path, e.g. "a/b/c";
 -  ref     ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT";
 -  tag     ::= # GIT tag name, e.g. "FIREFOX_1_5";
 -  sha1exp ::= # Any valid GIT SHA1 expression;
 -  hexsha1 ::= # SHA1 in hexadecimal format;
 -
 -     # note: name and email are UTF8 strings, however name must not
 -     # contain '<' or lf and email must not contain any of the
 -     # following: '<', '>', lf.
 -     #
 -  name  ::= # valid GIT author/committer name;
 -  email ::= # valid GIT author/committer email;
 -  ts    ::= # time since the epoch in seconds, ascii base10 notation;
 -  tz    ::= # GIT style timezone;
 -
 -     # note: comments, get-mark, ls-tree, and cat-blob requests may
 -     # appear anywhere in the input, except within a data command. Any
 -     # form of the data command always escapes the related input from
 -     # comment processing.
 -     #
 -     # In case it is not clear, the '#' that starts the comment
 -     # must be the first character on that line (an lf
 -     # preceded it).
 -     #
 -
 -  get_mark ::= 'get-mark' sp idnum lf;
 -  cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
 -  ls_tree  ::= 'ls' sp (hexsha1 | idnum) sp path_str lf;
 -
 -  comment ::= '#' not_lf* lf;
 -  not_lf  ::= # Any byte that is not ASCII newline (LF);
 -*/
 -
  #include "builtin.h"
  #include "cache.h"
  #include "repository.h"
@@@ -210,6 -364,7 +210,7 @@@ static uintmax_t next_mark
  static struct strbuf new_data = STRBUF_INIT;
  static int seen_data_command;
  static int require_explicit_termination;
+ static int allow_unsafe_features;
  
  /* Signal handling */
  static volatile sig_atomic_t checkpoint_requested;
@@@ -1672,6 -1827,12 +1673,12 @@@ static void dump_marks(void
        if (!export_marks_file || (import_marks_file && !import_marks_file_done))
                return;
  
+       if (safe_create_leading_directories_const(export_marks_file)) {
+               failure |= error_errno("unable to create leading directories of %s",
+                                      export_marks_file);
+               return;
+       }
        if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) {
                failure |= error_errno("Unable to write marks file %s",
                                       export_marks_file);
@@@ -1814,13 -1975,6 +1821,13 @@@ static void parse_mark(void
                next_mark = 0;
  }
  
 +static void parse_original_identifier(void)
 +{
 +      const char *v;
 +      if (skip_prefix(command_buf.buf, "original-oid ", &v))
 +              read_next_command();
 +}
 +
  static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
  {
        const char *data;
@@@ -1963,7 -2117,6 +1970,7 @@@ static void parse_new_blob(void
  {
        read_next_command();
        parse_mark();
 +      parse_original_identifier();
        parse_and_store_blob(&last_blob, NULL, next_mark);
  }
  
@@@ -2587,7 -2740,6 +2594,7 @@@ static void parse_new_commit(const cha
  
        read_next_command();
        parse_mark();
 +      parse_original_identifier();
        if (skip_prefix(command_buf.buf, "author ", &v)) {
                author = parse_ident(v);
                read_next_command();
@@@ -2720,9 -2872,6 +2727,9 @@@ static void parse_new_tag(const char *a
                die("Invalid ref name or SHA1 expression: %s", from);
        read_next_command();
  
 +      /* original-oid ... */
 +      parse_original_identifier();
 +
        /* tagger ... */
        if (skip_prefix(command_buf.buf, "tagger ", &v)) {
                tagger = parse_ident(v);
@@@ -3056,7 -3205,6 +3063,6 @@@ static void option_import_marks(const c
        }
  
        import_marks_file = make_fast_import_path(marks);
-       safe_create_leading_directories_const(import_marks_file);
        import_marks_file_from_stream = from_stream;
        import_marks_file_ignore_missing = ignore_missing;
  }
@@@ -3097,7 -3245,6 +3103,6 @@@ static void option_active_branches(cons
  static void option_export_marks(const char *marks)
  {
        export_marks_file = make_fast_import_path(marks);
-       safe_create_leading_directories_const(export_marks_file);
  }
  
  static void option_cat_blob_fd(const char *fd)
@@@ -3140,10 -3287,12 +3145,12 @@@ static int parse_one_option(const char 
                option_active_branches(option);
        } else if (skip_prefix(option, "export-pack-edges=", &option)) {
                option_export_pack_edges(option);
-       } else if (starts_with(option, "quiet")) {
+       } else if (!strcmp(option, "quiet")) {
                show_stats = 0;
-       } else if (starts_with(option, "stats")) {
+       } else if (!strcmp(option, "stats")) {
                show_stats = 1;
+       } else if (!strcmp(option, "allow-unsafe-features")) {
+               ; /* already handled during early option parsing */
        } else {
                return 0;
        }
        return 1;
  }
  
+ static void check_unsafe_feature(const char *feature, int from_stream)
+ {
+       if (from_stream && !allow_unsafe_features)
+               die(_("feature '%s' forbidden in input without --allow-unsafe-features"),
+                   feature);
+ }
  static int parse_one_feature(const char *feature, int from_stream)
  {
        const char *arg;
        if (skip_prefix(feature, "date-format=", &arg)) {
                option_date_format(arg);
        } else if (skip_prefix(feature, "import-marks=", &arg)) {
+               check_unsafe_feature("import-marks", from_stream);
                option_import_marks(arg, from_stream, 0);
        } else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) {
+               check_unsafe_feature("import-marks-if-exists", from_stream);
                option_import_marks(arg, from_stream, 1);
        } else if (skip_prefix(feature, "export-marks=", &arg)) {
+               check_unsafe_feature(feature, from_stream);
                option_export_marks(arg);
        } else if (!strcmp(feature, "get-mark")) {
                ; /* Don't die - this feature is supported */
@@@ -3288,6 -3447,20 +3305,20 @@@ int cmd_main(int argc, const char **arg
        avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
        marks = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
  
+       /*
+        * We don't parse most options until after we've seen the set of
+        * "feature" lines at the start of the stream (which allows the command
+        * line to override stream data). But we must do an early parse of any
+        * command-line options that impact how we interpret the feature lines.
+        */
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (*arg != '-' || !strcmp(arg, "--"))
+                       break;
+               if (!strcmp(arg, "--allow-unsafe-features"))
+                       allow_unsafe_features = 1;
+       }
        global_argc = argc;
        global_argv = argv;
  
diff --combined fsck.c
index 2260adb71e7a9f1091d14bb635a4f082592b9512,535f806c67e72875e91021c8ce3dab6b8d783c26..35bfb7f90e99ad43ab16d287c7414944bd94f9d3
--- 1/fsck.c
--- 2/fsck.c
+++ b/fsck.c
@@@ -68,6 -68,7 +68,7 @@@ static struct oidset gitmodules_done = 
        FUNC(GITMODULES_SYMLINK, ERROR) \
        FUNC(GITMODULES_URL, ERROR) \
        FUNC(GITMODULES_PATH, ERROR) \
+       FUNC(GITMODULES_UPDATE, ERROR) \
        /* warnings */ \
        FUNC(BAD_FILEMODE, WARN) \
        FUNC(EMPTY_NAME, WARN) \
@@@ -410,14 -411,14 +411,14 @@@ static int fsck_walk_tree(struct tree *
                        continue;
  
                if (S_ISDIR(entry.mode)) {
 -                      obj = (struct object *)lookup_tree(the_repository, entry.oid);
 +                      obj = (struct object *)lookup_tree(the_repository, &entry.oid);
                        if (name && obj)
                                put_object_name(options, obj, "%s%s/", name,
                                        entry.path);
                        result = options->walk(obj, OBJ_TREE, data, options);
                }
                else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) {
 -                      obj = (struct object *)lookup_blob(the_repository, entry.oid);
 +                      obj = (struct object *)lookup_blob(the_repository, &entry.oid);
                        if (name && obj)
                                put_object_name(options, obj, "%s%s", name,
                                        entry.path);
@@@ -605,7 -606,7 +606,7 @@@ static int fsck_tree(struct tree *item
  
        while (desc.size) {
                unsigned mode;
-               const char *name;
+               const char *name, *backslash;
                const struct object_id *oid;
  
                oid = tree_entry_extract(&desc, &name, &mode);
                                                 ".gitmodules is a symbolic link");
                }
  
+               if ((backslash = strchr(name, '\\'))) {
+                       while (backslash) {
+                               backslash++;
+                               has_dotgit |= is_ntfs_dotgit(backslash);
+                               if (is_ntfs_dotgitmodules(backslash)) {
+                                       if (!S_ISLNK(mode))
+                                               oidset_insert(&gitmodules_found, oid);
+                                       else
+                                               retval += report(options, &item->object,
+                                                                FSCK_MSG_GITMODULES_SYMLINK,
+                                                                ".gitmodules is a symbolic link");
+                               }
+                               backslash = strchr(backslash, '\\');
+                       }
+               }
                if (update_tree_entry_gently(&desc)) {
                        retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
                        break;
@@@ -1000,6 -1017,12 +1017,12 @@@ static int fsck_gitmodules_fn(const cha
                                    FSCK_MSG_GITMODULES_PATH,
                                    "disallowed submodule path: %s",
                                    value);
+       if (!strcmp(key, "update") && value &&
+           parse_submodule_update_type(value) == SM_UPDATE_COMMAND)
+               data->ret |= report(data->options, data->obj,
+                                   FSCK_MSG_GITMODULES_UPDATE,
+                                   "disallowed submodule update setting: %s",
+                                   value);
        free(name);
  
        return 0;
diff --combined git-compat-util.h
index 6573808ebd991b52eca7cc644af4c9611b9c3747,d17360e89789403ece1d81278822e7469cd296a1..4d62ad3491bcc8fcea2fb0ce4784ad00671fe966
  #endif
  
  #if defined(__CYGWIN__)
 -#include "compat/cygwin.h"
 +#include "compat/win32/path-utils.h"
  #endif
  #if defined(__MINGW32__)
  /* pull in Windows compatibility stuff */
 +#include "compat/win32/path-utils.h"
  #include "compat/mingw.h"
  #elif defined(_MSC_VER)
  #include "compat/msvc.h"
@@@ -386,6 -385,10 +386,10 @@@ static inline int git_offset_1st_compon
  #define offset_1st_component git_offset_1st_component
  #endif
  
+ #ifndef is_valid_path
+ #define is_valid_path(path) 1
+ #endif
  #ifndef find_last_dir_sep
  static inline char *git_find_last_dir_sep(const char *path)
  {
  #define query_user_email() NULL
  #endif
  
 +#ifdef __TANDEM
 +#include <floss.h(floss_execl,floss_execlp,floss_execv,floss_execvp)>
 +#include <floss.h(floss_getpwuid)>
 +#ifndef NSIG
 +/*
 + * NonStop NSE and NSX do not provide NSIG. SIGGUARDIAN(99) is the highest
 + * known, by detective work using kill -l as a list is all signals
 + * instead of signal.h where it should be.
 + */
 +# define NSIG 100
 +#endif
 +#endif
 +
  #if defined(__HP_cc) && (__HP_cc >= 61000)
  #define NORETURN __attribute__((noreturn))
  #define NORETURN_PTR
@@@ -735,7 -725,7 +739,7 @@@ extern const char *githstrerror(int her
  #ifdef NO_MEMMEM
  #define memmem gitmemmem
  void *gitmemmem(const void *haystack, size_t haystacklen,
 -                const void *needle, size_t needlelen);
 +              const void *needle, size_t needlelen);
  #endif
  
  #ifdef OVERRIDE_STRDUP
@@@ -1234,14 -1224,6 +1238,14 @@@ struct tm *git_gmtime_r(const time_t *
  #define getc_unlocked(fh) getc(fh)
  #endif
  
 +#ifdef FILENO_IS_A_MACRO
 +int git_fileno(FILE *stream);
 +# ifndef COMPAT_CODE
 +#  undef fileno
 +#  define fileno(p) git_fileno(p)
 +# endif
 +#endif
 +
  /*
   * Our code often opens a path to an optional file, to work on its
   * contents when we can successfully open it.  We can ignore a failure
diff --combined git-submodule.sh
index b5f2beee60ab59a04b5b236da8df39403009db1b,65d62c888c8f2288ca276b28744f51f773bee8f2..f2d4d177b2330afbe7852bc6f5a80993b676f629
@@@ -34,6 -34,7 +34,7 @@@ reference
  cached=
  recursive=
  init=
+ require_init=
  files=
  remote=
  nofetch=
@@@ -457,6 -458,10 +458,10 @@@ cmd_update(
                -i|--init)
                        init=1
                        ;;
+               --require-init)
+                       init=1
+                       require_init=1
+                       ;;
                --remote)
                        remote=1
                        ;;
                ${reference:+"$reference"} \
                ${dissociate:+"--dissociate"} \
                ${depth:+--depth "$depth"} \
+               ${require_init:+--require-init} \
                $recommend_shallow \
                $jobs \
                "$@" || echo "#unmatched" $?
        do
                die_if_unmatched "$quickabort" "$sha1"
  
 -              git submodule--helper ensure-core-worktree "$sm_path"
 +              git submodule--helper ensure-core-worktree "$sm_path" || exit 1
  
                update_module=$(git submodule--helper update-module-mode $just_cloned "$sm_path" $update)
  
@@@ -850,11 -856,8 +856,11 @@@ cmd_summary() 
                        ;;
                esac
  
 -              sha1_abbr_src=$(echo $sha1_src | cut -c1-7)
 -              sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
 +              sha1_abbr_src=$(GIT_DIR="$name/.git" git rev-parse --short $sha1_src 2>/dev/null ||
 +                      echo $sha1_src | cut -c1-7)
 +              sha1_abbr_dst=$(GIT_DIR="$name/.git" git rev-parse --short $sha1_dst 2>/dev/null ||
 +                      echo $sha1_dst | cut -c1-7)
 +
                if test $status = T
                then
                        blob="$(gettext "blob")"
diff --combined path.c
index 03ab712839a7878e3d3b5d79c698f4e2bb29c8b1,617545cd5d4390e4d171485b2ca13f010330b21e..10b66b8ad4730ff14be04be9575a7c829465689d
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -385,7 -385,7 +385,7 @@@ static void adjust_git_path(const struc
                strbuf_splice(buf, 0, buf->len,
                              repo->index_file, strlen(repo->index_file));
        else if (dir_prefix(base, "objects"))
 -              replace_dir(buf, git_dir_len + 7, repo->objects->objectdir);
 +              replace_dir(buf, git_dir_len + 7, repo->objects->odb->path);
        else if (git_hooks_path && dir_prefix(base, "hooks"))
                replace_dir(buf, git_dir_len + 5, git_hooks_path);
        else if (repo->different_commondir)
@@@ -1292,37 -1292,77 +1292,77 @@@ int daemon_avoid_alias(const char *p
        }
  }
  
- static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
+ /*
+  * On NTFS, we need to be careful to disallow certain synonyms of the `.git/`
+  * directory:
+  *
+  * - For historical reasons, file names that end in spaces or periods are
+  *   automatically trimmed. Therefore, `.git . . ./` is a valid way to refer
+  *   to `.git/`.
+  *
+  * - For other historical reasons, file names that do not conform to the 8.3
+  *   format (up to eight characters for the basename, three for the file
+  *   extension, certain characters not allowed such as `+`, etc) are associated
+  *   with a so-called "short name", at least on the `C:` drive by default.
+  *   Which means that `git~1/` is a valid way to refer to `.git/`.
+  *
+  *   Note: Technically, `.git/` could receive the short name `git~2` if the
+  *   short name `git~1` were already used. In Git, however, we guarantee that
+  *   `.git` is the first item in a directory, therefore it will be associated
+  *   with the short name `git~1` (unless short names are disabled).
+  *
+  * - For yet other historical reasons, NTFS supports so-called "Alternate Data
+  *   Streams", i.e. metadata associated with a given file, referred to via
+  *   `<filename>:<stream-name>:<stream-type>`. There exists a default stream
+  *   type for directories, allowing `.git/` to be accessed via
+  *   `.git::$INDEX_ALLOCATION/`.
+  *
+  * When this function returns 1, it indicates that the specified file/directory
+  * name refers to a `.git` file or directory, or to any of these synonyms, and
+  * Git should therefore not track it.
+  *
+  * For performance reasons, _all_ Alternate Data Streams of `.git/` are
+  * forbidden, not just `::$INDEX_ALLOCATION`.
+  *
+  * This function is intended to be used by `git fsck` even on platforms where
+  * the backslash is a regular filename character, therefore it needs to handle
+  * backlash characters in the provided `name` specially: they are interpreted
+  * as directory separators.
+  */
+ int is_ntfs_dotgit(const char *name)
  {
-       if (len < skip)
+       char c;
+       /*
+        * Note that when we don't find `.git` or `git~1` we end up with `name`
+        * advanced partway through the string. That's okay, though, as we
+        * return immediately in those cases, without looking at `name` any
+        * further.
+        */
+       c = *(name++);
+       if (c == '.') {
+               /* .git */
+               if (((c = *(name++)) != 'g' && c != 'G') ||
+                   ((c = *(name++)) != 'i' && c != 'I') ||
+                   ((c = *(name++)) != 't' && c != 'T'))
+                       return 0;
+       } else if (c == 'g' || c == 'G') {
+               /* git ~1 */
+               if (((c = *(name++)) != 'i' && c != 'I') ||
+                   ((c = *(name++)) != 't' && c != 'T') ||
+                   *(name++) != '~' ||
+                   *(name++) != '1')
+                       return 0;
+       } else
                return 0;
-       len -= skip;
-       path += skip;
-       while (len-- > 0) {
-               char c = *(path++);
-               if (c != ' ' && c != '.')
+       for (;;) {
+               c = *(name++);
+               if (!c || c == '\\' || c == '/' || c == ':')
+                       return 1;
+               if (c != '.' && c != ' ')
                        return 0;
        }
-       return 1;
- }
- int is_ntfs_dotgit(const char *name)
- {
-       size_t len;
-       for (len = 0; ; len++)
-               if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
-                       if (only_spaces_and_periods(name, len, 4) &&
-                                       !strncasecmp(name, ".git", 4))
-                               return 1;
-                       if (only_spaces_and_periods(name, len, 5) &&
-                                       !strncasecmp(name, "git~1", 5))
-                               return 1;
-                       if (name[len] != '\\')
-                               return 0;
-                       name += len + 1;
-                       len = -1;
-               }
  }
  
  static int is_ntfs_dot_generic(const char *name,
  only_spaces_and_periods:
                for (;;) {
                        char c = name[i++];
-                       if (!c)
+                       if (!c || c == ':')
                                return 1;
                        if (c != ' ' && c != '.')
                                return 0;
diff --combined read-cache.c
index 0e0c93edc9be5a7af5de1097eafe9d860e7d183e,1d82dbdd65c8f01db24d2e40b702988a412f1034..87d5db3fb059ca7e33750b7928125b9382a96ada
@@@ -3,6 -3,7 +3,6 @@@
   *
   * Copyright (C) Linus Torvalds, 2005
   */
 -#define NO_THE_INDEX_COMPATIBILITY_MACROS
  #include "cache.h"
  #include "config.h"
  #include "diff.h"
@@@ -94,6 -95,7 +94,6 @@@ static struct mem_pool *find_mem_pool(s
        return *pool_ptr;
  }
  
 -struct index_state the_index;
  static const char *alternate_index_output;
  
  static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
@@@ -314,7 -316,7 +314,7 @@@ static int ce_match_stat_basic(const st
                        changed |= DATA_CHANGED;
                return changed;
        default:
 -              die("internal error: ce_mode is %o", ce->ce_mode);
 +              BUG("unsupported ce_mode: %o", ce->ce_mode);
        }
  
        changed |= match_stat_data(&ce->ce_stat_data, st);
@@@ -670,8 -672,7 +670,8 @@@ static struct cache_entry *create_alias
        struct cache_entry *new_entry;
  
        if (alias->ce_flags & CE_ADDED)
 -              die("Will not add file alias '%s' ('%s' already exists in index)", ce->name, alias->name);
 +              die(_("will not add file alias '%s' ('%s' already exists in index)"),
 +                  ce->name, alias->name);
  
        /* Ok, create the new entry using the name of the existing alias */
        len = ce_namelen(alias);
@@@ -686,7 -687,7 +686,7 @@@ void set_object_name_for_intent_to_add_
  {
        struct object_id oid;
        if (write_object_file("", 0, blob_type, &oid))
 -              die("cannot create an empty blob in the object database");
 +              die(_("cannot create an empty blob in the object database"));
        oidcpy(&ce->oid, &oid);
  }
  
@@@ -701,13 -702,13 +701,13 @@@ int add_to_index(struct index_state *is
        int intent_only = flags & ADD_CACHE_INTENT;
        int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|
                          (intent_only ? ADD_CACHE_NEW_ONLY : 0));
 -      int newflags = HASH_WRITE_OBJECT;
 +      int hash_flags = HASH_WRITE_OBJECT;
  
 -      if (flags & HASH_RENORMALIZE)
 -              newflags |= HASH_RENORMALIZE;
 +      if (flags & ADD_CACHE_RENORMALIZE)
 +              hash_flags |= HASH_RENORMALIZE;
  
        if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode))
 -              return error("%s: can only add regular files, symbolic links or git-directories", path);
 +              return error(_("%s: can only add regular files, symbolic links or git-directories"), path);
  
        namelen = strlen(path);
        if (S_ISDIR(st_mode)) {
        if (ignore_case) {
                adjust_dirname_case(istate, ce->name);
        }
 -      if (!(flags & HASH_RENORMALIZE)) {
 +      if (!(flags & ADD_CACHE_RENORMALIZE)) {
                alias = index_file_exists(istate, ce->name,
                                          ce_namelen(ce), ignore_case);
                if (alias &&
                }
        }
        if (!intent_only) {
 -              if (index_path(istate, &ce->oid, path, st, newflags)) {
 +              if (index_path(istate, &ce->oid, path, st, hash_flags)) {
                        discard_cache_entry(ce);
 -                      return error("unable to index file %s", path);
 +                      return error(_("unable to index file '%s'"), path);
                }
        } else
                set_object_name_for_intent_to_add_entry(ce);
                discard_cache_entry(ce);
        else if (add_index_entry(istate, ce, add_option)) {
                discard_cache_entry(ce);
 -              return error("unable to add %s to index", path);
 +              return error(_("unable to add '%s' to index"), path);
        }
        if (verbose && !was_same)
                printf("add '%s'\n", path);
@@@ -792,7 -793,7 +792,7 @@@ int add_file_to_index(struct index_stat
  {
        struct stat st;
        if (lstat(path, &st))
 -              die_errno("unable to stat '%s'", path);
 +              die_errno(_("unable to stat '%s'"), path);
        return add_to_index(istate, path, &st, flags);
  }
  
@@@ -817,7 -818,7 +817,7 @@@ struct cache_entry *make_cache_entry(st
        int len;
  
        if (!verify_path(path, mode)) {
 -              error("Invalid path '%s'", path);
 +              error(_("invalid path '%s'"), path);
                return NULL;
        }
  
@@@ -843,7 -844,7 +843,7 @@@ struct cache_entry *make_transient_cach
        int len;
  
        if (!verify_path(path, mode)) {
 -              error("Invalid path '%s'", path);
 +              error(_("invalid path '%s'"), path);
                return NULL;
        }
  
@@@ -954,6 -955,9 +954,9 @@@ int verify_path(const char *path, unsig
        if (has_dos_drive_prefix(path))
                return 0;
  
+       if (!is_valid_path(path))
+               return 0;
        goto inside;
        for (;;) {
                if (!c)
@@@ -981,7 -985,15 +984,15 @@@ inside
                        if ((c == '.' && !verify_dotfile(path, mode)) ||
                            is_dir_sep(c) || c == '\0')
                                return 0;
+               } else if (c == '\\' && protect_ntfs) {
+                       if (is_ntfs_dotgit(path))
+                               return 0;
+                       if (S_ISLNK(mode)) {
+                               if (is_ntfs_dotgitmodules(path))
+                                       return 0;
+                       }
                }
                c = *path++;
        }
  }
@@@ -1296,12 -1308,12 +1307,12 @@@ static int add_index_entry_with_check(s
        if (!ok_to_add)
                return -1;
        if (!verify_path(ce->name, ce->ce_mode))
 -              return error("Invalid path '%s'", ce->name);
 +              return error(_("invalid path '%s'"), ce->name);
  
        if (!skip_df_check &&
            check_file_directory_conflict(istate, ce, pos, ok_to_replace)) {
                if (!ok_to_replace)
 -                      return error("'%s' appears as both a file and as a directory",
 +                      return error(_("'%s' appears as both a file and as a directory"),
                                     ce->name);
                pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
                pos = -pos-1;
@@@ -1490,11 -1502,11 +1501,11 @@@ int refresh_index(struct index_state *i
                                                  istate->cache_nr);
  
        trace_performance_enter();
 -      modified_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
 -      deleted_fmt = (in_porcelain ? "D\t%s\n" : "%s: needs update\n");
 -      typechange_fmt = (in_porcelain ? "T\t%s\n" : "%s needs update\n");
 -      added_fmt = (in_porcelain ? "A\t%s\n" : "%s needs update\n");
 -      unmerged_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");
 +      modified_fmt   = in_porcelain ? "M\t%s\n" : "%s: needs update\n";
 +      deleted_fmt    = in_porcelain ? "D\t%s\n" : "%s: needs update\n";
 +      typechange_fmt = in_porcelain ? "T\t%s\n" : "%s: needs update\n";
 +      added_fmt      = in_porcelain ? "A\t%s\n" : "%s: needs update\n";
 +      unmerged_fmt   = in_porcelain ? "U\t%s\n" : "%s: needs merge\n";
        /*
         * Use the multi-threaded preload_index() to refresh most of the
         * cache entries quickly then in the single threaded loop below,
@@@ -1681,10 -1693,10 +1692,10 @@@ static int verify_hdr(const struct cach
        int hdr_version;
  
        if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
 -              return error("bad signature");
 +              return error(_("bad signature 0x%08x"), hdr->hdr_signature);
        hdr_version = ntohl(hdr->hdr_version);
        if (hdr_version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < hdr_version)
 -              return error("bad index version %d", hdr_version);
 +              return error(_("bad index version %d"), hdr_version);
  
        if (!verify_index_checksum)
                return 0;
        the_hash_algo->update_fn(&c, hdr, size - the_hash_algo->rawsz);
        the_hash_algo->final_fn(hash, &c);
        if (!hasheq(hash, (unsigned char *)hdr + size - the_hash_algo->rawsz))
 -              return error("bad index file sha1 signature");
 +              return error(_("bad index file sha1 signature"));
        return 0;
  }
  
@@@ -1723,14 -1735,24 +1734,14 @@@ static int read_index_extension(struct 
                break;
        default:
                if (*ext < 'A' || 'Z' < *ext)
 -                      return error("index uses %.4s extension, which we do not understand",
 +                      return error(_("index uses %.4s extension, which we do not understand"),
                                     ext);
 -              fprintf(stderr, "ignoring %.4s extension\n", ext);
 +              fprintf_ln(stderr, _("ignoring %.4s extension"), ext);
                break;
        }
        return 0;
  }
  
 -int hold_locked_index(struct lock_file *lk, int lock_flags)
 -{
 -      return hold_lock_file_for_update(lk, get_index_file(), lock_flags);
 -}
 -
 -int read_index(struct index_state *istate)
 -{
 -      return read_index_from(istate, get_index_file(), get_git_dir());
 -}
 -
  static struct cache_entry *create_from_disk(struct mem_pool *ce_mem_pool,
                                            unsigned int version,
                                            struct ondisk_cache_entry *ondisk,
                extended_flags = get_be16(&ondisk2->flags2) << 16;
                /* We do not yet understand any bit out of CE_EXTENDED_FLAGS */
                if (extended_flags & ~CE_EXTENDED_FLAGS)
 -                      die("Unknown index entry format %08x", extended_flags);
 +                      die(_("unknown index entry format 0x%08x"), extended_flags);
                flags |= extended_flags;
                name = ondisk2->name;
        }
@@@ -1833,13 -1855,13 +1844,13 @@@ static void check_ce_order(struct index
                int name_compare = strcmp(ce->name, next_ce->name);
  
                if (0 < name_compare)
 -                      die("unordered stage entries in index");
 +                      die(_("unordered stage entries in index"));
                if (!name_compare) {
                        if (!ce_stage(ce))
 -                              die("multiple stage entries for merged file '%s'",
 +                              die(_("multiple stage entries for merged file '%s'"),
                                    ce->name);
                        if (ce_stage(ce) > ce_stage(next_ce))
 -                              die("unordered stage entries for '%s'",
 +                              die(_("unordered stage entries for '%s'"),
                                    ce->name);
                }
        }
@@@ -2133,19 -2155,19 +2144,19 @@@ int do_read_index(struct index_state *i
        if (fd < 0) {
                if (!must_exist && errno == ENOENT)
                        return 0;
 -              die_errno("%s: index file open failed", path);
 +              die_errno(_("%s: index file open failed"), path);
        }
  
        if (fstat(fd, &st))
 -              die_errno("cannot stat the open index");
 +              die_errno(_("%s: cannot stat the open index"), path);
  
        mmap_size = xsize_t(st.st_size);
        if (mmap_size < sizeof(struct cache_header) + the_hash_algo->rawsz)
 -              die("index file smaller than expected");
 +              die(_("%s: index file smaller than expected"), path);
  
        mmap = xmmap(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0);
        if (mmap == MAP_FAILED)
 -              die_errno("unable to map index file");
 +              die_errno(_("%s: unable to map index file"), path);
        close(fd);
  
        hdr = (const struct cache_header *)mmap;
  
  unmap:
        munmap((void *)mmap, mmap_size);
 -      die("index file corrupt");
 +      die(_("index file corrupt"));
  }
  
  /*
  static void freshen_shared_index(const char *shared_index, int warn)
  {
        if (!check_and_freshen_file(shared_index, 1) && warn)
 -              warning("could not freshen shared index '%s'", shared_index);
 +              warning(_("could not freshen shared index '%s'"), shared_index);
  }
  
  int read_index_from(struct index_state *istate, const char *path,
        base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
        ret = do_read_index(split_index->base, base_path, 1);
        if (!oideq(&split_index->base_oid, &split_index->base->oid))
 -              die("broken index, expect %s in %s, got %s",
 +              die(_("broken index, expect %s in %s, got %s"),
                    base_oid_hex, base_path,
                    oid_to_hex(&split_index->base->oid));
  
@@@ -2337,14 -2359,14 +2348,14 @@@ void validate_cache_entries(const struc
  
        for (i = 0; i < istate->cache_nr; i++) {
                if (!istate) {
 -                      die("internal error: cache entry is not allocated from expected memory pool");
 +                      BUG("cache entry is not allocated from expected memory pool");
                } else if (!istate->ce_mem_pool ||
                        !mem_pool_contains(istate->ce_mem_pool, istate->cache[i])) {
                        if (!istate->split_index ||
                                !istate->split_index->base ||
                                !istate->split_index->base->ce_mem_pool ||
                                !mem_pool_contains(istate->split_index->base->ce_mem_pool, istate->cache[i])) {
 -                              die("internal error: cache entry is not allocated from expected memory pool");
 +                              BUG("cache entry is not allocated from expected memory pool");
                        }
                }
        }
@@@ -2363,20 -2385,22 +2374,20 @@@ int unmerged_index(const struct index_s
        return 0;
  }
  
 -int index_has_changes(struct index_state *istate,
 -                    struct tree *tree,
 -                    struct strbuf *sb)
 +int repo_index_has_changes(struct repository *repo,
 +                         struct tree *tree,
 +                         struct strbuf *sb)
  {
 +      struct index_state *istate = repo->index;
        struct object_id cmp;
        int i;
  
 -      if (istate != &the_index) {
 -              BUG("index_has_changes cannot yet accept istate != &the_index; do_diff_cache needs updating first.");
 -      }
        if (tree)
                cmp = tree->object.oid;
        if (tree || !get_oid_tree("HEAD", &cmp)) {
                struct diff_options opt;
  
 -              repo_diff_setup(the_repository, &opt);
 +              repo_diff_setup(repo, &opt);
                opt.flags.exit_with_status = 1;
                if (!sb)
                        opt.flags.quick = 1;
@@@ -2650,9 -2674,9 +2661,9 @@@ out
        return 0;
  }
  
 -static int verify_index(const struct index_state *istate)
 +static int repo_verify_index(struct repository *repo)
  {
 -      return verify_index_from(istate, get_index_file());
 +      return verify_index_from(repo->index, repo->index_file);
  }
  
  static int has_racy_timestamp(struct index_state *istate)
        return 0;
  }
  
 -void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
 +void repo_update_index_if_able(struct repository *repo,
 +                             struct lock_file *lockfile)
  {
 -      if ((istate->cache_changed || has_racy_timestamp(istate)) &&
 -          verify_index(istate))
 -              write_locked_index(istate, lockfile, COMMIT_LOCK);
 +      if ((repo->index->cache_changed ||
 +           has_racy_timestamp(repo->index)) &&
 +          repo_verify_index(repo))
 +              write_locked_index(repo->index, lockfile, COMMIT_LOCK);
        else
                rollback_lock_file(lockfile);
  }
@@@ -3085,7 -3107,7 +3096,7 @@@ static int write_shared_index(struct in
                return ret;
        ret = adjust_shared_perm(get_tempfile_path(*temp));
        if (ret) {
 -              error("cannot fix permission bits on %s", get_tempfile_path(*temp));
 +              error(_("cannot fix permission bits on '%s'"), get_tempfile_path(*temp));
                return ret;
        }
        ret = rename_tempfile(temp,
@@@ -3135,7 -3157,7 +3146,7 @@@ int write_locked_index(struct index_sta
        struct split_index *si = istate->split_index;
  
        if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
 -              cache_tree_verify(istate);
 +              cache_tree_verify(the_repository, istate);
  
        if ((flags & SKIP_IF_UNCHANGED) && !istate->cache_changed) {
                if (flags & COMMIT_LOCK)
   * state can call this and check its return value, instead of calling
   * read_cache().
   */
 -int read_index_unmerged(struct index_state *istate)
 +int repo_read_index_unmerged(struct repository *repo)
  {
 +      struct index_state *istate;
        int i;
        int unmerged = 0;
  
 -      read_index(istate);
 +      repo_read_index(repo);
 +      istate = repo->index;
        for (i = 0; i < istate->cache_nr; i++) {
                struct cache_entry *ce = istate->cache[i];
                struct cache_entry *new_ce;
                new_ce->ce_namelen = len;
                new_ce->ce_mode = ce->ce_mode;
                if (add_index_entry(istate, new_ce, ADD_CACHE_SKIP_DFCHECK))
 -                      return error("%s: cannot drop to stage #0",
 +                      return error(_("%s: cannot drop to stage #0"),
                                     new_ce->name);
        }
        return unmerged;
@@@ -3485,71 -3505,71 +3496,71 @@@ static void write_eoie_extension(struc
  
  static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset)
  {
 -       const char *index = NULL;
 -       uint32_t extsize, ext_version;
 -       struct index_entry_offset_table *ieot;
 -       int i, nr;
 -
 -       /* find the IEOT extension */
 -       if (!offset)
 -             return NULL;
 -       while (offset <= mmap_size - the_hash_algo->rawsz - 8) {
 -             extsize = get_be32(mmap + offset + 4);
 -             if (CACHE_EXT((mmap + offset)) == CACHE_EXT_INDEXENTRYOFFSETTABLE) {
 -                     index = mmap + offset + 4 + 4;
 -                     break;
 -             }
 -             offset += 8;
 -             offset += extsize;
 -       }
 -       if (!index)
 -             return NULL;
 -
 -       /* validate the version is IEOT_VERSION */
 -       ext_version = get_be32(index);
 -       if (ext_version != IEOT_VERSION) {
 -             error("invalid IEOT version %d", ext_version);
 -             return NULL;
 -       }
 -       index += sizeof(uint32_t);
 -
 -       /* extension size - version bytes / bytes per entry */
 -       nr = (extsize - sizeof(uint32_t)) / (sizeof(uint32_t) + sizeof(uint32_t));
 -       if (!nr) {
 -             error("invalid number of IEOT entries %d", nr);
 -             return NULL;
 -       }
 -       ieot = xmalloc(sizeof(struct index_entry_offset_table)
 -             + (nr * sizeof(struct index_entry_offset)));
 -       ieot->nr = nr;
 -       for (i = 0; i < nr; i++) {
 -             ieot->entries[i].offset = get_be32(index);
 -             index += sizeof(uint32_t);
 -             ieot->entries[i].nr = get_be32(index);
 -             index += sizeof(uint32_t);
 -       }
 -
 -       return ieot;
 +      const char *index = NULL;
 +      uint32_t extsize, ext_version;
 +      struct index_entry_offset_table *ieot;
 +      int i, nr;
 +
 +      /* find the IEOT extension */
 +      if (!offset)
 +              return NULL;
 +      while (offset <= mmap_size - the_hash_algo->rawsz - 8) {
 +              extsize = get_be32(mmap + offset + 4);
 +              if (CACHE_EXT((mmap + offset)) == CACHE_EXT_INDEXENTRYOFFSETTABLE) {
 +                      index = mmap + offset + 4 + 4;
 +                      break;
 +              }
 +              offset += 8;
 +              offset += extsize;
 +      }
 +      if (!index)
 +              return NULL;
 +
 +      /* validate the version is IEOT_VERSION */
 +      ext_version = get_be32(index);
 +      if (ext_version != IEOT_VERSION) {
 +              error("invalid IEOT version %d", ext_version);
 +              return NULL;
 +      }
 +      index += sizeof(uint32_t);
 +
 +      /* extension size - version bytes / bytes per entry */
 +      nr = (extsize - sizeof(uint32_t)) / (sizeof(uint32_t) + sizeof(uint32_t));
 +      if (!nr) {
 +              error("invalid number of IEOT entries %d", nr);
 +              return NULL;
 +      }
 +      ieot = xmalloc(sizeof(struct index_entry_offset_table)
 +                     + (nr * sizeof(struct index_entry_offset)));
 +      ieot->nr = nr;
 +      for (i = 0; i < nr; i++) {
 +              ieot->entries[i].offset = get_be32(index);
 +              index += sizeof(uint32_t);
 +              ieot->entries[i].nr = get_be32(index);
 +              index += sizeof(uint32_t);
 +      }
 +
 +      return ieot;
  }
  
  static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot)
  {
 -       uint32_t buffer;
 -       int i;
 +      uint32_t buffer;
 +      int i;
  
 -       /* version */
 -       put_be32(&buffer, IEOT_VERSION);
 -       strbuf_add(sb, &buffer, sizeof(uint32_t));
 +      /* version */
 +      put_be32(&buffer, IEOT_VERSION);
 +      strbuf_add(sb, &buffer, sizeof(uint32_t));
  
 -       /* ieot */
 -       for (i = 0; i < ieot->nr; i++) {
 +      /* ieot */
 +      for (i = 0; i < ieot->nr; i++) {
  
 -             /* offset */
 -             put_be32(&buffer, ieot->entries[i].offset);
 -             strbuf_add(sb, &buffer, sizeof(uint32_t));
 +              /* offset */
 +              put_be32(&buffer, ieot->entries[i].offset);
 +              strbuf_add(sb, &buffer, sizeof(uint32_t));
  
 -             /* count */
 -             put_be32(&buffer, ieot->entries[i].nr);
 -             strbuf_add(sb, &buffer, sizeof(uint32_t));
 -       }
 +              /* count */
 +              put_be32(&buffer, ieot->entries[i].nr);
 +              strbuf_add(sb, &buffer, sizeof(uint32_t));
 +      }
  }
diff --combined submodule.c
index 934ecfa2943bcb5ee87e7ac431b5332a5f3adcde,37d29bb252474e43f1a942a0d19e39697ed1ff21..50ee9ab7476f8c3fa93836484b2e41fcdc48afc5
@@@ -1,3 -1,4 +1,3 @@@
 -#define NO_THE_INDEX_COMPATIBILITY_MACROS
  
  #include "cache.h"
  #include "repository.h"
@@@ -24,6 -25,7 +24,6 @@@
  #include "commit-reach.h"
  
  static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
 -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;
@@@ -455,7 -457,7 +455,7 @@@ static int prepare_submodule_summary(st
        return prepare_revision_walk(rev);
  }
  
 -static void print_submodule_summary(struct rev_info *rev, struct diff_options *o)
 +static void print_submodule_summary(struct repository *r, 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);
 -              format_commit_message(commit, format, &sb, &ctx);
 +              repo_format_commit_message(r, commit, format, &sb,
 +                                    &ctx);
                strbuf_addch(&sb, '\n');
                if (commit->object.flags & SYMMETRIC_LEFT)
                        diff_emit_submodule_del(o, sb.buf);
@@@ -494,52 -495,14 +494,52 @@@ void prepare_submodule_repo_env(struct 
                         DEFAULT_GIT_DIR_ENVIRONMENT);
  }
  
 -/* Helper function to display the submodule header line prior to the full
 - * summary output. If it can locate the submodule objects directory it will
 - * attempt to lookup both the left and right commits and put them into the
 - * left and right pointers.
 +static void prepare_submodule_repo_env_in_gitdir(struct argv_array *out)
 +{
 +      prepare_submodule_repo_env_no_git_dir(out);
 +      argv_array_pushf(out, "%s=.", GIT_DIR_ENVIRONMENT);
 +}
 +
 +/*
 + * Initialize a repository struct for a submodule based on the provided 'path'.
 + *
 + * Unlike repo_submodule_init, this tolerates submodules not present
 + * in .gitmodules. This function exists only to preserve historical behavior,
 + *
 + * Returns the repository struct on success,
 + * NULL when the submodule is not present.
   */
 -static void show_submodule_header(struct diff_options *o, const char *path,
 +static struct repository *open_submodule(const char *path)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      struct repository *out = xmalloc(sizeof(*out));
 +
 +      if (submodule_to_gitdir(&sb, path) || repo_init(out, sb.buf, NULL)) {
 +              strbuf_release(&sb);
 +              free(out);
 +              return NULL;
 +      }
 +
 +      /* Mark it as a submodule */
 +      out->submodule_prefix = xstrdup(path);
 +
 +      strbuf_release(&sb);
 +      return out;
 +}
 +
 +/*
 + * Helper function to display the submodule header line prior to the full
 + * summary output.
 + *
 + * If it can locate the submodule git directory it will create a repository
 + * handle for the submodule and lookup both the left and right commits and
 + * put them into the left and right pointers.
 + */
 +static void show_submodule_header(struct diff_options *o,
 +              const char *path,
                struct object_id *one, struct object_id *two,
                unsigned dirty_submodule,
 +              struct repository *sub,
                struct commit **left, struct commit **right,
                struct commit_list **merge_bases)
  {
        else if (is_null_oid(two))
                message = "(submodule deleted)";
  
 -      if (add_submodule_odb(path)) {
 +      if (!sub) {
                if (!message)
                        message = "(commits not present)";
                goto output_header;
         * Attempt to lookup the commit references, and determine if this is
         * a fast forward or fast backwards update.
         */
 -      *left = lookup_commit_reference(the_repository, one);
 -      *right = lookup_commit_reference(the_repository, two);
 +      *left = lookup_commit_reference(sub, one);
 +      *right = lookup_commit_reference(sub, two);
  
        /*
         * Warn about missing commits in the submodule project, but only if
             (!is_null_oid(two) && !*right))
                message = "(commits not present)";
  
 -      *merge_bases = get_merge_bases(*left, *right);
 +      *merge_bases = repo_get_merge_bases(sub, *left, *right);
        if (*merge_bases) {
                if ((*merge_bases)->item == *left)
                        fast_forward = 1;
@@@ -613,18 -576,16 +613,18 @@@ void show_submodule_summary(struct diff
        struct rev_info rev;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
 +      struct repository *sub;
  
 +      sub = open_submodule(path);
        show_submodule_header(o, path, one, two, dirty_submodule,
 -                            &left, &right, &merge_bases);
 +                            sub, &left, &right, &merge_bases);
  
        /*
         * If we don't have both a left and a right pointer, there is no
         * reason to try and display a summary. The header line should contain
         * all the information the user needs.
         */
 -      if (!left || !right)
 +      if (!left || !right || !sub)
                goto out;
  
        /* Treat revision walker failure the same as missing commits */
                goto out;
        }
  
 -      print_submodule_summary(&rev, o);
 +      print_submodule_summary(sub, &rev, o);
  
  out:
        if (merge_bases)
                free_commit_list(merge_bases);
        clear_commit_marks(left, ~0);
        clear_commit_marks(right, ~0);
 +      if (sub) {
 +              repo_clear(sub);
 +              free(sub);
 +      }
  }
  
  void show_submodule_inline_diff(struct diff_options *o, const char *path,
        struct commit_list *merge_bases = NULL;
        struct child_process cp = CHILD_PROCESS_INIT;
        struct strbuf sb = STRBUF_INIT;
 +      struct repository *sub;
  
 +      sub = open_submodule(path);
        show_submodule_header(o, path, one, two, dirty_submodule,
 -                            &left, &right, &merge_bases);
 +                            sub, &left, &right, &merge_bases);
  
        /* We need a valid left and right commit to display a difference */
        if (!(left || is_null_oid(one)) ||
@@@ -720,10 -675,6 +720,10 @@@ done
                clear_commit_marks(left, ~0);
        if (right)
                clear_commit_marks(right, ~0);
 +      if (sub) {
 +              repo_clear(sub);
 +              free(sub);
 +      }
  }
  
  int should_update_submodules(void)
@@@ -1048,6 -999,9 +1048,6 @@@ static int push_submodule(const char *p
                          const struct string_list *push_options,
                          int dry_run)
  {
 -      if (add_submodule_odb(path))
 -              return 1;
 -
        if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
                struct child_process cp = CHILD_PROCESS_INIT;
                argv_array_push(&cp.args, "push");
@@@ -1182,11 -1136,11 +1182,11 @@@ void check_for_new_submodule_commits(st
        oid_array_append(&ref_tips_after_fetch, oid);
  }
  
 -static void calculate_changed_submodule_paths(struct repository *r)
 +static void calculate_changed_submodule_paths(struct repository *r,
 +              struct string_list *changed_submodule_names)
  {
        struct argv_array argv = ARGV_ARRAY_INIT;
 -      struct string_list changed_submodules = STRING_LIST_INIT_DUP;
 -      const struct string_list_item *name;
 +      struct string_list_item *name;
  
        /* No need to check if there are no submodules configured */
        if (!submodule_from_path(r, NULL, NULL))
         * Collect all submodules (whether checked out or not) for which new
         * commits have been recorded upstream in "changed_submodule_names".
         */
 -      collect_changed_submodules(r, &changed_submodules, &argv);
 +      collect_changed_submodules(r, changed_submodule_names, &argv);
  
 -      for_each_string_list_item(name, &changed_submodules) {
 +      for_each_string_list_item(name, changed_submodule_names) {
                struct oid_array *commits = name->util;
                const struct submodule *submodule;
                const char *path = NULL;
                if (!path)
                        continue;
  
 -              if (!submodule_has_commits(r, path, commits))
 -                      string_list_append(&changed_submodule_names, name->string);
 +              if (submodule_has_commits(r, path, commits)) {
 +                      oid_array_clear(commits);
 +                      *name->string = '\0';
 +              }
        }
  
 -      free_submodules_oids(&changed_submodules);
 +      string_list_remove_empty_items(changed_submodule_names, 1);
 +
        argv_array_clear(&argv);
        oid_array_clear(&ref_tips_before_fetch);
        oid_array_clear(&ref_tips_after_fetch);
@@@ -1270,16 -1221,8 +1270,16 @@@ struct submodule_parallel_fetch 
        int default_option;
        int quiet;
        int result;
 +
 +      struct string_list changed_submodule_names;
 +
 +      /* Pending fetches by OIDs */
 +      struct fetch_task **oid_fetch_tasks;
 +      int oid_fetch_tasks_nr, oid_fetch_tasks_alloc;
  };
 -#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0}
 +#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0, \
 +                STRING_LIST_INIT_DUP, \
 +                NULL, 0, 0}
  
  static int get_fetch_recurse_config(const struct submodule *submodule,
                                    struct submodule_parallel_fetch *spf)
        return spf->default_option;
  }
  
 +/*
 + * Fetch in progress (if callback data) or
 + * pending (if in oid_fetch_tasks in struct submodule_parallel_fetch)
 + */
 +struct fetch_task {
 +      struct repository *repo;
 +      const struct submodule *sub;
 +      unsigned free_sub : 1; /* Do we need to free the submodule? */
 +
 +      struct oid_array *commits; /* Ensure these commits are fetched */
 +};
 +
 +/**
 + * When a submodule is not defined in .gitmodules, we cannot access it
 + * via the regular submodule-config. Create a fake submodule, which we can
 + * work on.
 + */
 +static const struct submodule *get_non_gitmodules_submodule(const char *path)
 +{
 +      struct submodule *ret = NULL;
 +      const char *name = default_name_or_path(path);
 +
 +      if (!name)
 +              return NULL;
 +
 +      ret = xmalloc(sizeof(*ret));
 +      memset(ret, 0, sizeof(*ret));
 +      ret->path = name;
 +      ret->name = name;
 +
 +      return (const struct submodule *) ret;
 +}
 +
 +static struct fetch_task *fetch_task_create(struct repository *r,
 +                                          const char *path)
 +{
 +      struct fetch_task *task = xmalloc(sizeof(*task));
 +      memset(task, 0, sizeof(*task));
 +
 +      task->sub = submodule_from_path(r, &null_oid, path);
 +      if (!task->sub) {
 +              /*
 +               * No entry in .gitmodules? Technically not a submodule,
 +               * but historically we supported repositories that happen to be
 +               * in-place where a gitlink is. Keep supporting them.
 +               */
 +              task->sub = get_non_gitmodules_submodule(path);
 +              if (!task->sub) {
 +                      free(task);
 +                      return NULL;
 +              }
 +
 +              task->free_sub = 1;
 +      }
 +
 +      return task;
 +}
 +
 +static void fetch_task_release(struct fetch_task *p)
 +{
 +      if (p->free_sub)
 +              free((void*)p->sub);
 +      p->free_sub = 0;
 +      p->sub = NULL;
 +
 +      if (p->repo)
 +              repo_clear(p->repo);
 +      FREE_AND_NULL(p->repo);
 +}
 +
 +static struct repository *get_submodule_repo_for(struct repository *r,
 +                                               const struct submodule *sub)
 +{
 +      struct repository *ret = xmalloc(sizeof(*ret));
 +
 +      if (repo_submodule_init(ret, r, sub)) {
 +              /*
 +               * No entry in .gitmodules? Technically not a submodule,
 +               * but historically we supported repositories that happen to be
 +               * in-place where a gitlink is. Keep supporting them.
 +               */
 +              struct strbuf gitdir = STRBUF_INIT;
 +              strbuf_repo_worktree_path(&gitdir, r, "%s/.git", sub->path);
 +              if (repo_init(ret, gitdir.buf, NULL)) {
 +                      strbuf_release(&gitdir);
 +                      free(ret);
 +                      return NULL;
 +              }
 +              strbuf_release(&gitdir);
 +      }
 +
 +      return ret;
 +}
 +
  static int get_next_submodule(struct child_process *cp,
                              struct strbuf *err, void *data, void **task_cb)
  {
 -      int ret = 0;
        struct submodule_parallel_fetch *spf = data;
  
        for (; spf->count < spf->r->index->cache_nr; spf->count++) {
 -              struct strbuf submodule_path = STRBUF_INIT;
 -              struct strbuf submodule_git_dir = STRBUF_INIT;
 -              struct strbuf submodule_prefix = STRBUF_INIT;
                const struct cache_entry *ce = spf->r->index->cache[spf->count];
 -              const char *git_dir, *default_argv;
 -              const struct submodule *submodule;
 -              struct submodule default_submodule = SUBMODULE_INIT;
 +              const char *default_argv;
 +              struct fetch_task *task;
  
                if (!S_ISGITLINK(ce->ce_mode))
                        continue;
  
 -              submodule = submodule_from_path(spf->r, &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;
 -                      }
 -              }
 +              task = fetch_task_create(spf->r, ce->name);
 +              if (!task)
 +                      continue;
  
 -              switch (get_fetch_recurse_config(submodule, spf))
 +              switch (get_fetch_recurse_config(task->sub, spf))
                {
                default:
                case RECURSE_SUBMODULES_DEFAULT:
                case RECURSE_SUBMODULES_ON_DEMAND:
 -                      if (!submodule || !unsorted_string_list_lookup(&changed_submodule_names,
 -                                                       submodule->name))
 +                      if (!task->sub ||
 +                          !string_list_lookup(
 +                                      &spf->changed_submodule_names,
 +                                      task->sub->name))
                                continue;
                        default_argv = "on-demand";
                        break;
                        continue;
                }
  
 -              strbuf_repo_worktree_path(&submodule_path, spf->r, "%s", ce->name);
 -              strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
 -              strbuf_addf(&submodule_prefix, "%s%s/", spf->prefix, ce->name);
 -              git_dir = read_gitfile(submodule_git_dir.buf);
 -              if (!git_dir)
 -                      git_dir = submodule_git_dir.buf;
 -              if (is_directory(git_dir)) {
 +              task->repo = get_submodule_repo_for(spf->r, task->sub);
 +              if (task->repo) {
 +                      struct strbuf submodule_prefix = STRBUF_INIT;
                        child_process_init(cp);
 -                      cp->dir = strbuf_detach(&submodule_path, NULL);
 -                      prepare_submodule_repo_env(&cp->env_array);
 +                      cp->dir = task->repo->gitdir;
 +                      prepare_submodule_repo_env_in_gitdir(&cp->env_array);
                        cp->git_cmd = 1;
                        if (!spf->quiet)
                                strbuf_addf(err, "Fetching submodule %s%s\n",
                        argv_array_pushv(&cp->args, spf->args.argv);
                        argv_array_push(&cp->args, default_argv);
                        argv_array_push(&cp->args, "--submodule-prefix");
 +
 +                      strbuf_addf(&submodule_prefix, "%s%s/",
 +                                                     spf->prefix,
 +                                                     task->sub->path);
                        argv_array_push(&cp->args, submodule_prefix.buf);
 -                      ret = 1;
 -              }
 -              strbuf_release(&submodule_path);
 -              strbuf_release(&submodule_git_dir);
 -              strbuf_release(&submodule_prefix);
 -              if (ret) {
 +
                        spf->count++;
 +                      *task_cb = task;
 +
 +                      strbuf_release(&submodule_prefix);
                        return 1;
 +              } else {
 +
 +                      fetch_task_release(task);
 +                      free(task);
 +
 +                      /*
 +                       * An empty directory is normal,
 +                       * the submodule is not initialized
 +                       */
 +                      if (S_ISGITLINK(ce->ce_mode) &&
 +                          !is_empty_dir(ce->name)) {
 +                              spf->result = 1;
 +                              strbuf_addf(err,
 +                                          _("Could not access submodule '%s'"),
 +                                          ce->name);
 +                      }
                }
        }
 +
 +      if (spf->oid_fetch_tasks_nr) {
 +              struct fetch_task *task =
 +                      spf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];
 +              struct strbuf submodule_prefix = STRBUF_INIT;
 +              spf->oid_fetch_tasks_nr--;
 +
 +              strbuf_addf(&submodule_prefix, "%s%s/",
 +                          spf->prefix, task->sub->path);
 +
 +              child_process_init(cp);
 +              prepare_submodule_repo_env_in_gitdir(&cp->env_array);
 +              cp->git_cmd = 1;
 +              cp->dir = task->repo->gitdir;
 +
 +              argv_array_init(&cp->args);
 +              argv_array_pushv(&cp->args, spf->args.argv);
 +              argv_array_push(&cp->args, "on-demand");
 +              argv_array_push(&cp->args, "--submodule-prefix");
 +              argv_array_push(&cp->args, submodule_prefix.buf);
 +
 +              /* NEEDSWORK: have get_default_remote from submodule--helper */
 +              argv_array_push(&cp->args, "origin");
 +              oid_array_for_each_unique(task->commits,
 +                                        append_oid_to_argv, &cp->args);
 +
 +              *task_cb = task;
 +              strbuf_release(&submodule_prefix);
 +              return 1;
 +      }
 +
        return 0;
  }
  
@@@ -1517,66 -1329,20 +1517,66 @@@ static int fetch_start_failure(struct s
                               void *cb, void *task_cb)
  {
        struct submodule_parallel_fetch *spf = cb;
 +      struct fetch_task *task = task_cb;
  
        spf->result = 1;
  
 +      fetch_task_release(task);
        return 0;
  }
  
 +static int commit_missing_in_sub(const struct object_id *oid, void *data)
 +{
 +      struct repository *subrepo = data;
 +
 +      enum object_type type = oid_object_info(subrepo, oid, NULL);
 +
 +      return type != OBJ_COMMIT;
 +}
 +
  static int fetch_finish(int retvalue, struct strbuf *err,
                        void *cb, void *task_cb)
  {
        struct submodule_parallel_fetch *spf = cb;
 +      struct fetch_task *task = task_cb;
 +
 +      struct string_list_item *it;
 +      struct oid_array *commits;
  
        if (retvalue)
                spf->result = 1;
  
 +      if (!task || !task->sub)
 +              BUG("callback cookie bogus");
 +
 +      /* Is this the second time we process this submodule? */
 +      if (task->commits)
 +              goto out;
 +
 +      it = string_list_lookup(&spf->changed_submodule_names, task->sub->name);
 +      if (!it)
 +              /* Could be an unchanged submodule, not contained in the list */
 +              goto out;
 +
 +      commits = it->util;
 +      oid_array_filter(commits,
 +                       commit_missing_in_sub,
 +                       task->repo);
 +
 +      /* Are there commits we want, but do not exist? */
 +      if (commits->nr) {
 +              task->commits = commits;
 +              ALLOC_GROW(spf->oid_fetch_tasks,
 +                         spf->oid_fetch_tasks_nr + 1,
 +                         spf->oid_fetch_tasks_alloc);
 +              spf->oid_fetch_tasks[spf->oid_fetch_tasks_nr] = task;
 +              spf->oid_fetch_tasks_nr++;
 +              return 0;
 +      }
 +
 +out:
 +      fetch_task_release(task);
 +
        return 0;
  }
  
@@@ -1607,8 -1373,7 +1607,8 @@@ int fetch_populated_submodules(struct r
        argv_array_push(&spf.args, "--recurse-submodules-default");
        /* default value, "--submodule-prefix" and its value are added later */
  
 -      calculate_changed_submodule_paths(r);
 +      calculate_changed_submodule_paths(r, &spf.changed_submodule_names);
 +      string_list_sort(&spf.changed_submodule_names);
        run_processes_parallel(max_parallel_jobs,
                               get_next_submodule,
                               fetch_start_failure,
  
        argv_array_clear(&spf.args);
  out:
 -      string_list_clear(&changed_submodule_names, 1);
 +      free_submodules_oids(&spf.changed_submodule_names);
        return spf.result;
  }
  
        return ret;
  }
  
 +void submodule_unset_core_worktree(const struct submodule *sub)
 +{
 +      char *config_path = xstrfmt("%s/modules/%s/config",
 +                                  get_git_common_dir(), sub->name);
 +
 +      if (git_config_set_in_file_gently(config_path, "core.worktree", NULL))
 +              warning(_("Could not unset core.worktree setting in submodule '%s'"),
 +                        sub->path);
 +
 +      free(config_path);
 +}
 +
  static const char *get_super_prefix_or_empty(void)
  {
        const char *s = get_super_prefix();
@@@ -1973,14 -1726,53 +1973,55 @@@ int submodule_move_head(const char *pat
  
                        if (is_empty_dir(path))
                                rmdir_or_warn(path);
 +
 +                      submodule_unset_core_worktree(sub);
                }
        }
  out:
        return ret;
  }
  
+ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
+ {
+       size_t len = strlen(git_dir), suffix_len = strlen(submodule_name);
+       char *p;
+       int ret = 0;
+       if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' ||
+           strcmp(p, submodule_name))
+               BUG("submodule name '%s' not a suffix of git dir '%s'",
+                   submodule_name, git_dir);
+       /*
+        * We prevent the contents of sibling submodules' git directories to
+        * clash.
+        *
+        * Example: having a submodule named `hippo` and another one named
+        * `hippo/hooks` would result in the git directories
+        * `.git/modules/hippo/` and `.git/modules/hippo/hooks/`, respectively,
+        * but the latter directory is already designated to contain the hooks
+        * of the former.
+        */
+       for (; *p; p++) {
+               if (is_dir_sep(*p)) {
+                       char c = *p;
+                       *p = '\0';
+                       if (is_git_directory(git_dir))
+                               ret = -1;
+                       *p = c;
+                       if (ret < 0)
+                               return error(_("submodule git dir '%s' is "
+                                              "inside git dir '%.*s'"),
+                                            git_dir,
+                                            (int)(p - git_dir), git_dir);
+               }
+       }
+       return 0;
+ }
  /*
   * Embeds a single submodules git directory into the superprojects git dir,
   * non recursively.
@@@ -1989,7 -1781,7 +2030,7 @@@ static void relocate_single_git_dir_int
                                                      const char *path)
  {
        char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL;
-       const char *new_git_dir;
+       char *new_git_dir;
        const struct submodule *sub;
  
        if (submodule_uses_worktrees(path))
        if (!sub)
                die(_("could not lookup name for submodule '%s'"), path);
  
-       new_git_dir = git_path("modules/%s", sub->name);
+       new_git_dir = git_pathdup("modules/%s", sub->name);
+       if (validate_submodule_git_dir(new_git_dir, sub->name) < 0)
+               die(_("refusing to move '%s' into an existing git dir"),
+                   real_old_git_dir);
        if (safe_create_leading_directories_const(new_git_dir) < 0)
                die(_("could not create directory '%s'"), new_git_dir);
        real_new_git_dir = real_pathdup(new_git_dir, 1);
+       free(new_git_dir);
  
        fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
                get_super_prefix_or_empty(), path,
diff --combined submodule.h
index 9e18e9b80760ad1562682125bc90d08fb6e1c0d3,ac206dc182a8e65d8fa066ece747593bf8b781ea..4fd2db532932928cba075afd4978c4074f736b9c
@@@ -124,6 -124,11 +124,11 @@@ int push_unpushed_submodules(struct rep
   */
  int submodule_to_gitdir(struct strbuf *buf, const char *submodule);
  
+ /*
+  * Make sure that no submodule's git dir is nested in a sibling submodule's.
+  */
+ int validate_submodule_git_dir(char *git_dir, const char *submodule_name);
  #define SUBMODULE_MOVE_HEAD_DRY_RUN (1<<0)
  #define SUBMODULE_MOVE_HEAD_FORCE   (1<<1)
  int submodule_move_head(const char *path,
                        const char *new_head,
                        unsigned flags);
  
 +void submodule_unset_core_worktree(const struct submodule *sub);
 +
  /*
   * Prepare the "env_array" parameter of a "struct child_process" for executing
   * a submodule by clearing any repo-specific environment variables, but
index 5d543ad21f89c76534e8efe32d6ead512dc7d0c3,e737a941d3bc1d58c768b2e2819d0759e17f83c2..409034cf4eef59363653b39cf3aae3fc37c07856
@@@ -177,14 -177,99 +177,107 @@@ static int is_dotgitmodules(const char 
        return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path);
  }
  
 +static int cmp_by_st_size(const void *a, const void *b)
 +{
 +      intptr_t x = (intptr_t)((struct string_list_item *)a)->util;
 +      intptr_t y = (intptr_t)((struct string_list_item *)b)->util;
 +
 +      return x > y ? -1 : (x < y ? +1 : 0);
 +}
 +
+ /*
+  * A very simple, reproducible pseudo-random generator. Copied from
+  * `test-genrandom.c`.
+  */
+ static uint64_t my_random_value = 1234;
+ static uint64_t my_random(void)
+ {
+       my_random_value = my_random_value * 1103515245 + 12345;
+       return my_random_value;
+ }
+ /*
+  * A fast approximation of the square root, without requiring math.h.
+  *
+  * It uses Newton's method to approximate the solution of 0 = x^2 - value.
+  */
+ static double my_sqrt(double value)
+ {
+       const double epsilon = 1e-6;
+       double x = value;
+       if (value == 0)
+               return 0;
+       for (;;) {
+               double delta = (value / x - x) / 2;
+               if (delta < epsilon && delta > -epsilon)
+                       return x + delta;
+               x += delta;
+       }
+ }
+ static int protect_ntfs_hfs_benchmark(int argc, const char **argv)
+ {
+       size_t i, j, nr, min_len = 3, max_len = 20;
+       char **names;
+       int repetitions = 15, file_mode = 0100644;
+       uint64_t begin, end;
+       double m[3][2], v[3][2];
+       uint64_t cumul;
+       double cumul2;
+       if (argc > 1 && !strcmp(argv[1], "--with-symlink-mode")) {
+               file_mode = 0120000;
+               argc--;
+               argv++;
+       }
+       nr = argc > 1 ? strtoul(argv[1], NULL, 0) : 1000000;
+       ALLOC_ARRAY(names, nr);
+       if (argc > 2) {
+               min_len = strtoul(argv[2], NULL, 0);
+               if (argc > 3)
+                       max_len = strtoul(argv[3], NULL, 0);
+               if (min_len > max_len)
+                       die("min_len > max_len");
+       }
+       for (i = 0; i < nr; i++) {
+               size_t len = min_len + (my_random() % (max_len + 1 - min_len));
+               names[i] = xmallocz(len);
+               while (len > 0)
+                       names[i][--len] = (char)(' ' + (my_random() % ('\x7f' - ' ')));
+       }
+       for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++)
+               for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) {
+                       cumul = 0;
+                       cumul2 = 0;
+                       for (i = 0; i < repetitions; i++) {
+                               begin = getnanotime();
+                               for (j = 0; j < nr; j++)
+                                       verify_path(names[j], file_mode);
+                               end = getnanotime();
+                               printf("protect_ntfs = %d, protect_hfs = %d: %lfms\n", protect_ntfs, protect_hfs, (end-begin) / (double)1e6);
+                               cumul += end - begin;
+                               cumul2 += (end - begin) * (end - begin);
+                       }
+                       m[protect_ntfs][protect_hfs] = cumul / (double)repetitions;
+                       v[protect_ntfs][protect_hfs] = my_sqrt(cumul2 / (double)repetitions - m[protect_ntfs][protect_hfs] * m[protect_ntfs][protect_hfs]);
+                       printf("mean: %lfms, stddev: %lfms\n", m[protect_ntfs][protect_hfs] / (double)1e6, v[protect_ntfs][protect_hfs] / (double)1e6);
+               }
+       for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++)
+               for (protect_hfs = 0; protect_hfs < 2; protect_hfs++)
+                       printf("ntfs=%d/hfs=%d: %lf%% slower\n", protect_ntfs, protect_hfs, (m[protect_ntfs][protect_hfs] - m[0][0]) * 100 / m[0][0]);
+       return 0;
+ }
  int cmd__path_utils(int argc, const char **argv)
  {
        if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
                return !!res;
        }
  
 +      if (argc > 2 && !strcmp(argv[1], "file-size")) {
 +              int res = 0, i;
 +              struct stat st;
 +
 +              for (i = 2; i < argc; i++)
 +                      if (stat(argv[i], &st))
 +                              res = error_errno("Cannot stat '%s'", argv[i]);
 +                      else
 +                              printf("%"PRIuMAX"\n", (uintmax_t)st.st_size);
 +              return !!res;
 +      }
 +
 +      if (argc == 4 && !strcmp(argv[1], "skip-n-bytes")) {
 +              int fd = open(argv[2], O_RDONLY), offset = atoi(argv[3]);
 +              char buffer[65536];
 +
 +              if (fd < 0)
 +                      die_errno("could not open '%s'", argv[2]);
 +              if (lseek(fd, offset, SEEK_SET) < 0)
 +                      die_errno("could not skip %d bytes", offset);
 +              for (;;) {
 +                      ssize_t count = read(fd, buffer, sizeof(buffer));
 +                      if (count < 0)
 +                              die_errno("could not read '%s'", argv[2]);
 +                      if (!count)
 +                              break;
 +                      if (write(1, buffer, count) < 0)
 +                              die_errno("could not write to stdout");
 +              }
 +              close(fd);
 +              return 0;
 +      }
 +
 +      if (argc > 5 && !strcmp(argv[1], "slice-tests")) {
 +              int res = 0;
 +              long offset, stride, i;
 +              struct string_list list = STRING_LIST_INIT_NODUP;
 +              struct stat st;
 +
 +              offset = strtol(argv[2], NULL, 10);
 +              stride = strtol(argv[3], NULL, 10);
 +              if (stride < 1)
 +                      stride = 1;
 +              for (i = 4; i < argc; i++)
 +                      if (stat(argv[i], &st))
 +                              res = error_errno("Cannot stat '%s'", argv[i]);
 +                      else
 +                              string_list_append(&list, argv[i])->util =
 +                                      (void *)(intptr_t)st.st_size;
 +              QSORT(list.items, list.nr, cmp_by_st_size);
 +              for (i = offset; i < list.nr; i+= stride)
 +                      printf("%s\n", list.items[i].string);
 +
 +              return !!res;
 +      }
 +
+       if (argc > 1 && !strcmp(argv[1], "protect_ntfs_hfs"))
+               return !!protect_ntfs_hfs_benchmark(argc - 1, argv + 1);
+       if (argc > 1 && !strcmp(argv[1], "is_valid_path")) {
+               int res = 0, expect = 1, i;
+               for (i = 2; i < argc; i++)
+                       if (!strcmp("--not", argv[i]))
+                               expect = 0;
+                       else if (expect != is_valid_path(argv[i]))
+                               res = error("'%s' is%s a valid path",
+                                           argv[i], expect ? " not" : "");
+                       else
+                               fprintf(stderr,
+                                       "'%s' is%s a valid path\n",
+                                       argv[i], expect ? "" : " not");
+               return !!res;
+       }
        fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
                argv[1] ? argv[1] : "(there was none)");
        return 1;
diff --combined t/t1450-fsck.sh
index c61f97214137f5fcf5a9d6b7dd98ea8162e9916d,c767e2783b50fce0f60254b1b7e1b27a82587eea..e61d4acd8321955fd5437ad1fece23c86e113f2e
@@@ -70,7 -70,7 +70,7 @@@ test_expect_success 'object with bad sh
  
        test_must_fail git fsck 2>out &&
        cat out &&
 -      grep "$sha.*corrupt" out
 +      test_i18ngrep "$sha.*corrupt" out
  '
  
  test_expect_success 'branch pointing to non-commit' '
@@@ -78,7 -78,7 +78,7 @@@
        test_when_finished "git update-ref -d refs/heads/invalid" &&
        test_must_fail git fsck 2>out &&
        cat out &&
 -      grep "not a commit" out
 +      test_i18ngrep "not a commit" out
  '
  
  test_expect_success 'HEAD link pointing at a funny object' '
@@@ -88,7 -88,7 +88,7 @@@
        # avoid corrupt/broken HEAD from interfering with repo discovery
        test_must_fail env GIT_DIR=.git git fsck 2>out &&
        cat out &&
 -      grep "detached HEAD points" out
 +      test_i18ngrep "detached HEAD points" out
  '
  
  test_expect_success 'HEAD link pointing at a funny place' '
@@@ -98,7 -98,7 +98,7 @@@
        # avoid corrupt/broken HEAD from interfering with repo discovery
        test_must_fail env GIT_DIR=.git git fsck 2>out &&
        cat out &&
 -      grep "HEAD points to something strange" out
 +      test_i18ngrep "HEAD points to something strange" out
  '
  
  test_expect_success 'HEAD link pointing at a funny object (from different wt)' '
        echo $ZERO_OID >.git/HEAD &&
        # avoid corrupt/broken HEAD from interfering with repo discovery
        test_must_fail git -C wt fsck 2>out &&
 -      grep "main-worktree/HEAD: detached HEAD points" out
 +      test_i18ngrep "main-worktree/HEAD: detached HEAD points" out
  '
  
  test_expect_success 'other worktree HEAD link pointing at a funny object' '
        git worktree add other &&
        echo $ZERO_OID >.git/worktrees/other/HEAD &&
        test_must_fail git fsck 2>out &&
 -      grep "worktrees/other/HEAD: detached HEAD points" out
 +      test_i18ngrep "worktrees/other/HEAD: detached HEAD points" out
  '
  
  test_expect_success 'other worktree HEAD link pointing at missing object' '
        git worktree add other &&
        echo "Contents missing from repo" | git hash-object --stdin >.git/worktrees/other/HEAD &&
        test_must_fail git fsck 2>out &&
 -      grep "worktrees/other/HEAD: invalid sha1 pointer" out
 +      test_i18ngrep "worktrees/other/HEAD: invalid sha1 pointer" out
  '
  
  test_expect_success 'other worktree HEAD link pointing at a funny place' '
        git worktree add other &&
        echo "ref: refs/funny/place" >.git/worktrees/other/HEAD &&
        test_must_fail git fsck 2>out &&
 -      grep "worktrees/other/HEAD points to something strange" out
 +      test_i18ngrep "worktrees/other/HEAD points to something strange" out
  '
  
  test_expect_success 'email without @ is okay' '
@@@ -157,7 -157,7 +157,7 @@@ test_expect_success 'email with embedde
        test_when_finished "git update-ref -d refs/heads/bogus" &&
        test_must_fail git fsck 2>out &&
        cat out &&
 -      grep "error in commit $new" out
 +      test_i18ngrep "error in commit $new" out
  '
  
  test_expect_success 'missing < email delimiter is reported nicely' '
        test_when_finished "git update-ref -d refs/heads/bogus" &&
        test_must_fail git fsck 2>out &&
        cat out &&
 -      grep "error in commit $new.* - bad name" out
 +      test_i18ngrep "error in commit $new.* - bad name" out
  '
  
  test_expect_success 'missing email is reported nicely' '
        test_when_finished "git update-ref -d refs/heads/bogus" &&
        test_must_fail git fsck 2>out &&
        cat out &&
 -      grep "error in commit $new.* - missing email" out
 +      test_i18ngrep "error in commit $new.* - missing email" out
  '
  
  test_expect_success '> in name is reported' '
        test_when_finished "git update-ref -d refs/heads/bogus" &&
        test_must_fail git fsck 2>out &&
        cat out &&
 -      grep "error in commit $new" out
 +      test_i18ngrep "error in commit $new" out
  '
  
  # date is 2^64 + 1
@@@ -207,7 -207,7 +207,7 @@@ test_expect_success 'integer overflow i
        test_when_finished "git update-ref -d refs/heads/bogus" &&
        test_must_fail git fsck 2>out &&
        cat out &&
 -      grep "error in commit $new.*integer overflow" out
 +      test_i18ngrep "error in commit $new.*integer overflow" out
  '
  
  test_expect_success 'commit with NUL in header' '
        test_when_finished "git update-ref -d refs/heads/bogus" &&
        test_must_fail git fsck 2>out &&
        cat out &&
 -      grep "error in commit $new.*unterminated header: NUL at offset" out
 +      test_i18ngrep "error in commit $new.*unterminated header: NUL at offset" out
  '
  
  test_expect_success 'tree object with duplicate entries' '
                git hash-object -w -t tree --stdin
        ) &&
        test_must_fail git fsck 2>out &&
 -      grep "error in tree .*contains duplicate file entries" out
 +      test_i18ngrep "error in tree .*contains duplicate file entries" out
  '
  
  test_expect_success 'unparseable tree object' '
@@@ -294,7 -294,7 +294,7 @@@ test_expect_success 'tag pointing to no
        test_when_finished "git update-ref -d refs/tags/invalid" &&
        test_must_fail git fsck --tags >out &&
        cat out &&
 -      grep "broken link" out
 +      test_i18ngrep "broken link" out
  '
  
  test_expect_success 'tag pointing to something else than its type' '
@@@ -336,7 -336,7 +336,7 @@@ test_expect_success 'tag with incorrec
        warning in tag $tag: badTagName: invalid '\''tag'\'' name: wrong name format
        warning in tag $tag: missingTaggerEntry: invalid format - expected '\''tagger'\'' line
        EOF
 -      test_cmp expect out
 +      test_i18ncmp expect out
  '
  
  test_expect_success 'tag with bad tagger' '
        echo $tag >.git/refs/tags/wrong &&
        test_when_finished "git update-ref -d refs/tags/wrong" &&
        test_must_fail git fsck --tags 2>out &&
 -      grep "error in tag .*: invalid author/committer" out
 +      test_i18ngrep "error in tag .*: invalid author/committer" out
  '
  
  test_expect_success 'tag with NUL in header' '
        test_when_finished "git update-ref -d refs/tags/wrong" &&
        test_must_fail git fsck --tags 2>out &&
        cat out &&
 -      grep "error in tag $tag.*unterminated header: NUL at offset" out
 +      test_i18ngrep "error in tag $tag.*unterminated header: NUL at offset" out
  '
  
  test_expect_success 'cleaned up' '
@@@ -406,7 -406,7 +406,7 @@@ test_expect_success 'rev-list --verify-
  
        test_might_fail git rev-list --verify-objects refs/heads/bogus >/dev/null 2>out &&
        cat out &&
 -      test_i18ngrep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out
 +      test_i18ngrep -q "error: hash mismatch 63ffffffffffffffffffffffffffffffffffffff" out
  '
  
  test_expect_success 'force fsck to ignore double author' '
@@@ -431,7 -431,7 +431,7 @@@ test_expect_success 'fsck notices blob 
               git hash-object -w --stdin -t tree) &&
          git fsck 2>out &&
          cat out &&
 -        grep "warning.*null sha1" out
 +        test_i18ngrep "warning.*null sha1" out
        )
  '
  
@@@ -442,7 -442,7 +442,7 @@@ test_expect_success 'fsck notices submo
               git hash-object -w --stdin -t tree) &&
          git fsck 2>out &&
          cat out &&
 -        grep "warning.*null sha1" out
 +        test_i18ngrep "warning.*null sha1" out
        )
  '
  
@@@ -453,6 -453,7 +453,7 @@@ while read name path pretty; d
                (
                        git init $name-$type &&
                        cd $name-$type &&
+                       git config core.protectNTFS false &&
                        echo content >file &&
                        git add file &&
                        git commit -m base &&
                        bad_tree=$(git mktree <bad) &&
                        git fsck 2>out &&
                        cat out &&
 -                      grep "warning.*tree $bad_tree" out
 +                      test_i18ngrep "warning.*tree $bad_tree" out
                )'
        done <<-\EOF
        100644 blob
@@@ -509,9 -510,9 +510,9 @@@ test_expect_success 'NUL in commit' 
                git branch bad $(cat name) &&
  
                test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 &&
 -              grep nulInCommit warn.1 &&
 +              test_i18ngrep nulInCommit warn.1 &&
                git fsck 2>warn.2 &&
 -              grep nulInCommit warn.2
 +              test_i18ngrep nulInCommit warn.2
        )
  '
  
@@@ -629,7 -630,7 +630,7 @@@ test_expect_success 'fsck --name-object
                remove_object $(git rev-parse julius:caesar.t) &&
                test_must_fail git fsck --name-objects >out &&
                tree=$(git rev-parse --verify julius:) &&
 -              egrep "$tree \((refs/heads/master|HEAD)@\{[0-9]*\}:" out
 +              test_i18ngrep -E "$tree \((refs/heads/master|HEAD)@\{[0-9]*\}:" out
        )
  '
  
@@@ -640,7 -641,7 +641,7 @@@ test_expect_success 'alternate objects 
        mkdir alt.git/objects/12 &&
        >alt.git/objects/12/34567890123456789012345678901234567890 &&
        test_must_fail git fsck >out 2>&1 &&
 -      grep alt.git out
 +      test_i18ngrep alt.git out
  '
  
  test_expect_success 'fsck errors in packed objects' '
        remove_object $one &&
        remove_object $two &&
        test_must_fail git fsck 2>out &&
 -      grep "error in commit $one.* - bad name" out &&
 -      grep "error in commit $two.* - bad name" out &&
 +      test_i18ngrep "error in commit $one.* - bad name" out &&
 +      test_i18ngrep "error in commit $two.* - bad name" out &&
        ! grep corrupt out
  '
  
@@@ -760,7 -761,7 +761,7 @@@ test_expect_success 'fsck notices dangl
                git fsck >actual &&
                # the output order is non-deterministic, as it comes from a hash
                sort <actual >actual.sorted &&
 -              test_cmp expect actual.sorted
 +              test_i18ncmp expect actual.sorted
        )
  '
  
@@@ -808,7 -809,7 +809,7 @@@ test_expect_success 'detect corrupt ind
        test_when_finished "mv .git/index.backup .git/index" &&
        corrupt_index_checksum &&
        test_must_fail git fsck --cache 2>errors &&
 -      grep "bad index file" errors
 +      test_i18ngrep "bad index file" errors
  '
  
  test_done
index 49a37efe9c1d12567dab3997bd8307896ccaa01c,140ea8cb8d97f0b5399c2d705bc62e8973a3b639..0f040e30a43cc027d521ebfb2944ba7a8139f2af
@@@ -154,7 -154,7 +154,7 @@@ test_expect_success 'fsck detects symli
                # symlink detector; this grep string comes from the config
                # variable name and will not be translated.
                test_must_fail git fsck 2>output &&
 -              grep gitmodulesSymlink output
 +              test_i18ngrep gitmodulesSymlink output
        )
  '
  
@@@ -172,7 -172,7 +172,7 @@@ test_expect_success 'fsck detects non-b
                git ls-tree HEAD | sed s/subdir/.gitmodules/ | git mktree &&
  
                test_must_fail git fsck 2>output &&
 -              grep gitmodulesBlob output
 +              test_i18ngrep gitmodulesBlob output
        )
  '
  
@@@ -186,9 -186,65 +186,65 @@@ test_expect_success 'fsck detects corru
                git commit -m "broken gitmodules" &&
  
                git fsck 2>output &&
 -              grep gitmodulesParse output &&
 +              test_i18ngrep gitmodulesParse output &&
                test_i18ngrep ! "bad config" output
        )
  '
  
+ test_expect_success MINGW 'prevent git~1 squatting on Windows' '
+       git init squatting &&
+       (
+               cd squatting &&
+               mkdir a &&
+               touch a/..git &&
+               git add a/..git &&
+               test_tick &&
+               git commit -m initial &&
+               modules="$(test_write_lines \
+                       "[submodule \"b.\"]" "url = ." "path = c" \
+                       "[submodule \"b\"]" "url = ." "path = d\\\\a" |
+                       git hash-object -w --stdin)" &&
+               rev="$(git rev-parse --verify HEAD)" &&
+               hash="$(echo x | git hash-object -w --stdin)" &&
+               git -c core.protectNTFS=false update-index --add \
+                       --cacheinfo 100644,$modules,.gitmodules \
+                       --cacheinfo 160000,$rev,c \
+                       --cacheinfo 160000,$rev,d\\a \
+                       --cacheinfo 100644,$hash,d./a/x \
+                       --cacheinfo 100644,$hash,d./a/..git &&
+               test_tick &&
+               git -c core.protectNTFS=false commit -m "module" &&
+               test_must_fail git show HEAD: 2>err &&
+               test_i18ngrep backslash err
+       ) &&
+       test_must_fail git -c core.protectNTFS=false \
+               clone --recurse-submodules squatting squatting-clone 2>err &&
+       test_i18ngrep -e "directory not empty" -e "not an empty directory" err &&
+       ! grep gitdir squatting-clone/d/a/git~2
+ '
+ test_expect_success 'git dirs of sibling submodules must not be nested' '
+       git init nested &&
+       test_commit -C nested nested &&
+       (
+               cd nested &&
+               cat >.gitmodules <<-EOF &&
+               [submodule "hippo"]
+                       url = .
+                       path = thing1
+               [submodule "hippo/hooks"]
+                       url = .
+                       path = thing2
+               EOF
+               git clone . thing1 &&
+               git clone . thing2 &&
+               git add .gitmodules thing1 thing2 &&
+               test_tick &&
+               git commit -m nested
+       ) &&
+       test_must_fail git clone --recurse-submodules nested clone 2>err &&
+       test_i18ngrep -E "(is inside git dir|hippo already exists|not a git repository: .*/hippo)" err
+ '
  test_done
diff --combined t/t9350-fast-export.sh
index 5690fe28106624f3d666ccdda1dbcdd58673fb13,ef1e01805be86a4574ec8ea84bfc123baa9d67f5..cd4bb9352eec4ae1065bc1691c5d50e12059d94f
@@@ -66,34 -66,6 +66,34 @@@ test_expect_success 'fast-export master
  
  '
  
 +test_expect_success 'fast-export --reference-excluded-parents master~2..master' '
 +
 +      git fast-export --reference-excluded-parents master~2..master >actual &&
 +      grep commit.refs/heads/master actual >commit-count &&
 +      test_line_count = 2 commit-count &&
 +      sed "s/master/rewrite/" actual |
 +              (cd new &&
 +               git fast-import &&
 +               test $MASTER = $(git rev-parse --verify refs/heads/rewrite))
 +'
 +
 +test_expect_success 'fast-export --show-original-ids' '
 +
 +      git fast-export --show-original-ids master >output &&
 +      grep ^original-oid output| sed -e s/^original-oid.// | sort >actual &&
 +      git rev-list --objects master muss >objects-and-names &&
 +      awk "{print \$1}" objects-and-names | sort >commits-trees-blobs &&
 +      comm -23 actual commits-trees-blobs >unfound &&
 +      test_must_be_empty unfound
 +'
 +
 +test_expect_success 'fast-export --show-original-ids | git fast-import' '
 +
 +      git fast-export --show-original-ids master muss | git fast-import --quiet &&
 +      test $MASTER = $(git rev-parse --verify refs/heads/master) &&
 +      test $MUSS = $(git rev-parse --verify refs/tags/muss)
 +'
 +
  test_expect_success 'iso-8859-1' '
  
        git config i18n.commitencoding ISO8859-1 &&
@@@ -353,22 -325,6 +353,22 @@@ test_expect_success 'rewriting tag of f
  )
  '
  
 +test_expect_success 'rewrite tag predating pathspecs to nothing' '
 +      test_create_repo rewrite_tag_predating_pathspecs &&
 +      (
 +              cd rewrite_tag_predating_pathspecs &&
 +
 +              test_commit initial &&
 +
 +              git tag -a -m "Some old tag" v0.0.0.0.0.0.1 &&
 +
 +              test_commit bar &&
 +
 +              git fast-export --tag-of-filtered-object=rewrite --all -- bar.t >output &&
 +              grep from.$ZERO_OID output
 +      )
 +'
 +
  cat > limit-by-paths/expected << EOF
  blob
  mark :1
@@@ -410,26 -366,6 +410,26 @@@ test_expect_success 'path limiting wit
        grep file0 actual
  '
  
 +test_expect_success 'avoid corrupt stream with non-existent mark' '
 +      test_create_repo avoid_non_existent_mark &&
 +      (
 +              cd avoid_non_existent_mark &&
 +
 +              test_commit important-path &&
 +
 +              test_commit ignored &&
 +
 +              git branch A &&
 +              git branch B &&
 +
 +              echo foo >>important-path.t &&
 +              git add important-path.t &&
 +              test_commit more changes &&
 +
 +              git fast-export --all -- important-path.t | git fast-import --force
 +      )
 +'
 +
  test_expect_success 'full-tree re-shows unmodified files'        '
        git checkout -f simple &&
        git fast-export --full-tree simple >actual &&
@@@ -482,9 -418,10 +482,10 @@@ test_expect_success 'directory becomes 
  
  test_expect_success 'fast-export quotes pathnames' '
        git init crazy-paths &&
+       test_config -C crazy-paths core.protectNTFS false &&
        (cd crazy-paths &&
         blob=$(echo foo | git hash-object -w --stdin) &&
-        git update-index --add \
+        git -c core.protectNTFS=false update-index --add \
                --cacheinfo 100644 $blob "$(printf "path with\\nnewline")" \
                --cacheinfo 100644 $blob "path with \"quote\"" \
                --cacheinfo 100644 $blob "path with \\backslash" \
@@@ -572,20 -509,10 +573,20 @@@ test_expect_success 'use refspec' 
        test_cmp expected actual
  '
  
 -test_expect_success 'delete refspec' '
 +test_expect_success 'delete ref because entire history excluded' '
        git branch to-delete &&
 -      git fast-export --refspec :refs/heads/to-delete to-delete ^to-delete > actual &&
 -      cat > expected <<-EOF &&
 +      git fast-export to-delete ^to-delete >actual &&
 +      cat >expected <<-EOF &&
 +      reset refs/heads/to-delete
 +      from 0000000000000000000000000000000000000000
 +
 +      EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'delete refspec' '
 +      git fast-export --refspec :refs/heads/to-delete >actual &&
 +      cat >expected <<-EOF &&
        reset refs/heads/to-delete
        from 0000000000000000000000000000000000000000
  
diff --combined transport-helper.c
index 1f52c95fd87b1a02968727bde71dcc1eddc50410,f79221cc9495b8766f190234377b0a113f6f1d70..d4683c4d0fb2c416e91c36229cf0e513e1639aea
@@@ -423,6 -423,7 +423,7 @@@ static int get_importer(struct transpor
        child_process_init(fastimport);
        fastimport->in = helper->out;
        argv_array_push(&fastimport->args, "fast-import");
+       argv_array_push(&fastimport->args, "--allow-unsafe-features");
        argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet");
  
        if (data->bidi_import) {
@@@ -679,15 -680,10 +680,15 @@@ static int fetch(struct transport *tran
        if (data->transport_options.update_shallow)
                set_helper_option(transport, "update-shallow", "true");
  
 -      if (data->transport_options.filter_options.choice)
 -              set_helper_option(
 -                      transport, "filter",
 -                      data->transport_options.filter_options.filter_spec);
 +      if (data->transport_options.filter_options.choice) {
 +              struct strbuf expanded_filter_spec = STRBUF_INIT;
 +              expand_list_objects_filter_spec(
 +                      &data->transport_options.filter_options,
 +                      &expanded_filter_spec);
 +              set_helper_option(transport, "filter",
 +                                expanded_filter_spec.buf);
 +              strbuf_release(&expanded_filter_spec);
 +      }
  
        if (data->transport_options.negotiation_tips)
                warning("Ignoring --negotiation-tip because the protocol does not support it.");
@@@ -1031,8 -1027,7 +1032,8 @@@ static int push_refs(struct transport *
  }
  
  
 -static int has_attribute(const char *attrs, const char *attr) {
 +static int has_attribute(const char *attrs, const char *attr)
 +{
        int len;
        if (!attrs)
                return 0;
@@@ -1231,8 -1226,9 +1232,8 @@@ static int udt_do_read(struct unidirect
                return 0;       /* No space for more. */
  
        transfer_debug("%s is readable", t->src_name);
 -      bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
 -      if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
 -              errno != EINTR) {
 +      bytes = xread(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
 +      if (bytes < 0) {
                error_errno(_("read(%s) failed"), t->src_name);
                return -1;
        } else if (bytes == 0) {
@@@ -1259,7 -1255,7 +1260,7 @@@ static int udt_do_write(struct unidirec
  
        transfer_debug("%s is writable", t->dest_name);
        bytes = xwrite(t->dest, t->buf, t->bufuse);
 -      if (bytes < 0 && errno != EWOULDBLOCK) {
 +      if (bytes < 0) {
                error_errno(_("write(%s) failed"), t->dest_name);
                return -1;
        } else if (bytes > 0) {
diff --combined tree-walk.c
index 1e4bbc8a0e48c6afe61dc8aab76488356d7e1cbc,bf07946ec49cf1fc0c25f8a9f46b1e48e1b9635e..bf6f23397a39385bc500ddbcb6e6bed2709c21e5
@@@ -43,13 -43,18 +43,19 @@@ static int decode_tree_entry(struct tre
                strbuf_addstr(err, _("empty filename in tree entry"));
                return -1;
        }
+ #ifdef GIT_WINDOWS_NATIVE
+       if (protect_ntfs && strchr(path, '\\')) {
+               strbuf_addf(err, _("filename in tree entry contains backslash: '%s'"), path);
+               return -1;
+       }
+ #endif
        len = strlen(path) + 1;
  
        /* Initialize the descriptor entry */
        desc->entry.path = path;
        desc->entry.mode = canon_mode(mode);
 -      desc->entry.oid  = (const struct object_id *)(path + len);
 +      desc->entry.pathlen = len - 1;
 +      hashcpy(desc->entry.oid.hash, (const unsigned char *)path + len);
  
        return 0;
  }
@@@ -108,7 -113,7 +114,7 @@@ static void entry_extract(struct tree_d
  static int update_tree_entry_internal(struct tree_desc *desc, struct strbuf *err)
  {
        const void *buf = desc->buffer;
 -      const unsigned char *end = desc->entry.oid->hash + the_hash_algo->rawsz;
 +      const unsigned char *end = (const unsigned char *)desc->entry.path + desc->entry.pathlen + 1 + the_hash_algo->rawsz;
        unsigned long size = desc->size;
        unsigned long len = end - (const unsigned char *)buf;
  
@@@ -176,11 -181,9 +182,11 @@@ void setup_traverse_info(struct travers
                pathlen--;
        info->pathlen = pathlen ? pathlen + 1 : 0;
        info->name.path = base;
 -      info->name.oid = (void *)(base + pathlen + 1);
 -      if (pathlen)
 +      info->name.pathlen = pathlen;
 +      if (pathlen) {
 +              hashcpy(info->name.oid.hash, (const unsigned char *)base + pathlen + 1);
                info->prev = &dummy;
 +      }
  }
  
  char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n)
@@@ -368,8 -371,7 +374,8 @@@ static void free_extended_entry(struct 
        }
  }
  
 -static inline int prune_traversal(struct name_entry *e,
 +static inline int prune_traversal(struct index_state *istate,
 +                                struct name_entry *e,
                                  struct traverse_info *info,
                                  struct strbuf *base,
                                  int still_interesting)
                return 2;
        if (still_interesting < 0)
                return still_interesting;
 -      return tree_entry_interesting(e, base, 0, info->pathspec);
 +      return tree_entry_interesting(istate, e, base,
 +                                    0, info->pathspec);
  }
  
 -int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
 +int traverse_trees(struct index_state *istate,
 +                 int n, struct tree_desc *t,
 +                 struct traverse_info *info)
  {
        int error = 0;
        struct name_entry *entry = xmalloc(n*sizeof(*entry));
                }
                if (!mask)
                        break;
 -              interesting = prune_traversal(e, info, &base, interesting);
 +              interesting = prune_traversal(istate, e, info, &base, interesting);
                if (interesting < 0)
                        break;
                if (interesting) {
@@@ -505,10 -504,10 +511,10 @@@ static int find_tree_entry(struct tree_
        int namelen = strlen(name);
        while (t->size) {
                const char *entry;
 -              const struct object_id *oid;
 +              struct object_id oid;
                int entrylen, cmp;
  
 -              oid = tree_entry_extract(t, &entry, mode);
 +              oidcpy(&oid, tree_entry_extract(t, &entry, mode));
                entrylen = tree_entry_len(&t->entry);
                update_tree_entry(t);
                if (entrylen > namelen)
                if (cmp < 0)
                        break;
                if (entrylen == namelen) {
 -                      oidcpy(result, oid);
 +                      oidcpy(result, &oid);
                        return 0;
                }
                if (name[entrylen] != '/')
                if (!S_ISDIR(*mode))
                        break;
                if (++entrylen == namelen) {
 -                      oidcpy(result, oid);
 +                      oidcpy(result, &oid);
                        return 0;
                }
 -              return get_tree_entry(oid, name + entrylen, result, mode);
 +              return get_tree_entry(&oid, name + entrylen, result, mode);
        }
        return -1;
  }
@@@ -582,10 -581,10 +588,10 @@@ int get_tree_entry(const struct object_
   * with the sha1 of the found object, and *mode will hold the mode of
   * the object.
   *
 - * See the code for enum follow_symlink_result for a description of
 + * See the code for enum get_oid_result for a description of
   * the return values.
   */
 -enum follow_symlinks_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode)
 +enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode)
  {
        int retval = MISSING_OBJECT;
        struct dir_state *parents = NULL;
@@@ -935,8 -934,7 +941,8 @@@ static int match_wildcard_base(const st
   * Pre-condition: either baselen == base_offset (i.e. empty path)
   * or base[baselen-1] == '/' (i.e. with trailing slash).
   */
 -static enum interesting do_match(const struct name_entry *entry,
 +static enum interesting do_match(struct index_state *istate,
 +                               const struct name_entry *entry,
                                 struct strbuf *base, int base_offset,
                                 const struct pathspec *ps,
                                 int exclude)
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
 -                     PATHSPEC_EXCLUDE);
 +                     PATHSPEC_EXCLUDE |
 +                     PATHSPEC_ATTR);
  
        if (!ps->nr) {
                if (!ps->recursive ||
  
                        if (!ps->recursive ||
                            !(ps->magic & PATHSPEC_MAXDEPTH) ||
 -                          ps->max_depth == -1)
 -                              return all_entries_interesting;
 -
 -                      return within_depth(base_str + matchlen + 1,
 -                                          baselen - matchlen - 1,
 -                                          !!S_ISDIR(entry->mode),
 -                                          ps->max_depth) ?
 -                              entry_interesting : entry_not_interesting;
 +                          ps->max_depth == -1) {
 +                              if (!item->attr_match_nr)
 +                                      return all_entries_interesting;
 +                              else
 +                                      goto interesting;
 +                      }
 +
 +                      if (within_depth(base_str + matchlen + 1,
 +                                       baselen - matchlen - 1,
 +                                       !!S_ISDIR(entry->mode),
 +                                       ps->max_depth))
 +                              goto interesting;
 +                      else
 +                              return entry_not_interesting;
                }
  
                /* Either there must be no base, or the base must match. */
                        if (match_entry(item, entry, pathlen,
                                        match + baselen, matchlen - baselen,
                                        &never_interesting))
 -                              return entry_interesting;
 +                              goto interesting;
  
                        if (item->nowildcard_len < item->len) {
                                if (!git_fnmatch(item, match + baselen, entry->path,
                                                 item->nowildcard_len - baselen))
 -                                      return entry_interesting;
 +                                      goto interesting;
  
                                /*
                                 * Match all directories. We'll try to
                                    !ps_strncmp(item, match + baselen,
                                                entry->path,
                                                item->nowildcard_len - baselen))
 -                                      return entry_interesting;
 +                                      goto interesting;
                        }
  
                        continue;
@@@ -1067,7 -1058,7 +1073,7 @@@ match_wildcards
                if (!git_fnmatch(item, match, base->buf + base_offset,
                                 item->nowildcard_len)) {
                        strbuf_setlen(base, base_offset + baselen);
 -                      return entry_interesting;
 +                      goto interesting;
                }
  
                /*
                    !ps_strncmp(item, match, base->buf + base_offset,
                                item->nowildcard_len)) {
                        strbuf_setlen(base, base_offset + baselen);
 -                      return entry_interesting;
 +                      goto interesting;
                }
  
                strbuf_setlen(base, base_offset + baselen);
                 */
                if (ps->recursive && S_ISDIR(entry->mode))
                        return entry_interesting;
 +              continue;
 +interesting:
 +              if (item->attr_match_nr) {
 +                      int ret;
 +
 +                      /*
 +                       * Must not return all_entries_not_interesting
 +                       * prematurely. We do not know if all entries do not
 +                       * match some attributes with current attr API.
 +                       */
 +                      never_interesting = entry_not_interesting;
 +
 +                      /*
 +                       * Consider all directories interesting (because some
 +                       * of those files inside may match some attributes
 +                       * even though the parent dir does not)
 +                       *
 +                       * FIXME: attributes _can_ match directories and we
 +                       * can probably return all_entries_interesting or
 +                       * all_entries_not_interesting here if matched.
 +                       */
 +                      if (S_ISDIR(entry->mode))
 +                              return entry_interesting;
 +
 +                      strbuf_add(base, entry->path, pathlen);
 +                      ret = match_pathspec_attrs(istate, base->buf + base_offset,
 +                                                 base->len - base_offset, item);
 +                      strbuf_setlen(base, base_offset + baselen);
 +                      if (!ret)
 +                              continue;
 +              }
 +              return entry_interesting;
        }
        return never_interesting; /* No matches */
  }
   * Pre-condition: either baselen == base_offset (i.e. empty path)
   * or base[baselen-1] == '/' (i.e. with trailing slash).
   */
 -enum interesting tree_entry_interesting(const struct name_entry *entry,
 +enum interesting tree_entry_interesting(struct index_state *istate,
 +                                      const struct name_entry *entry,
                                        struct strbuf *base, int base_offset,
                                        const struct pathspec *ps)
  {
        enum interesting positive, negative;
 -      positive = do_match(entry, base, base_offset, ps, 0);
 +      positive = do_match(istate, entry, base, base_offset, ps, 0);
  
        /*
         * case | entry | positive | negative | result
            positive <= entry_not_interesting) /* #1, #2, #11, #12 */
                return positive;
  
 -      negative = do_match(entry, base, base_offset, ps, 1);
 +      negative = do_match(istate, entry, base, base_offset, ps, 1);
  
        /* #8, #18 */
        if (positive == all_entries_interesting &&
diff --combined unpack-trees.c
index 3563daae1aa6bceb4f7a4715ee574e1359622239,545d5668fdf79015784cafcfbc24322b68e8927c..725351b287c1e83fbb5b4470cf54b881b9f5d41b
@@@ -1,3 -1,4 +1,3 @@@
 -#define NO_THE_INDEX_COMPATIBILITY_MACROS
  #include "cache.h"
  #include "argv-array.h"
  #include "repository.h"
@@@ -293,7 -294,7 +293,7 @@@ static void load_gitmodules_file(struc
                        repo_read_gitmodules(the_repository);
                } else if (state && (ce->ce_flags & CE_UPDATE)) {
                        submodule_free(the_repository);
 -                      checkout_entry(ce, state, NULL);
 +                      checkout_entry(ce, state, NULL, NULL);
                        repo_read_gitmodules(the_repository);
                }
        }
@@@ -449,12 -450,12 +449,12 @@@ static int check_updates(struct unpack_
                        display_progress(progress, ++cnt);
                        ce->ce_flags &= ~CE_UPDATE;
                        if (o->update && !o->dry_run) {
 -                              errs |= checkout_entry(ce, &state, NULL);
 +                              errs |= checkout_entry(ce, &state, NULL, NULL);
                        }
                }
        }
        stop_progress(&progress);
 -      errs |= finish_delayed_checkout(&state);
 +      errs |= finish_delayed_checkout(&state, NULL);
        if (o->update)
                git_attr_set_direction(GIT_ATTR_CHECKIN);
  
@@@ -678,7 -679,7 +678,7 @@@ static int switch_cache_bottom(struct t
  
  static inline int are_same_oid(struct name_entry *name_j, struct name_entry *name_k)
  {
 -      return name_j->oid && name_k->oid && oideq(name_j->oid, name_k->oid);
 +      return !is_null_oid(&name_j->oid) && !is_null_oid(&name_k->oid) && oideq(&name_j->oid, &name_k->oid);
  }
  
  static int all_trees_same_as_cache_tree(int n, unsigned long dirmask,
@@@ -793,7 -794,6 +793,7 @@@ static int traverse_trees_recursive(in
                                    struct name_entry *names,
                                    struct traverse_info *info)
  {
 +      struct unpack_trees_options *o = info->data;
        int i, ret, bottom;
        int nr_buf = 0;
        struct tree_desc t[MAX_UNPACK_TREES];
  
        nr_entries = all_trees_same_as_cache_tree(n, dirmask, names, info);
        if (nr_entries > 0) {
 -              struct unpack_trees_options *o = info->data;
                int pos = index_pos_by_traverse_info(names, info);
  
                if (!o->merge || df_conflicts)
                else {
                        const struct object_id *oid = NULL;
                        if (dirmask & 1)
 -                              oid = names[i].oid;
 +                              oid = &names[i].oid;
                        buf[nr_buf++] = fill_tree_descriptor(t + i, oid);
                }
        }
  
        bottom = switch_cache_bottom(&newinfo);
 -      ret = traverse_trees(n, t, &newinfo);
 +      ret = traverse_trees(o->src_index, n, t, &newinfo);
        restore_cache_bottom(&newinfo, bottom);
  
        for (i = 0; i < nr_buf; i++)
@@@ -980,7 -981,7 +980,7 @@@ static struct cache_entry *create_ce_en
        ce->ce_mode = create_ce_mode(n->mode);
        ce->ce_flags = create_ce_flags(stage);
        ce->ce_namelen = len;
 -      oidcpy(&ce->oid, n->oid);
 +      oidcpy(&ce->oid, &n->oid);
        make_traverse_path(ce->name, info, n);
  
        return ce;
@@@ -1549,7 -1550,7 +1549,7 @@@ int unpack_trees(unsigned len, struct t
                }
  
                trace_performance_enter();
 -              ret = traverse_trees(len, t, &info);
 +              ret = traverse_trees(o->src_index, len, t, &info);
                trace_performance_leave("traverse_trees");
                if (ret < 0)
                        goto return_failed;
                move_index_extensions(&o->result, o->src_index);
                if (!ret) {
                        if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
 -                              cache_tree_verify(&o->result);
 +                              cache_tree_verify(the_repository, &o->result);
                        if (!o->result.cache_tree)
                                o->result.cache_tree = cache_tree();
                        if (!cache_tree_fully_valid(o->result.cache_tree))
@@@ -2072,7 -2073,8 +2072,8 @@@ static int merged_entry(const struct ca
                invalidate_ce_path(old, o);
        }
  
-       do_add_entry(o, merge, update, CE_STAGEMASK);
+       if (do_add_entry(o, merge, update, CE_STAGEMASK) < 0)
+               return -1;
        return 1;
  }