diff --git a/src/iterator.c b/src/iterator.c index ee83a4fda..75985355f 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -10,6 +10,7 @@ #include "ignore.h" #include "buffer.h" #include "git2/submodule.h" +#include #define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC) do { \ (P) = git__calloc(1, sizeof(NAME_LC ## _iterator)); \ @@ -465,6 +466,23 @@ static int git_path_with_stat_cmp_icase(const void *a, const void *b) return strcasecmp(path_with_stat_a->path, path_with_stat_b->path); } +GIT_INLINE(bool) path_is_dotgit(const git_path_with_stat *ps) +{ + if (!ps) + return false; + else { + const char *path = ps->path; + size_t len = ps->path_len; + + return len >= 4 && + tolower(path[len - 1]) == 't' && + tolower(path[len - 2]) == 'i' && + tolower(path[len - 3]) == 'g' && + path[len - 4] == '.' && + (len == 4 || path[len - 5] == '/'); + } +} + static workdir_iterator_frame *workdir_iterator__alloc_frame(workdir_iterator *wi) { workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame)); @@ -531,6 +549,9 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi) CASESELECT(wi->base.ignore_case, workdir_iterator__entry_cmp_icase, workdir_iterator__entry_cmp_case), wf->start); + if (path_is_dotgit(git_vector_get(&wf->entries, wf->index))) + wf->index++; + wf->next = wi->stack; wi->stack = wf; @@ -574,8 +595,7 @@ static int workdir_iterator__advance( next = git_vector_get(&wf->entries, ++wf->index); if (next != NULL) { /* match git's behavior of ignoring anything named ".git" */ - if (STRCMP_CASESELECT(wi->base.ignore_case, next->path, DOT_GIT "/") == 0 || - STRCMP_CASESELECT(wi->base.ignore_case, next->path, DOT_GIT) == 0) + if (path_is_dotgit(next)) continue; /* else found a good entry */ break; @@ -658,8 +678,7 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) wi->entry.path = ps->path; /* skip over .git entries */ - if (STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT "/") == 0 || - STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT) == 0) + if (path_is_dotgit(ps)) return workdir_iterator__advance((git_iterator *)wi, NULL); wi->is_ignored = -1; diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c index 368903200..1d8396099 100644 --- a/tests-clar/diff/iterator.c +++ b/tests-clar/diff/iterator.c @@ -668,3 +668,59 @@ void test_diff_iterator__workdir_1_ranged_empty_2(void) "status", NULL, "aaaa_empty_before", 0, 0, NULL, NULL); } + +void test_diff_iterator__workdir_builtin_ignores(void) +{ + git_repository *repo = cl_git_sandbox_init("attr"); + git_iterator *i; + const git_index_entry *entry; + int idx; + static struct { + const char *path; + bool ignored; + } expected[] = { + { "dir/", true }, + { "file", false }, + { "ign", true }, + { "macro_bad", false }, + { "macro_test", false }, + { "root_test1", false }, + { "root_test2", false }, + { "root_test3", false }, + { "root_test4.txt", false }, + { "sub/", false }, + { "sub/.gitattributes", false }, + { "sub/abc", false }, + { "sub/dir/", true }, + { "sub/file", false }, + { "sub/ign/", true }, + { "sub/sub/", false }, + { "sub/sub/.gitattributes", false }, + { "sub/sub/dir", false }, /* file is not actually a dir */ + { "sub/sub/file", false }, + { NULL, false } + }; + + cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777)); + cl_git_mkfile("attr/sub/.git", "whatever"); + + cl_git_pass( + git_iterator_for_workdir_range(&i, repo, "dir", "sub/sub/file")); + cl_git_pass(git_iterator_current(i, &entry)); + + for (idx = 0; entry != NULL; ++idx) { + int ignored = git_iterator_current_is_ignored(i); + + cl_assert_equal_s(expected[idx].path, entry->path); + cl_assert_(ignored == expected[idx].ignored, expected[idx].path); + + if (!ignored && S_ISDIR(entry->mode)) + cl_git_pass(git_iterator_advance_into_directory(i, &entry)); + else + cl_git_pass(git_iterator_advance(i, &entry)); + } + + cl_assert(expected[idx].path == NULL); + + git_iterator_free(i); +}