Sync with v2.0.5
authorJunio C Hamano <gitster@pobox.com>
Wed, 17 Dec 2014 19:42:28 +0000 (11:42 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 17 Dec 2014 19:42:28 +0000 (11:42 -0800)
* maint-2.0:
Git 2.0.5
Git 1.9.5
Git 1.8.5.6
fsck: complain about NTFS ".git" aliases in trees
read-cache: optionally disallow NTFS .git variants
path: add is_ntfs_dotgit() helper
fsck: complain about HFS+ ".git" aliases in trees
read-cache: optionally disallow HFS+ .git variants
utf8: add is_hfs_dotgit() helper
fsck: notice .git case-insensitively
t1450: refactor ".", "..", and ".git" fsck tests
verify_dotfile(): reject .git case-insensitively
read-tree: add tests for confusing paths like ".." and ".git"
unpack-trees: propagate errors adding entries to the index

13 files changed:
1  2 
Documentation/config.txt
Documentation/git.txt
cache.h
config.c
config.mak.uname
environment.c
fsck.c
path.c
read-cache.c
t/t1450-fsck.sh
t/test-lib.sh
unpack-trees.c
utf8.c
diff --combined Documentation/config.txt
index 6ae4d907088348c35eed1698e0428326c0e8788e,0c325976390ff0878f8aede3a416587f9b9af75f..0a4d22aaf7ccf57ec67cd342898977e24d2185a8
@@@ -233,6 -233,17 +233,17 @@@ core.precomposeunicode:
        When false, file names are handled fully transparent by Git,
        which is backward compatible with older versions of Git.
  
+ core.protectHFS::
+       If set to true, do not allow checkout of paths that would
+       be considered equivalent to `.git` on an HFS+ filesystem.
+       Defaults to `true` on Mac OS, and `false` elsewhere.
+ core.protectNTFS::
+       If set to true, do not allow checkout of paths that would
+       cause problems with the NTFS filesystem, e.g. conflict with
+       8.3 "short" names.
+       Defaults to `true` on Windows, and `false` elsewhere.
  core.trustctime::
        If false, the ctime differences between the index and the
        working tree are ignored; useful when the inode change time
@@@ -381,7 -392,7 +392,7 @@@ false), while all other repositories ar
  core.worktree::
        Set the path to the root of the working tree.
        This can be overridden by the GIT_WORK_TREE environment
 -      variable and the '--work-tree' command line option.
 +      variable and the '--work-tree' command-line option.
        The value can be an absolute path or relative to the path to
        the .git directory, which is either specified by --git-dir
        or GIT_DIR, or automatically discovered.
@@@ -489,7 -500,7 +500,7 @@@ core.deltaBaseCacheLimit:
        to avoid unpacking and decompressing frequently used base
        objects multiple times.
  +
 -Default is 16 MiB on all platforms.  This should be reasonable
 +Default is 96 MiB on all platforms.  This should be reasonable
  for all users/operating systems, except on the largest projects.
  You probably do not need to adjust this value.
  +
@@@ -523,7 -534,7 +534,7 @@@ core.askpass:
        environment variable. If not set, fall back to the value of the
        'SSH_ASKPASS' environment variable or, failing that, a simple password
        prompt. The external program shall be given a suitable prompt as
 -      command line argument and write the password on its STDOUT.
 +      command-line argument and write the password on its STDOUT.
  
  core.attributesfile::
        In addition to '.gitattributes' (per-directory) and
@@@ -544,9 -555,6 +555,9 @@@ core.commentchar:
        messages consider a line that begins with this character
        commented, and removes them after the editor returns
        (default '#').
 ++
 +If set to "auto", `git-commit` would select a character that is not
 +the beginning character of any line in existing commit messages.
  
  sequence.editor::
        Text editor used by `git rebase -i` for editing the rebase instruction file.
@@@ -561,19 -569,14 +572,19 @@@ core.pager:
        configuration, then `$PAGER`, and then the default chosen at
        compile time (usually 'less').
  +
 -When the `LESS` environment variable is unset, Git sets it to `FRSX`
 +When the `LESS` environment variable is unset, Git sets it to `FRX`
  (if `LESS` environment variable is set, Git does not change it at
  all).  If you want to selectively override Git's default setting
 -for `LESS`, you can set `core.pager` to e.g. `less -+S`.  This will
 +for `LESS`, you can set `core.pager` to e.g. `less -S`.  This will
  be passed to the shell by Git, which will translate the final
 -command to `LESS=FRSX less -+S`. The environment tells the command
 -to set the `S` option to chop long lines but the command line
 -resets it to the default to fold long lines.
 +command to `LESS=FRX less -S`. The environment does not set the
 +`S` option but the command line does, instructing less to truncate
 +long lines. Similarly, setting `core.pager` to `less -+F` will
 +deactivate the `F` option specified by the environment from the
 +command-line, deactivating the "quit if one screen" behavior of
 +`less`.  One can specifically activate some flags for particular
 +commands: for example, setting `pager.blame` to `less -S` enables
 +line truncation only for `git blame`.
  +
  Likewise, when the `LV` environment variable is unset, Git sets it
  to `-c`.  You can override this setting by exporting `LV` with
@@@ -621,9 -624,9 +632,9 @@@ core.preloadindex:
  +
  This can speed up operations like 'git diff' and 'git status' especially
  on filesystems like NFS that have weak caching semantics and thus
 -relatively high IO latencies.  With this set to 'true', Git will do the
 +relatively high IO latencies.  When enabled, Git will do the
  index comparison to the filesystem data in parallel, allowing
 -overlapping IO's.
 +overlapping IO's.  Defaults to true.
  
  core.createObject::
        You can set this to 'link', in which case a hardlink followed by
@@@ -669,7 -672,7 +680,7 @@@ alias.*:
        confusion and troubles with script usage, aliases that
        hide existing Git commands are ignored. Arguments are split by
        spaces, the usual shell quoting and escaping is supported.
 -      quote pair and a backslash can be used to quote them.
 +      A quote pair or a backslash can be used to quote them.
  +
  If the alias expansion is prefixed with an exclamation point,
  it will be treated as a shell command.  For example, defining
@@@ -1122,10 -1125,6 +1133,10 @@@ format.signature:
        Set this variable to the empty string ("") to suppress
        signature generation.
  
 +format.signaturefile::
 +      Works just like format.signature except the contents of the
 +      file specified by this variable will be used as the signature.
 +
  format.suffix::
        The default for format-patch is to output files with the suffix
        `.patch`. Use this variable to change that suffix (make sure to
@@@ -1336,7 -1335,7 +1347,7 @@@ grep.extendedRegexp:
  gpg.program::
        Use this custom program instead of "gpg" found on $PATH when
        making or verifying a PGP signature. The program must support the
 -      same command line interface as GPG, namely, to verify a detached
 +      same command-line interface as GPG, namely, to verify a detached
        signature, "gpg --verify $file - <$signature" is run, and the
        program is expected to signal a good signature by exiting with
        code 0, and to generate an ascii-armored detached signature, the
@@@ -1754,15 -1753,6 +1765,15 @@@ mergetool.<tool>.trustExitCode:
        if the file has been updated, otherwise the user is prompted to
        indicate the success of the merge.
  
 +mergetool.meld.hasOutput::
 +      Older versions of `meld` do not support the `--output` option.
 +      Git will attempt to detect whether `meld` supports `--output`
 +      by inspecting the output of `meld --help`.  Configuring
 +      `mergetool.meld.hasOutput` will make Git skip these checks and
 +      use the configured value instead.  Setting `mergetool.meld.hasOutput`
 +      to `true` tells Git to unconditionally use the `--output` option,
 +      and `false` avoids using `--output`.
 +
  mergetool.keepBackup::
        After performing a merge, the original file with conflict markers
        can be saved as a file with a `.orig` extension.  If this variable
@@@ -1914,7 -1904,12 +1925,7 @@@ pack.useBitmaps:
        you are debugging pack bitmaps.
  
  pack.writebitmaps::
 -      When true, git will write a bitmap index when packing all
 -      objects to disk (e.g., when `git repack -a` is run).  This
 -      index can speed up the "counting objects" phase of subsequent
 -      packs created for clones and fetches, at the cost of some disk
 -      space and extra time spent on the initial repack.  Defaults to
 -      false.
 +      This is a deprecated synonym for `repack.writeBitmaps`.
  
  pack.writeBitmapHashCache::
        When true, git will include a "hash cache" section in the bitmap
@@@ -2191,15 -2186,7 +2202,15 @@@ repack.packKeptObjects:
        `--pack-kept-objects` was passed. See linkgit:git-repack[1] for
        details. Defaults to `false` normally, but `true` if a bitmap
        index is being written (either via `--write-bitmap-index` or
 -      `pack.writeBitmaps`).
 +      `repack.writeBitmaps`).
 +
 +repack.writeBitmaps::
 +      When true, git will write a bitmap index when packing all
 +      objects to disk (e.g., when `git repack -a` is run).  This
 +      index can speed up the "counting objects" phase of subsequent
 +      packs created for clones and fetches, at the cost of some disk
 +      space and extra time spent on the initial repack.  Defaults to
 +      false.
  
  rerere.autoupdate::
        When set to true, `git-rerere` updates the index with the
@@@ -2295,7 -2282,7 +2306,7 @@@ status.showUntrackedFiles:
        files which are not currently tracked by Git. Directories which
        contain only untracked files, are shown with the directory name
        only. Showing untracked files means that Git needs to lstat() all
 -      all the files in the whole repository, which might be slow on some
 +      the files in the whole repository, which might be slow on some
        systems. So, this variable controls how the commands displays
        the untracked files. Possible values are:
  +
@@@ -2321,7 -2308,7 +2332,7 @@@ status.submodulesummary:
        exception to that rule is that status and commit will show staged
        submodule changes. To
        also view the summary for ignored submodules you can either use
 -      the --ignore-submodules=dirty command line option or the 'git
 +      the --ignore-submodules=dirty command-line option or the 'git
        submodule summary' command, which shows a similar output but does
        not honor these settings.
  
@@@ -2343,7 -2330,7 +2354,7 @@@ submodule.<name>.branch:
  submodule.<name>.fetchRecurseSubmodules::
        This option can be used to control recursive fetching of this
        submodule. It can be overridden by using the --[no-]recurse-submodules
 -      command line option to "git fetch" and "git pull".
 +      command-line option to "git fetch" and "git pull".
        This setting will override that from in the linkgit:gitmodules[5]
        file.
  
@@@ -2363,11 -2350,6 +2374,11 @@@ submodule.<name>.ignore:
        "--ignore-submodules" option. The 'git submodule' commands are not
        affected by this setting.
  
 +tag.sort::
 +      This variable controls the sort ordering of tags when displayed by
 +      linkgit:git-tag[1]. Without the "--sort=<value>" option provided, the
 +      value of this variable will be used as the default.
 +
  tar.umask::
        This variable can be used to restrict the permission bits of
        tar archive entries.  The default is 0002, which turns off the
diff --combined Documentation/git.txt
index 56278451140b49fa13332ebf74f58a6713df2c26,674ecf9a3ba9dded1fd9226133ccdc894f79ac23..ffde2b80b9beaad3fb7a930e7ac54b24ef527143
@@@ -29,7 -29,7 +29,7 @@@ in-depth introduction
  After you mastered the basic concepts, you can come back to this
  page to learn what commands Git offers.  You can learn more about
  individual Git commands with "git help command".  linkgit:gitcli[7]
 -manual page gives you an overview of the command line command syntax.
 +manual page gives you an overview of the command-line command syntax.
  
  Formatted and hyperlinked version of the latest Git documentation
  can be viewed at `http://git-htmldocs.googlecode.com/git/git.html`.
