[PATCH] ls-files: fix overeager pathspec optimization
From: Junio C Hamano <hidden>
Date: 2016-06-15 22:47:59
Given pathspecs that share a common prefix, ls-files optimized its call
into recursive directory reader by starting at the common prefix
directory.
If you have a directory "t" with an untracked file "t/junk" in it, but the
top-level .gitignore file told us to ignore "t/", this resulted in an
unexpected behaviour:
$ git ls-files -o --exclude-standard
$ cd t && git ls-files -o --exclude-standard
junk
Signed-off-by: Junio C Hamano <redacted>
---
Junio C Hamano [off-list ref] writes:
quoted
I think a proper fix should be in dir.c::read_directory() where it calls read_directory_recursive() without first checking if the directory itself should be ignored. read_directory_recursive() itself has a logic to see if the dirent it found is worth recursing into and a similar logic should be in the toplevel caller.
So here it is. dir.c | 37 ++++++++++++++++++++++++++++++++++ t/t3001-ls-files-others-exclude.sh | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 0 deletions(-)
diff --git a/dir.c b/dir.c
index f9ac454..9316eb6 100644
--- a/dir.c
+++ b/dir.c@@ -787,6 +787,40 @@ static void free_simplify(struct path_simplify *simplify) free(simplify); } +static int has_leading_ignored_dir(struct dir_struct *dir, + const char *path_, int len, + const struct path_simplify *simplify) +{ + int at, dtype = DT_DIR; + char path[PATH_MAX]; + + at = 0; + memcpy(path, path_, len); + while (1) { + char *cp; + path[at] = '\0'; + /* + * NOTE! NOTE! NOTE!: we might want to actually lstat(2) + * path[] to make sure it is a directory. + */ + if (excluded(dir, path, &dtype)) + return 1; + if (at < len) { + path[at] = path_[at]; + if (at < len && path[at] == '/') + at++; + } + if (len <= at) + break; + cp = memchr(path + at, '/', len - at); + if (!cp) + at = len; + else + at = cp - path; + } + return 0; +} + int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec) { struct path_simplify *simplify;
@@ -795,6 +829,9 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const char return dir->nr; simplify = create_simplify(pathspec); + if (has_leading_ignored_dir(dir, path, len, simplify)) + return dir->nr; + read_directory_recursive(dir, path, len, 0, simplify); free_simplify(simplify); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index c65bca8..9e71260 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh@@ -153,4 +153,43 @@ test_expect_success 'negated exclude matches can override previous ones' ' grep "^a.1" output ' +test_expect_success 'subdirectory ignore (setup)' ' + mkdir -p top/l1/l2 && + ( + cd top && + git init && + echo /.gitignore >.gitignore && + echo l1 >>.gitignore && + echo l2 >l1/.gitignore && + >l1/l2/l1 + ) +' + +test_expect_success 'subdirectory ignore (toplevel)' ' + ( + cd top && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'subdirectory ignore (l1/l2)' ' + ( + cd top/l1/l2 && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'subdirectory ignore (l1)' ' + ( + cd top/l1 && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + test_done
--
1.6.6.209.g52296.dirty