The second version of this patch series fixes the problematic case
insensitive fnmatch call in patch 1 that relied on an apparently GNU-only
extension. Instead, the pattern and string are lowercased into
temporary buffers, and the standard fnmatch is called without relying
on the GNU extension.
Patches 2-6 received no modifications.
The original cover for the patch series follows as posted by Johannes Sixt:
The following patch series extends the core.ignorecase=true support to
handle case insensitive comparisons for the .gitignore file, git status,
and git ls-files. git add and git fast-import will fold the case of the
file being added, matching that of an already added directory entry. Case
folding is also applied to git fast-import for renames, copies, and deletes.
The most notable benefit, IMO, is that the case of directories in the
worktree does not matter if, and only if, the directory exists already in
the index with some different case variant. This helps applications on
Windows that change the case even of directories in unpredictable ways.
Joshua mentioned Perforce as the primary example.
Concerning the implementation, Joshua explained when he initially submitted
the series to the msysgit mailing list:
git status and add both use an update made to name-hash.c where
directories, specifically names with a trailing slash, can be looked up
in a case insensitive manner. After trying a myriad of solutions, this
seemed to be the cleanest. Does anyone see a problem with embedding the
directory names in the same hash as the file names? I couldn't find one,
especially since I append a slash to each directory name.
The git add path case folding functionality is a somewhat radical
departure from what Git does now. It is described in detail in patch 5.
Does anyone have any concerns?
I support the idea of this patch, and I can confirm that it works: I've
used this series in production both with core.ignorecase set to true and
to false, and in the former case, with directories and files with case
different from the index.
Joshua Jensen (6):
Add string comparison functions that respect the ignore_case variable.
Case insensitivity support for .gitignore via core.ignorecase
Add case insensitivity support for directories when using git status
Add case insensitivity support when using git ls-files
Support case folding for git add when core.ignorecase=true
Support case folding in git fast-import when core.ignorecase=true
dir.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
dir.h | 4 ++
fast-import.c | 7 ++-
name-hash.c | 72 +++++++++++++++++++++++++++
read-cache.c | 23 +++++++++
5 files changed, 235 insertions(+), 23 deletions(-)
This is especially beneficial when using Windows and Perforce and the
git-p4 bridge. Internally, Perforce preserves a given file's full path
including its case at the time it was added to the Perforce repository.
When syncing a file down via Perforce, missing directories are created,
if necessary, using the case as stored with the filename. Unfortunately,
two files in the same directory can have differing cases for their
respective paths, such as /diRa/file1.c and /DirA/file2.c. Depending on
sync order, DirA/ may get created instead of diRa/.
It is possible to handle directory names in a case insensitive manner
without this patch, but it is highly inconvenient, requiring each
character to be specified like so: [Bb][Uu][Ii][Ll][Dd]. With this patch, the
gitignore exclusions honor the core.ignorecase=true configuration
setting and make the process less error prone. The above is specified
like so: Build
Signed-off-by: Joshua Jensen <redacted>
---
dir.c | 12 ++++++------
1 files changed, 6 insertions(+), 6 deletions(-)
When mydir/filea.txt is added, mydir/ is renamed to MyDir/, and
MyDir/fileb.txt is added, running git ls-files mydir only shows
mydir/filea.txt. Running git ls-files MyDir shows MyDir/fileb.txt.
Running git ls-files mYdIR shows nothing.
With this patch running git ls-files for mydir, MyDir, and mYdIR shows
mydir/filea.txt and MyDir/fileb.txt.
Wildcards are not handled case insensitively in this patch. Example:
MyDir/aBc/file.txt is added. git ls-files MyDir/a* works fine, but git
ls-files mydir/a* does not.
Signed-off-by: Joshua Jensen <redacted>
---
dir.c | 38 ++++++++++++++++++++++++++------------
1 files changed, 26 insertions(+), 12 deletions(-)
Multiple locations within this patch series alter a case sensitive
string comparison call such as strcmp() to be a call to a string
comparison call that selects case comparison based on the global
ignore_case variable. Behaviorally, when core.ignorecase=false, the
*_icase() versions are functionally equivalent to their C runtime
counterparts. When core.ignorecase=true, the *_icase() versions perform
a case insensitive comparison.
Like Linus' earlier ignorecase patch, these may ignore filename
conventions on certain file systems. By isolating filename comparisons
to certain functions, support for those filename conventions may be more
easily met.
Signed-off-by: Joshua Jensen <redacted>
---
dir.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dir.h | 4 ++++
2 files changed, 66 insertions(+), 0 deletions(-)
@@ -18,6 +18,68 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, inintcheck_only,conststructpath_simplify*simplify);staticintget_dtype(structdirent*de,constchar*path,intlen);+/* helper string functions with support for the ignore_case flag */+intstrcmp_icase(constchar*a,constchar*b)+{+returnignore_case?strcasecmp(a,b):strcmp(a,b);+}++intstrncmp_icase(constchar*a,constchar*b,size_tcount)+{+returnignore_case?strncasecmp(a,b,count):strncmp(a,b,count);+}++intfnmatch_casefold(constchar*pattern,constchar*string,intflags)+{+charlowerPatternBuf[MAX_PATH];+charlowerStringBuf[MAX_PATH];+char*lowerPattern;+char*lowerString;+size_tpatternLen;+size_tstringLen;+char*out;+intret;++/*+*Usetheprovidedstackbuffer,ifpossible.Ifthestringistoo+*large,allocatebufferspace.+*/+patternLen=strlen(pattern);+if(patternLen+1>sizeof(lowerPatternBuf))+lowerPattern=xmalloc(patternLen+1);+else+lowerPattern=lowerPatternBuf;++stringLen=strlen(string);+if(stringLen+1>sizeof(lowerStringBuf))+lowerString=xmalloc(stringLen+1);+else+lowerString=lowerStringBuf;++/* Make the pattern and string lowercase to pass to fnmatch. */+for(out=lowerPattern;*pattern;++out,++pattern)+*out=tolower(*pattern);+*out=0;++for(out=lowerString;*string;++out,++string)+*out=tolower(*string);+*out=0;++ret=fnmatch(lowerPattern,lowerString,flags);++/* Free the pattern or string if it was allocated. */+if(lowerPattern!=lowerPatternBuf)+free(lowerPattern);+if(lowerString!=lowerStringBuf)+free(lowerString);+returnret;+}++intfnmatch_icase(constchar*pattern,constchar*string,intflags)+{+returnignore_case?fnmatch_casefold(pattern,string,flags):fnmatch(pattern,string,flags);+}+staticintcommon_prefix(constchar**pathspec){constchar*path,*slash,*next;
@@ -101,4 +101,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);/* tries to remove the path with empty directories along it, ignores ENOENT */externintremove_path(constchar*path);+externintstrcmp_icase(constchar*a,constchar*b);+externintstrncmp_icase(constchar*a,constchar*b,size_tcount);+externintfnmatch_icase(constchar*pattern,constchar*string,intflags);+#endif
When using a case preserving but case insensitive file system, directory
case can differ but still refer to the same physical directory. git
status reports the directory with the alternate case as an Untracked
file. (That is, when mydir/filea.txt is added to the repository and
then the directory on disk is renamed from mydir/ to MyDir/, git status
shows MyDir/ as being untracked.)
Support has been added in name-hash.c for hashing directories with a
terminating slash into the name hash. When index_name_exists() is called
with a directory (a name with a terminating slash), the name is not
found via the normal cache_name_compare() call, but it is found in the
slow_same_name() function.
Additionally, in dir.c, directory_exists_in_index_icase() allows newly
added directories deeper in the directory chain to be identified.
Ultimately, it would be better if the file list was read in case
insensitive alphabetical order from disk, but this change seems to
suffice for now.
The end result is the directory is looked up in a case insensitive
manner and does not show in the Untracked files list.
Signed-off-by: Joshua Jensen <redacted>
---
dir.c | 40 ++++++++++++++++++++++++++++++++-
name-hash.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 110 insertions(+), 2 deletions(-)
@@ -531,6 +531,39 @@ enum exist_status {};/*+*Donotusethealphabeticallystoredindextolookup+*thedirectoryname;instead,usethecaseinsensitive+*namehash.+*/+staticenumexist_statusdirectory_exists_in_index_icase(constchar*dirname,intlen)+{+structcache_entry*ce=index_name_exists(&the_index,dirname,len+1,ignore_case);+unsignedcharendchar;++if(!ce)+returnindex_nonexistent;+endchar=ce->name[len];++/*+*Thecache_entrystructurereturnedwillcontainthisdirname+*andpossiblyadditionalpathcomponents.+*/+if(endchar=='/')+returnindex_directory;++/*+*Iftherearenoadditionalpathcomponents,thenthiscache_entry+*representsasubmodule.Submodules,despitebeingdirectories,+*arestoredinthecachewithoutaclosingslash.+*/+if(!endchar&&S_ISGITLINK(ce->ce_mode))+returnindex_gitdir;++/* This should never be hit, but it exists just in case. */+returnindex_nonexistent;+}++/**Theindexsortsalphabeticallybyentryname,which*meansthatagitlinksortsas'\0'attheend,while*adirectory(whichisdefinednotasanentry,butas
When MyDir/ABC/filea.txt is added to Git, the disk directory MyDir/ABC/
is renamed to mydir/aBc/, and then mydir/aBc/fileb.txt is added, the
index will contain MyDir/ABC/filea.txt and mydir/aBc/fileb.txt. Although
the earlier portions of this patch series account for those differences
in case, this patch makes the pathing consistent by folding the case of
newly added files against the first file added with that path.
In read-cache.c's add_to_index(), the index_name_exists() support used
for git status's case insensitive directory lookups is used to find the
proper directory case according to what the user already checked in.
That is, MyDir/ABC/'s case is used to alter the stored path for
fileb.txt to MyDir/ABC/fileb.txt (instead of mydir/aBc/fileb.txt).
This is especially important when cloning a repository to a case
sensitive file system. MyDir/ABC/ and mydir/aBc/ exist in the same
directory on a Windows machine, but on Linux, the files exist in two
separate directories. The update to add_to_index(), in effect, treats a
Windows file system as case sensitive by making path case consistent.
Signed-off-by: Joshua Jensen <redacted>
---
read-cache.c | 23 +++++++++++++++++++++++
1 files changed, 23 insertions(+), 0 deletions(-)
@@ -608,6 +608,29 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,ce->ce_mode=ce_mode_from_stat(ent,st_mode);}+/* When core.ignorecase=true, determine if a directory of the same name but differing+*casealreadyexistswithintheGitrepository.Ifitdoes,ensurethedirectory+*caseofthefilebeingaddedtotherepositorymatches(isfoldedinto)theexisting+*entry'sdirectorycase.+*/+if(ignore_case){+constchar*startPtr=ce->name;+constchar*ptr=startPtr;+while(*ptr){+while(*ptr&&*ptr!='/')+++ptr;+if(*ptr=='/'){+structcache_entry*foundce;+++ptr;+foundce=index_name_exists(&the_index,ce->name,ptr-ce->name,ignore_case);+if(foundce){+memcpy((void*)startPtr,foundce->name+(startPtr-ce->name),ptr-startPtr);+startPtr=ptr;+}+}+}+}+alias=index_name_exists(istate,ce->name,ce_namelen(ce),ignore_case);if(alias&&!ce_stage(alias)&&!ie_match_stat(istate,alias,st,ce_option)){/* Nothing changed, really */
From: Johannes Sixt <hidden> Date: 2016-06-15 22:49:40
Thank you for the resend.
On Sonntag, 3. Oktober 2010, Joshua Jensen wrote:
git status and add both use an update made to name-hash.c where
directories, specifically names with a trailing slash, can be looked up
in a case insensitive manner. After trying a myriad of solutions, this
seemed to be the cleanest. Does anyone see a problem with embedding the
directory names in the same hash as the file names? I couldn't find one,
especially since I append a slash to each directory name.
The git add path case folding functionality is a somewhat radical
departure from what Git does now. It is described in detail in patch 5.
Does anyone have any concerns?
Since I'm not an expert in the area that is touched by this series, I'd like
to draw the list's attention to the questions in these two paragraphs.
Junio, IIRC, the series appeared in next for some time before the 1.7.3
release. Does this imply that you reviewed the series and deemed the
implementation sound?
-- Hannes
@@ -18,6 +18,68 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, in
int check_only, const struct path_simplify *simplify);
static int get_dtype(struct dirent *de, const char *path, int len);
+/* helper string functions with support for the ignore_case flag */
+int strcmp_icase(const char *a, const char *b)
+{
+ return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
+}
+
+int strncmp_icase(const char *a, const char *b, size_t count)
+{
+ return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
+}
+
+int fnmatch_casefold(const char *pattern, const char *string, int flags)
+{
+ char lowerPatternBuf[MAX_PATH];
+ char lowerStringBuf[MAX_PATH];
+ char* lowerPattern;
+ char* lowerString;
+ size_t patternLen;
+ size_t stringLen;
+ char* out;
+ int ret;
+
+ /*
+ * Use the provided stack buffer, if possible. If the string is too
+ * large, allocate buffer space.
+ */
+ patternLen = strlen(pattern);
+ if (patternLen + 1 > sizeof(lowerPatternBuf))
+ lowerPattern = xmalloc(patternLen + 1);
+ else
+ lowerPattern = lowerPatternBuf;
+
+ stringLen = strlen(string);
+ if (stringLen + 1 > sizeof(lowerStringBuf))
+ lowerString = xmalloc(stringLen + 1);
+ else
+ lowerString = lowerStringBuf;
+
+ /* Make the pattern and string lowercase to pass to fnmatch. */
+ for (out = lowerPattern; *pattern; ++out, ++pattern)
+ *out = tolower(*pattern);
+ *out = 0;
+
+ for (out = lowerString; *string; ++out, ++string)
+ *out = tolower(*string);
+ *out = 0;
+
+ ret = fnmatch(lowerPattern, lowerString, flags);
+
+ /* Free the pattern or string if it was allocated. */
+ if (lowerPattern != lowerPatternBuf)
+ free(lowerPattern);
+ if (lowerString != lowerStringBuf)
+ free(lowerString);
+ return ret;
+}
+
+int fnmatch_icase(const char *pattern, const char *string, int flags)
+{
+ return ignore_case ? fnmatch_casefold(pattern, string, flags) : fnmatch(pattern, string, flags);
+}
I liked v1 of this patch better, although it obviously had portability
issues. But I think it would be better to handle this with:
#ifndef FNM_CASEFOLD
int fnmatch_casefold(const char *pattern, const char *string, int flags)
{
...
}
#endf
int fnmatch_icase(const char *pattern, const char *string, int flags)
{
#ifndef FNM_CASEFOLD
return ignore_case ? fnmatch_casefold(pattern, string,
flags) : fnmatch(pattern, string, flags);
#else
return fnmatch(pattern, string, flags | (ignore_case ?
FNM_CASEFOLD : 0));
#endif
}
Or simply use fnmatch(..., FNM_CASEFOLD) everywhere and include
compat/fnmatch/* on platforms like Solaris that don't have the GNU
extension.
That would allow the GNU libc, FreeBSD libc and others that implement
the GNU extension to do case folding for us, and we wouldn't have to
maintain our own fnmatch_casefold.
----- Original Message -----
From: Ævar Arnfjörð Bjarmason
Date: 10/3/2010 2:30 AM
On Sun, Oct 3, 2010 at 04:32, Joshua Jensen[off-list ref] wrote
quoted
+int fnmatch_casefold(const char *pattern, const char *string, int flags)
+{
+ char lowerPatternBuf[MAX_PATH];
+ char lowerStringBuf[MAX_PATH];
+ char* lowerPattern;
+ char* lowerString;
+ size_t patternLen;
+ size_t stringLen;
+ char* out;
+ int ret;
+
+ /*
+ * Use the provided stack buffer, if possible. If the string is too
+ * large, allocate buffer space.
+ */
+ patternLen = strlen(pattern);
+ if (patternLen + 1> sizeof(lowerPatternBuf))
+ lowerPattern = xmalloc(patternLen + 1);
+ else
+ lowerPattern = lowerPatternBuf;
+
+ stringLen = strlen(string);
+ if (stringLen + 1> sizeof(lowerStringBuf))
+ lowerString = xmalloc(stringLen + 1);
+ else
+ lowerString = lowerStringBuf;
+
+ /* Make the pattern and string lowercase to pass to fnmatch. */
+ for (out = lowerPattern; *pattern; ++out, ++pattern)
+ *out = tolower(*pattern);
+ *out = 0;
+
+ for (out = lowerString; *string; ++out, ++string)
+ *out = tolower(*string);
+ *out = 0;
+
+ ret = fnmatch(lowerPattern, lowerString, flags);
+
+ /* Free the pattern or string if it was allocated. */
+ if (lowerPattern != lowerPatternBuf)
+ free(lowerPattern);
+ if (lowerString != lowerStringBuf)
+ free(lowerString);
+ return ret;
+}
+
+int fnmatch_icase(const char *pattern, const char *string, int flags)
+{
+ return ignore_case ? fnmatch_casefold(pattern, string, flags) : fnmatch(pattern, string, flags);
+}
I liked v1 of this patch better, although it obviously had portability
issues. But I think it would be better to handle this with:
#ifndef FNM_CASEFOLD
int fnmatch_casefold(const char *pattern, const char *string, int flags)
{
...
}
#endf
int fnmatch_icase(const char *pattern, const char *string, int flags)
{
#ifndef FNM_CASEFOLD
return ignore_case ? fnmatch_casefold(pattern, string,
flags) : fnmatch(pattern, string, flags);
#else
return fnmatch(pattern, string, flags | (ignore_case ?
FNM_CASEFOLD : 0));
#endif
}
Or simply use fnmatch(..., FNM_CASEFOLD) everywhere and include
compat/fnmatch/* on platforms like Solaris that don't have the GNU
extension.
The real problem with compat/fnmatch is determining which random
platforms need that support and updating the makefile accordingly.
Further, the compat/fnmatch/* code would need to be rejigged somewhat,
so there is no possible conflict (now or in the future) with the
provided symbols. We discussed this as a potential problem developers
would need to be aware of if the system fnmatch.h (or whatever it is
called) gets #included.
Anyway, what you describe above creates two code paths. I would imagine
that would be harder to debug; that is, on some platforms, it uses
fnmatch_casefold and on others, it hands it off to fnmatch(...,
FNM_CASEFOLD).
In any case, I'd like to find a solution to get this series working for
everyone. I've been out of commission for a month (deploying Git to 80+
programmers at an organization, by the way), but I'm back now and can
work this until it is complete.
Josh
From: Joshua Jensen <redacted>
Multiple locations within this patch series alter a case sensitive
string comparison call such as strcmp() to be a call to a string
comparison call that selects case comparison based on the global
ignore_case variable. Behaviorally, when core.ignorecase=false, the
*_icase() versions are functionally equivalent to their C runtime
counterparts. When core.ignorecase=true, the *_icase() versions perform
a case insensitive comparison.
Like Linus' earlier ignorecase patch, these may ignore filename
conventions on certain file systems. By isolating filename comparisons
to certain functions, support for those filename conventions may be more
easily met.
Signed-off-by: Joshua Jensen <redacted>
Signed-off-by: Johannes Sixt <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
dir.c | 16 ++++++++++++++++
dir.h | 4 ++++
2 files changed, 20 insertions(+), 0 deletions(-)
@@ -18,6 +18,22 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, inintcheck_only,conststructpath_simplify*simplify);staticintget_dtype(structdirent*de,constchar*path,intlen);+/* helper string functions with support for the ignore_case flag */+intstrcmp_icase(constchar*a,constchar*b)+{+returnignore_case?strcasecmp(a,b):strcmp(a,b);+}++intstrncmp_icase(constchar*a,constchar*b,size_tcount)+{+returnignore_case?strncasecmp(a,b,count):strncmp(a,b,count);+}++intfnmatch_icase(constchar*pattern,constchar*string,intflags)+{+returnfnmatch(pattern,string,flags|(ignore_case?FNM_CASEFOLD:0));+}+staticintcommon_prefix(constchar**pathspec){constchar*path,*slash,*next;
@@ -101,4 +101,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);/* tries to remove the path with empty directories along it, ignores ENOENT */externintremove_path(constchar*path);+externintstrcmp_icase(constchar*a,constchar*b);+externintstrncmp_icase(constchar*a,constchar*b,size_tcount);+externintfnmatch_icase(constchar*pattern,constchar*string,intflags);+#endif
Windows and MinGW both lack fnmatch() in their C library and needed
compat/fnmatch, but they had duplicate code for adding the compat
function, and there was no Makefile flag or configure check for
fnmatch.
Change the Makefile it so that it's now possible to compile the compat
function with a NO_FNMATCH=YesPlease flag, and add a configure probe
for it.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
Makefile | 18 +++++++++++++-----
config.mak.in | 1 +
configure.ac | 6 ++++++
3 files changed, 20 insertions(+), 5 deletions(-)
@@ -70,6 +70,8 @@ all::## Define NO_STRTOK_R if you don't have strtok_r in the C library.#+# Define NO_FNMATCH if you don't have fnmatch in the C library.+## Define NO_LIBGEN_H if you don't have libgen.h.## Define NEEDS_LIBGEN if your libgen needs -lgen when linking
@@ -818,6 +818,12 @@ GIT_CHECK_FUNC(strtok_r, [NO_STRTOK_R=YesPlease]) AC_SUBST(NO_STRTOK_R) #+# Define NO_FNMATCH if you don't have fnmatch+GIT_CHECK_FUNC(fnmatch,+[NO_FNMATCH=],+[NO_FNMATCH=YesPlease])+AC_SUBST(NO_FNMATCH)+# # Define NO_MEMMEM if you don't have memmem. GIT_CHECK_FUNC(memmem, [NO_MEMMEM=],
On Sun, Oct 3, 2010 at 09:07, Joshua Jensen [off-list ref] wrote:
----- Original Message -----
From: Ævar Arnfjörð Bjarmason
Date: 10/3/2010 2:30 AM
quoted
On Sun, Oct 3, 2010 at 04:32, Joshua Jensen[off-list ref]
wrote
quoted
+int fnmatch_casefold(const char *pattern, const char *string, int flags)
+{
+ char lowerPatternBuf[MAX_PATH];
+ char lowerStringBuf[MAX_PATH];
+ char* lowerPattern;
+ char* lowerString;
+ size_t patternLen;
+ size_t stringLen;
+ char* out;
+ int ret;
+
+ /*
+ * Use the provided stack buffer, if possible. If the string is
too
+ * large, allocate buffer space.
+ */
+ patternLen = strlen(pattern);
+ if (patternLen + 1> sizeof(lowerPatternBuf))
+ lowerPattern = xmalloc(patternLen + 1);
+ else
+ lowerPattern = lowerPatternBuf;
+
+ stringLen = strlen(string);
+ if (stringLen + 1> sizeof(lowerStringBuf))
+ lowerString = xmalloc(stringLen + 1);
+ else
+ lowerString = lowerStringBuf;
+
+ /* Make the pattern and string lowercase to pass to fnmatch. */
+ for (out = lowerPattern; *pattern; ++out, ++pattern)
+ *out = tolower(*pattern);
+ *out = 0;
+
+ for (out = lowerString; *string; ++out, ++string)
+ *out = tolower(*string);
+ *out = 0;
+
+ ret = fnmatch(lowerPattern, lowerString, flags);
+
+ /* Free the pattern or string if it was allocated. */
+ if (lowerPattern != lowerPatternBuf)
+ free(lowerPattern);
+ if (lowerString != lowerStringBuf)
+ free(lowerString);
+ return ret;
+}
+
+int fnmatch_icase(const char *pattern, const char *string, int flags)
+{
+ return ignore_case ? fnmatch_casefold(pattern, string, flags) :
fnmatch(pattern, string, flags);
+}
I liked v1 of this patch better, although it obviously had portability
issues. But I think it would be better to handle this with:
#ifndef FNM_CASEFOLD
int fnmatch_casefold(const char *pattern, const char *string, int
flags)
{
...
}
#endf
int fnmatch_icase(const char *pattern, const char *string, int flags)
{
#ifndef FNM_CASEFOLD
return ignore_case ? fnmatch_casefold(pattern, string,
flags) : fnmatch(pattern, string, flags);
#else
return fnmatch(pattern, string, flags | (ignore_case ?
FNM_CASEFOLD : 0));
#endif
}
Or simply use fnmatch(..., FNM_CASEFOLD) everywhere and include
compat/fnmatch/* on platforms like Solaris that don't have the GNU
extension.
I offered before to help with making this portable, so I've gone ahead
and done it. This series is like your v1, but it has two of my patches
at the front to add Makefile & configure checks & fallbacks for
fnmatch if the function either doesn't exist, or it doesn't support
the FNM_CASEFOLD flag.
The real problem with compat/fnmatch is determining which random platforms
need that support and updating the makefile accordingly.
We already do this for a bunch of NO_WHATEVER= flags. Adding one more
isn't going to be too hard to maintain.
Further, the compat/fnmatch/* code would need to be rejigged
somewhat, so there is no possible conflict (now or in the future)
with the provided symbols. We discussed this as a potential problem
developers would need to be aware of if the system fnmatch.h (or
whatever it is called) gets #included.
Since we do -Icompat/fnmatch it's going to be our fnmatch.h that's
picked up, so we aren't going to get a symbol conflict I should think.
Anyway, what you describe above creates two code paths. I would imagine
that would be harder to debug; that is, on some platforms, it uses
fnmatch_casefold and on others, it hands it off to fnmatch(...,
FNM_CASEFOLD).
My ad-hoc example pseudocode created two codepaths, but this version
doesn't.
In any case, I'd like to find a solution to get this series working for
everyone. I've been out of commission for a month (deploying Git to 80+
programmers at an organization, by the way), but I'm back now and can work
this until it is complete.
This is all from your v1:
Joshua Jensen (6):
Add string comparison functions that respect the ignore_case
variable.
Case insensitivity support for .gitignore via core.ignorecase
Add case insensitivity support for directories when using git status
Add case insensitivity support when using git ls-files
Support case folding for git add when core.ignorecase=true
Support case folding in git fast-import when core.ignorecase=true
These two are new:
Ævar Arnfjörð Bjarmason (2):
Makefile & configure: add a NO_FNMATCH flag
This one is a good idea in general. We shouldn't be duplicating setup
code in both the Windows and MinGW portions of the Makefile, and if we
ever get another odd system that doesn't have fnmatch() this will make
things just work there.
Needs testing from someone with Windows.
Makefile & configure: add a NO_FNMATCH_CASEFOLD flag
The code needed to make Joshua's code portable. On Solaris this
returns with the configure script:
$ make configure && ./configure | grep -i -e fnmatch && grep -i -e fnmatch config.mak.autogen
GEN configure
checking for fnmatch... yes
checking for library containing fnmatch... none required
checking whether the fnmatch function supports the FNMATCH_CASEFOLD GNU extension... no
NO_FNMATCH=
NO_FNMATCH_CASEFOLD=YesPlease
And on Linux:
$ make configure && ./configure | grep -i -e fnmatch && grep -i -e fnmatch config.mak.autogen
GEN configure
checking for fnmatch... yes
checking for library containing fnmatch... none required
checking whether the fnmatch function supports the FNMATCH_CASEFOLD GNU extension... yes
NO_FNMATCH=
NO_FNMATCH_CASEFOLD=
Makefile | 27 ++++++++++++---
config.mak.in | 2 +
configure.ac | 28 +++++++++++++++
dir.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++----------
dir.h | 4 ++
fast-import.c | 7 ++--
name-hash.c | 72 ++++++++++++++++++++++++++++++++++++++-
read-cache.c | 23 ++++++++++++
8 files changed, 241 insertions(+), 28 deletions(-)
--
1.7.3.159.g610493
From: Joshua Jensen <redacted>
This is especially beneficial when using Windows and Perforce and the
git-p4 bridge. Internally, Perforce preserves a given file's full path
including its case at the time it was added to the Perforce repository.
When syncing a file down via Perforce, missing directories are created,
if necessary, using the case as stored with the filename. Unfortunately,
two files in the same directory can have differing cases for their
respective paths, such as /diRa/file1.c and /DirA/file2.c. Depending on
sync order, DirA/ may get created instead of diRa/.
It is possible to handle directory names in a case insensitive manner
without this patch, but it is highly inconvenient, requiring each
character to be specified like so: [Bb][Uu][Ii][Ll][Dd]. With this patch, the
gitignore exclusions honor the core.ignorecase=true configuration
setting and make the process less error prone. The above is specified
like so: Build
Signed-off-by: Joshua Jensen <redacted>
Signed-off-by: Johannes Sixt <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
dir.c | 12 ++++++------
1 files changed, 6 insertions(+), 6 deletions(-)
On some platforms (like Solaris) there is a fnmatch, but it doesn't
support the GNU FNM_CASEFOLD extension that's used by the
jj/icase-directory series' fnmatch_icase wrapper.
Change the Makefile so that it's now possible to set
NO_FNMATCH_CASEFOLD=YesPlease on those systems, and add a configure
probe for it.
Unlike the NO_REGEX check we don't add AC_INCLUDES_DEFAULT to our
headers. This is because on a GNU system the definition of
FNM_CASEFOLD in fnmatch.h is guarded by:
#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 2 || defined _GNU_SOURCE
One of the headers AC_INCLUDES_DEFAULT includes ends up defining one
of those, so if we'd use it we'd always get
NO_FNMATCH_CASEFOLD=YesPlease on GNU systems, even though they have
FNM_CASEFOLD.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
Makefile | 9 +++++++++
config.mak.in | 1 +
configure.ac | 22 ++++++++++++++++++++++
3 files changed, 32 insertions(+), 0 deletions(-)
@@ -72,6 +72,9 @@ all::## Define NO_FNMATCH if you don't have fnmatch in the C library.#+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the+# FNM_CASEFOLD GNU extension.+## Define NO_LIBGEN_H if you don't have libgen.h.## Define NEEDS_LIBGEN if your libgen needs -lgen when linking
@@ -824,6 +824,28 @@ GIT_CHECK_FUNC(fnmatch, [NO_FNMATCH=YesPlease]) AC_SUBST(NO_FNMATCH) #+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the+# FNM_CASEFOLD GNU extension.+AC_CACHE_CHECK([whether the fnmatch function supports the FNMATCH_CASEFOLD GNU extension],+ [ac_cv_c_excellent_fnmatch], [+AC_EGREP_CPP(yippeeyeswehaveit,+ AC_LANG_PROGRAM([+#include <fnmatch.h>+],+[#ifdef FNM_CASEFOLD+yippeeyeswehaveit+#endif+]),+ [ac_cv_c_excellent_fnmatch=yes],+ [ac_cv_c_excellent_fnmatch=no])+])+if test $ac_cv_c_excellent_fnmatch = yes; then+ NO_FNMATCH_CASEFOLD=+else+ NO_FNMATCH_CASEFOLD=YesPlease+fi+AC_SUBST(NO_FNMATCH_CASEFOLD)+# # Define NO_MEMMEM if you don't have memmem. GIT_CHECK_FUNC(memmem, [NO_MEMMEM=],
From: Joshua Jensen <redacted>
When using a case preserving but case insensitive file system, directory
case can differ but still refer to the same physical directory. git
status reports the directory with the alternate case as an Untracked
file. (That is, when mydir/filea.txt is added to the repository and
then the directory on disk is renamed from mydir/ to MyDir/, git status
shows MyDir/ as being untracked.)
Support has been added in name-hash.c for hashing directories with a
terminating slash into the name hash. When index_name_exists() is called
with a directory (a name with a terminating slash), the name is not
found via the normal cache_name_compare() call, but it is found in the
slow_same_name() function.
Additionally, in dir.c, directory_exists_in_index_icase() allows newly
added directories deeper in the directory chain to be identified.
Ultimately, it would be better if the file list was read in case
insensitive alphabetical order from disk, but this change seems to
suffice for now.
The end result is the directory is looked up in a case insensitive
manner and does not show in the Untracked files list.
Signed-off-by: Joshua Jensen <redacted>
Signed-off-by: Johannes Sixt <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
dir.c | 40 ++++++++++++++++++++++++++++++++-
name-hash.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 110 insertions(+), 2 deletions(-)
@@ -485,6 +485,39 @@ enum exist_status {};/*+*Donotusethealphabeticallystoredindextolookup+*thedirectoryname;instead,usethecaseinsensitive+*namehash.+*/+staticenumexist_statusdirectory_exists_in_index_icase(constchar*dirname,intlen)+{+structcache_entry*ce=index_name_exists(&the_index,dirname,len+1,ignore_case);+unsignedcharendchar;++if(!ce)+returnindex_nonexistent;+endchar=ce->name[len];++/*+*Thecache_entrystructurereturnedwillcontainthisdirname+*andpossiblyadditionalpathcomponents.+*/+if(endchar=='/')+returnindex_directory;++/*+*Iftherearenoadditionalpathcomponents,thenthiscache_entry+*representsasubmodule.Submodules,despitebeingdirectories,+*arestoredinthecachewithoutaclosingslash.+*/+if(!endchar&&S_ISGITLINK(ce->ce_mode))+returnindex_gitdir;++/* This should never be hit, but it exists just in case. */+returnindex_nonexistent;+}++/**Theindexsortsalphabeticallybyentryname,which*meansthatagitlinksortsas'\0'attheend,while*adirectory(whichisdefinednotasanentry,butas
From: Joshua Jensen <redacted>
When mydir/filea.txt is added, mydir/ is renamed to MyDir/, and
MyDir/fileb.txt is added, running git ls-files mydir only shows
mydir/filea.txt. Running git ls-files MyDir shows MyDir/fileb.txt.
Running git ls-files mYdIR shows nothing.
With this patch running git ls-files for mydir, MyDir, and mYdIR shows
mydir/filea.txt and MyDir/fileb.txt.
Wildcards are not handled case insensitively in this patch. Example:
MyDir/aBc/file.txt is added. git ls-files MyDir/a* works fine, but git
ls-files mydir/a* does not.
Signed-off-by: Joshua Jensen <redacted>
Signed-off-by: Johannes Sixt <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
dir.c | 38 ++++++++++++++++++++++++++------------
1 files changed, 26 insertions(+), 12 deletions(-)
From: Joshua Jensen <redacted>
When MyDir/ABC/filea.txt is added to Git, the disk directory MyDir/ABC/
is renamed to mydir/aBc/, and then mydir/aBc/fileb.txt is added, the
index will contain MyDir/ABC/filea.txt and mydir/aBc/fileb.txt. Although
the earlier portions of this patch series account for those differences
in case, this patch makes the pathing consistent by folding the case of
newly added files against the first file added with that path.
In read-cache.c's add_to_index(), the index_name_exists() support used
for git status's case insensitive directory lookups is used to find the
proper directory case according to what the user already checked in.
That is, MyDir/ABC/'s case is used to alter the stored path for
fileb.txt to MyDir/ABC/fileb.txt (instead of mydir/aBc/fileb.txt).
This is especially important when cloning a repository to a case
sensitive file system. MyDir/ABC/ and mydir/aBc/ exist in the same
directory on a Windows machine, but on Linux, the files exist in two
separate directories. The update to add_to_index(), in effect, treats a
Windows file system as case sensitive by making path case consistent.
Signed-off-by: Joshua Jensen <redacted>
Signed-off-by: Johannes Sixt <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
read-cache.c | 23 +++++++++++++++++++++++
1 files changed, 23 insertions(+), 0 deletions(-)
@@ -608,6 +608,29 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,ce->ce_mode=ce_mode_from_stat(ent,st_mode);}+/* When core.ignorecase=true, determine if a directory of the same name but differing+*casealreadyexistswithintheGitrepository.Ifitdoes,ensurethedirectory+*caseofthefilebeingaddedtotherepositorymatches(isfoldedinto)theexisting+*entry'sdirectorycase.+*/+if(ignore_case){+constchar*startPtr=ce->name;+constchar*ptr=startPtr;+while(*ptr){+while(*ptr&&*ptr!='/')+++ptr;+if(*ptr=='/'){+structcache_entry*foundce;+++ptr;+foundce=index_name_exists(&the_index,ce->name,ptr-ce->name,ignore_case);+if(foundce){+memcpy((void*)startPtr,foundce->name+(startPtr-ce->name),ptr-startPtr);+startPtr=ptr;+}+}+}+}+alias=index_name_exists(istate,ce->name,ce_namelen(ce),ignore_case);if(alias&&!ce_stage(alias)&&!ie_match_stat(istate,alias,st,ce_option)){/* Nothing changed, really */
From: Robert Buck <hidden> Date: 2016-06-15 22:49:40
Referring back to my earlier comment to this patch series, which was
proposed on August 17, while I tend to agree to the changes that help
listing- operations, those changes that fold case concern me. Let me
explain...
You may find there is a strong contingent of people that would approve
of, and would want to use, case insensitivity for gitignore and ls,
for example; but by tying the same single property (core.ignorecase)
to the case folding behaviors some people would avoid the feature all
together, which would be unfortunate, when they otherwise could
benefit from at least one part of the new behavior.
There were several key things that went wrong in early git
development, this and the eol support were two casualties. The eol
support, as you recall, deprecated the old property in favor of a
couple new superior ones. I would recommend that the same thing be
done here, deprecate the old ignorecase property by introducing two
better ones.
So I could we please separate the behaviors that change intent
(folding) from the behaviors that merely alter how things are
displayed (listing) by splitting this into two separate properties?
For example,
core.casepreserving=true|false
core.caseinsensitive=true|false
The former property would control folding, the latter property would
apply to listing and pattern matching. Then people could opt out of
the folding behaviors (add, import), while continuing to adopt listing
and pattern matching (status, ls, ignore).
Again, deprecate core.ignorecase by making it default to {false,false}
for the new properties if unspecified, which would also be the default
if all three of the properties are unspecified. If ignorecase is
specified to be true, then default to {false, true}, respectively.
Would this be possible?
On Sun, Oct 3, 2010 at 12:32 AM, Joshua Jensen
[off-list ref] wrote:
The second version of this patch series fixes the problematic case
insensitive fnmatch call in patch 1 that relied on an apparently GNU-only
extension. Instead, the pattern and string are lowercased into
temporary buffers, and the standard fnmatch is called without relying
on the GNU extension.
Patches 2-6 received no modifications.
The original cover for the patch series follows as posted by Johannes Sixt:
The following patch series extends the core.ignorecase=true support to
handle case insensitive comparisons for the .gitignore file, git status,
and git ls-files. git add and git fast-import will fold the case of the
file being added, matching that of an already added directory entry. Case
folding is also applied to git fast-import for renames, copies, and deletes.
The most notable benefit, IMO, is that the case of directories in the
worktree does not matter if, and only if, the directory exists already in
the index with some different case variant. This helps applications on
Windows that change the case even of directories in unpredictable ways.
Joshua mentioned Perforce as the primary example.
Concerning the implementation, Joshua explained when he initially submitted
the series to the msysgit mailing list:
git status and add both use an update made to name-hash.c where
directories, specifically names with a trailing slash, can be looked up
in a case insensitive manner. After trying a myriad of solutions, this
seemed to be the cleanest. Does anyone see a problem with embedding the
directory names in the same hash as the file names? I couldn't find one,
especially since I append a slash to each directory name.
The git add path case folding functionality is a somewhat radical
departure from what Git does now. It is described in detail in patch 5.
Does anyone have any concerns?
I support the idea of this patch, and I can confirm that it works: I've
used this series in production both with core.ignorecase set to true and
to false, and in the former case, with directories and files with case
different from the index.
Joshua Jensen (6):
Add string comparison functions that respect the ignore_case variable.
Case insensitivity support for .gitignore via core.ignorecase
Add case insensitivity support for directories when using git status
Add case insensitivity support when using git ls-files
Support case folding for git add when core.ignorecase=true
Support case folding in git fast-import when core.ignorecase=true
dir.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
dir.h | 4 ++
fast-import.c | 7 ++-
name-hash.c | 72 +++++++++++++++++++++++++++
read-cache.c | 23 +++++++++
5 files changed, 235 insertions(+), 23 deletions(-)
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
It's a real shame about the code duplication here. Can we not avoid
it just by doing:
unsigned char c1 = (ignore_case) ? tolower(*match) : *match;
unisgned char c2 = (ignore_case) ? tolower(*name) : *name;
I appreciate that to some it might look like perl golf, but...
-- Thomas Adam
Heya,
On Sun, Oct 3, 2010 at 06:32, Joshua Jensen [off-list ref] wrote:
When core.ignorecase=true, imported file paths will be folded to match
existing directory case.
I haven't checked if you got all the relevant cases in fast-import.c,
but the cases you do address look good.
How would this handle incremental imports? How does this handle
someone adding a file twice, with the same name but different case?
--
Cheers,
Sverre Rabbelier
I think you should protect against defining both NO_FNMATCH and
NO_FNMATCH_CASEFOLD (your version would link compat/fnmatch/fnmatch.o twice
in this case):
ifdef NO_FNMATCH
...
else
ifdef NO_FNMATCH_CASEFOLD
...
endif
endif
Otherwise, the patch looks fine.
-- Hannes
From: Johannes Sixt <hidden> Date: 2016-06-15 22:49:40
On Sonntag, 3. Oktober 2010, Robert Buck wrote:
So I could we please separate the behaviors that change intent
(folding) from the behaviors that merely alter how things are
displayed (listing) by splitting this into two separate properties?
For example,
core.casepreserving=true|false
core.caseinsensitive=true|false
The former property would control folding, the latter property would
apply to listing and pattern matching. Then people could opt out of
the folding behaviors (add, import), while continuing to adopt listing
and pattern matching (status, ls, ignore).
core.ignorecase has a very well-defined meaning: It describes whether the
worktree lives on a filesystem that is case-insensitive. Perhaps you could
help me understand your case if you gave examples and a use-case? I have a
slight suspicion that your wish is orthogonal to core.ignorecase.
-- Hannes
It's a real shame about the code duplication here. Can we not avoid
it just by doing:
unsigned char c1 = (ignore_case) ? tolower(*match) : *match;
unisgned char c2 = (ignore_case) ? tolower(*name) : *name;
I appreciate that to some it might look like perl golf, but...
It has been discussed, and IIRC, the concensus was to keep the code
duplication because this is an inner loop.
-- Hannes
It's a real shame about the code duplication here. Can we not avoid
it just by doing:
unsigned char c1 = (ignore_case) ? tolower(*match) : *match;
unisgned char c2 = (ignore_case) ? tolower(*name) : *name;
I appreciate that to some it might look like perl golf, but...
It has been discussed, and IIRC, the concensus was to keep the code
duplication because this is an inner loop.
I must have missed the discussion -- but why/how does making it an
inner-loop somehow prevent it from such an obvious (and readable)
optimisation, which would have fitted in well in other areas.
-- Thomas Adam
From: Junio C Hamano <hidden> Date: 2016-06-15 22:49:41
Johannes Sixt [off-list ref] writes:
Junio, IIRC, the series appeared in next for some time before the 1.7.3
release. Does this imply that you reviewed the series and deemed the
implementation sound?
Not really. I knew that I had an opportunity to rewind whatever crap I
throw in 'next' soon, and wanted to see if anybody screams upon stumbling
on breakages ;-)
On some platforms (like Solaris) there is a fnmatch, but it doesn't
support the GNU FNM_CASEFOLD extension that's used by the
jj/icase-directory series' fnmatch_icase wrapper.
Change the Makefile so that it's now possible to set
NO_FNMATCH_CASEFOLD=YesPlease on those systems, and add a configure
probe for it.
Unlike the NO_REGEX check we don't add AC_INCLUDES_DEFAULT to our
headers. This is because on a GNU system the definition of
FNM_CASEFOLD in fnmatch.h is guarded by:
#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 2 || defined _GNU_SOURCE
One of the headers AC_INCLUDES_DEFAULT includes ends up defining one
of those, so if we'd use it we'd always get
NO_FNMATCH_CASEFOLD=YesPlease on GNU systems, even though they have
FNM_CASEFOLD.
When checking the flags we use:
ifdef NO_FNMATCH
...
else
ifdef NO_FNMATCH_CASEFOLD
...
endif
endif
The "else" so that we don't link against compat/fnmatch/fnmatch.o
twice if both NO_FNMATCH and NO_FNMATCH_CASEFOLD are defined.
Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
On Sun, Oct 3, 2010 at 17:58, Johannes Sixt [off-list ref] wrote:
I think you should protect against defining both NO_FNMATCH and
NO_FNMATCH_CASEFOLD (your version would link compat/fnmatch/fnmatch.o twice
in this case):
Well spotted. That's fixed in this version.
Makefile | 10 ++++++++++
config.mak.in | 1 +
configure.ac | 22 ++++++++++++++++++++++
3 files changed, 33 insertions(+), 0 deletions(-)
@@ -72,6 +72,9 @@ all::## Define NO_FNMATCH if you don't have fnmatch in the C library.#+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the+# FNM_CASEFOLD GNU extension.+## Define NO_LIBGEN_H if you don't have libgen.h.## Define NEEDS_LIBGEN if your libgen needs -lgen when linking
@@ -824,6 +824,28 @@ GIT_CHECK_FUNC(fnmatch, [NO_FNMATCH=YesPlease]) AC_SUBST(NO_FNMATCH) #+# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the+# FNM_CASEFOLD GNU extension.+AC_CACHE_CHECK([whether the fnmatch function supports the FNMATCH_CASEFOLD GNU extension],+ [ac_cv_c_excellent_fnmatch], [+AC_EGREP_CPP(yippeeyeswehaveit,+ AC_LANG_PROGRAM([+#include <fnmatch.h>+],+[#ifdef FNM_CASEFOLD+yippeeyeswehaveit+#endif+]),+ [ac_cv_c_excellent_fnmatch=yes],+ [ac_cv_c_excellent_fnmatch=no])+])+if test $ac_cv_c_excellent_fnmatch = yes; then+ NO_FNMATCH_CASEFOLD=+else+ NO_FNMATCH_CASEFOLD=YesPlease+fi+AC_SUBST(NO_FNMATCH_CASEFOLD)+# # Define NO_MEMMEM if you don't have memmem. GIT_CHECK_FUNC(memmem, [NO_MEMMEM=],
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:49:41
Johannes Sixt wrote:
On Sonntag, 3. Oktober 2010, Thomas Adam wrote:
quoted
It's a real shame about the code duplication here. Can we not avoid
it just by doing:
unsigned char c1 = (ignore_case) ? tolower(*match) : *match;
unisgned char c2 = (ignore_case) ? tolower(*name) : *name;
I appreciate that to some it might look like perl golf, but...
It has been discussed, and IIRC, the concensus was to keep the code
duplication because this is an inner loop.
Did anyone time it? If it really is not dwarfed by other computation,
then how about (warning: ugly!)
static inline int step(unsigned char c1, unsigned char c2,
const char **match, const char **name, int *namelen)
{
if (c1 == '\0' || is_glob_special(c1))
return 1; /* break */
if (c1 != c2)
return 0; /* found mismatch! */
(*match)++;
(*name)++;
(*namelen)--;
return 2; /* continue */
}
...
int r = 1;
if (!ignore_case) {
while ((r = step(*match, *name, &match, &name, &namelen)) == 2)
; /* matches so far */
} else {
while ((r = step(tolower(*match), tolower(*name),
&match, &name, &namelen)) == 2)
; /* matches so far */
}
if (!r) /* found mismatch! */
return 0;
From: Erik Faye-Lund <hidden> Date: 2016-06-15 22:49:41
On Mon, Oct 4, 2010 at 9:49 AM, Jonathan Nieder [off-list ref] wrote:
Johannes Sixt wrote:
quoted
On Sonntag, 3. Oktober 2010, Thomas Adam wrote:
quoted
quoted
It's a real shame about the code duplication here. Can we not avoid
it just by doing:
unsigned char c1 = (ignore_case) ? tolower(*match) : *match;
unisgned char c2 = (ignore_case) ? tolower(*name) : *name;
I appreciate that to some it might look like perl golf, but...
It has been discussed, and IIRC, the concensus was to keep the code
duplication because this is an inner loop.
Did anyone time it? If it really is not dwarfed by other computation,
then how about (warning: ugly!)
I believe it was timed. I was the one who reacted on this the first
time around, and I seem to remember that the performance impact was
indeed significant. This function is used all the time when updating
the index etc IIRC.
----- Original Message -----
From: Erik Faye-Lund
Date: 10/4/2010 8:03 AM
On Mon, Oct 4, 2010 at 9:49 AM, Jonathan Nieder[off-list ref] wrote:
quoted
Johannes Sixt wrote:
quoted
On Sonntag, 3. Oktober 2010, Thomas Adam wrote:
quoted
It's a real shame about the code duplication here. Can we not avoid
it just by doing:
unsigned char c1 = (ignore_case) ? tolower(*match) : *match;
unisgned char c2 = (ignore_case) ? tolower(*name) : *name;
I appreciate that to some it might look like perl golf, but...
It has been discussed, and IIRC, the concensus was to keep the code
duplication because this is an inner loop.
Did anyone time it? If it really is not dwarfed by other computation,
then how about (warning: ugly!)
I believe it was timed. I was the one who reacted on this the first
time around, and I seem to remember that the performance impact was
indeed significant. This function is used all the time when updating
the index etc IIRC.
In a good sized repository I have in front of me now, running 'git
ls-files' through this code path results in 705,374 characters being
processed by this body of code. Given the code listed above, that means
we add 1,410,748 additional comparisons that everyone has to suffer
through, even those on a case sensitive file system. Sure, the code
could be optimized to not perform the double comparison, and the
compiler may actually perform that optimization. Still, it is hundreds
of thousands of additional comparisons and branches that were not there
before.
I'm running on a really, really fast machine, a Xeon X5560. The
difference in time for the above code versus what is in the patch seems
to average about 0.07 seconds. Remember, this is an incredibly fast
machine, and I imagine it will be worse on machines with slower
processors and less cache.
As discussed in the original thread (which, I believe, was on the
msysGit mailing list), one of Git's features is its speed. Maintaining
that speed in the core.ignorecase=false case is top priority for me, but
others with more know how can tell me I'm wrong.
Josh
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:49:41
söndagen den 3 oktober 2010 11.56.44 skrev Ævar Arnfjörð Bjarmason:
quoted hunk
From: Joshua Jensen <redacted>
When mydir/filea.txt is added, mydir/ is renamed to MyDir/, and
MyDir/fileb.txt is added, running git ls-files mydir only shows
mydir/filea.txt. Running git ls-files MyDir shows MyDir/fileb.txt.
Running git ls-files mYdIR shows nothing.
With this patch running git ls-files for mydir, MyDir, and mYdIR shows
mydir/filea.txt and MyDir/fileb.txt.
Wildcards are not handled case insensitively in this patch. Example:
MyDir/aBc/file.txt is added. git ls-files MyDir/a* works fine, but git
ls-files mydir/a* does not.
Signed-off-by: Joshua Jensen <redacted>
Signed-off-by: Johannes Sixt <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
dir.c | 38 ++++++++++++++++++++++++++------------
1 files changed, 26 insertions(+), 12 deletions(-)
On Mon, Oct 4, 2010 at 16:02, Robin Rosenberg
[off-list ref] wrote:
Is anyone thinking "unicode" around here?
Thinking yeah, doing the massive work to implement it: no.
It's much harder when you don't know what encoding the data is in,
encoding is only by repository convention in Git.
From: Erik Faye-Lund <hidden> Date: 2016-06-15 22:49:41
On Mon, Oct 4, 2010 at 6:02 PM, Robin Rosenberg
[off-list ref] wrote:
söndagen den 3 oktober 2010 11.56.44 skrev Ævar Arnfjörð Bjarmason:
quoted
From: Joshua Jensen <redacted>
When mydir/filea.txt is added, mydir/ is renamed to MyDir/, and
MyDir/fileb.txt is added, running git ls-files mydir only shows
mydir/filea.txt. Running git ls-files MyDir shows MyDir/fileb.txt.
Running git ls-files mYdIR shows nothing.
With this patch running git ls-files for mydir, MyDir, and mYdIR shows
mydir/filea.txt and MyDir/fileb.txt.
Wildcards are not handled case insensitively in this patch. Example:
MyDir/aBc/file.txt is added. git ls-files MyDir/a* works fine, but git
ls-files mydir/a* does not.
Signed-off-by: Joshua Jensen <redacted>
Signed-off-by: Johannes Sixt <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
dir.c | 38 ++++++++++++++++++++++++++------------
1 files changed, 26 insertions(+), 12 deletions(-)
*name, int namelen) if (!*match)
return MATCHED_RECURSIVELY;
- for (;;) {
- unsigned char c1 = *match;
- unsigned char c2 = *name;
- if (c1 == '\0' || is_glob_special(c1))
- break;
- if (c1 != c2)
- return 0;
- match++;
- name++;
- namelen--;
+ if (ignore_case) {
+ for (;;) {
+ unsigned char c1 = tolower(*match);
+ unsigned char c2 = tolower(*name);
Is anyone thinking "unicode" around here?
You're not the first to think about the combination of core.ignorecase
and unicode, but unfortunately way too few people have.
slow_same_name() (and index_name_exists() by proxy) already does the
Wrong Thing (tm), so the problem is already rooted in the index. The
consensus on the msysGit mailing list last time this was brought up
[1] was simply to ignore the combination of unicode and
core.ignorecase, but I'm not sure I'm convinced myself that it's a
good idea. We might end up painting our selves further into a corner,
in the end making it nearly impossible to fix.
One complicating factor is that Windows' definition of what
character-pairs compare as identical depends on a table stored
somewhere in NTFS[2]. The time your drive was formatted decides what
that table looks like, and I haven't been able to retrieve it. This
might be going a little too far, as this table is likely to be very
rarely changed, but I think it's worth noting.
[1]: http://groups.google.com/group/msysgit/browse_thread/thread/675ad16102f6233f/a25cd7bb8dfa2abb#a25cd7bb8dfa2abb
[2]: http://blogs.msdn.com/b/michkap/archive/2007/10/24/5641619.aspx
On Windows, Unicode filenames are 16-bit wide characters. The current
code doesn't handle them at all.
I do not know about other file systems and what Git actually handles. I
was under the impression it didn't handle Unicode filenames well in
general... ?
Josh
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:49:41
Joshua Jensen wrote:
I'm running on a really, really fast machine, a Xeon X5560. The
difference in time for the above code versus what is in the patch
seems to average about 0.07 seconds.
The useful information would be a percentage...
Remember, this is an
incredibly fast machine, and I imagine it will be worse on machines
with slower processors and less cache.
... but the subarch and cache size may indeed also be relevant.
Here's a revised version of the ugly speed hack. Using a separate
function like this is probably a bad idea unless it speeds things up.
/* Returns match length, or -1 for mismatch. */
static inline int match_until_glob_special(const char *match, const char *name,
int namelen, int ignore_case)
{
int remaining = namelen;
for (;;) {
unsigned char c1 = (ignore_case) ? tolower(*match) : *match;
unsigned char c2 = (ignore_case) ? tolower(*name) : *name;
if (c1 == '\0' || is_glob_special(c1))
return namelen - remaining;
if (c1 != c2)
return -1;
match++;
name++;
remaining--;
}
}
[...]
int matched;
/* If the match was just the prefix, we matched */
if (!*match)
return MATCHED_RECURSIVELY;
/*
* Note: this funny "if" is to ensure each case gets inlined separately.
* Please don't optimize it away unless you've checked the assembler
* to ensure it wasn't helping.
*/
if (ignore_case)
matched = match_until_glob_special(match, name, namelen, 1);
else
matched = match_until_glob_special(match, name, namelen, 0);
if (matched == -1) /* mismatch! */
return 0;
match += matched;
name += matched;
remaining -= matched;
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:49:41
Joshua Jensen wrote:
I do not know about other file systems and what Git actually
handles. I was under the impression it didn't handle Unicode
filenames well in general... ?
Except in cases like ignorecase handling, git treats path components
as arbitrary streams of bytes ('\0' and path separators are forbidden,
of course). It works pretty well if that's what you need.
On Mon, Oct 4, 2010 at 16:49, Joshua Jensen [off-list ref] wrote:
quoted
Is anyone thinking "unicode" around here?
On Windows, Unicode filenames are 16-bit wide characters. The current code
doesn't handle them at all.
I do not know about other file systems and what Git actually handles. I was
under the impression it didn't handle Unicode filenames well in general... ?
The only sane way of doing this sort of thing is to have a defined
*internal* encoding that gets converted to whatever the native
encoding is at the input/output points.
So Git could use Unicode represented by UTF-8, UTF-16 (whatever's
convenient) internally, but when you check out files those checked out
files can be in whatever encoding you choose.
So you could have a UTF-8 repository but check out UTF-8 filenames on
Windows. I.e. internally we'd have the file:
æab
Represented by UTF-8:
c3 a6 61 62 \0
But would check out UTF-16:
ff fe e6 00 61 00 62 00
Then when you add a new file it'll know it's in UTF-16 and convert it
to UTF-8 before writing to the repository. All invisible to the user.
Perl handles encoding issues like this, and it's awesome. The only
thing you have to do is make sure that the system knows the encoding
of data going into it, and what encoding you want out of it.
But any implementation of this is far off, and just storing raw byte
streams is Good Enough now that almost everyone uses UTF-8 anyway, so
nobody's seriously worked on this.
From: Johannes Sixt <hidden> Date: 2016-06-15 22:49:42
On Montag, 4. Oktober 2010, Robin Rosenberg wrote:
Is anyone thinking "unicode" around here?
My recommendation these days is that you should not use git if you care about
Unicode filenames: git is tied to POSIX in this regard, which defines
filenames as streams of *bytes*.
-- Hannes
On Mon, Oct 4, 2010 at 19:02, Johannes Sixt [off-list ref] wrote:
On Montag, 4. Oktober 2010, Robin Rosenberg wrote:
quoted
Is anyone thinking "unicode" around here?
My recommendation these days is that you should not use git if you care about
Unicode filenames: git is tied to POSIX in this regard, which defines
filenames as streams of *bytes*.
A SCM is all about giving meaning to streams of bytes. Just because
POSIX only says that filenames are \0-delimited blobs that doesn't
mean you can't have some annotation elsewhere that says "hey, these
blobs are in $encoding".
<insert another disclaimer here about implementing this being a huge
task, but I'm just saying...>
From: Robert Buck <hidden> Date: 2016-06-15 22:49:43
Hello Johannes,
Here is the use case I was thinking of.
Let's say I want to have .gitignore be case insensitive with respect
to matches so I can simplify the file by not having [D][d]ebug sorts
of messes. But let's say I also want to support files whose names only
differ by case (just like Unix supports). Can your current patch
series support this? Does the current patch series break this?
Could you share how this would or would not work, and if not, how you
might accomplish this?
Thanks,
Bob
On Sun, Oct 3, 2010 at 2:12 PM, Johannes Sixt [off-list ref] wrote:
On Sonntag, 3. Oktober 2010, Robert Buck wrote:
quoted
So I could we please separate the behaviors that change intent
(folding) from the behaviors that merely alter how things are
displayed (listing) by splitting this into two separate properties?
For example,
core.casepreserving=true|false
core.caseinsensitive=true|false
The former property would control folding, the latter property would
apply to listing and pattern matching. Then people could opt out of
the folding behaviors (add, import), while continuing to adopt listing
and pattern matching (status, ls, ignore).
core.ignorecase has a very well-defined meaning: It describes whether the
worktree lives on a filesystem that is case-insensitive. Perhaps you could
help me understand your case if you gave examples and a use-case? I have a
slight suspicion that your wish is orthogonal to core.ignorecase.
-- Hannes
----- Original Message -----
From: Robert Buck
Date: 10/6/2010 4:04 PM
Let's say I want to have .gitignore be case insensitive with respect
to matches so I can simplify the file by not having [D][d]ebug sorts
of messes. But let's say I also want to support files whose names only
differ by case (just like Unix supports). Can your current patch
series support this? Does the current patch series break this?
Could you share how this would or would not work, and if not, how you
might accomplish this?
With this patch series, you are either on a case sensitive file system
(core.ignorecase = false) or a case insensitive file system
(core.ignorecase = true).
There is no specific configuration for .gitignore case insensitivity.
It only pays attention to core.ignorecase.
This is something that could be added, but I don't fully understand the
need. On case sensitive file systems, the case of the resultant
filename is guaranteed. If you have both a Debug/ and debug/ directory,
I would expect two entries in the .gitignore.
?
Josh
From: Junio C Hamano <hidden> Date: 2016-06-15 22:49:43
Joshua Jensen [off-list ref] writes:
In any case, I'd like to find a solution to get this series working
for everyone. I've been out of commission for a month (deploying Git
to 80+ programmers at an organization, by the way), but I'm back now
and can work this until it is complete.
Thanks; I'll queue Ævar's v3 (with [v4 2/8]) for now.
----- Original Message -----
From: Junio C Hamano
Date: 10/6/2010 10:13 PM
Joshua Jensen[off-list ref] writes:
quoted
In any case, I'd like to find a solution to get this series working
for everyone. I've been out of commission for a month (deploying Git
to 80+ programmers at an organization, by the way), but I'm back now
and can work this until it is complete.
Thanks; I'll queue Ævar's v3 (with [v4 2/8]) for now.