@@@ -39,39 -39,34 +39,42 @@@ ifdef::stalenotes[
  ============
  
  You are reading the documentation for the latest (possibly
 -unreleased) version of Git, that is available from 'master'
 +unreleased) version of Git, that is available from the 'master'
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
- * link:v2.0.4/git.html[documentation for release 2.0.4]
 +* link:v2.1.3/git.html[documentation for release 2.1.3]
 +
 +* release notes for
 +  link:RelNotes/2.1.3.txt[2.1.3],
 +  link:RelNotes/2.1.2.txt[2.1.2],
 +  link:RelNotes/2.1.1.txt[2.1.1],
 +  link:RelNotes/2.1.0.txt[2.1].
 +
+ * link:v2.0.5/git.html[documentation for release 2.0.5]
  
  * release notes for
+   link:RelNotes/2.0.5.txt[2.0.5],
    link:RelNotes/2.0.4.txt[2.0.4],
    link:RelNotes/2.0.3.txt[2.0.3],
    link:RelNotes/2.0.2.txt[2.0.2],
    link:RelNotes/2.0.1.txt[2.0.1],
    link:RelNotes/2.0.0.txt[2.0.0].
  
- * link:v1.9.4/git.html[documentation for release 1.9.4]
+ * link:v1.9.5/git.html[documentation for release 1.9.5]
  
  * release notes for
+   link:RelNotes/1.9.5.txt[1.9.5],
    link:RelNotes/1.9.4.txt[1.9.4],
    link:RelNotes/1.9.3.txt[1.9.3],
    link:RelNotes/1.9.2.txt[1.9.2],
    link:RelNotes/1.9.1.txt[1.9.1],
    link:RelNotes/1.9.0.txt[1.9.0].
  
- * link:v1.8.5.5/git.html[documentation for release 1.8.5.5]
+ * link:v1.8.5.6/git.html[documentation for release 1.8.5.6]
  
  * release notes for
+   link:RelNotes/1.8.5.6.txt[1.8.5.6],
    link:RelNotes/1.8.5.5.txt[1.8.5.5],
    link:RelNotes/1.8.5.4.txt[1.8.5.4],
    link:RelNotes/1.8.5.3.txt[1.8.5.3],
@@@ -455,11 -450,6 +458,11 @@@ example the following invocations are e
        given will override values from configuration files.
        The <name> is expected in the same format as listed by
        'git config' (subkeys separated by dots).
 ++
 +Note that omitting the `=` in `git -c foo.bar ...` is allowed and sets
 +`foo.bar` to the boolean true value (just like `[foo]bar` would in a
 +config file). Including the equals but with an empty value (like `git -c
 +foo.bar= ...`) sets `foo.bar` to the empty string.
  
  --exec-path[=<path>]::
        Path to wherever your core Git programs are installed.
@@@ -772,7 -762,7 +775,7 @@@ Git so take care if using Cogito etc
  
  'GIT_WORK_TREE'::
        Set the path to the root of the working tree.
 -      This can also be controlled by the '--work-tree' command line
 +      This can also be controlled by the '--work-tree' command-line
        option and the core.worktree configuration variable.
  
  'GIT_NAMESPACE'::
@@@ -897,7 -887,7 +900,7 @@@ for further details
  'GIT_ASKPASS'::
        If this environment variable is set, then Git commands which need to
        acquire passwords or passphrases (e.g. for HTTP or IMAP authentication)
 -      will call this program with a suitable prompt as command line argument
 +      will call this program with a suitable prompt as command-line argument
        and read the password from its STDOUT. See also the 'core.askpass'
        option in linkgit:git-config[1].
  
        based on whether stdout appears to be redirected to a file or not.
  
  'GIT_TRACE'::
 -      If this variable is set to "1", "2" or "true" (comparison
 -      is case insensitive), Git will print `trace:` messages on
 -      stderr telling about alias expansion, built-in command
 -      execution and external command execution.
 -      If this variable is set to an integer value greater than 1
 -      and lower than 10 (strictly) then Git will interpret this
 -      value as an open file descriptor and will try to write the
 -      trace messages into this file descriptor.
 -      Alternatively, if this variable is set to an absolute path
 -      (starting with a '/' character), Git will interpret this
 -      as a file path and will try to write the trace messages
 -      into it.
 +      Enables general trace messages, e.g. alias expansion, built-in
 +      command execution and external command execution.
 ++
 +If this variable is set to "1", "2" or "true" (comparison
 +is case insensitive), trace messages will be printed to
 +stderr.
 ++
 +If the variable is set to an integer value greater than 2
 +and lower than 10 (strictly) then Git will interpret this
 +value as an open file descriptor and will try to write the
 +trace messages into this file descriptor.
 ++
 +Alternatively, if the variable is set to an absolute path
 +(starting with a '/' character), Git will interpret this
 +as a file path and will try to write the trace messages
 +into it.
 ++
 +Unsetting the variable, or setting it to empty, "0" or
 +"false" (case insensitive) disables trace messages.
  
  'GIT_TRACE_PACK_ACCESS'::
 -      If this variable is set to a path, a file will be created at
 -      the given path logging all accesses to any packs. For each
 +      Enables trace messages for all accesses to any packs. For each
        access, the pack file name and an offset in the pack is
        recorded. This may be helpful for troubleshooting some
        pack-related performance problems.
 +      See 'GIT_TRACE' for available trace output options.
  
  'GIT_TRACE_PACKET'::
 -      If this variable is set, it shows a trace of all packets
 -      coming in or out of a given program. This can help with
 -      debugging object negotiation or other protocol issues. Tracing
 -      is turned off at a packet starting with "PACK".
 +      Enables trace messages for all packets coming in or out of a
 +      given program. This can help with debugging object negotiation
 +      or other protocol issues. Tracing is turned off at a packet
 +      starting with "PACK".
 +      See 'GIT_TRACE' for available trace output options.
 +
 +'GIT_TRACE_PERFORMANCE'::
 +      Enables performance related trace messages, e.g. total execution
 +      time of each Git command.
 +      See 'GIT_TRACE' for available trace output options.
 +
 +'GIT_TRACE_SETUP'::
 +      Enables trace messages printing the .git, working tree and current
 +      working directory after Git has completed its setup phase.
 +      See 'GIT_TRACE' for available trace output options.
 +
 +'GIT_TRACE_SHALLOW'::
 +      Enables trace messages that can help debugging fetching /
 +      cloning of shallow repositories.
 +      See 'GIT_TRACE' for available trace output options.
  
  GIT_LITERAL_PATHSPECS::
        Setting this variable to `1` will cause Git to treat all
diff --combined cache.h
index dcf3a2afe9d782e4b2474d8834602f80610ba25f,f23fdbee96fc9ad14e3a16e77d277293207dd6c3..a258d805cda7c155cbe60629f38bd3f323c21d78
+++ b/cache.h
@@@ -7,7 -7,6 +7,7 @@@
  #include "advice.h"
  #include "gettext.h"
  #include "convert.h"
 +#include "trace.h"
  
  #include SHA1_HEADER
  #ifndef git_SHA_CTX
@@@ -75,21 -74,6 +75,21 @@@ unsigned long git_deflate_bound(git_zst
  #define S_IFGITLINK   0160000
  #define S_ISGITLINK(m)        (((m) & S_IFMT) == S_IFGITLINK)
  
 +/*
 + * Some mode bits are also used internally for computations.
 + *
 + * They *must* not overlap with any valid modes, and they *must* not be emitted
 + * to outside world - i.e. appear on disk or network. In other words, it's just
 + * temporary fields, which we internally use, but they have to stay in-house.
 + *
 + * ( such approach is valid, as standard S_IF* fits into 16 bits, and in Git
 + *   codebase mode is `unsigned int` which is assumed to be at least 32 bits )
 + */
 +
 +/* used internally in tree-diff */
 +#define S_DIFFTREE_IFXMIN_NEQ 0x80000000
 +
 +
  /*
   * Intensive research over the course of many years has shown that
   * port 9418 is totally unused by anything else. Or
@@@ -151,7 -135,6 +151,7 @@@ struct cache_entry 
        unsigned int ce_mode;
        unsigned int ce_flags;
        unsigned int ce_namelen;
 +      unsigned int index;     /* for link extension */
        unsigned char sha1[20];
        char name[FLEX_ARRAY]; /* more */
  };
  #define CE_STAGESHIFT 12
  
  /*
 - * Range 0xFFFF0000 in ce_flags is divided into
 + * Range 0xFFFF0FFF in ce_flags is divided into
   * two parts: in-memory flags and on-disk ones.
   * Flags in CE_EXTENDED_FLAGS will get saved on-disk
   * if you want to save a new flag, add it in
  /* used to temporarily mark paths matched by pathspecs */
  #define CE_MATCHED           (1 << 26)
  
 +#define CE_UPDATE_IN_BASE    (1 << 27)
 +#define CE_STRIP_NAME        (1 << 28)
 +
  /*
   * Extended on-disk flags
   */
