Commit 500ec5431564d9167985a02909823e1286136d5b

Edward Thomson 2015-02-03T01:46:01

checkout: hold seen dir paths in a map

diff --git a/src/checkout.c b/src/checkout.c
index 04493a3..3f65a9e 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -29,6 +29,10 @@
 #include "merge_file.h"
 #include "path.h"
 #include "attr.h"
+#include "pool.h"
+#include "strmap.h"
+
+GIT__USE_STRMAP;
 
 /* See docs/checkout-internals.md for more information */
 
@@ -69,7 +73,7 @@ typedef struct {
 	size_t total_steps;
 	size_t completed_steps;
 	git_checkout_perfdata perfdata;
-	git_buf last_mkdir;
+	git_strmap *mkdir_map;
 	git_attr_session attr_session;
 } checkout_data;
 
@@ -1293,25 +1297,6 @@ fail:
 	return error;
 }
 
-static int checkout_mkdir(
-	checkout_data *data,
-	const char *path,
-	const char *base,
-	mode_t mode,
-	unsigned int flags)
-{
-	struct git_futils_mkdir_perfdata mkdir_perfdata = {0};
-
-	int error = git_futils_mkdir_withperf(
-		path, base, mode, flags, &mkdir_perfdata);
-
-	data->perfdata.mkdir_calls += mkdir_perfdata.mkdir_calls;
-	data->perfdata.stat_calls += mkdir_perfdata.stat_calls;
-	data->perfdata.chmod_calls += mkdir_perfdata.chmod_calls;
-
-	return error;
-}
-
 static bool should_remove_existing(checkout_data *data)
 {
 	int ignorecase = 0;
@@ -1327,31 +1312,43 @@ static bool should_remove_existing(checkout_data *data)
 #define MKDIR_REMOVE_EXISTING \
 	MKDIR_NORMAL | GIT_MKDIR_REMOVE_FILES | GIT_MKDIR_REMOVE_SYMLINKS
 
+static int checkout_mkdir(
+	checkout_data *data,
+	const char *path,
+	const char *base,
+	mode_t mode,
+	unsigned int flags)
+{
+	struct git_futils_mkdir_options mkdir_opts = {0};
+	int error;
+
+	mkdir_opts.dir_map = data->mkdir_map;
+	mkdir_opts.pool = &data->pool;
+
+	error = git_futils_mkdir_ext(
+		path, base, mode, flags, &mkdir_opts);
+
+	data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls;
+	data->perfdata.stat_calls += mkdir_opts.perfdata.stat_calls;
+	data->perfdata.chmod_calls += mkdir_opts.perfdata.chmod_calls;
+
+	return error;
+}
+
 static int mkpath2file(
 	checkout_data *data, const char *path, unsigned int mode)
 {
-	git_buf *mkdir_path = &data->tmp;
 	struct stat st;
 	bool remove_existing = should_remove_existing(data);
+	unsigned int flags =
+		(remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL) |
+		GIT_MKDIR_SKIP_LAST;
 	int error;
 
-	if ((error = git_buf_sets(mkdir_path, path)) < 0)
+	if ((error = checkout_mkdir(
+			data, path, data->opts.target_directory, mode, flags)) < 0)
 		return error;
 
-	git_buf_rtruncate_at_char(mkdir_path, '/');
-
-	if (!data->last_mkdir.size ||
-		data->last_mkdir.size != mkdir_path->size ||
-		memcmp(mkdir_path->ptr, data->last_mkdir.ptr, mkdir_path->size) != 0) {
-
-		if ((error = checkout_mkdir(
-				data, mkdir_path->ptr, data->opts.target_directory, mode,
-				remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0)
-			return error;
-
-		git_buf_swap(&data->last_mkdir, mkdir_path);
-	}
-
 	if (remove_existing) {
 		data->perfdata.stat_calls++;
 
@@ -2215,13 +2212,16 @@ static void checkout_data_clear(checkout_data *data)
 	git__free(data->pfx);
 	data->pfx = NULL;
 
-	git_buf_free(&data->last_mkdir);
+	git_strmap_free(data->mkdir_map);
+
 	git_buf_free(&data->path);
 	git_buf_free(&data->tmp);
 
 	git_index_free(data->index);
 	data->index = NULL;
 
+	git_strmap_free(data->mkdir_map);
+
 	git_attr_session__free(&data->attr_session);
 }
 
@@ -2360,7 +2360,8 @@ static int checkout_data_init(
 		(error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 ||
 		(error = git_pool_init(&data->pool, 1, 0)) < 0 ||
 		(error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 ||
-		(error = git_path_to_dir(&data->path)) < 0)
+		(error = git_path_to_dir(&data->path)) < 0 ||
+		(error = git_strmap_alloc(&data->mkdir_map)) < 0)
 		goto cleanup;
 
 	data->workdir_len = git_buf_len(&data->path);
diff --git a/src/fileops.c b/src/fileops.c
index 2ee9535..ff2acfc 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -7,11 +7,14 @@
 #include "common.h"
 #include "fileops.h"
 #include "global.h"
+#include "strmap.h"
 #include <ctype.h>
 #if GIT_WIN32
 #include "win32/findfile.h"
 #endif
 
+GIT__USE_STRMAP;
+
 int git_futils_mkpath2file(const char *file_path, const mode_t mode)
 {
 	return git_futils_mkdir(
@@ -321,12 +324,12 @@ GIT_INLINE(int) validate_existing(
 	return 0;
 }
 
-int git_futils_mkdir_withperf(
+int git_futils_mkdir_ext(
 	const char *path,
 	const char *base,
 	mode_t mode,
 	uint32_t flags,
-	struct git_futils_mkdir_perfdata *perfdata)
+	struct git_futils_mkdir_options *opts)
 {
 	int error = -1;
 	git_buf make_path = GIT_BUF_INIT;
@@ -394,11 +397,14 @@ int git_futils_mkdir_withperf(
 		*tail = '\0';
 		st.st_mode = 0;
 
+		if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
+			continue;
+
 		/* See what's going on with this path component */
-		perfdata->stat_calls++;
+		opts->perfdata.stat_calls++;
 
 		if (p_lstat(make_path.ptr, &st) < 0) {
-			perfdata->mkdir_calls++;
+			opts->perfdata.mkdir_calls++;
 
 			if (errno != ENOENT || p_mkdir(make_path.ptr, mode) < 0) {
 				giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
@@ -416,7 +422,7 @@ int git_futils_mkdir_withperf(
 			}
 
 			if ((error = validate_existing(
-				make_path.ptr, &st, mode, flags, perfdata)) < 0)
+				make_path.ptr, &st, mode, flags, &opts->perfdata)) < 0)
 					goto done;
 		}
 
@@ -425,7 +431,7 @@ int git_futils_mkdir_withperf(
 			 (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) &&
 			st.st_mode != mode) {
 
-			perfdata->chmod_calls++;
+			opts->perfdata.chmod_calls++;
 
 			if ((error = p_chmod(make_path.ptr, mode)) < 0 &&
 				lastch == '\0') {
@@ -434,6 +440,17 @@ int git_futils_mkdir_withperf(
 				goto done;
 			}
 		}
+
+		if (opts->dir_map && opts->pool) {
+			char *cache_path = git_pool_malloc(opts->pool, make_path.size + 1);
+			GITERR_CHECK_ALLOC(cache_path);
+
+			memcpy(cache_path, make_path.ptr, make_path.size + 1);
+
+			git_strmap_insert(opts->dir_map, cache_path, cache_path, error);
+			if (error < 0)
+				goto done;
+		}
 	}
 
 	error = 0;
@@ -441,7 +458,7 @@ int git_futils_mkdir_withperf(
 	/* check that full path really is a directory if requested & needed */
 	if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
 		lastch != '\0') {
-		perfdata->stat_calls++;
+		opts->perfdata.stat_calls++;
 
 		if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
 			giterr_set(GITERR_OS, "Path is not a directory '%s'",
@@ -461,8 +478,8 @@ int git_futils_mkdir(
 	mode_t mode,
 	uint32_t flags)
 {
-	struct git_futils_mkdir_perfdata perfdata = {0};
-	return git_futils_mkdir_withperf(path, base, mode, flags, &perfdata);
+	struct git_futils_mkdir_options options = {0};
+	return git_futils_mkdir_ext(path, base, mode, flags, &options);
 }
 
 int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
diff --git a/src/fileops.h b/src/fileops.h
index 4aaf178..0f6466c 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -11,6 +11,8 @@
 #include "map.h"
 #include "posix.h"
 #include "path.h"
+#include "pool.h"
+#include "strmap.h"
 
 /**
  * Filebuffer methods
@@ -95,6 +97,13 @@ struct git_futils_mkdir_perfdata
 	size_t chmod_calls;
 };
 
+struct git_futils_mkdir_options
+{
+	git_strmap *dir_map;
+	git_pool *pool;
+	struct git_futils_mkdir_perfdata perfdata;
+};
+
 /**
  * Create a directory or entire path.
  *
@@ -106,10 +115,10 @@ struct git_futils_mkdir_perfdata
  * @param base Root for relative path.  These directories will never be made.
  * @param mode The mode to use for created directories.
  * @param flags Combination of the mkdir flags above.
- * @param perfdata Performance data, use `git_futils_mkdir` if you don't want this data.
+ * @param opts Extended options, use `git_futils_mkdir` if you are not interested.
  * @return 0 on success, else error code
  */
-extern int git_futils_mkdir_withperf(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_perfdata *perfdata);
+extern int git_futils_mkdir_ext(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts);
 
 /**
  * Create a directory or entire path.  Similar to `git_futils_mkdir_withperf`