Improve iterator ignoring .git file The workdir iterator has always tried to ignore .git files, but it turns out there were some bugs. This makes it more robust at ignoring .git files. This also makes iterators always check ".git" case insensitively regardless of the properties of the system. This will make libgit2 skip ".GIT" and the like. This is different from core git, but on systems with case insensitive but case preserving file systems, allowing ".GIT" to be added is problematic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
diff --git a/src/iterator.c b/src/iterator.c
index ee83a4f..7598535 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -10,6 +10,7 @@
#include "ignore.h"
#include "buffer.h"
#include "git2/submodule.h"
+#include <ctype.h>
#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 3689032..1d83960 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);
+}