Commit 5690f02e87e4fd31dfa9fd7c9c01aba03603cde8

Shawn O. Pearce 2008-12-31T15:35:36

Rewrite git_foreach_dirent into gitfo_dirent Our fileops API is currently private. We aren't planning on supplying a cross-platform file API to applications that link to us. If we did, we'd probably whole-sale publish fileops, not just the dirent code. By moving it to be private we can also change the call signature to permit the buffer to be passed down through the call chain. This is very helpful when we are doing a recursive scan as we can reuse just one buffer in all stack frames, reducing the impact the recursion has on the stack frames in the data cache. Signed-off-by: Shawn O. Pearce <spearce@spearce.org>

diff --git a/src/fileops.c b/src/fileops.c
index 1dd35dc..0867c7b 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -187,31 +187,25 @@ int gitfo_close_cached(gitfo_cache *ioc)
 	return gitfo_close(fd);
 }
 
-/**
- * Walk a directory and run 'fn' for each encountered entry
- * (except '.' and '..').
- */
-int git_foreach_dirent(const char *wd, int (*fn)(void *, const char *), void *arg)
+int gitfo_dirent(
+	char *path,
+	size_t path_sz,
+	int (*fn)(void *, char *),
+	void *arg)
 {
-	char path[GIT_PATH_MAX];
-	size_t wd_len;
+	size_t wd_len = strlen(path);
 	DIR *dir;
 	struct dirent *de;
 
-	if (!wd)
+	if (!wd_len || path_sz < wd_len + 2)
 		return GIT_ERROR;
 
-	wd_len = strlen(wd);
-	if (!wd_len || sizeof(path) < wd_len + 2)
-		return GIT_ERROR;
-
-	strcpy(path, wd);
 	while (path[wd_len - 1] == '/')
 		wd_len--;
 	path[wd_len++] = '/';
 	path[wd_len] = '\0';
 
-	dir = opendir(wd);
+	dir = opendir(path);
 	if (!dir)
 		return git_os_error();
 
@@ -228,7 +222,7 @@ int git_foreach_dirent(const char *wd, int (*fn)(void *, const char *), void *ar
 		}
 
 		de_len = strlen(de->d_name);
-		if (sizeof(path) < wd_len + de_len + 1) {
+		if (path_sz < wd_len + de_len + 1) {
 			closedir(dir);
 			return GIT_ERROR;
 		}
diff --git a/src/fileops.h b/src/fileops.h
index 336ab27..86726c5 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -9,6 +9,7 @@
 /** Force 64 bit off_t size on POSIX. */
 #define _FILE_OFFSET_BITS 64
 
+#include "common.h"
 #include <errno.h>
 #include <unistd.h>
 #include <sys/stat.h>
@@ -18,8 +19,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <dirent.h>
-#include "errors.h"
-#include "git/fileops.h"
 
 #define GITFO_BUF_INIT {NULL, 0}
 
@@ -43,6 +42,23 @@ extern off_t gitfo_size(git_file fd);
 extern int gitfo_read_file(gitfo_buf *obj, const char *path);
 extern void gitfo_free_buf(gitfo_buf *obj);
 
+/**
+ * Walk each directory entry, except '.' and '..', calling fn(state).
+ *
+ * @param pathbuf buffer the function reads the initial directory
+ * 		path from, and updates with each successive entry's name.
+ * @param pathmax maximum allocation of pathbuf.
+ * @param fn function to invoke with each entry.  The first arg is
+ *		the input state and the second arg is pathbuf.  The function
+ *		may modify the pathbuf, but only by appending new text.
+ * @param state to pass to fn as the first arg.
+ */
+extern int gitfo_dirent(
+	char *pathbuf,
+	size_t pathmax,
+	int (*fn)(void *, char *),
+	void *state);
+
 extern gitfo_cache *gitfo_enable_caching(git_file fd, size_t cache_size);
 extern int gitfo_write_cached(gitfo_cache *ioc, void *buf, size_t len);
 extern int gitfo_flush_cached(gitfo_cache *ioc);
diff --git a/src/git/fileops.h b/src/git/fileops.h
deleted file mode 100644
index 657cec1..0000000
--- a/src/git/fileops.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef INCLUDE_git_fileops_h__
-#define INCLUDE_git_fileops_h__
-
-#include "common.h"
-
-/**
- * @file git/fileops.h
- * @brief Git platform agnostic filesystem operations
- * @defgroup git_fileops Git filesystem operations
- * @ingroup Git
- * @{
- */
-GIT_BEGIN_DECL
-
-/**
- * For each directory entry (except "." and ".."), run the function
- * "fn", passing it "arg" as its first argument and the path to
- * the entry as the second argument.
- * @param dir The directory to walk
- * @param fn The callback function to run for each entry in *dir.
- *        "fn" may return >0 to signal "I'm done. Stop parsing and
- *        return successfully" or <0 to signal an error.  All non-zero
- *        return codes cause directory traversal to stop.
- * @param arg The first argument that will be passed to 'fn'
- * @return GIT_SUCCESS if all entries were successfully traversed,
- *         otherwise the result of fn.
- */
-GIT_EXTERN(int) git_foreach_dirent(const char *dir,
-			int (*fn)(void *, const char *), void *arg);
-
-/** @} */
-GIT_END_DECL
-#endif /* INCLUDE_git_fileops_h__ */
diff --git a/tests/t0020-dirent.c b/tests/t0020-dirent.c
new file mode 100644
index 0000000..4c65453
--- /dev/null
+++ b/tests/t0020-dirent.c
@@ -0,0 +1,48 @@
+#include "test_lib.h"
+#include "fileops.h"
+
+static char path_buffer[GIT_PATH_MAX];
+static int state_loc;
+static const char* names[] = {
+	"./a",
+	"./asdf",
+	"./pack-foo.pack",
+	NULL
+};
+
+static int one_entry(void *state, char *path)
+{
+	const char **c;
+
+	must_be_true(state == &state_loc);
+	must_be_true(path == path_buffer);
+	for (c = names; *c; c++) {
+		if (!strcmp(*c, path)) {
+			*c = "";
+			return 0;
+		}
+	}
+	test_die("unexpected path \"%s\"", path);
+}
+
+BEGIN_TEST(setup)
+	const char **c;
+	for (c = names; *c; c++) {
+		git_file fd = gitfo_creat(*c, 0600);
+		must_be_true(fd >= 0);
+		gitfo_close(fd);
+	}
+END_TEST
+
+BEGIN_TEST(direent_walk)
+	const char **c;
+
+	strcpy(path_buffer, ".");
+	must_pass(gitfo_dirent(path_buffer,
+	                       sizeof(path_buffer),
+	                       one_entry,
+	                       &state_loc));
+
+	for (c = names; *c; c++)
+		must_pass(strcmp("", *c));
+END_TEST