Commit edbfc52cdd8657371c53070c5e09b58e004bb67a

Edward Thomson 2015-04-29T11:05:27

git_path: introduce 'git_path_diriter' Introduce a new `git_path_diriter` that can iterate directories efficiently for each platform.

diff --git a/src/path.c b/src/path.c
index 6a636bb..2390b4f 100644
--- a/src/path.c
+++ b/src/path.c
@@ -260,6 +260,20 @@ int git_path_root(const char *path)
 	return -1;	/* Not a real error - signals that path is not rooted */
 }
 
+void git_path_trim_slashes(git_buf *path)
+{
+	int ceiling = git_path_root(path->ptr) + 1;
+	assert(ceiling >= 0);
+
+	while (path->size > (size_t)ceiling) {
+		if (path->ptr[path->size-1] != '/')
+			break;
+
+		path->ptr[path->size-1] = '\0';
+		path->size--;
+	}
+}
+
 int git_path_join_unrooted(
 	git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
 {
@@ -1181,6 +1195,122 @@ int git_path_with_stat_cmp_icase(const void *a, const void *b)
 	return strcasecmp(psa->path, psb->path);
 }
 
+int git_path_diriter_init(
+	git_path_diriter *diriter,
+	const char *path,
+	unsigned int flags)
+{
+	assert(diriter && path);
+
+	memset(diriter, 0, sizeof(git_path_diriter));
+
+	if (git_buf_puts(&diriter->path, path) < 0)
+		return -1;
+
+	git_path_mkposix(diriter->path.ptr);
+	git_path_trim_slashes(&diriter->path);
+
+	if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) {
+		git_buf_free(&diriter->path);
+
+		giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
+		return -1;
+	}
+
+#ifdef GIT_USE_ICONV
+	if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
+		(void)git_path_iconv_init_precompose(&ic);
+#endif
+
+	diriter->parent_len = diriter->path.size;
+	diriter->flags = flags;
+
+	return 0;
+}
+
+int git_path_diriter_next(
+	const char **out,
+	size_t *out_len,
+	git_path_diriter *diriter)
+{
+	struct dirent *de;
+	const char *filename;
+	size_t filename_len;
+	bool skip_dot = !(diriter->flags & GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT);
+	int error = 0;
+
+	assert(out && out_len && diriter);
+
+	*out = NULL;
+	*out_len = 0;
+
+	errno = 0;
+
+	do {
+		if ((de = readdir(diriter->dir)) == NULL) {
+			if (!errno)
+				return GIT_ITEROVER;
+
+			giterr_set(GITERR_OS,
+				"Could not read directory '%s'", diriter->path);
+			return -1;
+		}
+	} while (skip_dot && git_path_is_dot_or_dotdot(de->d_name));
+
+	filename = de->d_name;
+	filename_len = strlen(filename);
+
+#ifdef GIT_USE_ICONV
+	if ((error = git_path_iconv(&diriter->ic, &filename, &filename_len)) < 0)
+		return error;
+#endif
+
+	git_buf_truncate(&diriter->path, diriter->parent_len);
+	git_buf_putc(&diriter->path, '/');
+	git_buf_put(&diriter->path, filename, filename_len);
+
+	if (git_buf_oom(&diriter->path))
+		return -1;
+
+	*out = &diriter->path.ptr[diriter->parent_len+1];
+	*out_len = filename_len;
+
+	return error;
+}
+
+int git_path_diriter_fullpath(
+	const char **out,
+	size_t *out_len,
+	git_path_diriter *diriter)
+{
+	assert(out && out_len && diriter);
+
+	*out = diriter->path.ptr;
+	*out_len = diriter->path.size;
+	return 0;
+}
+
+int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter)
+{
+	assert(out && diriter);
+
+	return git_path_lstat(diriter->path.ptr, out);
+}
+
+void git_path_diriter_free(git_path_diriter *diriter)
+{
+	if (diriter == NULL)
+		return;
+
+	closedir(diriter->dir);
+
+#ifdef GIT_USE_ICONV
+	git_path_iconv_clear(&diriter->ic);
+#endif
+
+	git_buf_free(&diriter->path);
+}
+
 int git_path_dirload_with_stat(
 	const char *path,
 	size_t prefix_len,
diff --git a/src/path.h b/src/path.h
index 440b542..3a25d4a 100644
--- a/src/path.h
+++ b/src/path.h
@@ -273,6 +273,7 @@ extern int git_path_apply_relative(git_buf *target, const char *relpath);
 enum {
 	GIT_PATH_DIR_IGNORE_CASE = (1u << 0),
 	GIT_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1),
+	GIT_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2),
 };
 
 /**
@@ -326,6 +327,37 @@ extern int git_path_walk_up(
 	int (*callback)(void *payload, const char *path),
 	void *payload);
 
+typedef struct git_path_diriter git_path_diriter;
+
+struct git_path_diriter
+{
+	git_buf path;
+	size_t parent_len;
+
+	unsigned int flags;
+
+	DIR *dir;
+};
+
+extern int git_path_diriter_init(
+	git_path_diriter *diriter,
+	const char *path,
+	unsigned int flags);
+
+extern int git_path_diriter_next(
+	const char **out,
+	size_t *out_len,
+	git_path_diriter *diriter);
+
+extern int git_path_diriter_fullpath(
+	const char **out,
+	size_t *out_len,
+	git_path_diriter *diriter);
+
+extern int git_path_diriter_stat(struct stat *out, git_path_diriter *diriter);
+
+extern void git_path_diriter_free(git_path_diriter *diriter);
+
 /**
  * Load all directory entries (except '.' and '..') into a vector.
  *
diff --git a/src/posix.h b/src/posix.h
index 22f472c..8785a4c 100644
--- a/src/posix.h
+++ b/src/posix.h
@@ -122,7 +122,6 @@ extern int git__page_size(size_t *page_size);
 #include "strnlen.h"
 
 #ifdef NO_READDIR_R
-#	include <dirent.h>
 GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
 {
 	GIT_UNUSED(entry);
diff --git a/src/unix/posix.h b/src/unix/posix.h
index e4f3ac6..8b4f427 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -8,6 +8,7 @@
 #define INCLUDE_posix__unix_h__
 
 #include <stdio.h>
+#include <dirent.h>
 #include <sys/param.h>
 
 typedef int GIT_SOCKET;