@@@ -288,22 -268,12 +288,22 @@@ static inline unsigned int canon_mode(u
  
  #define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1)
  
 +#define SOMETHING_CHANGED     (1 << 0) /* unclassified changes go here */
 +#define CE_ENTRY_CHANGED      (1 << 1)
 +#define CE_ENTRY_REMOVED      (1 << 2)
 +#define CE_ENTRY_ADDED                (1 << 3)
 +#define RESOLVE_UNDO_CHANGED  (1 << 4)
 +#define CACHE_TREE_CHANGED    (1 << 5)
 +#define SPLIT_INDEX_ORDERED   (1 << 6)
 +
 +struct split_index;
  struct index_state {
        struct cache_entry **cache;
        unsigned int version;
        unsigned int cache_nr, cache_alloc, cache_changed;
        struct string_list *resolve_undo;
        struct cache_tree *cache_tree;
 +      struct split_index *split_index;
        struct cache_time timestamp;
        unsigned name_hash_initialized : 1,
                 initialized : 1;
@@@ -332,6 -302,7 +332,6 @@@ extern void free_name_hash(struct index
  #define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec))
  #define is_cache_unborn() is_index_unborn(&the_index)
  #define read_cache_unmerged() read_index_unmerged(&the_index)
 -#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
  #define discard_cache() discard_index(&the_index)
  #define unmerged_cache() unmerged_index(&the_index)
  #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
@@@ -486,17 -457,12 +486,17 @@@ extern int daemonize(void)
        } while (0)
  
  /* Initialize and use the cache information */
 +struct lock_file;
  extern int read_index(struct index_state *);
  extern int read_index_preload(struct index_state *, const struct pathspec *pathspec);
 +extern int do_read_index(struct index_state *istate, const char *path,
 +                       int must_exist); /* for testting only! */
  extern int read_index_from(struct index_state *, const char *path);
  extern int is_index_unborn(struct index_state *);
  extern int read_index_unmerged(struct index_state *);
 -extern int write_index(struct index_state *, int newfd);
 +#define COMMIT_LOCK           (1 << 0)
 +#define CLOSE_LOCK            (1 << 1)
 +extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
  extern int discard_index(struct index_state *);
  extern int unmerged_index(const struct index_state *);
  extern int verify_path(const char *path);
@@@ -508,7 -474,6 +508,7 @@@ extern int index_name_pos(const struct 
  #define ADD_CACHE_SKIP_DFCHECK 4      /* Ok to skip DF conflict checks */
  #define ADD_CACHE_JUST_APPEND 8               /* Append only; tree.c::read_tree() */
  #define ADD_CACHE_NEW_ONLY 16         /* Do not replace existing ones */
 +#define ADD_CACHE_KEEP_CACHE_TREE 32  /* Do not invalidate cache-tree */
  extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
  extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
  extern int remove_index_entry_at(struct index_state *, int pos);
@@@ -579,8 -544,6 +579,8 @@@ struct lock_file 
  #define LOCK_DIE_ON_ERROR 1
  #define LOCK_NODEREF 2
  extern int unable_to_lock_error(const char *path, int err);
 +extern void unable_to_lock_message(const char *path, int err,
 +                                 struct strbuf *buf);
  extern NORETURN void unable_to_lock_index_die(const char *path, int err);
  extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
  extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
@@@ -588,6 -551,7 +588,6 @@@ extern int commit_lock_file(struct lock
  extern void update_index_if_able(struct index_state *, struct lock_file *);
  
  extern int hold_locked_index(struct lock_file *, int);
 -extern int commit_locked_index(struct lock_file *);
  extern void set_alternate_index_output(const char *);
  extern int close_lock_file(struct lock_file *);
  extern void rollback_lock_file(struct lock_file *);
@@@ -633,13 -597,14 +633,15 @@@ extern int fsync_object_files
  extern int core_preload_index;
  extern int core_apply_sparse_checkout;
  extern int precomposed_unicode;
+ extern int protect_hfs;
+ extern int protect_ntfs;
  
  /*
   * The character that begins a commented line in user-editable file
   * that is subject to stripspace.
   */
  extern char comment_line_char;
 +extern int auto_comment_line_char;
  
  enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
@@@ -847,6 -812,8 +849,7 @@@ int normalize_path_copy(char *dst, cons
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
  int daemon_avoid_alias(const char *path);
 -int offset_1st_component(const char *path);
+ extern int is_ntfs_dotgit(const char *name);
  
  /* object replacement */
  #define LOOKUP_REPLACE_OBJECT 1
@@@ -998,7 -965,7 +1001,7 @@@ extern int read_ref(const char *refname
   * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
   * give up and return NULL.
   *
 - * errno is sometimes set on errors, but not always.
 + * errno is set to something meaningful on error.
   */
  extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag);
  extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag);
@@@ -1020,7 -987,7 +1023,7 @@@ extern int validate_headref(const char 
  
  extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
  extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
 -extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
 +extern int name_compare(const char *name1, size_t len1, const char *name2, size_t len2);
  extern int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2);
  
  extern void *read_object_with_reference(const unsigned char *sha1,
@@@ -1082,13 -1049,6 +1085,13 @@@ struct ident_split 
   */
  extern int split_ident_line(struct ident_split *, const char *, int);
  
 +/*
 + * Like show_date, but pull the timestamp and tz parameters from
 + * the ident_split. It will also sanity-check the values and produce
 + * a well-known sentinel date if they appear bogus.
 + */
 +const char *show_ident_date(const struct ident_split *id, enum date_mode mode);
 +
  /*
   * Compare split idents for equality or strict ordering. Note that we
   * compare only the ident part of the line, ignoring any timestamp.
  extern int ident_cmp(const struct ident_split *, const struct ident_split *);
  
  struct checkout {
 +      struct index_state *istate;
        const char *base_dir;
        int base_dir_len;
        unsigned force:1,
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
  
  struct cache_def {
 -      char path[PATH_MAX + 1];
 -      int len;
 +      struct strbuf path;
        int flags;
        int track_flags;
        int prefix_len_stat_func;
  };
 +#define CACHE_DEF_INIT { STRBUF_INIT, 0, 0, 0 }
 +static inline void cache_def_clear(struct cache_def *cache)
 +{
 +      strbuf_release(&cache->path);
 +}
  
  extern int has_symlink_leading_path(const char *name, int len);
  extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
@@@ -1281,8 -1236,6 +1284,8 @@@ extern int update_server_info(int)
  #define CONFIG_INVALID_PATTERN 6
  #define CONFIG_GENERIC_ERROR 7
  
 +#define CONFIG_REGEX_NONE ((void *)1)
 +
  struct git_config_source {
        unsigned int use_stdin:1;
        const char *file;
@@@ -1322,8 -1275,8 +1325,8 @@@ extern int check_repository_format_vers
  extern int git_env_bool(const char *, int);
  extern int git_config_system(void);
  extern int config_error_nonbool(const char *);
 -#if defined(__GNUC__) && ! defined(__clang__)
 -#define config_error_nonbool(s) (config_error_nonbool(s), -1)
 +#if defined(__GNUC__)
 +#define config_error_nonbool(s) (config_error_nonbool(s), const_error())
  #endif
  extern const char *get_log_output_encoding(void);
  extern const char *get_commit_output_encoding(void);
@@@ -1406,7 -1359,17 +1409,7 @@@ extern void *alloc_object_node(void)
  extern void alloc_report(void);
  extern unsigned int alloc_commit_index(void);
  
 -/* trace.c */
 -__attribute__((format (printf, 1, 2)))
 -extern void trace_printf(const char *format, ...);
 -__attribute__((format (printf, 2, 3)))
 -extern void trace_argv_printf(const char **argv, const char *format, ...);
 -extern void trace_repo_setup(const char *prefix);
 -extern int trace_want(const char *key);
 -__attribute__((format (printf, 2, 3)))
 -extern void trace_printf_key(const char *key, const char *fmt, ...);
 -extern void trace_strbuf(const char *key, const struct strbuf *buf);
 -
 +/* pkt-line.c */
  void packet_trace_identity(const char *prog);
  
  /* add */
diff --combined config.c
index 9e42d3832bbcce428705ef616d5b2008c47ec91a,61a9c296f48c929177835c6128133449a1e14a6b..73d8205ae401a8ffc18c37a9bf91d96407f7222d
+++ b/config.c
@@@ -138,7 -138,8 +138,7 @@@ int git_config_include(const char *var
        if (ret < 0)
                return ret;
  
 -      type = skip_prefix(var, "include.");
 -      if (!type)
 +      if (!skip_prefix(var, "include.", &type))
                return ret;
  
        if (!strcmp(type, "path"))
        return ret;
  }
  
 -static void lowercase(char *p)
 -{
 -      for (; *p; p++)
 -              *p = tolower(*p);
 -}
 -
  void git_config_push_parameter(const char *text)
  {
        struct strbuf env = STRBUF_INIT;
  int git_config_parse_parameter(const char *text,
                               config_fn_t fn, void *data)
  {
 +      const char *value;
        struct strbuf **pair;
 +
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
                return error("bogus config parameter: %s", text);
 -      if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=')
 +
 +      if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') {
                strbuf_setlen(pair[0], pair[0]->len - 1);
 +              value = pair[1] ? pair[1]->buf : "";
 +      } else {
 +              value = NULL;
 +      }
 +
        strbuf_trim(pair[0]);
        if (!pair[0]->len) {
                strbuf_list_free(pair);
                return error("bogus config parameter: %s", text);
        }
 -      lowercase(pair[0]->buf);
 -      if (fn(pair[0]->buf, pair[1] ? pair[1]->buf : NULL, data) < 0) {
 +      strbuf_tolower(pair[0]);
 +      if (fn(pair[0]->buf, value, data) < 0) {
                strbuf_list_free(pair);
                return -1;
        }
@@@ -825,16 -824,11 +825,16 @@@ static int git_default_core_config(cons
                return git_config_string(&editor_program, var, value);
  
        if (!strcmp(var, "core.commentchar")) {
 -              const char *comment;
 -              int ret = git_config_string(&comment, var, value);
 -              if (!ret)
 -                      comment_line_char = comment[0];
 -              return ret;
 +              if (!value)
 +                      return config_error_nonbool(var);
 +              else if (!strcasecmp(value, "auto"))
 +                      auto_comment_line_char = 1;
 +              else if (value[0] && !value[1]) {
 +                      comment_line_char = value[0];
 +                      auto_comment_line_char = 0;
 +              } else
 +                      return error("core.commentChar should only be one character");
 +              return 0;
        }
  
        if (!strcmp(var, "core.askpass"))
        if (!strcmp(var, "core.precomposeunicode")) {
                precomposed_unicode = git_config_bool(var, value);
                return 0;
+       }
+       if (!strcmp(var, "core.protecthfs")) {
+               protect_hfs = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "core.protectntfs")) {
+               protect_ntfs = git_config_bool(var, value);
+               return 0;
        }
  
        /* Add other config variables here and to Documentation/config.txt. */
@@@ -1236,15 -1240,10 +1246,15 @@@ static struct 
  
  static int matches(const char *key, const char *value)
  {
 -      return !strcmp(key, store.key) &&
 -              (store.value_regex == NULL ||
 -               (store.do_not_match ^
 -                !regexec(store.value_regex, value, 0, NULL, 0)));
 +      if (strcmp(key, store.key))
 +              return 0; /* not ours */
 +      if (!store.value_regex)
 +              return 1; /* always matches */
 +      if (store.value_regex == CONFIG_REGEX_NONE)
 +              return 0; /* never matches */
 +
 +      return store.do_not_match ^
 +              (value && !regexec(store.value_regex, value, 0, NULL, 0));
  }
  
  static int store_aux(const char *key, const char *value, void *cb)
@@@ -1506,8 -1505,6 +1516,8 @@@ out_free_ret_1
  /*
   * If value==NULL, unset in (remove from) config,
   * if value_regex!=NULL, disregard key/value pairs where value does not match.
 + * if value_regex==CONFIG_REGEX_NONE, do not match any existing values
 + *     (only add a new one)
   * if multi_replace==0, nothing, or only one matching key/value is replaced,
   *     else all matching key/values (regardless how many) are removed,
   *     before the new pair is written.
@@@ -1591,8 -1588,6 +1601,8 @@@ int git_config_set_multivar_in_file(con
  
                if (value_regex == NULL)
                        store.value_regex = NULL;
 +              else if (value_regex == CONFIG_REGEX_NONE)
 +                      store.value_regex = CONFIG_REGEX_NONE;
                else {
                        if (value_regex[0] == '!') {
                                store.do_not_match = 1;
                if (git_config_from_file(store_aux, config_filename, NULL)) {
                        error("invalid config file %s", config_filename);
                        free(store.key);
 -                      if (store.value_regex != NULL) {
 +                      if (store.value_regex != NULL &&
 +                          store.value_regex != CONFIG_REGEX_NONE) {
                                regfree(store.value_regex);
                                free(store.value_regex);
                        }
                }
  
                free(store.key);
 -              if (store.value_regex != NULL) {
 +              if (store.value_regex != NULL &&
 +                  store.value_regex != CONFIG_REGEX_NONE) {
                        regfree(store.value_regex);
                        free(store.value_regex);
                }
                        MAP_PRIVATE, in_fd, 0);
                close(in_fd);
  
 +              if (chmod(lock->filename, st.st_mode & 07777) < 0) {
 +                      error("chmod on %s failed: %s",
 +                              lock->filename, strerror(errno));
 +                      ret = CONFIG_NO_WRITE;
 +                      goto out_free;
 +              }
 +
                if (store.seen == 0)
                        store.seen = 1;
  
@@@ -1808,7 -1794,6 +1818,7 @@@ int git_config_rename_section_in_file(c
        int out_fd;
        char buf[1024];
        FILE *config_file;
 +      struct stat st;
  
        if (new_name && !section_name_is_ok(new_name)) {
                ret = error("invalid section name: %s", new_name);
                goto unlock_and_out;
        }
  
 +      fstat(fileno(config_file), &st);
 +
 +      if (chmod(lock->filename, st.st_mode & 07777) < 0) {
 +              ret = error("chmod on %s failed: %s",
 +                              lock->filename, strerror(errno));
 +              goto out;
 +      }
 +
        while (fgets(buf, sizeof(buf), config_file)) {
                int i;
                int length;
diff --combined config.mak.uname
index 15ee15e98c2cb232e1e42315dc44f7efd87b4f1c,aba56dcfad74919491360d0914d1390a9cbba110..0941e30a67faa3c0ab2e0ff312ca5f58c92f9dff
@@@ -28,16 -28,13 +28,16 @@@ ifeq ($(uname_S),OSF1
        NO_NSEC = YesPlease
  endif
  ifeq ($(uname_S),Linux)
 +      HAVE_ALLOCA_H = YesPlease
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
        LIBC_CONTAINS_LIBINTL = YesPlease
        HAVE_DEV_TTY = YesPlease
 +      HAVE_CLOCK_GETTIME = YesPlease
  endif
  ifeq ($(uname_S),GNU/kFreeBSD)
 +      HAVE_ALLOCA_H = YesPlease
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
@@@ -100,13 -97,13 +100,14 @@@ ifeq ($(uname_S),Darwin
        HAVE_DEV_TTY = YesPlease
        COMPAT_OBJS += compat/precompose_utf8.o
        BASIC_CFLAGS += -DPRECOMPOSE_UNICODE
+       BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1
  endif
  ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
        NEEDS_NSL = YesPlease
        SHELL_PATH = /bin/bash
        SANE_TOOL_PATH = /usr/xpg6/bin:/usr/xpg4/bin
 +      HAVE_ALLOCA_H = YesPlease
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
        NO_MKDTEMP = YesPlease
        endif
        INSTALL = /usr/ucb/install
        TAR = gtar
 -      BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H
 +      BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__
  endif
  ifeq ($(uname_O),Cygwin)
        ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4)
        else
                NO_REGEX = UnfortunatelyYes
        endif
 +      HAVE_ALLOCA_H = YesPlease
        NEEDS_LIBICONV = YesPlease
        NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
@@@ -243,7 -239,6 +244,7 @@@ ifeq ($(uname_S),AIX
  endif
  ifeq ($(uname_S),GNU)
        # GNU/Hurd
 +      HAVE_ALLOCA_H = YesPlease
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
@@@ -318,7 -313,6 +319,7 @@@ endi
  ifeq ($(uname_S),Windows)
        GIT_VERSION := $(GIT_VERSION).MSVC
        pathsep = ;
 +      HAVE_ALLOCA_H = YesPlease
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
        NO_IPV6 = YesPlease
        NO_UNIX_SOCKETS = YesPlease
        NO_SETENV = YesPlease
 -      NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
        NO_STRLCPY = YesPlease
        NO_MEMMEM = YesPlease
        NO_POSIX_GOODIES = UnfortunatelyYes
        NATIVE_CRLF = YesPlease
        DEFAULT_HELP_FORMAT = html
 +      NO_D_INO_IN_DIRENT = YesPlease
  
        CC = compat/vcbuild/scripts/clink.pl
        AR = compat/vcbuild/scripts/lib.pl
        COMPAT_OBJS = compat/msvc.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
                compat/win32/dirent.o
 -      COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
 -      BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
 +      COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
 +      BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE
        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 -MT
 +      BASIC_CFLAGS += -GL -Os -MD
        BASIC_LDFLAGS += -LTCG
        AR += -LTCG
  else
 -      BASIC_CFLAGS += -Zi -MTd
 +      BASIC_CFLAGS += -Zi -MDd
  endif
        X = .exe
  endif
@@@ -471,7 -466,6 +473,7 @@@ ifeq ($(uname_S),NONSTOP_KERNEL
  endif
  ifneq (,$(findstring MINGW,$(uname_S)))
        pathsep = ;
 +      HAVE_ALLOCA_H = YesPlease
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_UNIX_SOCKETS = YesPlease
        NO_SETENV = YesPlease
 -      NO_UNSETENV = YesPlease
        NO_STRCASESTR = YesPlease
        NO_STRLCPY = YesPlease
        NO_MEMMEM = YesPlease
        NO_INET_NTOP = YesPlease
        NO_POSIX_GOODIES = UnfortunatelyYes
        DEFAULT_HELP_FORMAT = html
 +      NO_D_INO_IN_DIRENT = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -D_USE_32BIT_TIME_T -DNOGDI -Icompat -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
                compat/win32/dirent.o
+       BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
        BASIC_LDFLAGS += -Wl,--large-address-aware
        EXTLIBS += -lws2_32
        GITLIBS += git.res
diff --combined environment.c
index 565f65293bb25ebdcb6ada262bcca9164c3ea113,9daa0ba4a36ced9f63541203e7bcc2ab9e1eae56..1ade5c9684a9f901db7d0b3fff04d0cad443caf7
@@@ -37,7 -37,7 +37,7 @@@ int core_compression_seen
  int fsync_object_files;
  size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
  size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
 -size_t delta_base_cache_limit = 16 * 1024 * 1024;
 +size_t delta_base_cache_limit = 96 * 1024 * 1024;
  unsigned long big_file_threshold = 512 * 1024 * 1024;
  const char *pager_program;
  int pager_use_color = 1;
@@@ -64,15 -64,24 +64,25 @@@ int precomposed_unicode = -1; /* see pr
  struct startup_info *startup_info;
  unsigned long pack_size_limit_cfg;
  
+ #ifndef PROTECT_HFS_DEFAULT
+ #define PROTECT_HFS_DEFAULT 0
+ #endif
+ int protect_hfs = PROTECT_HFS_DEFAULT;
+ #ifndef PROTECT_NTFS_DEFAULT
+ #define PROTECT_NTFS_DEFAULT 0
+ #endif
+ int protect_ntfs = PROTECT_NTFS_DEFAULT;
  /*
   * The character that begins a commented line in user-editable file
   * that is subject to stripspace.
   */
  char comment_line_char = '#';
 +int auto_comment_line_char;
  
  /* Parallel index stat data preload? */
 -int core_preload_index = 0;
 +int core_preload_index = 1;
  
  /* This is set by setup_git_dir_gently() and/or git_default_config() */
  char *git_work_tree_cfg;
@@@ -124,12 -133,6 +134,12 @@@ static char *expand_namespace(const cha
        return strbuf_detach(&buf, NULL);
  }
  
 +static char *git_path_from_env(const char *envvar, const char *path)
 +{
 +      const char *value = getenv(envvar);
 +      return value ? xstrdup(value) : git_pathdup("%s", path);
 +}
 +
  static void setup_git_env(void)
  {
        const char *gitfile;
                git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
        gitfile = read_gitfile(git_dir);
        git_dir = xstrdup(gitfile ? gitfile : git_dir);
 -      git_object_dir = getenv(DB_ENVIRONMENT);
 -      if (!git_object_dir) {
 -              git_object_dir = xmalloc(strlen(git_dir) + 9);
 -              sprintf(git_object_dir, "%s/objects", git_dir);
 -      }
 -      git_index_file = getenv(INDEX_ENVIRONMENT);
 -      if (!git_index_file) {
 -              git_index_file = xmalloc(strlen(git_dir) + 7);
 -              sprintf(git_index_file, "%s/index", git_dir);
 -      }
 -      git_graft_file = getenv(GRAFT_ENVIRONMENT);
 -      if (!git_graft_file)
 -              git_graft_file = git_pathdup("info/grafts");
 +      git_object_dir = git_path_from_env(DB_ENVIRONMENT, "objects");
 +      git_index_file = git_path_from_env(INDEX_ENVIRONMENT, "index");
 +      git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, "info/grafts");
        if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
                check_replace_refs = 0;
        namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
diff --combined fsck.c
index 56156fff44a3556e2fa0b691ab103612a4678a3e,5e6723d410532a6e8eec100c524d4e4c24d4a4f4..50c6507d60734a95beea11de1b7b7d5e5965e2e6
--- 1/fsck.c
--- 2/fsck.c
+++ b/fsck.c
@@@ -6,6 -6,7 +6,7 @@@
  #include "commit.h"
  #include "tag.h"
  #include "fsck.h"
+ #include "utf8.h"
  
  static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
  {
@@@ -170,7 -171,9 +171,9 @@@ static int fsck_tree(struct tree *item
                has_empty_name |= !*name;
                has_dot |= !strcmp(name, ".");
                has_dotdot |= !strcmp(name, "..");
-               has_dotgit |= !strcmp(name, ".git");
+               has_dotgit |= (!strcmp(name, ".git") ||
+                              is_hfs_dotgit(name) ||
+                              is_ntfs_dotgit(name));
                has_zero_pad |= *(char *)desc.buffer == '0';
                update_tree_entry(&desc);
  
@@@ -279,39 -282,54 +282,39 @@@ static int fsck_ident(const char **iden
  static int fsck_commit_buffer(struct commit *commit, const char *buffer,
                              fsck_error error_func)
  {
 -      const char *tmp;
        unsigned char tree_sha1[20], sha1[20];
        struct commit_graft *graft;
 -      int parents = 0;
 +      unsigned parent_count, parent_line_count = 0;
        int err;
  
 -      buffer = skip_prefix(buffer, "tree ");
 -      if (!buffer)
 +      if (!skip_prefix(buffer, "tree ", &buffer))
                return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line");
        if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n')
                return error_func(&commit->object, FSCK_ERROR, "invalid 'tree' line format - bad sha1");
        buffer += 41;
 -      while ((tmp = skip_prefix(buffer, "parent "))) {
 -              buffer = tmp;
 +      while (skip_prefix(buffer, "parent ", &buffer)) {
                if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n')
                        return error_func(&commit->object, FSCK_ERROR, "invalid 'parent' line format - bad sha1");
                buffer += 41;
 -              parents++;
 +              parent_line_count++;
        }
        graft = lookup_commit_graft(commit->object.sha1);
 +      parent_count = commit_list_count(commit->parents);
        if (graft) {
 -              struct commit_list *p = commit->parents;
 -              parents = 0;
 -              while (p) {
 -                      p = p->next;
 -                      parents++;
 -              }
 -              if (graft->nr_parent == -1 && !parents)
 +              if (graft->nr_parent == -1 && !parent_count)
                        ; /* shallow commit */
 -              else if (graft->nr_parent != parents)
 +              else if (graft->nr_parent != parent_count)
                        return error_func(&commit->object, FSCK_ERROR, "graft objects missing");
        } else {
 -              struct commit_list *p = commit->parents;
 -              while (p && parents) {
 -                      p = p->next;
 -                      parents--;
 -              }
 -              if (p || parents)
 +              if (parent_count != parent_line_count)
                        return error_func(&commit->object, FSCK_ERROR, "parent objects missing");
        }
 -      buffer = skip_prefix(buffer, "author ");
 -      if (!buffer)
 +      if (!skip_prefix(buffer, "author ", &buffer))
                return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'author' line");
        err = fsck_ident(&buffer, &commit->object, error_func);
        if (err)
                return err;
 -      buffer = skip_prefix(buffer, "committer ");
 -      if (!buffer)
 +      if (!skip_prefix(buffer, "committer ", &buffer))
                return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'committer' line");
        err = fsck_ident(&buffer, &commit->object, error_func);
        if (err)
diff --combined path.c
index 3afcdb432a009b5e1c869123139192cf1a688292,f10c91a92708d2e9915aa2855658243d90a4b9df..757d0b057d3d72842e182864cc0f52b6457e4079
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -275,16 -275,16 +275,16 @@@ char *expand_user_path(const char *path
                        const char *home = getenv("HOME");
                        if (!home)
                                goto return_null;
 -                      strbuf_add(&user_path, home, strlen(home));
 +                      strbuf_addstr(&user_path, home);
                } else {
                        struct passwd *pw = getpw_str(username, username_len);
                        if (!pw)
                                goto return_null;
 -                      strbuf_add(&user_path, pw->pw_dir, strlen(pw->pw_dir));
 +                      strbuf_addstr(&user_path, pw->pw_dir);
                }
                to_copy = first_slash;
        }
 -      strbuf_add(&user_path, to_copy, strlen(to_copy));
 +      strbuf_addstr(&user_path, to_copy);
        return strbuf_detach(&user_path, NULL);
  return_null:
        strbuf_release(&user_path);
@@@ -821,3 -821,43 +821,36 @@@ int daemon_avoid_alias(const char *p
                }
        }
  }
 -int offset_1st_component(const char *path)
 -{
 -      if (has_dos_drive_prefix(path))
 -              return 2 + is_dir_sep(path[2]);
 -      return is_dir_sep(path[0]);
 -}
 -
+ static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
+ {
+       if (len < skip)
+               return 0;
+       len -= skip;
+       path += skip;
+       while (len-- > 0) {
+               char c = *(path++);
+               if (c != ' ' && c != '.')
+                       return 0;
+       }
+       return 1;
+ }
+ int is_ntfs_dotgit(const char *name)
+ {
+       int 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;
+               }
+ }
diff --combined read-cache.c
index 6f0057fe66a59e1239703ee4458de200304f727a,36a6f73fedc2fdb7c5dc2e1fb73ca5c5c76979c0..f0426938565e89d870aa07b65557e7d5b890b382
@@@ -14,8 -14,7 +14,9 @@@
  #include "resolve-undo.h"
  #include "strbuf.h"
  #include "varint.h"
 +#include "split-index.h"
 +#include "sigchain.h"
+ #include "utf8.h"
  
  static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
                                               unsigned int options);
  #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
  #define CACHE_EXT_TREE 0x54524545     /* "TREE" */
  #define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
 +#define CACHE_EXT_LINK 0x6c696e6b       /* "link" */
 +
 +/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
 +#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
 +               CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \
 +               SPLIT_INDEX_ORDERED)
  
  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)
  {
@@@ -56,12 -48,10 +57,12 @@@ static void replace_index_entry(struct 
  {
        struct cache_entry *old = istate->cache[nr];
  
 +      replace_index_entry_in_base(istate, old, ce);
        remove_name_hash(istate, old);
        free(old);
        set_index_entry(istate, nr, ce);
 -      istate->cache_changed = 1;
 +      ce->ce_flags |= CE_UPDATE_IN_BASE;
 +      istate->cache_changed |= CE_ENTRY_CHANGED;
  }
  
  void rename_index_entry_at(struct index_state *istate, int nr, const char *new_name)
        copy_cache_entry(new, old);
        new->ce_flags &= ~CE_HASHED;
        new->ce_namelen = namelen;
 +      new->index = 0;
        memcpy(new->name, new_name, namelen + 1);
  
 -      cache_tree_invalidate_path(istate->cache_tree, old->name);
 +      cache_tree_invalidate_path(istate, old->name);
        remove_index_entry_at(istate, nr);
        add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
  }
@@@ -434,26 -423,18 +435,26 @@@ int df_name_compare(const char *name1, 
        return c1 - c2;
  }
  
 -int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2)
 +int name_compare(const char *name1, size_t len1, const char *name2, size_t len2)
  {
 -      int len = len1 < len2 ? len1 : len2;
 -      int cmp;
 -
 -      cmp = memcmp(name1, name2, len);
 +      size_t min_len = (len1 < len2) ? len1 : len2;
 +      int cmp = memcmp(name1, name2, min_len);
        if (cmp)
                return cmp;
        if (len1 < len2)
                return -1;
        if (len1 > len2)
                return 1;
 +      return 0;
 +}
 +
 +int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2)
 +{
 +      int cmp;
 +
 +      cmp = name_compare(name1, len1, name2, len2);
 +      if (cmp)
 +              return cmp;
  
        if (stage1 < stage2)
                return -1;
        return 0;
  }
  
 -int cache_name_compare(const char *name1, int len1, const char *name2, int len2)
 -{
 -      return cache_name_stage_compare(name1, len1, 0, name2, len2, 0);
 -}
 -
  static int index_name_stage_pos(const struct index_state *istate, const char *name, int namelen, int stage)
  {
        int first, last;
@@@ -495,8 -481,8 +496,8 @@@ int remove_index_entry_at(struct index_
  
        record_resolve_undo(istate, ce);
        remove_name_hash(istate, ce);
 -      free(ce);
 -      istate->cache_changed = 1;
 +      save_or_free_index_entry(istate, ce);
 +      istate->cache_changed |= CE_ENTRY_REMOVED;
        istate->cache_nr--;
        if (pos >= istate->cache_nr)
                return 0;
@@@ -519,14 -505,12 +520,14 @@@ void remove_marked_cache_entries(struc
        for (i = j = 0; i < istate->cache_nr; i++) {
                if (ce_array[i]->ce_flags & CE_REMOVE) {
                        remove_name_hash(istate, ce_array[i]);
 -                      free(ce_array[i]);
 +                      save_or_free_index_entry(istate, ce_array[i]);
                }
                else
                        ce_array[j++] = ce_array[i];
        }
 -      istate->cache_changed = 1;
 +      if (j == istate->cache_nr)
 +              return;
 +      istate->cache_changed |= CE_ENTRY_REMOVED;
        istate->cache_nr = j;
  }
  
@@@ -535,7 -519,7 +536,7 @@@ int remove_file_from_index(struct index
        int pos = index_name_pos(istate, path, strlen(path));
        if (pos < 0)
                pos = -pos-1;
 -      cache_tree_invalidate_path(istate->cache_tree, path);
 +      cache_tree_invalidate_path(istate, path);
        while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))
                remove_index_entry_at(istate, pos);
        return 0;
@@@ -584,9 -568,7 +585,9 @@@ static int different_name(struct cache_
   * So we use the CE_ADDED flag to verify that the alias was an old
   * one before we accept it as
   */
 -static struct cache_entry *create_alias_ce(struct cache_entry *ce, struct cache_entry *alias)
 +static struct cache_entry *create_alias_ce(struct index_state *istate,
 +                                         struct cache_entry *ce,
 +                                         struct cache_entry *alias)
  {
        int len;
        struct cache_entry *new;
        new = xcalloc(1, cache_entry_size(len));
        memcpy(new->name, alias->name, len);
        copy_cache_entry(new, ce);
 -      free(ce);
 +      save_or_free_index_entry(istate, ce);
        return new;
  }
  
@@@ -692,7 -674,7 +693,7 @@@ int add_to_index(struct index_state *is
                set_object_name_for_intent_to_add_entry(ce);
  
        if (ignore_case && alias && different_name(ce, alias))
 -              ce = create_alias_ce(ce, alias);
 +              ce = create_alias_ce(istate, ce, alias);
        ce->ce_flags |= CE_ADDED;
  
        /* It was suspected to be racily clean, but it turns out to be Ok */
@@@ -775,9 -757,10 +776,10 @@@ static int verify_dotfile(const char *r
         * shares the path end test with the ".." case.
         */
        case 'g':
-               if (rest[1] != 'i')
+       case 'G':
+               if (rest[1] != 'i' && rest[1] != 'I')
                        break;
-               if (rest[2] != 't')
+               if (rest[2] != 't' && rest[2] != 'T')
                        break;
                rest += 2;
        /* fallthrough */
@@@ -801,6 -784,10 +803,10 @@@ int verify_path(const char *path
                        return 1;
                if (is_dir_sep(c)) {
  inside:
+                       if (protect_hfs && is_hfs_dotgit(path))
+                               return 0;
+                       if (protect_ntfs && is_ntfs_dotgit(path))
+                               return 0;
                        c = *path++;
                        if ((c == '.' && !verify_dotfile(path)) ||
                            is_dir_sep(c) || c == '\0')
@@@ -955,8 -942,7 +961,8 @@@ static int add_index_entry_with_check(s
        int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
        int new_only = option & ADD_CACHE_NEW_ONLY;
  
 -      cache_tree_invalidate_path(istate->cache_tree, ce->name);
 +      if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
 +              cache_tree_invalidate_path(istate, ce->name);
        pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
  
        /* existing match? Just replace it. */
@@@ -1019,7 -1005,7 +1025,7 @@@ int add_index_entry(struct index_state 
                        istate->cache + pos,
                        (istate->cache_nr - pos - 1) * sizeof(ce));
        set_index_entry(istate, pos, ce);
 -      istate->cache_changed = 1;
 +      istate->cache_changed |= CE_ENTRY_ADDED;
        return 0;
  }
  
@@@ -1064,14 -1050,6 +1070,14 @@@ static struct cache_entry *refresh_cach
                return ce;
        }
  
 +      if (has_symlink_leading_path(ce->name, ce_namelen(ce))) {
 +              if (ignore_missing)
 +                      return ce;
 +              if (err)
 +                      *err = ENOENT;
 +              return NULL;
 +      }
 +
        if (lstat(ce->name, &st) < 0) {
                if (ignore_missing && errno == ENOENT)
                        return ce;
            !(ce->ce_flags & CE_VALID))
                updated->ce_flags &= ~CE_VALID;
  
 +      /* istate->cache_changed is updated in the caller */
        return updated;
  }
  
@@@ -1208,8 -1185,7 +1214,8 @@@ int refresh_index(struct index_state *i
                                 * means the index is not valid anymore.
                                 */
                                ce->ce_flags &= ~CE_VALID;
 -                              istate->cache_changed = 1;
 +                              ce->ce_flags |= CE_UPDATE_IN_BASE;
 +                              istate->cache_changed |= CE_ENTRY_CHANGED;
                        }
                        if (quiet)
                                continue;
@@@ -1361,10 -1337,6 +1367,10 @@@ static int read_index_extension(struct 
        case CACHE_EXT_RESOLVE_UNDO:
                istate->resolve_undo = resolve_undo_read(data, sz);
                break;
 +      case CACHE_EXT_LINK:
 +              if (read_link_extension(istate, data, sz))
 +                      return -1;
 +              break;
        default:
                if (*ext < 'A' || 'Z' < *ext)
                        return error("index uses %.4s extension, which we do not understand",
@@@ -1399,7 -1371,6 +1405,7 @@@ static struct cache_entry *cache_entry_
        ce->ce_stat_data.sd_size  = get_be32(&ondisk->size);
        ce->ce_flags = flags & ~CE_NAMEMASK;
        ce->ce_namelen = len;
 +      ce->index = 0;
        hashcpy(ce->sha1, ondisk->sha1);
        memcpy(ce->name, name, len);
        ce->name[len] = '\0';
@@@ -1474,7 -1445,7 +1480,7 @@@ static struct cache_entry *create_from_
  }
  
  /* remember to discard_cache() before reading a different cache! */
 -int read_index_from(struct index_state *istate, const char *path)
 +int do_read_index(struct index_state *istate, const char *path, int must_exist)
  {
        int fd, i;
        struct stat st;
        istate->timestamp.nsec = 0;
        fd = open(path, O_RDONLY);
        if (fd < 0) {
 -              if (errno == ENOENT)
 +              if (!must_exist && errno == ENOENT)
                        return 0;
 -              die_errno("index file open failed");
 +              die_errno("%s: index file open failed", path);
        }
  
        if (fstat(fd, &st))
        if (verify_hdr(hdr, mmap_size) < 0)
                goto unmap;
  
 -      hashcpy(istate->sha1, (unsigned char *)hdr + mmap_size - 20);
 +      hashcpy(istate->sha1, (const unsigned char *)hdr + mmap_size - 20);
        istate->version = ntohl(hdr->hdr_version);
        istate->cache_nr = ntohl(hdr->hdr_entries);
        istate->cache_alloc = alloc_nr(istate->cache_nr);
@@@ -1566,40 -1537,6 +1572,40 @@@ unmap
        die("index file corrupt");
  }
  
 +int read_index_from(struct index_state *istate, const char *path)
 +{
 +      struct split_index *split_index;
 +      int ret;
 +
 +      /* istate->initialized covers both .git/index and .git/sharedindex.xxx */
 +      if (istate->initialized)
 +              return istate->cache_nr;
 +
 +      ret = do_read_index(istate, path, 0);
 +      split_index = istate->split_index;
 +      if (!split_index)
 +              return ret;
 +
 +      if (is_null_sha1(split_index->base_sha1))
 +              return ret;
 +
 +      if (split_index->base)
 +              discard_index(split_index->base);
 +      else
 +              split_index->base = xcalloc(1, sizeof(*split_index->base));
 +      ret = do_read_index(split_index->base,
 +                          git_path("sharedindex.%s",
 +                                   sha1_to_hex(split_index->base_sha1)), 1);
 +      if (hashcmp(split_index->base_sha1, split_index->base->sha1))
 +              die("broken index, expect %s in %s, got %s",
 +                  sha1_to_hex(split_index->base_sha1),
 +                  git_path("sharedindex.%s",
 +                                   sha1_to_hex(split_index->base_sha1)),
 +                  sha1_to_hex(split_index->base->sha1));
 +      merge_base_index(istate);
 +      return ret;
 +}
 +
  int is_index_unborn(struct index_state *istate)
  {
        return (!istate->cache_nr && !istate->timestamp.sec);
@@@ -1609,15 -1546,8 +1615,15 @@@ int discard_index(struct index_state *i
  {
        int i;
  
 -      for (i = 0; i < istate->cache_nr; i++)
 +      for (i = 0; i < istate->cache_nr; i++) {
 +              if (istate->cache[i]->index &&
 +                  istate->split_index &&
 +                  istate->split_index->base &&
 +                  istate->cache[i]->index <= istate->split_index->base->cache_nr &&
 +                  istate->cache[i] == istate->split_index->base->cache[istate->cache[i]->index - 1])
 +                      continue;
                free(istate->cache[i]);
 +      }
        resolve_undo_clear_index(istate);
        istate->cache_nr = 0;
        istate->cache_changed = 0;
        free(istate->cache);
        istate->cache = NULL;
        istate->cache_alloc = 0;
 +      discard_split_index(istate);
        return 0;
  }
  
@@@ -1690,7 -1619,7 +1696,7 @@@ static int write_index_ext_header(git_S
                (ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0;
  }
  
 -static int ce_flush(git_SHA_CTX *context, int fd)
 +static int ce_flush(git_SHA_CTX *context, int fd, unsigned char *sha1)
  {
        unsigned int left = write_buffer_len;
  
  
        /* Append the SHA1 signature at the end */
        git_SHA1_Final(write_buffer + left, context);
 +      hashcpy(sha1, write_buffer + left);
        left += 20;
        return (write_in_full(fd, write_buffer, left) != left) ? -1 : 0;
  }
@@@ -1780,7 -1708,7 +1786,7 @@@ static char *copy_cache_entry_to_ondisk
        ondisk->size = htonl(ce->ce_stat_data.sd_size);
        hashcpy(ondisk->sha1, ce->sha1);
  
 -      flags = ce->ce_flags;
 +      flags = ce->ce_flags & ~CE_NAMEMASK;
        flags |= (ce_namelen(ce) >= CE_NAMEMASK ? CE_NAMEMASK : ce_namelen(ce));
        ondisk->flags = htons(flags);
        if (ce->ce_flags & CE_EXTENDED) {
@@@ -1799,15 -1727,9 +1805,15 @@@ static int ce_write_entry(git_SHA_CTX *
  {
        int size;
        struct ondisk_cache_entry *ondisk;
 +      int saved_namelen = saved_namelen; /* compiler workaround */
        char *name;
        int result;
  
 +      if (ce->ce_flags & CE_STRIP_NAME) {
 +              saved_namelen = ce_namelen(ce);
 +              ce->ce_namelen = 0;
 +      }
 +
        if (!previous_name) {
                size = ondisk_ce_size(ce);
                ondisk = xcalloc(1, size);
                strbuf_splice(previous_name, common, to_remove,
                              ce->name + common, ce_namelen(ce) - common);
        }
 +      if (ce->ce_flags & CE_STRIP_NAME) {
 +              ce->ce_namelen = saved_namelen;
 +              ce->ce_flags &= ~CE_STRIP_NAME;
 +      }
  
        result = ce_write(c, fd, ondisk, size);
        free(ondisk);
@@@ -1912,13 -1830,13 +1918,13 @@@ static int has_racy_timestamp(struct in
  void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
  {
        if ((istate->cache_changed || has_racy_timestamp(istate)) &&
 -          verify_index(istate) && !write_index(istate, lockfile->fd))
 -              commit_locked_index(lockfile);
 -      else
 +          verify_index(istate) &&
 +          write_locked_index(istate, lockfile, COMMIT_LOCK))
                rollback_lock_file(lockfile);
  }
  
 -int write_index(struct index_state *istate, int newfd)
 +static int do_write_index(struct index_state *istate, int newfd,
 +                        int strip_extensions)
  {
        git_SHA_CTX c;
        struct cache_header hdr;
                }
        }
  
 -      if (!istate->version)
 +      if (!istate->version) {
                istate->version = get_index_format_default();
 +              if (getenv("GIT_TEST_SPLIT_INDEX"))
 +                      init_split_index(istate);
 +      }
  
        /* demote version 3 to version 2 when the latter suffices */
        if (istate->version == 3 || istate->version == 2)
        strbuf_release(&previous_name_buf);
  
        /* Write extension data here */
 -      if (istate->cache_tree) {
 +      if (!strip_extensions && istate->split_index) {
 +              struct strbuf sb = STRBUF_INIT;
 +
 +              err = write_link_extension(&sb, istate) < 0 ||
 +                      write_index_ext_header(&c, newfd, CACHE_EXT_LINK,
 +                                             sb.len) < 0 ||
 +                      ce_write(&c, newfd, sb.buf, sb.len) < 0;
 +              strbuf_release(&sb);
 +              if (err)
 +                      return -1;
 +      }
 +      if (!strip_extensions && istate->cache_tree) {
                struct strbuf sb = STRBUF_INIT;
  
                cache_tree_write(&sb, istate->cache_tree);
                if (err)
                        return -1;
        }
 -      if (istate->resolve_undo) {
 +      if (!strip_extensions && istate->resolve_undo) {
                struct strbuf sb = STRBUF_INIT;
  
                resolve_undo_write(&sb, istate->resolve_undo);
                        return -1;
        }
  
 -      if (ce_flush(&c, newfd) || fstat(newfd, &st))
 +      if (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st))
                return -1;
        istate->timestamp.sec = (unsigned int)st.st_mtime;
        istate->timestamp.nsec = ST_MTIME_NSEC(st);
        return 0;
  }
  
 +void set_alternate_index_output(const char *name)
 +{
 +      alternate_index_output = name;
 +}
 +
 +static int commit_locked_index(struct lock_file *lk)
 +{
 +      if (alternate_index_output) {
 +              if (lk->fd >= 0 && close_lock_file(lk))
 +                      return -1;
 +              if (rename(lk->filename, alternate_index_output))
 +                      return -1;
 +              lk->filename[0] = 0;
 +              return 0;
 +      } else {
 +              return commit_lock_file(lk);
 +      }
 +}
 +
 +static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,
 +                               unsigned flags)
 +{
 +      int ret = do_write_index(istate, lock->fd, 0);
 +      if (ret)
 +              return ret;
 +      assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) !=
 +             (COMMIT_LOCK | CLOSE_LOCK));
 +      if (flags & COMMIT_LOCK)
 +              return commit_locked_index(lock);
 +      else if (flags & CLOSE_LOCK)
 +              return close_lock_file(lock);
 +      else
 +              return ret;
 +}
 +
 +static int write_split_index(struct index_state *istate,
 +                           struct lock_file *lock,
 +                           unsigned flags)
 +{
 +      int ret;
 +      prepare_to_write_split_index(istate);
 +      ret = do_write_locked_index(istate, lock, flags);
 +      finish_writing_split_index(istate);
 +      return ret;
 +}
 +
 +static char *temporary_sharedindex;
 +
 +static void remove_temporary_sharedindex(void)
 +{
 +      if (temporary_sharedindex) {
 +              unlink_or_warn(temporary_sharedindex);
 +              free(temporary_sharedindex);
 +              temporary_sharedindex = NULL;
 +      }
 +}
 +
 +static void remove_temporary_sharedindex_on_signal(int signo)
 +{
 +      remove_temporary_sharedindex();
 +      sigchain_pop(signo);
 +      raise(signo);
 +}
 +
 +static int write_shared_index(struct index_state *istate,
 +                            struct lock_file *lock, unsigned flags)
 +{
 +      struct split_index *si = istate->split_index;
 +      static int installed_handler;
 +      int fd, ret;
 +
 +      temporary_sharedindex = git_pathdup("sharedindex_XXXXXX");
 +      fd = mkstemp(temporary_sharedindex);
 +      if (fd < 0) {
 +              free(temporary_sharedindex);
 +              temporary_sharedindex = NULL;
 +              hashclr(si->base_sha1);
 +              return do_write_locked_index(istate, lock, flags);
 +      }
 +      if (!installed_handler) {
 +              atexit(remove_temporary_sharedindex);
 +              sigchain_push_common(remove_temporary_sharedindex_on_signal);
 +      }
 +      move_cache_to_base_index(istate);
 +      ret = do_write_index(si->base, fd, 1);
 +      close(fd);
 +      if (ret) {
 +              remove_temporary_sharedindex();
 +              return ret;
 +      }
 +      ret = rename(temporary_sharedindex,
 +                   git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
 +      free(temporary_sharedindex);
 +      temporary_sharedindex = NULL;
 +      if (!ret)
 +              hashcpy(si->base_sha1, si->base->sha1);
 +      return ret;
 +}
 +
 +int write_locked_index(struct index_state *istate, struct lock_file *lock,
 +                     unsigned flags)
 +{
 +      struct split_index *si = istate->split_index;
 +
 +      if (!si || alternate_index_output ||
 +          (istate->cache_changed & ~EXTMASK)) {
 +              if (si)
 +                      hashclr(si->base_sha1);
 +              return do_write_locked_index(istate, lock, flags);
 +      }
 +
 +      if (getenv("GIT_TEST_SPLIT_INDEX")) {
 +              int v = si->base_sha1[0];
 +              if ((v & 15) < 6)
 +                      istate->cache_changed |= SPLIT_INDEX_ORDERED;
 +      }
 +      if (istate->cache_changed & SPLIT_INDEX_ORDERED) {
 +              int ret = write_shared_index(istate, lock, flags);
 +              if (ret)
 +                      return ret;
 +      }
 +
 +      return write_split_index(istate, lock, flags);
 +}
 +
  /*
   * Read the index file that is potentially unmerged into given
   * index_state, dropping any unmerged entries.  Returns true if
diff --combined t/t1450-fsck.sh
index b52397afd3352b873c9c7fabb91eb850aaf0db3b,983568a4b9f81d95c5142b02a3760be3644b5bd8..426f753fe3e4907a151be87be247a32bf9741905
@@@ -69,7 -69,7 +69,7 @@@ test_expect_success 'object with bad sh
        git update-ref refs/heads/bogus $cmt &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
  
 -      test_might_fail git fsck 2>out &&
 +      test_must_fail git fsck 2>out &&
        cat out &&
        grep "$sha.*corrupt" out
  '
@@@ -101,7 -101,7 +101,7 @@@ test_expect_success 'email with embedde
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
 -      git fsck 2>out &&
 +      test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new" out
  '
@@@ -113,7 -113,7 +113,7 @@@ test_expect_success 'missing < email de
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
 -      git fsck 2>out &&
 +      test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new.* - bad name" out
  '
@@@ -125,7 -125,7 +125,7 @@@ test_expect_success 'missing email is r
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
 -      git fsck 2>out &&
 +      test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new.* - missing email" out
  '
@@@ -137,7 -137,7 +137,7 @@@ test_expect_success '> in name is repor
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
 -      git fsck 2>out &&
 +      test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new" out
  '
@@@ -151,31 -151,11 +151,31 @@@ test_expect_success 'integer overflow i
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
 -      git fsck 2>out &&
 +      test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new.*integer overflow" out
  '
  
 +test_expect_success 'malformatted tree object' '
 +      test_when_finished "git update-ref -d refs/tags/wrong" &&
 +      test_when_finished "remove_object \$T" &&
 +      T=$(
 +              GIT_INDEX_FILE=test-index &&
 +              export GIT_INDEX_FILE &&
 +              rm -f test-index &&
 +              >x &&
 +              git add x &&
 +              T=$(git write-tree) &&
 +              (
 +                      git cat-file tree $T &&
 +                      git cat-file tree $T
 +              ) |
 +              git hash-object -w -t tree --stdin
 +      ) &&
 +      test_must_fail git fsck 2>out &&
 +      grep "error in tree .*contains duplicate file entries" out
 +'
 +
  test_expect_success 'tag pointing to nonexistent' '
        cat >invalid-tag <<-\EOF &&
        object ffffffffffffffffffffffffffffffffffffffff
@@@ -271,91 -251,40 +271,96 @@@ test_expect_success 'fsck notices submo
        )
  '
  
- test_expect_success 'fsck notices "." and ".." in trees' '
-       (
-               git init dots &&
-               cd dots &&
-               blob=$(echo foo | git hash-object -w --stdin) &&
-               tab=$(printf "\\t") &&
-               git mktree <<-EOF &&
-               100644 blob $blob$tab.
-               100644 blob $blob$tab..
-               EOF
-               git fsck 2>out &&
-               cat out &&
-               grep "warning.*\\." out
-       )
- '
- test_expect_success 'fsck notices ".git" in trees' '
-       (
-               git init dotgit &&
-               cd dotgit &&
-               blob=$(echo foo | git hash-object -w --stdin) &&
-               tab=$(printf "\\t") &&
-               git mktree <<-EOF &&
-               100644 blob $blob$tab.git
-               EOF
-               git fsck 2>out &&
-               cat out &&
-               grep "warning.*\\.git" out
-       )
- '
+ while read name path pretty; do
+       while read mode type; do
+               : ${pretty:=$path}
+               test_expect_success "fsck notices $pretty as $type" '
+               (
+                       git init $name-$type &&
+                       cd $name-$type &&
+                       echo content >file &&
+                       git add file &&
+                       git commit -m base &&
+                       blob=$(git rev-parse :file) &&
+                       tree=$(git rev-parse HEAD^{tree}) &&
+                       value=$(eval "echo \$$type") &&
+                       printf "$mode $type %s\t%s" "$value" "$path" >bad &&
+                       bad_tree=$(git mktree <bad) &&
+                       git fsck 2>out &&
+                       cat out &&
+                       grep "warning.*tree $bad_tree" out
+               )'
+       done <<-\EOF
+       100644 blob
+       040000 tree
+       EOF
+ done <<-EOF
+ dot .
+ dotdot ..
+ dotgit .git
+ dotgit-case .GIT
+ dotgit-unicode .gI${u200c}T .gI{u200c}T
+ dotgit-case2 .Git
+ git-tilde1 git~1
+ dotgitdot .git.
+ dot-backslash-case .\\\\.GIT\\\\foobar
+ dotgit-case-backslash .git\\\\foobar
+ EOF
  
 +# create a static test repo which is broken by omitting
 +# one particular object ($1, which is looked up via rev-parse
 +# in the new repository).
 +create_repo_missing () {
 +      rm -rf missing &&
 +      git init missing &&
 +      (
 +              cd missing &&
 +              git commit -m one --allow-empty &&
 +              mkdir subdir &&
 +              echo content >subdir/file &&
 +              git add subdir/file &&
 +              git commit -m two &&
 +              unrelated=$(echo unrelated | git hash-object --stdin -w) &&
 +              git tag -m foo tag $unrelated &&
 +              sha1=$(git rev-parse --verify "$1") &&
 +              path=$(echo $sha1 | sed 's|..|&/|') &&
 +              rm .git/objects/$path
 +      )
 +}
 +
 +test_expect_success 'fsck notices missing blob' '
 +      create_repo_missing HEAD:subdir/file &&
 +      test_must_fail git -C missing fsck
 +'
 +
 +test_expect_success 'fsck notices missing subtree' '
 +      create_repo_missing HEAD:subdir &&
 +      test_must_fail git -C missing fsck
 +'
 +
 +test_expect_success 'fsck notices missing root tree' '
 +      create_repo_missing HEAD^{tree} &&
 +      test_must_fail git -C missing fsck
 +'
 +
 +test_expect_success 'fsck notices missing parent' '
 +      create_repo_missing HEAD^ &&
 +      test_must_fail git -C missing fsck
 +'
 +
 +test_expect_success 'fsck notices missing tagged object' '
 +      create_repo_missing tag^{blob} &&
 +      test_must_fail git -C missing fsck
 +'
 +
 +test_expect_success 'fsck notices ref pointing to missing commit' '
 +      create_repo_missing HEAD &&
 +      test_must_fail git -C missing fsck
 +'
 +
 +test_expect_success 'fsck notices ref pointing to missing tag' '
 +      create_repo_missing tag &&
 +      test_must_fail git -C missing fsck
 +'
 +
  test_done
diff --combined t/test-lib.sh
index b1bc65bfb564ca85d35c42071f825360d1998392,e0266f03570ad969ac0d82230b1be75c9ab3ff2b..a7a4639d7c61d43c2772207d118ee8897456f1ec
@@@ -91,7 -91,6 +91,7 @@@ unset VISUAL EMAIL LANGUAGE COLUMNS $("
                VALGRIND
                UNZIP
                PERF_
 +              CURL_VERBOSE
        ));
        my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
        print join("\n", @vars);
@@@ -109,10 -108,6 +109,10 @@@ export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAM
  export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
  export EDITOR
  
 +# Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output
 +GIT_TRACE_BARE=1
 +export GIT_TRACE_BARE
 +
  if test -n "${TEST_GIT_INDEX_VERSION:+isset}"
  then
        GIT_INDEX_VERSION="$TEST_GIT_INDEX_VERSION"
@@@ -169,7 -164,11 +169,11 @@@ _z40=0000000000000000000000000000000000
  LF='
  '
  
- export _x05 _x40 _z40 LF
+ # UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores
+ # when case-folding filenames
+ u200c=$(printf '\342\200\214')
+ export _x05 _x40 _z40 LF u200c
  
  # Each test should start with something like this, after copyright notices:
  #
                immediate=t; shift ;;
        -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
                GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;;
 +      -r)
 +              shift; test "$#" -ne 0 || {
 +                      echo 'error: -r requires an argument' >&2;
 +                      exit 1;
 +              }
 +              run_list=$1; shift ;;
 +      --run=*)
 +              run_list=$(expr "z$1" : 'z[^=]*=\(.*\)'); shift ;;
        -h|--h|--he|--hel|--help)
                help=t; shift ;;
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
@@@ -379,99 -370,6 +383,99 @@@ match_pattern_list () 
        return 1
  }
  
 +match_test_selector_list () {
 +      title="$1"
 +      shift
 +      arg="$1"
 +      shift
 +      test -z "$1" && return 0
 +
 +      # Both commas and whitespace are accepted as separators.
 +      OLDIFS=$IFS
 +      IFS='   ,'
 +      set -- $1
 +      IFS=$OLDIFS
 +
 +      # If the first selector is negative we include by default.
 +      include=
 +      case "$1" in
 +              !*) include=t ;;
 +      esac
 +
 +      for selector
 +      do
 +              orig_selector=$selector
 +
 +              positive=t
 +              case "$selector" in
 +                      !*)
 +                              positive=
 +                              selector=${selector##?}
 +                              ;;
 +              esac
 +
 +              test -z "$selector" && continue
 +
 +              case "$selector" in
 +                      *-*)
 +                              if expr "z${selector%%-*}" : "z[0-9]*[^0-9]" >/dev/null
 +                              then
 +                                      echo "error: $title: invalid non-numeric in range" \
 +                                              "start: '$orig_selector'" >&2
 +                                      exit 1
 +                              fi
 +                              if expr "z${selector#*-}" : "z[0-9]*[^0-9]" >/dev/null
 +                              then
 +                                      echo "error: $title: invalid non-numeric in range" \
 +                                              "end: '$orig_selector'" >&2
 +                                      exit 1
 +                              fi
 +                              ;;
 +                      *)
 +                              if expr "z$selector" : "z[0-9]*[^0-9]" >/dev/null
 +                              then
 +                                      echo "error: $title: invalid non-numeric in test" \
 +                                              "selector: '$orig_selector'" >&2
 +                                      exit 1
 +                              fi
 +              esac
 +
 +              # Short cut for "obvious" cases
 +              test -z "$include" && test -z "$positive" && continue
 +              test -n "$include" && test -n "$positive" && continue
 +
 +              case "$selector" in
 +                      -*)
 +                              if test $arg -le ${selector#-}
 +                              then
 +                                      include=$positive
 +                              fi
 +                              ;;
 +                      *-)
 +                              if test $arg -ge ${selector%-}
 +                              then
 +                                      include=$positive
 +                              fi
 +                              ;;
 +                      *-*)
 +                              if test ${selector%%-*} -le $arg \
 +                                      && test $arg -le ${selector#*-}
 +                              then
 +                                      include=$positive
 +                              fi
 +                              ;;
 +                      *)
 +                              if test $arg -eq $selector
 +                              then
 +                                      include=$positive
 +                              fi
 +                              ;;
 +              esac
 +      done
 +
 +      test -n "$include"
 +}
 +
  maybe_teardown_verbose () {
        test -z "$verbose_only" && return
        exec 4>/dev/null 3>/dev/null
@@@ -558,35 -456,25 +562,35 @@@ test_finish_ () 
  
  test_skip () {
        to_skip=
 +      skipped_reason=
        if match_pattern_list $this_test.$test_count $GIT_SKIP_TESTS
        then
                to_skip=t
 +              skipped_reason="GIT_SKIP_TESTS"
        fi
        if test -z "$to_skip" && test -n "$test_prereq" &&
           ! test_have_prereq "$test_prereq"
        then
                to_skip=t
 -      fi
 -      case "$to_skip" in
 -      t)
 +
                of_prereq=
                if test "$missing_prereq" != "$test_prereq"
                then
                        of_prereq=" of $test_prereq"
                fi
 +              skipped_reason="missing $missing_prereq${of_prereq}"
 +      fi
 +      if test -z "$to_skip" && test -n "$run_list" &&
 +              ! match_test_selector_list '--run' $test_count "$run_list"
 +      then
 +              to_skip=t
 +              skipped_reason="--run"
 +      fi
  
 +      case "$to_skip" in
 +      t)
                say_color skip >&3 "skipping test: $@"
 -              say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})"
 +              say_color skip "ok $test_count # skip $1 ($skipped_reason)"
                : true
                ;;
        *)
@@@ -980,14 -868,6 +984,14 @@@ test_lazy_prereq AUTOIDENT 
        git var GIT_AUTHOR_IDENT
  '
  
 +test_lazy_prereq EXPENSIVE '
 +      test -n "$GIT_TEST_LONG"
 +'
 +
 +test_lazy_prereq USR_BIN_TIME '
 +      test -x /usr/bin/time
 +'
 +
  # When the tests are run as root, permission tests will report that
  # things are writable when they shouldn't be.
  test -w / || test_set_prereq SANITY
diff --combined unpack-trees.c
index 629c658c46a1b4f4bcd8bbe9770d7fd767ae2216,02f69aeea3dc2e60336fa4ac018ec14ae10f911e..256df47b3533bc085428d3fc5239173200a8e648
@@@ -8,7 -8,6 +8,7 @@@
  #include "progress.h"
  #include "refs.h"
  #include "attr.h"
 +#include "split-index.h"
  
  /*
   * Error messages expected by scripts out of plumbing commands such as
@@@ -57,15 -56,17 +57,15 @@@ void setup_unpack_trees_porcelain(struc
        int i;
        const char **msgs = opts->msgs;
        const char *msg;
 -      char *tmp;
        const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches";
 +
        if (advice_commit_before_merge)
                msg = "Your local changes to the following files would be overwritten by %s:\n%%s"
                        "Please, commit your changes or stash them before you can %s.";
        else
                msg = "Your local changes to the following files would be overwritten by %s:\n%%s";
 -      tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen(cmd2) - 2);
 -      sprintf(tmp, msg, cmd, cmd2);
 -      msgs[ERROR_WOULD_OVERWRITE] = tmp;
 -      msgs[ERROR_NOT_UPTODATE_FILE] = tmp;
 +      msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] =
 +              xstrfmt(msg, cmd, cmd2);
  
        msgs[ERROR_NOT_UPTODATE_DIR] =
                "Updating the following directories would lose untracked files in it:\n%s";
                        "Please move or remove them before you can %s.";
        else
                msg = "The following untracked working tree files would be %s by %s:\n%%s";
 -      tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("removed") + strlen(cmd2) - 4);
 -      sprintf(tmp, msg, "removed", cmd, cmd2);
 -      msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = tmp;
 -      tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("overwritten") + strlen(cmd2) - 4);
 -      sprintf(tmp, msg, "overwritten", cmd, cmd2);
 -      msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = tmp;
 +
 +      msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, "removed", cmd, cmd2);
 +      msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, "overwritten", cmd, cmd2);
  
        /*
         * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we
                opts->unpack_rejects[i].strdup_strings = 1;
  }
  
- static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
+ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
                         unsigned int set, unsigned int clear)
  {
        clear |= CE_HASHED;
                set |= CE_WT_REMOVE;
  
        ce->ce_flags = (ce->ce_flags & ~clear) | set;
-       add_index_entry(&o->result, ce,
-                       ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+       return add_index_entry(&o->result, ce,
+                              ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
  }
  
  static struct cache_entry *dup_entry(const struct cache_entry *ce)
@@@ -242,9 -246,7 +242,9 @@@ static int verify_absent_sparse(const s
                                enum unpack_trees_error_types,
                                struct unpack_trees_options *o);
  
 -static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o)
 +static int apply_sparse_checkout(struct index_state *istate,
 +                               struct cache_entry *ce,
 +                               struct unpack_trees_options *o)
  {
        int was_skip_worktree = ce_skip_worktree(ce);
  
                ce->ce_flags |= CE_SKIP_WORKTREE;
        else
                ce->ce_flags &= ~CE_SKIP_WORKTREE;
 +      if (was_skip_worktree != ce_skip_worktree(ce)) {
 +              ce->ce_flags |= CE_UPDATE_IN_BASE;
 +              istate->cache_changed |= CE_ENTRY_CHANGED;
 +      }
  
        /*
         * if (!was_skip_worktree && !ce_skip_worktree()) {
@@@ -609,7 -607,9 +609,9 @@@ static int unpack_nondirectories(int n
  
        for (i = 0; i < n; i++)
                if (src[i] && src[i] != o->df_conflict_entry)
-                       do_add_entry(o, src[i], 0, 0);
+                       if (do_add_entry(o, src[i], 0, 0))
+                               return -1;
        return 0;
  }
  
@@@ -624,6 -624,17 +626,6 @@@ static int unpack_failed(struct unpack_
        return -1;
  }
  
 -/* NEEDSWORK: give this a better name and share with tree-walk.c */
 -static int name_compare(const char *a, int a_len,
 -                      const char *b, int b_len)
 -{
 -      int len = (a_len < b_len) ? a_len : b_len;
 -      int cmp = memcmp(a, b, len);
 -      if (cmp)
 -              return cmp;
 -      return (a_len - b_len);
 -}
 -
  /*
   * The tree traversal is looking at name p.  If we have a matching entry,
   * return it.  If name p is a directory in the index, do not return
@@@ -1016,7 -1027,6 +1018,7 @@@ int unpack_trees(unsigned len, struct t
        state.force = 1;
        state.quiet = 1;
        state.refresh_cache = 1;
 +      state.istate = &o->result;
  
        memset(&el, 0, sizeof(el));
        if (!core_apply_sparse_checkout || !o->update)
        o->result.timestamp.sec = o->src_index->timestamp.sec;
        o->result.timestamp.nsec = o->src_index->timestamp.nsec;
        o->result.version = o->src_index->version;
 +      o->result.split_index = o->src_index->split_index;
 +      if (o->result.split_index)
 +              o->result.split_index->refcount++;
 +      hashcpy(o->result.sha1, o->src_index->sha1);
        o->merge_size = len;
        mark_all_ce_unused(o->src_index);
  
                                ret = -1;
                        }
  
 -                      if (apply_sparse_checkout(ce, o)) {
 +                      if (apply_sparse_checkout(&o->result, ce, o)) {
                                if (!o->show_all_errors)
                                        goto return_failed;
                                ret = -1;
@@@ -1176,8 -1182,7 +1178,8 @@@ return_failed
  static int reject_merge(const struct cache_entry *ce,
                        struct unpack_trees_options *o)
  {
 -      return add_rejected_path(o, ERROR_WOULD_OVERWRITE, ce->name);
 +      return o->gently ? -1 :
 +              add_rejected_path(o, ERROR_WOULD_OVERWRITE, ce->name);
  }
  
  static int same(const struct cache_entry *a, const struct cache_entry *b)
@@@ -1256,7 -1261,7 +1258,7 @@@ static void invalidate_ce_path(const st
                               struct unpack_trees_options *o)
  {
        if (ce)
 -              cache_tree_invalidate_path(o->src_index->cache_tree, ce->name);
 +              cache_tree_invalidate_path(o->src_index, ce->name);
  }
  
  /*
@@@ -1632,7 -1637,7 +1634,7 @@@ int threeway_merge(const struct cache_e
        /* #14, #14ALT, #2ALT */
        if (remote && !df_conflict_head && head_match && !remote_match) {
                if (index && !same(index, remote) && !same(index, head))
 -                      return o->gently ? -1 : reject_merge(index, o);
 +                      return reject_merge(index, o);
                return merged_entry(remote, index, o);
        }
        /*
         * make sure that it matches head.
         */
        if (index && !same(index, head))
 -              return o->gently ? -1 : reject_merge(index, o);
 +              return reject_merge(index, o);
  
        if (head) {
                /* #5ALT, #15 */
@@@ -1769,8 -1774,9 +1771,8 @@@ int twoway_merge(const struct cache_ent
                                else
                                        return merged_entry(newtree, current, o);
                        }
 -                      return o->gently ? -1 : reject_merge(current, o);
 -              }
 -              else if ((!oldtree && !newtree) || /* 4 and 5 */
 +                      return reject_merge(current, o);
 +              } else if ((!oldtree && !newtree) || /* 4 and 5 */
                         (!oldtree && newtree &&
                          same(current, newtree)) || /* 6 and 7 */
                         (oldtree && newtree &&
                          !same(oldtree, newtree) && /* 18 and 19 */
                          same(current, newtree))) {
                        return keep_entry(current, o);
 -              }
 -              else if (oldtree && !newtree && same(current, oldtree)) {
 +              } else if (oldtree && !newtree && same(current, oldtree)) {
                        /* 10 or 11 */
                        return deleted_entry(oldtree, current, o);
 -              }
 -              else if (oldtree && newtree &&
 +              } else if (oldtree && newtree &&
                         same(current, oldtree) && !same(current, newtree)) {
                        /* 20 or 21 */
                        return merged_entry(newtree, current, o);
 -              }
 -              else {
 -                      /* all other failures */
 -                      if (oldtree)
 -                              return o->gently ? -1 : reject_merge(oldtree, o);
 -                      if (current)
 -                              return o->gently ? -1 : reject_merge(current, o);
 -                      if (newtree)
 -                              return o->gently ? -1 : reject_merge(newtree, o);
 -                      return -1;
 -              }
 +              } else
 +                      return reject_merge(current, o);
        }
        else if (newtree) {
                if (oldtree && !o->initial_checkout) {
diff --combined utf8.c
index b30790d043aa4b01da00686654dfb615a92e75b6,a89704017a0997fd93ba14f198eb3e199d0f4274..3b77e9765811b2dc0b7c4031b1229d432e1942d2
--- 1/utf8.c
--- 2/utf8.c
+++ b/utf8.c
@@@ -80,8 -80,52 +80,8 @@@ static int git_wcwidth(ucs_char_t ch
  {
        /*
         * Sorted list of non-overlapping intervals of non-spacing characters,
 -       * generated by
 -       *   "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c".
         */
 -      static const struct interval combining[] = {
 -              { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD },
 -              { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 },
 -              { 0x05C7, 0x05C7 }, { 0x0600, 0x0604 }, { 0x0610, 0x061A },
 -              { 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 },
 -              { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F },
 -              { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 },
 -              { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 },
 -              { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 },
 -              { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 },
 -              { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 },
 -              { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 },
 -              { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 },
 -              { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 },
 -              { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 },
 -              { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 },
 -              { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 },
 -              { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 },
 -              { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 },
 -              { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 },
 -              { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
 -              { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
 -              { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
 -              { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
 -              { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
 -              { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
 -              { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
 -              { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
 -              { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
 -              { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x1712, 0x1714 },
 -              { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 },
 -              { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 },
 -              { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D },
 -              { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 },
 -              { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x200B, 0x200F },
 -              { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F },
 -              { 0x20D0, 0x20EA }, { 0x302A, 0x302F }, { 0x3099, 0x309A },
 -              { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 },
 -              { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 },
 -              { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B },
 -              { 0x1D1AA, 0x1D1AD }, { 0xE0001, 0xE0001 },
 -              { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF }
 -      };
 +#include "unicode_width.h"
  
        /* test for 8-bit control characters */
        if (ch == 0)
                return -1;
  
        /* binary search in table of non-spacing characters */
 -      if (bisearch(ch, combining, sizeof(combining)
 +      if (bisearch(ch, zero_width, sizeof(zero_width)
                                / sizeof(struct interval) - 1))
                return 0;
  
 -      /*
 -       * If we arrive here, ch is neither a combining nor a C0/C1
 -       * control character.
 -       */
 +      /* binary search in table of double width characters */
 +      if (bisearch(ch, double_width, sizeof(double_width)
 +                              / sizeof(struct interval) - 1))
 +              return 2;
  
 -      return 1 +
 -              (ch >= 0x1100 &&
 -                    /* Hangul Jamo init. consonants */
 -               (ch <= 0x115f ||
 -                ch == 0x2329 || ch == 0x232a ||
 -                  /* CJK ... Yi */
 -                (ch >= 0x2e80 && ch <= 0xa4cf &&
 -                 ch != 0x303f) ||
 -                /* Hangul Syllables */
 -                (ch >= 0xac00 && ch <= 0xd7a3) ||
 -                /* CJK Compatibility Ideographs */
 -                (ch >= 0xf900 && ch <= 0xfaff) ||
 -                /* CJK Compatibility Forms */
 -                (ch >= 0xfe30 && ch <= 0xfe6f) ||
 -                /* Fullwidth Forms */
 -                (ch >= 0xff00 && ch <= 0xff60) ||
 -                (ch >= 0xffe0 && ch <= 0xffe6) ||
 -                (ch >= 0x20000 && ch <= 0x2fffd) ||
 -                (ch >= 0x30000 && ch <= 0x3fffd)));
 +      return 1;
  }
  
  /*
@@@ -565,3 -627,67 +565,67 @@@ int mbs_chrlen(const char **text, size_
  
        return chrlen;
  }
+ /*
+  * Pick the next char from the stream, folding as an HFS+ filename comparison
+  * would. Note that this is _not_ complete by any means. It's just enough
+  * to make is_hfs_dotgit() work, and should not be used otherwise.
+  */
+ static ucs_char_t next_hfs_char(const char **in)
+ {
+       while (1) {
+               ucs_char_t out = pick_one_utf8_char(in, NULL);
+               /*
+                * check for malformed utf8. Technically this
+                * gets converted to a percent-sequence, but
+                * returning 0 is good enough for is_hfs_dotgit
+                * to realize it cannot be .git
+                */
+               if (!*in)
+                       return 0;
+               /* these code points are ignored completely */
+               switch (out) {
+               case 0x200c: /* ZERO WIDTH NON-JOINER */
+               case 0x200d: /* ZERO WIDTH JOINER */
+               case 0x200e: /* LEFT-TO-RIGHT MARK */
+               case 0x200f: /* RIGHT-TO-LEFT MARK */
+               case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */
+               case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */
+               case 0x202c: /* POP DIRECTIONAL FORMATTING */
+               case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */
+               case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */
+               case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */
+               case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */
+               case 0x206c: /* INHIBIT ARABIC FORM SHAPING */
+               case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */
+               case 0x206e: /* NATIONAL DIGIT SHAPES */
+               case 0x206f: /* NOMINAL DIGIT SHAPES */
+               case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */
+                       continue;
+               }
+               /*
+                * there's a great deal of other case-folding that occurs,
+                * but this is enough to catch anything that will convert
+                * to ".git"
+                */
+               return tolower(out);
+       }
+ }
+ int is_hfs_dotgit(const char *path)
+ {
+       ucs_char_t c;
+       if (next_hfs_char(&path) != '.' ||
+           next_hfs_char(&path) != 'g' ||
+           next_hfs_char(&path) != 'i' ||
+           next_hfs_char(&path) != 't')
+               return 0;
+       c = next_hfs_char(&path);
+       if (c && !is_dir_sep(c))
+               return 0;
+       return 1;
+ }