Commit 4c09e19a3764a1e5f3340dabf8104dfed32e7673

J Wyman 2015-03-30T14:07:44

Improvements to ignore performance on Windows. Minimizing the number directory and file opens, minimizes the amount of IO thus reducing the overall cost of performing ignore operations.

diff --git a/src/attr.c b/src/attr.c
index 3842080..102d024 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -55,7 +55,7 @@ int git_attr_get(
 
 	*value = NULL;
 
-	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
 		return -1;
 
 	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
@@ -114,7 +114,7 @@ int git_attr_get_many_with_session(
 
 	assert(values && repo && names);
 
-	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
 		return -1;
 
 	if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
@@ -193,7 +193,7 @@ int git_attr_foreach(
 
 	assert(repo && callback);
 
-	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
 		return -1;
 
 	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
diff --git a/src/attr_file.c b/src/attr_file.c
index eed3966..ef98aac 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -457,7 +457,7 @@ git_attr_assignment *git_attr_rule__lookup_assignment(
 }
 
 int git_attr_path__init(
-	git_attr_path *info, const char *path, const char *base)
+	git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
 {
 	ssize_t root;
 
@@ -488,7 +488,21 @@ int git_attr_path__init(
 	if (!info->basename || !*info->basename)
 		info->basename = info->path;
 
-	info->is_dir = (int)git_path_isdir(info->full.ptr);
+	switch (dir_flag)
+	{
+	case GIT_DIR_FLAG_FALSE:
+		info->is_dir = 0;
+		break;
+
+	case GIT_DIR_FLAG_TRUE:
+		info->is_dir = 1;
+		break;
+
+	case GIT_DIR_FLAG_UNKNOWN:
+	default:
+		info->is_dir = (int)git_path_isdir(info->full.ptr);
+		break;
+	}
 
 	return 0;
 }
diff --git a/src/attr_file.h b/src/attr_file.h
index aa9a16d..388ecf4 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -202,8 +202,10 @@ extern bool git_attr_rule__match(
 extern git_attr_assignment *git_attr_rule__lookup_assignment(
 	git_attr_rule *rule, const char *name);
 
+typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag;
+
 extern int git_attr_path__init(
-	git_attr_path *info, const char *path, const char *base);
+	git_attr_path *info, const char *path, const char *base, git_dir_flag is_dir);
 
 extern void git_attr_path__free(git_attr_path *info);
 
diff --git a/src/ignore.c b/src/ignore.c
index 3a5efed..7ad8500 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -388,7 +388,7 @@ static bool ignore_lookup_in_rules(
 }
 
 int git_ignore__lookup(
-	int *out, git_ignores *ignores, const char *pathname)
+	int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag)
 {
 	unsigned int i;
 	git_attr_file *file;
@@ -397,7 +397,7 @@ int git_ignore__lookup(
 	*out = GIT_IGNORE_NOTFOUND;
 
 	if (git_attr_path__init(
-		&path, pathname, git_repository_workdir(ignores->repo)) < 0)
+		&path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0)
 		return -1;
 
 	/* first process builtins - success means path was found */
@@ -470,7 +470,7 @@ int git_ignore_path_is_ignored(
 	memset(&path, 0, sizeof(path));
 	memset(&ignores, 0, sizeof(ignores));
 
-	if ((error = git_attr_path__init(&path, pathname, workdir)) < 0 ||
+	if ((error = git_attr_path__init(&path, pathname, workdir, GIT_DIR_FLAG_UNKNOWN)) < 0 ||
 		(error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
 		goto cleanup;
 
diff --git a/src/ignore.h b/src/ignore.h
index 77668c6..d40bd60 100644
--- a/src/ignore.h
+++ b/src/ignore.h
@@ -49,7 +49,7 @@ enum {
 	GIT_IGNORE_TRUE = 1,
 };
 
-extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path);
+extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path, git_dir_flag dir_flag);
 
 /* command line Git sometimes generates an error message if given a
  * pathspec that contains an exact match to an ignored file (provided
diff --git a/src/iterator.c b/src/iterator.c
index 9ddaceb..8bab1aa 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -1344,6 +1344,16 @@ static int is_submodule(workdir_iterator *wi, git_path_with_stat *ie)
 	return is_submodule;
 }
 
+GIT_INLINE(git_dir_flag) git_entry__dir_flag(git_index_entry *entry) {
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+	return (entry && entry->mode)
+		? S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE
+		: GIT_DIR_FLAG_UNKNOWN;
+#else
+	return GIT_DIR_FLAG_UNKNOWN;
+#endif
+}
+
 static int workdir_iterator__enter_dir(fs_iterator *fi)
 {
 	workdir_iterator *wi = (workdir_iterator *)fi;
@@ -1352,9 +1362,10 @@ static int workdir_iterator__enter_dir(fs_iterator *fi)
 	git_path_with_stat *entry;
 	bool found_submodules = false;
 
+	git_dir_flag dir_flag = git_entry__dir_flag(&fi->entry);
+
 	/* check if this directory is ignored */
-	if (git_ignore__lookup(
-			&ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len) < 0) {
+	if (git_ignore__lookup(&ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len, dir_flag) < 0) {
 		giterr_clear();
 		ff->is_ignored = GIT_IGNORE_NOTFOUND;
 	}
@@ -1483,7 +1494,6 @@ int git_iterator_for_workdir_ext(
 	return fs_iterator__initialize(out, &wi->fi, repo_workdir);
 }
 
-
 void git_iterator_free(git_iterator *iter)
 {
 	if (iter == NULL)
@@ -1574,8 +1584,9 @@ int git_iterator_current_parent_tree(
 
 static void workdir_iterator_update_is_ignored(workdir_iterator *wi)
 {
-	if (git_ignore__lookup(
-			&wi->is_ignored, &wi->ignores, wi->fi.entry.path) < 0) {
+	git_dir_flag dir_flag = git_entry__dir_flag(&wi->fi.entry);
+
+	if (git_ignore__lookup(&wi->is_ignored, &wi->ignores, wi->fi.entry.path, dir_flag) < 0) {
 		giterr_clear();
 		wi->is_ignored = GIT_IGNORE_NOTFOUND;
 	}
diff --git a/tests/attr/lookup.c b/tests/attr/lookup.c
index 030ea07..71e87cb 100644
--- a/tests/attr/lookup.c
+++ b/tests/attr/lookup.c
@@ -13,7 +13,7 @@ void test_attr_lookup__simple(void)
 	cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path);
 	cl_assert(file->rules.length == 1);
 
-	cl_git_pass(git_attr_path__init(&path, "test", NULL));
+	cl_git_pass(git_attr_path__init(&path, "test", NULL, GIT_DIR_FLAG_UNKNOWN));
 	cl_assert_equal_s("test", path.path);
 	cl_assert_equal_s("test", path.basename);
 	cl_assert(!path.is_dir);
@@ -36,7 +36,7 @@ static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int
 	int error;
 
 	for (c = cases; c->path != NULL; c++) {
-		cl_git_pass(git_attr_path__init(&path, c->path, NULL));
+		cl_git_pass(git_attr_path__init(&path, c->path, NULL, GIT_DIR_FLAG_UNKNOWN));
 
 		if (force_dir)
 			path.is_dir = 1;
@@ -133,7 +133,7 @@ void test_attr_lookup__match_variants(void)
 	cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path);
 	cl_assert(file->rules.length == 10);
 
-	cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL));
+	cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL, GIT_DIR_FLAG_UNKNOWN));
 	cl_assert_equal_s("pat0", path.basename);
 
 	run_test_cases(file, cases, 0);