Commit e78f5c9f4282319cf8e1afc8631e6259e91653a6

Edward Thomson 2015-01-22T16:11:36

checkout: stream the blob into the filters Use the new streaming filter API during checkout.

diff --git a/src/checkout.c b/src/checkout.c
index 880af3d..623ac92 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -17,6 +17,7 @@
 #include "git2/diff.h"
 #include "git2/submodule.h"
 #include "git2/sys/index.h"
+#include "git2/sys/filter.h"
 
 #include "refs.h"
 #include "repository.h"
@@ -1371,39 +1372,37 @@ static int mkpath2file(
 	return error;
 }
 
-static int buffer_to_file(
-	checkout_data *data,
-	struct stat *st,
-	git_buf *buf,
-	const char *path,
-	mode_t file_mode)
-{
-	int error;
-
-	if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0)
-		return error;
+struct checkout_stream {
+	git_filter_stream base;
+	const char *path;
+	int fd;
+	int open;
+};
 
-	if ((error = git_futils_writebuffer(
-			buf, path, data->opts.file_open_flags, file_mode)) < 0)
-		return error;
+static int checkout_stream_write(
+	git_filter_stream *s, const char *buffer, size_t len)
+{
+	struct checkout_stream *stream = (struct checkout_stream *)s;
+	int ret;
 
-	if (st) {
-		data->perfdata.stat_calls++;
+	if ((ret = p_write(stream->fd, buffer, len)) < 0)
+		giterr_set(GITERR_OS, "Could not write to '%s'", stream->path);
 
-		if ((error = p_stat(path, st)) < 0) {
-			giterr_set(GITERR_OS, "Error statting '%s'", path);
-			return error;
-		}
-	}
+	return ret;
+}
 
-	if (GIT_PERMS_IS_EXEC(file_mode)) {
-		data->perfdata.chmod_calls++;
+static int checkout_stream_close(git_filter_stream *s)
+{
+	struct checkout_stream *stream = (struct checkout_stream *)s;
+	assert(stream && stream->open);
 
-		if ((error = p_chmod(path, file_mode)) < 0)
-			giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
-	}
+	stream->open = 0;
+	return 0;
+}
 
-	return error;
+static void checkout_stream_free(git_filter_stream *s)
+{
+	GIT_UNUSED(s);
 }
 
 static int blob_content_to_file(
@@ -1411,36 +1410,80 @@ static int blob_content_to_file(
 	struct stat *st,
 	git_blob *blob,
 	const char *path,
-	const char * hint_path,
+	const char *hint_path,
 	mode_t entry_filemode)
 {
+	int flags = data->opts.file_open_flags;
 	mode_t file_mode = data->opts.file_mode ?
 		data->opts.file_mode : entry_filemode;
-	git_buf out = GIT_BUF_INIT;
+	struct checkout_stream writer;
+	mode_t mode;
 	git_filter_list *fl = NULL;
+	int fd;
 	int error = 0;
 
 	if (hint_path == NULL)
 		hint_path = path;
 
-	if (!data->opts.disable_filters)
-		error = git_filter_list__load_with_attr_session(
+	if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0)
+		return error;
+
+	if (flags <= 0)
+		flags = O_CREAT | O_TRUNC | O_WRONLY;
+	if (!(mode = file_mode))
+		mode = GIT_FILEMODE_BLOB;
+
+	if ((fd = p_open(path, flags, mode)) < 0) {
+		giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
+		return fd;
+	}
+
+	if (!data->opts.disable_filters &&
+		(error = git_filter_list__load_with_attr_session(
 			&fl, data->repo, &data->attr_session, blob, hint_path,
-			GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT);
+			GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT)))
+		return error;
+
+	/* setup the writer */
+	memset(&writer, 0, sizeof(struct checkout_stream));
+	writer.base.write = checkout_stream_write;
+	writer.base.close = checkout_stream_close;
+	writer.base.free = checkout_stream_free;
+	writer.path = path;
+	writer.fd = fd;
+	writer.open = 1;
+
+	error = git_filter_list_stream_blob(fl, blob, (git_filter_stream *)&writer);
 
-	if (!error)
-		error = git_filter_list_apply_to_blob(&out, fl, blob);
+	assert(writer.open == 0);
 
 	git_filter_list_free(fl);
+	p_close(fd);
 
-	if (!error) {
-		error = buffer_to_file(data, st, &out, path, file_mode);
-		st->st_mode = entry_filemode;
+	if (error < 0)
+		return error;
+
+	if (GIT_PERMS_IS_EXEC(mode)) {
+		data->perfdata.chmod_calls++;
+
+		if ((error = p_chmod(path, mode)) < 0) {
+			giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
+			return error;
+		}
+	}
 
-		git_buf_free(&out);
+	if (st) {
+		data->perfdata.stat_calls++;
+
+		if ((error = p_stat(path, st)) < 0) {
+			giterr_set(GITERR_OS, "Error statting '%s'", path);
+			return error;
+		}
+
+		st->st_mode = entry_filemode;
 	}
 
-	return error;
+	return 0;
 }
 
 static int blob_content_to_link(
diff --git a/src/filter.c b/src/filter.c
index 700afba..d930b23 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -522,6 +522,7 @@ int git_filter_list__load_with_attr_session(
 			fe = git_array_alloc(fl->filters);
 			GITERR_CHECK_ALLOC(fe);
 			fe->filter  = fdef->filter;
+			fe->stream  = NULL;
 			fe->payload = payload;
 		}
 	}
@@ -590,6 +591,7 @@ int git_filter_list_push(
 	fe = git_array_alloc(fl->filters);
 	GITERR_CHECK_ALLOC(fe);
 	fe->filter  = filter;
+	fe->stream = NULL;
 	fe->payload = payload;
 
 	return 0;