{"thread":{"id":"23574","subject":"core.ignorecase = true support for .gitignore, status, ls-files, and add","startedAt":"2010-04-23T02:26:21Z","lastAt":"2010-04-27T04:51:35Z","messageCount":2,"participants":["Joshua Jensen"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"140181","messageId":"4BD1054D.4000402@workspacewhiz.com","threadId":"23574","inReplyTo":null,"subject":"core.ignorecase = true support for .gitignore, status, ls-files, and add","fromName":"Joshua Jensen","fromEmail":"jjensen@workspacewhiz.com","sentAt":"2010-04-23T02:26:21Z","receivedAt":"2010-04-23T02:26:21Z","isPatch":false,"sender":{"key":"jjensen@workspacewhiz.com","avatar":"https://avatars.githubusercontent.com/u/111687?v=4"},"body":"This is a follow up patch to one I posted mid last year.  At that time, \nunfortunately, I was unable to make the updates to the patch requested \nby list members.  Still, I continued to use the .gitignore case \ninsensitivity updates.  In recent days, I have been able to clean up the \ncode and provide some other useful features.  I am still in the progress \nof testing the behavior to ensure it works the way I intend it to.  I \nalso need to clean up my case insensitivity updates to fast-import and \ngit-p4.  Those aren't included (yet) in this patch.  When I have answers \nto my questions and the code is properly cleaned up, I'll sign off on \nthe patch and submit it to the list.\n\nI have a few questions.\n\ngit status and add both use an update made to name-hash.c where \ndirectories, specifically names with a trailing slash, can be looked up \nin a case insensitive manner.  After trying a myriad of solutions, this \nseemed to be the cleanest.  Does anyone see a problem with embedding the \ndirectory names in the same hash as the file names?  I couldn't find \none, especially since I append a slash to each directory name.\n\nThe new directory_exists_in_index_icase() duplicates some of the \nfunctionality in directory_exists_in_index().  I don't understand some \nof the functionality I just mindlessly cloned.  Can anyone clue me in on \nwhat directory_exists_in_index() achieves in the case of S_ISGITLINK()?\n\nUpdating match_one() bought case insensitive matching support for git \nls-files?  Are there other side effects I haven't found yet?\n\nThe git add path case \"normalization\" functionality is a somewhat \nradical departure from what Git does now.  I describe it in detail \nbelow.  Does anyone have any concerns?\n\nThanks for the help!\n\nJosh\n\n---------------------------------------------------------------------------------------\n\n From 6acd0b78400cf4f0249eaab0e7da922d5f961860 Mon Sep 17 00:00:00 2001\nFrom: Joshua Jensen <jjensen@workspacewhiz.com>\nDate: Thu, 22 Apr 2010 20:00:24 -0600\nSubject: [PATCH] core.ignorecase=true support for .gitignore, status, \nls-files, and add.\n\n* .gitignore exclusions are processed in a case insensitive manner.  This\nis especially beneficial when using Windows and Perforce and the git-p4\nbridge. Perforce preserves a given file's full path including case. When\nsyncing a file down, directories are created, if necessary, using the case\nas stored with the filename. Unfortunately, two files in the same\ndirectory can have differing cases for their respective paths, such as\n/dirA/file1.c and /DirA/file2.c. Depending on sync order, DirA/ may get\ncreated instead of dirA/.\n\nTrying to catch all case combinations for a set of gitignore entries is\nvery difficult. Having the exclusions honor the core.ignorecase=true makes\nthe process less error prone.\n\n* git status - When mydir/filea.txt is added and mydir/ is renamed to\nMyDir/, git status shows MyDir/ in the Untracked files section.  There are\nother combinations where this happens, too.  Support was added in\nname-hash.c for hashing directories with a terminating slash into the\nname hash.  When index_name_exists() is called with a directory (a name\nwith a terminating slash), the name is not found via the normal\ncache_name_compare() call, but it is found in slow_same_name().\n\n* git ls-files - When mydir/filea.txt is added, mydir/ is renamed to\nMyDir/, and MyDir/fileb.txt is added, previous to this patch, running\ngit ls-files mydir only shows mydir/filea.txt.  Running git ls-files\nMyDir shows MyDir/fileb.txt.  Running git ls-files mYdIR shows nothing.\nWhen the patch to dir.c's match_one() is applied, running git ls-files\nfor mydir, MyDir, and mYdIR shows mydir/filea.txt and MyDir/fileb.txt.\n\n* git add - When MyDir/ABC/filea.txt is added, MyDir/ABC/ is renamed to\nmydir/aBc/, and mydir/aBc/fileb.txt is added, previous to this patch, the\nindex will contain MyDir/ABC/filea.txt and mydir/aBc/fileb.txt.  Although\nthe rest of this patch accounts for those differences in case, this patch\nmakes the pathing consistent based on the first file added with that path.\n\nIn read-cache.c's add_to_index(), the index_name_exists() support used for\ngit status's case insensitive directory lookups is used to find the proper\ndirectory case according to what the user already checked in.  That is,\nMyDir/ABC/'s case is used to alter the stored path for fileb.txt to\nMyDir/ABC/fileb.txt (instead of mydir/aBc/fileb.txt).\n\nThis is especially important when cloning a repository to a case sensitive\nfile system.  MyDir/ABC/ and mydir/aBc/ exist in the same directory on\na Windows machine, but on Linux, the files exist in two separate\ndirectories.  The update to add_to_index(), in effect, treats a Windows\nfile system as case sensitive by making path case consistent.\n---\n  dir.c        |   91 \n+++++++++++++++++++++++++++++++++++++++++++++------------\n  name-hash.c  |   44 +++++++++++++++++++++++++++-\n  read-cache.c |   16 ++++++++++\n  3 files changed, 131 insertions(+), 20 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex cb83332..ef97a2a 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -18,6 +18,22 @@ static int read_directory_recursive(struct dir_struct \n*dir, const char *path, in\n      int check_only, const struct path_simplify *simplify);\n  static int get_dtype(struct dirent *de, const char *path, int len);\n\n+/* helper string functions with support for the ignore_case flag */\n+int strcmp_icase(const char *a, const char *b)\n+{\n+    return ignore_case ? strcasecmp(a, b) : strcmp(a, b);\n+}\n+\n+int strncmp_icase(const char *a, const char *b, size_t count)\n+{\n+    return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);\n+}\n+\n+int fnmatch_icase(const char *pattern, const char *string, int flags)\n+{\n+    return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD \n: 0));\n+}\n+\n  static int common_prefix(const char **pathspec)\n  {\n      const char *path, *slash, *next;\n@@ -91,16 +107,30 @@ static int match_one(const char *match, const char \n*name, int namelen)\n      if (!*match)\n          return MATCHED_RECURSIVELY;\n\n-    for (;;) {\n-        unsigned char c1 = *match;\n-        unsigned char c2 = *name;\n-        if (c1 == '\\0' || is_glob_special(c1))\n-            break;\n-        if (c1 != c2)\n-            return 0;\n-        match++;\n-        name++;\n-        namelen--;\n+    if (ignore_case) {\n+        for (;;) {\n+            unsigned char c1 = tolower(*match);\n+            unsigned char c2 = tolower(*name);\n+            if (c1 == '\\0' || is_glob_special(c1))\n+                break;\n+            if (c1 != c2)\n+                return 0;\n+            match++;\n+            name++;\n+            namelen--;\n+        }\n+    } else {\n+        for (;;) {\n+            unsigned char c1 = *match;\n+            unsigned char c2 = *name;\n+            if (c1 == '\\0' || is_glob_special(c1))\n+                break;\n+            if (c1 != c2)\n+                return 0;\n+            match++;\n+            name++;\n+            namelen--;\n+        }\n      }\n\n\n@@ -109,8 +139,8 @@ static int match_one(const char *match, const char \n*name, int namelen)\n       * we need to match by fnmatch\n       */\n      matchlen = strlen(match);\n-    if (strncmp(match, name, matchlen))\n-        return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;\n+    if (strncmp_icase(match, name, matchlen))\n+        return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;\n\n      if (namelen == matchlen)\n          return MATCHED_EXACTLY;\n@@ -374,14 +404,14 @@ int excluded_from_list(const char *pathname,\n              if (x->flags & EXC_FLAG_NODIR) {\n                  /* match basename */\n                  if (x->flags & EXC_FLAG_NOWILDCARD) {\n-                    if (!strcmp(exclude, basename))\n+                    if (!strcmp_icase(exclude, basename))\n                          return to_exclude;\n                  } else if (x->flags & EXC_FLAG_ENDSWITH) {\n                      if (x->patternlen - 1 <= pathlen &&\n-                        !strcmp(exclude + 1, pathname + pathlen - \nx->patternlen + 1))\n+                        !strcmp_icase(exclude + 1, pathname + pathlen - \nx->patternlen + 1))\n                          return to_exclude;\n                  } else {\n-                    if (fnmatch(exclude, basename, 0) == 0)\n+                    if (fnmatch_icase(exclude, basename, 0) == 0)\n                          return to_exclude;\n                  }\n              }\n@@ -396,14 +426,14 @@ int excluded_from_list(const char *pathname,\n\n                  if (pathlen < baselen ||\n                      (baselen && pathname[baselen-1] != '/') ||\n-                    strncmp(pathname, x->base, baselen))\n+                    strncmp_icase(pathname, x->base, baselen))\n                      continue;\n\n                  if (x->flags & EXC_FLAG_NOWILDCARD) {\n-                    if (!strcmp(exclude, pathname + baselen))\n+                    if (!strcmp_icase(exclude, pathname + baselen))\n                          return to_exclude;\n                  } else {\n-                    if (fnmatch(exclude, pathname+baselen,\n+                    if (fnmatch_icase(exclude, pathname+baselen,\n                              FNM_PATHNAME) == 0)\n                          return to_exclude;\n                  }\n@@ -468,6 +498,24 @@ enum exist_status {\n      index_gitdir,\n  };\n\n+static enum exist_status directory_exists_in_index_icase(const char \n*dirname, int len)\n+{\n+    struct cache_entry *ce = index_name_exists(&the_index, dirname, len \n+ 1, ignore_case);\n+    if (!ce)\n+        return index_nonexistent;\n+\n+    if (!strncmp_icase(ce->name, dirname, len)) {\n+        unsigned char endchar = ce->name[len];\n+        if (endchar <= '/') {\n+            if (endchar == '/')\n+                return index_directory;\n+            if (!endchar && S_ISGITLINK(ce->ce_mode))\n+                return index_gitdir;\n+        }\n+    }\n+    return index_nonexistent;\n+}\n+\n  /*\n   * The index sorts alphabetically by entry name, which\n   * means that a gitlink sorts as '\\0' at the end, while\n@@ -477,7 +525,12 @@ enum exist_status {\n   */\n  static enum exist_status directory_exists_in_index(const char \n*dirname, int len)\n  {\n-    int pos = cache_name_pos(dirname, len);\n+    int pos;\n+\n+    if (ignore_case)\n+        return directory_exists_in_index_icase(dirname, len);\n+\n+    pos = cache_name_pos(dirname, len);\n      if (pos < 0)\n          pos = -pos-1;\n      while (pos < active_nr) {\ndiff --git a/name-hash.c b/name-hash.c\nindex 0031d78..b10b5b1 100644\n--- a/name-hash.c\n+++ b/name-hash.c\n@@ -32,6 +32,30 @@ static unsigned int hash_name(const char *name, int \nnamelen)\n      return hash;\n  }\n\n+static void hash_index_entry_directories(struct index_state *istate, \nstruct cache_entry *ce)\n+{\n+    /* throw each directory component in the hash for quick lookup \nduring a git status */\n+    unsigned int hash;\n+    void **pos;\n+\n+    const char *ptr = ce->name;\n+    while (*ptr) {\n+        while (*ptr && *ptr != '/')\n+            ++ptr;\n+        if (*ptr == '/') {\n+            ++ptr;\n+            hash = hash_name(ce->name, ptr - ce->name);\n+            if (!lookup_hash(hash, &istate->name_hash)) {\n+                pos = insert_hash(hash, ce, &istate->name_hash);\n+                if (pos) {\n+                    ce->next = *pos;\n+                    *pos = ce;\n+                }\n+            }\n+        }\n+    }\n+}\n+\n  static void hash_index_entry(struct index_state *istate, struct \ncache_entry *ce)\n  {\n      void **pos;\n@@ -47,6 +71,9 @@ static void hash_index_entry(struct index_state \n*istate, struct cache_entry *ce)\n          ce->next = *pos;\n          *pos = ce;\n      }\n+\n+    if (ignore_case)\n+        hash_index_entry_directories(istate, ce);\n  }\n\n  static void lazy_init_name_hash(struct index_state *istate)\n@@ -97,7 +124,22 @@ static int same_name(const struct cache_entry *ce, \nconst char *name, int namelen\n      if (len == namelen && !cache_name_compare(name, namelen, ce->name, \nlen))\n          return 1;\n\n-    return icase && slow_same_name(name, namelen, ce->name, len);\n+    if (!icase)\n+        return 0;\n+\n+    /*\n+     * If the entry we're comparing is a filename (no trailing slash), \nthen compare\n+     * the lengths exactly.\n+     */\n+    if (name[namelen - 1] != '/') {\n+        return slow_same_name(name, namelen, ce->name, len);\n+    }\n+\n+    /*\n+     * For a directory, we point to an arbitrary cache_entry filename.  \nJust\n+     * make sure the directory portion matches.\n+     */\n+    return slow_same_name(name, namelen, ce->name, namelen < len ? \nnamelen : len);\n  }\n\n  struct cache_entry *index_name_exists(struct index_state *istate, \nconst char *name, int namelen, int icase)\ndiff --git a/read-cache.c b/read-cache.c\nindex f1f789b..b3954d5 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -608,6 +608,22 @@ int add_to_index(struct index_state *istate, const \nchar *path, struct stat *st,\n          ce->ce_mode = ce_mode_from_stat(ent, st_mode);\n      }\n\n+    if (ignore_case) {\n+        const char *startPtr = ce->name;\n+        const char *ptr = startPtr;\n+        while (*ptr) {\n+            while (*ptr && *ptr != '/')\n+                ++ptr;\n+            if (*ptr == '/') {\n+                struct cache_entry *foundce;\n+                ++ptr;\n+                foundce = index_name_exists(&the_index, ce->name, ptr - \nce->name, ignore_case);\n+                if (foundce)\n+                    memcpy((void*)startPtr, foundce->name + (startPtr - \nce->name), ptr - startPtr);\n+            }\n+        }\n+    }\n+\n      alias = index_name_exists(istate, ce->name, ce_namelen(ce), \nignore_case);\n      if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, \nce_option)) {\n          /* Nothing changed, really */\n-- \n1.7.1.msysgit.0.dirty\n"},{"id":"140471","messageId":"4BD66D57.3060903@workspacewhiz.com","threadId":"23574","inReplyTo":"4BD1054D.4000402@workspacewhiz.com","subject":"Re: core.ignorecase support for .gitignore, add, fast-import, log, ls-files, ls-tree, and status","fromName":"Joshua Jensen","fromEmail":"jjensen@workspacewhiz.com","sentAt":"2010-04-27T04:51:35Z","receivedAt":"2010-04-27T04:51:35Z","isPatch":false,"sender":{"key":"jjensen@workspacewhiz.com","avatar":"https://avatars.githubusercontent.com/u/111687?v=4"},"body":"This is a follow up patch to one I posted mid last year and a few days \nback.  It includes case insensitivity support for .gitignore, git add, \nfast-import, log, ls-files, ls-tree, and status.\n\nI'll repost my questions from the other day in this mail.\n\ngit status and add both use an update made to name-hash.c where \ndirectories, specifically names with a trailing slash, can be looked up \nin a case insensitive manner.  After trying a myriad of solutions, this \nseemed to be the cleanest.  Does anyone see a problem with embedding the \ndirectory names in the same hash as the file names?  I couldn't find \none, especially since I append a slash to each directory name.\n\nThe new directory_exists_in_index_icase() duplicates some of the \nfunctionality in directory_exists_in_index().  I don't understand some \nof the functionality I just mindlessly cloned.  Can anyone clue me in on \nwhat directory_exists_in_index() achieves in the case of S_ISGITLINK()?\n\nUpdating match_one() bought case insensitive matching support for git \nls-files?  Are there other side effects I haven't found yet?\n\nThe git add path case \"normalization\" functionality is a somewhat \nradical departure from what Git does now.  I describe it in detail \nbelow.  Does anyone have any concerns?\n\nThanks for the help!\n\nJosh\n\n---------------------------------------------------------------------------------------\n\n From 21bfa80e60ec33d039c5d1addcae9f42a4abb024 Mon Sep 17 00:00:00 2001\nFrom: Joshua Jensen <jjensen@workspacewhiz.com>\nDate: Mon, 26 Apr 2010 22:36:29 -0600\nSubject: [PATCH] core.ignorecase support for .gitignore, add, \nfast-import, log, ls-files, ls-tree, and status.\n\n* .gitignore exclusions are processed in a case insensitive manner.  This\nis especially beneficial when using Windows and Perforce and the git-p4\nbridge. Perforce preserves a given file's full path including case. When\nsyncing a file down, directories are created, if necessary, using the case\nas stored with the filename. Unfortunately, two files in the same\ndirectory can have differing cases for their respective paths, such as\n/dirA/file1.c and /DirA/file2.c. Depending on sync order, DirA/ may get\ncreated instead of dirA/.\n\nTrying to catch all case combinations for a set of gitignore entries is\nvery difficult. Having the exclusions honor the core.ignorecase=true makes\nthe process less error prone.\n\n* git status - When mydir/filea.txt is added and mydir/ is renamed to\nMyDir/, git status shows MyDir/ in the Untracked files section.  There are\nother combinations where this happens, too.  Support was added in\nname-hash.c for hashing directories with a terminating slash into the\nname hash.  When index_name_exists() is called with a directory (a name\nwith a terminating slash), the name is not found via the normal\ncache_name_compare() call, but it is found in slow_same_name().\n\n* git ls-files - When mydir/filea.txt is added, mydir/ is renamed to\nMyDir/, and MyDir/fileb.txt is added, previous to this patch, running\ngit ls-files mydir only shows mydir/filea.txt.  Running git ls-files\nMyDir shows MyDir/fileb.txt.  Running git ls-files mYdIR shows nothing.\nWhen the patch to dir.c's match_one() is applied, running git ls-files\nfor mydir, MyDir, and mYdIR shows mydir/filea.txt and MyDir/fileb.txt.\n\n* git add - When MyDir/ABC/filea.txt is added, MyDir/ABC/ is renamed to\nmydir/aBc/, and mydir/aBc/fileb.txt is added, previous to this patch, the\nindex will contain MyDir/ABC/filea.txt and mydir/aBc/fileb.txt.  Although\nthe rest of this patch accounts for those differences in case, this patch\nmakes the pathing consistent based on the first file added with that path.\n\nIn read-cache.c's add_to_index(), the index_name_exists() support used for\ngit status's case insensitive directory lookups is used to find the proper\ndirectory case according to what the user already checked in.  That is,\nMyDir/ABC/'s case is used to alter the stored path for fileb.txt to\nMyDir/ABC/fileb.txt (instead of mydir/aBc/fileb.txt).\n\nThis is especially important when cloning a repository to a case sensitive\nfile system.  MyDir/ABC/ and mydir/aBc/ exist in the same directory on\na Windows machine, but on Linux, the files exist in two separate\ndirectories.  The update to add_to_index(), in effect, treats a Windows\nfile system as case sensitive by making path case consistent.\n\n* git ls-tree - Support for case insensitive directory and file lookups.\n\n* git fast-import - When core.ignorecase=true, imported files will match\nexisting directory case.\n\n* git log - Support for case insensitive directory and file lookups.\n---\n  builtin/ls-tree.c |    4 +-\n  dir.c             |  110 \n+++++++++++++++++++++++++++++++++++++++++++---------\n  dir.h             |    5 ++\n  fast-import.c     |    3 +-\n  name-hash.c       |   44 +++++++++++++++++++++-\n  read-cache.c      |   16 ++++++++\n  tree-diff.c       |    9 ++--\n  7 files changed, 164 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex dc86b0d..e1bc76c 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -45,14 +45,14 @@ static int show_recursive(const char *base, int \nbaselen, const char *pathname)\n\n          if (!spec)\n              return 0;\n-        if (strncmp(base, spec, baselen))\n+        if (strncmp_icase(base, spec, baselen))\n              continue;\n          len = strlen(pathname);\n          spec += baselen;\n          speclen = strlen(spec);\n          if (speclen <= len)\n              continue;\n-        if (memcmp(pathname, spec, len))\n+        if (memcmp_icase(pathname, spec, len))\n              continue;\n          return 1;\n      }\ndiff --git a/dir.c b/dir.c\nindex cb83332..d67ec68 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -18,6 +18,41 @@ static int read_directory_recursive(struct dir_struct \n*dir, const char *path, in\n      int check_only, const struct path_simplify *simplify);\n  static int get_dtype(struct dirent *de, const char *path, int len);\n\n+/* helper string functions with support for the ignore_case flag */\n+int strcmp_icase(const char *a, const char *b)\n+{\n+    return ignore_case ? strcasecmp(a, b) : strcmp(a, b);\n+}\n+\n+int strncmp_icase(const char *a, const char *b, size_t count)\n+{\n+    return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);\n+}\n+\n+int fnmatch_icase(const char *pattern, const char *string, int flags)\n+{\n+    return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD \n: 0));\n+}\n+\n+int memcmp_icase(const char *a, const char *b, size_t count)\n+{\n+    if (ignore_case) {\n+        int lowera = 0;\n+        int lowerb = 0;\n+        while (--count) {\n+            lowera = tolower(*a++);\n+            lowerb = tolower(*b++);\n+            if (lowera != lowerb)\n+                break;\n+        }\n+        return lowera - lowerb;\n+\n+        return 0;\n+    } else {\n+        return memcmp(a, b, count);\n+    }\n+}\n+\n  static int common_prefix(const char **pathspec)\n  {\n      const char *path, *slash, *next;\n@@ -91,16 +126,30 @@ static int match_one(const char *match, const char \n*name, int namelen)\n      if (!*match)\n          return MATCHED_RECURSIVELY;\n\n-    for (;;) {\n-        unsigned char c1 = *match;\n-        unsigned char c2 = *name;\n-        if (c1 == '\\0' || is_glob_special(c1))\n-            break;\n-        if (c1 != c2)\n-            return 0;\n-        match++;\n-        name++;\n-        namelen--;\n+    if (ignore_case) {\n+        for (;;) {\n+            unsigned char c1 = tolower(*match);\n+            unsigned char c2 = tolower(*name);\n+            if (c1 == '\\0' || is_glob_special(c1))\n+                break;\n+            if (c1 != c2)\n+                return 0;\n+            match++;\n+            name++;\n+            namelen--;\n+        }\n+    } else {\n+        for (;;) {\n+            unsigned char c1 = *match;\n+            unsigned char c2 = *name;\n+            if (c1 == '\\0' || is_glob_special(c1))\n+                break;\n+            if (c1 != c2)\n+                return 0;\n+            match++;\n+            name++;\n+            namelen--;\n+        }\n      }\n\n\n@@ -109,8 +158,8 @@ static int match_one(const char *match, const char \n*name, int namelen)\n       * we need to match by fnmatch\n       */\n      matchlen = strlen(match);\n-    if (strncmp(match, name, matchlen))\n-        return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;\n+    if (strncmp_icase(match, name, matchlen))\n+        return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;\n\n      if (namelen == matchlen)\n          return MATCHED_EXACTLY;\n@@ -374,14 +423,14 @@ int excluded_from_list(const char *pathname,\n              if (x->flags & EXC_FLAG_NODIR) {\n                  /* match basename */\n                  if (x->flags & EXC_FLAG_NOWILDCARD) {\n-                    if (!strcmp(exclude, basename))\n+                    if (!strcmp_icase(exclude, basename))\n                          return to_exclude;\n                  } else if (x->flags & EXC_FLAG_ENDSWITH) {\n                      if (x->patternlen - 1 <= pathlen &&\n-                        !strcmp(exclude + 1, pathname + pathlen - \nx->patternlen + 1))\n+                        !strcmp_icase(exclude + 1, pathname + pathlen - \nx->patternlen + 1))\n                          return to_exclude;\n                  } else {\n-                    if (fnmatch(exclude, basename, 0) == 0)\n+                    if (fnmatch_icase(exclude, basename, 0) == 0)\n                          return to_exclude;\n                  }\n              }\n@@ -396,14 +445,14 @@ int excluded_from_list(const char *pathname,\n\n                  if (pathlen < baselen ||\n                      (baselen && pathname[baselen-1] != '/') ||\n-                    strncmp(pathname, x->base, baselen))\n+                    strncmp_icase(pathname, x->base, baselen))\n                      continue;\n\n                  if (x->flags & EXC_FLAG_NOWILDCARD) {\n-                    if (!strcmp(exclude, pathname + baselen))\n+                    if (!strcmp_icase(exclude, pathname + baselen))\n                          return to_exclude;\n                  } else {\n-                    if (fnmatch(exclude, pathname+baselen,\n+                    if (fnmatch_icase(exclude, pathname+baselen,\n                              FNM_PATHNAME) == 0)\n                          return to_exclude;\n                  }\n@@ -468,6 +517,24 @@ enum exist_status {\n      index_gitdir,\n  };\n\n+static enum exist_status directory_exists_in_index_icase(const char \n*dirname, int len)\n+{\n+    struct cache_entry *ce = index_name_exists(&the_index, dirname, len \n+ 1, ignore_case);\n+    if (!ce)\n+        return index_nonexistent;\n+\n+    if (!strncmp_icase(ce->name, dirname, len)) {\n+        unsigned char endchar = ce->name[len];\n+        if (endchar <= '/') {\n+            if (endchar == '/')\n+                return index_directory;\n+            if (!endchar && S_ISGITLINK(ce->ce_mode))\n+                return index_gitdir;\n+        }\n+    }\n+    return index_nonexistent;\n+}\n+\n  /*\n   * The index sorts alphabetically by entry name, which\n   * means that a gitlink sorts as '\\0' at the end, while\n@@ -477,7 +544,12 @@ enum exist_status {\n   */\n  static enum exist_status directory_exists_in_index(const char \n*dirname, int len)\n  {\n-    int pos = cache_name_pos(dirname, len);\n+    int pos;\n+\n+    if (ignore_case)\n+        return directory_exists_in_index_icase(dirname, len);\n+\n+    pos = cache_name_pos(dirname, len);\n      if (pos < 0)\n          pos = -pos-1;\n      while (pos < active_nr) {\ndiff --git a/dir.h b/dir.h\nindex 3bead5f..aced818 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -100,4 +100,9 @@ extern int remove_dir_recursively(struct strbuf \n*path, int flag);\n  /* tries to remove the path with empty directories along it, ignores \nENOENT */\n  extern int remove_path(const char *path);\n\n+extern int strcmp_icase(const char *a, const char *b);\n+extern int strncmp_icase(const char *a, const char *b, size_t count);\n+extern int fnmatch_icase(const char *pattern, const char *string, int \nflags);\n+extern int memcmp_icase(const char *a, const char *b, size_t count);\n+\n  #endif\ndiff --git a/fast-import.c b/fast-import.c\nindex 352e2e3..2279276 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -156,6 +156,7 @@ Format of STDIN stream:\n  #include \"csum-file.h\"\n  #include \"quote.h\"\n  #include \"exec_cmd.h\"\n+#include \"dir.h\"\n\n  #define PACK_ID_BITS 16\n  #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)\n@@ -1459,7 +1460,7 @@ static int tree_content_set(\n\n      for (i = 0; i < t->entry_count; i++) {\n          e = t->entries[i];\n-        if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {\n+        if (e->name->str_len == n && !strncmp_icase(p, \ne->name->str_dat, n)) {\n              if (!slash1) {\n                  if (!S_ISDIR(mode)\n&& e->versions[1].mode == mode\ndiff --git a/name-hash.c b/name-hash.c\nindex 0031d78..b10b5b1 100644\n--- a/name-hash.c\n+++ b/name-hash.c\n@@ -32,6 +32,30 @@ static unsigned int hash_name(const char *name, int \nnamelen)\n      return hash;\n  }\n\n+static void hash_index_entry_directories(struct index_state *istate, \nstruct cache_entry *ce)\n+{\n+    /* throw each directory component in the hash for quick lookup \nduring a git status */\n+    unsigned int hash;\n+    void **pos;\n+\n+    const char *ptr = ce->name;\n+    while (*ptr) {\n+        while (*ptr && *ptr != '/')\n+            ++ptr;\n+        if (*ptr == '/') {\n+            ++ptr;\n+            hash = hash_name(ce->name, ptr - ce->name);\n+            if (!lookup_hash(hash, &istate->name_hash)) {\n+                pos = insert_hash(hash, ce, &istate->name_hash);\n+                if (pos) {\n+                    ce->next = *pos;\n+                    *pos = ce;\n+                }\n+            }\n+        }\n+    }\n+}\n+\n  static void hash_index_entry(struct index_state *istate, struct \ncache_entry *ce)\n  {\n      void **pos;\n@@ -47,6 +71,9 @@ static void hash_index_entry(struct index_state \n*istate, struct cache_entry *ce)\n          ce->next = *pos;\n          *pos = ce;\n      }\n+\n+    if (ignore_case)\n+        hash_index_entry_directories(istate, ce);\n  }\n\n  static void lazy_init_name_hash(struct index_state *istate)\n@@ -97,7 +124,22 @@ static int same_name(const struct cache_entry *ce, \nconst char *name, int namelen\n      if (len == namelen && !cache_name_compare(name, namelen, ce->name, \nlen))\n          return 1;\n\n-    return icase && slow_same_name(name, namelen, ce->name, len);\n+    if (!icase)\n+        return 0;\n+\n+    /*\n+     * If the entry we're comparing is a filename (no trailing slash), \nthen compare\n+     * the lengths exactly.\n+     */\n+    if (name[namelen - 1] != '/') {\n+        return slow_same_name(name, namelen, ce->name, len);\n+    }\n+\n+    /*\n+     * For a directory, we point to an arbitrary cache_entry filename.  \nJust\n+     * make sure the directory portion matches.\n+     */\n+    return slow_same_name(name, namelen, ce->name, namelen < len ? \nnamelen : len);\n  }\n\n  struct cache_entry *index_name_exists(struct index_state *istate, \nconst char *name, int namelen, int icase)\ndiff --git a/read-cache.c b/read-cache.c\nindex f1f789b..b3954d5 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -608,6 +608,22 @@ int add_to_index(struct index_state *istate, const \nchar *path, struct stat *st,\n          ce->ce_mode = ce_mode_from_stat(ent, st_mode);\n      }\n\n+    if (ignore_case) {\n+        const char *startPtr = ce->name;\n+        const char *ptr = startPtr;\n+        while (*ptr) {\n+            while (*ptr && *ptr != '/')\n+                ++ptr;\n+            if (*ptr == '/') {\n+                struct cache_entry *foundce;\n+                ++ptr;\n+                foundce = index_name_exists(&the_index, ce->name, ptr - \nce->name, ignore_case);\n+                if (foundce)\n+                    memcpy((void*)startPtr, foundce->name + (startPtr - \nce->name), ptr - startPtr);\n+            }\n+        }\n+    }\n+\n      alias = index_name_exists(istate, ce->name, ce_namelen(ce), \nignore_case);\n      if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, \nce_option)) {\n          /* Nothing changed, really */\ndiff --git a/tree-diff.c b/tree-diff.c\nindex fe9f52c..5110980 100644\n--- a/tree-diff.c\n+++ b/tree-diff.c\n@@ -5,6 +5,7 @@\n  #include \"diff.h\"\n  #include \"diffcore.h\"\n  #include \"tree.h\"\n+#include \"dir.h\"\n\n  static char *malloc_base(const char *base, int baselen, const char \n*path, int pathlen)\n  {\n@@ -114,7 +115,7 @@ static int tree_entry_interesting(struct tree_desc \n*desc, const char *base, int\n\n          if (baselen >= matchlen) {\n              /* If it doesn't match, move along... */\n-            if (strncmp(base, match, matchlen))\n+            if (strncmp_icase(base, match, matchlen))\n                  continue;\n\n              /*\n@@ -131,7 +132,7 @@ static int tree_entry_interesting(struct tree_desc \n*desc, const char *base, int\n          }\n\n          /* Does the base match? */\n-        if (strncmp(base, match, baselen))\n+        if (strncmp_icase(base, match, baselen))\n              continue;\n\n          match += baselen;\n@@ -147,7 +148,7 @@ static int tree_entry_interesting(struct tree_desc \n*desc, const char *base, int\n               * Does match sort strictly earlier than path\n               * with their common parts?\n               */\n-            m = strncmp(match, path,\n+            m = strncmp_icase(match, path,\n                      (matchlen < pathlen) ? matchlen : pathlen);\n              if (m < 0)\n                  continue;\n@@ -183,7 +184,7 @@ static int tree_entry_interesting(struct tree_desc \n*desc, const char *base, int\n               * we cheated and did not do strncmp(), so we do\n               * that here.\n               */\n-            m = strncmp(match, path, pathlen);\n+            m = strncmp_icase(match, path, pathlen);\n\n          /*\n           * If common part matched earlier then it is a hit,\n-- \n1.7.1.msysgit.4.dirty\n"}]}