Although undocumented, directory_exists_in_index_icase(dirname,len)
unconditionally assumes the presence of a '/' at dirname[len], despite
being past the end-of-string. Callers are expected to respect this
assumption by ensuring that a '/' is present beyond the last character
of the passed path.

directory_exists_in_index(), on the other hand, expects no trailing '/'
(and never looks beyond end-of-string). Callers are expected to respect
this by ensuring the absence of '/'.

2eac2a4cc4bdc8d7 (ls-files -k: a directory only can be killed if the
index has a non-directory; 2013-08-15) adds a caller which forgets to
ensure the trailing '/' beyond end-of-string, which leads to
inconsistent behavior between directory_exists_in_index() and
directory_exists_in_index_icase(), depending upon the setting of
core.ignorecase.

One approach to fix this would be to augment the new caller (added by
2eac2a4cc4bdc8d7) to add a '/' beyond end-of-string, but this places
extra burden on the caller to account for an implementation detail of
directory_exists_in_index_icase().

Another approach would be for directory_exists_in_index_icase() to take
responsibility for its own requirements by copying the incoming path and
adding a trailing '/', but such copying can become expensive.

The approach taken by this patch is to pass the strbufs already used by
their callers into directory_exists_in_index() and
directory_exists_in_index_icase() rather than 'const char *' + 'size_t
len'. This allows both functions to satisfy their own internal
requirements -- by manipulating the strbuf to add or remove the trailing
'/' -- without placing burden upon callers and without having to make
expensive copies of each incoming string.

This also fixes an initially-unnoticed failure, when core.ignorecase is
true, in a t3010 test added by 3c56875176390eee (t3010: update to
demonstrate "ls-files -k" optimization pitfalls; 2013-08-15).

Signed-off-by: Eric Sunshine <sunsh...@sunshineco.com>
---
 dir.c                               | 42 +++++++++++++++++++++++--------------
 t/t3010-ls-files-killed-modified.sh |  2 +-
 2 files changed, 27 insertions(+), 17 deletions(-)

diff --git a/dir.c b/dir.c
index edd666a..6f761a1 100644
--- a/dir.c
+++ b/dir.c
@@ -887,14 +887,21 @@ enum exist_status {
  * the directory name; instead, use the case insensitive
  * name hash.
  */
-static enum exist_status directory_exists_in_index_icase(const char *dirname, 
int len)
+static enum exist_status directory_exists_in_index_icase(struct strbuf 
*dirname)
 {
-       const struct cache_entry *ce = cache_name_exists(dirname, len + 1, 
ignore_case);
+       const struct cache_entry *ce;
        unsigned char endchar;
+       size_t len = dirname->len;
+       int need_slash = len && dirname->buf[len - 1] != '/';
+
+       if (need_slash)
+               strbuf_addch(dirname, '/');
+       ce = cache_name_exists(dirname->buf, dirname->len, ignore_case);
+       strbuf_setlen(dirname, len);
 
        if (!ce)
                return index_nonexistent;
-       endchar = ce->name[len];
+       endchar = ce->name[need_slash ? len : len - 1];
 
        /*
         * The cache_entry structure returned will contain this dirname
@@ -922,21 +929,25 @@ static enum exist_status 
directory_exists_in_index_icase(const char *dirname, in
  * the files it contains) will sort with the '/' at the
  * end.
  */
-static enum exist_status directory_exists_in_index(const char *dirname, int 
len)
+static enum exist_status directory_exists_in_index(struct strbuf *dirname)
 {
-       int pos;
+       int pos, len;
 
        if (ignore_case)
-               return directory_exists_in_index_icase(dirname, len);
+               return directory_exists_in_index_icase(dirname);
+
+       len = dirname->len;
+       if (len && dirname->buf[len - 1] == '/')
+               len--;
 
-       pos = cache_name_pos(dirname, len);
+       pos = cache_name_pos(dirname->buf, len);
        if (pos < 0)
                pos = -pos-1;
        while (pos < active_nr) {
                const struct cache_entry *ce = active_cache[pos++];
                unsigned char endchar;
 
-               if (strncmp(ce->name, dirname, len))
+               if (strncmp(ce->name, dirname->buf, len))
                        break;
                endchar = ce->name[len];
                if (endchar > '/')
@@ -983,11 +994,10 @@ static enum exist_status directory_exists_in_index(const 
char *dirname, int len)
  *  (c) otherwise, we recurse into it.
  */
 static enum path_treatment treat_directory(struct dir_struct *dir,
-       const char *dirname, int len, int exclude,
+       struct strbuf *dirname, int exclude,
        const struct path_simplify *simplify)
 {
-       /* The "len-1" is to strip the final '/' */
-       switch (directory_exists_in_index(dirname, len-1)) {
+       switch (directory_exists_in_index(dirname)) {
        case index_directory:
                return path_recurse;
 
@@ -999,7 +1009,7 @@ static enum path_treatment treat_directory(struct 
dir_struct *dir,
                        break;
                if (!(dir->flags & DIR_NO_GITLINKS)) {
                        unsigned char sha1[20];
-                       if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
+                       if (!resolve_gitlink_ref(dirname->buf, "HEAD", sha1))
                                return path_untracked;
                }
                return path_recurse;
@@ -1010,7 +1020,8 @@ static enum path_treatment treat_directory(struct 
dir_struct *dir,
        if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
                return exclude ? path_excluded : path_untracked;
 
-       return read_directory_recursive(dir, dirname, len, 1, simplify);
+       return read_directory_recursive(dir, dirname->buf, dirname->len, 1,
+                                       simplify);
 }
 
 /*
@@ -1161,7 +1172,7 @@ static enum path_treatment treat_one_path(struct 
dir_struct *dir,
        if ((dir->flags & DIR_COLLECT_KILLED_ONLY) &&
            (dtype == DT_DIR) &&
            !has_path_in_index &&
-           (directory_exists_in_index(path->buf, path->len) == 
index_nonexistent))
+           (directory_exists_in_index(path) == index_nonexistent))
                return path_none;
 
        exclude = is_excluded(dir, path->buf, &dtype);
@@ -1178,8 +1189,7 @@ static enum path_treatment treat_one_path(struct 
dir_struct *dir,
                return path_none;
        case DT_DIR:
                strbuf_addch(path, '/');
-               return treat_directory(dir, path->buf, path->len, exclude,
-                       simplify);
+               return treat_directory(dir, path, exclude, simplify);
        case DT_REG:
        case DT_LNK:
                return exclude ? path_excluded : path_untracked;
diff --git a/t/t3010-ls-files-killed-modified.sh 
b/t/t3010-ls-files-killed-modified.sh
index 198d308..78954bd 100755
--- a/t/t3010-ls-files-killed-modified.sh
+++ b/t/t3010-ls-files-killed-modified.sh
@@ -107,7 +107,7 @@ test_expect_success 'git ls-files -k to show killed files 
(icase).' '
        git -c core.ignorecase=true ls-files -k >.output
 '
 
-test_expect_failure 'validate git ls-files -k output (icase).' '
+test_expect_success 'validate git ls-files -k output (icase).' '
        test_cmp .expected .output
 '
 
-- 
1.8.4.rc4.557.g46c5bb2

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to