Commit 87aaefe20b2e2fad8f0b8b236d1c23ce9ec5340f

Michael Tesch 2016-08-09T12:23:19

write_tree: use shared buffer for writing trees The function to write trees allocates a new buffer for each tree. This causes problems with performance when performing a lot of actions involving writing trees, e.g. when doing many merges. Fix the issue by instead handing in a shared buffer, which is then re-used across the calls without having to re-allocate between calls.

diff --git a/include/git2/tree.h b/include/git2/tree.h
index 2e4735c..a38215f 100644
--- a/include/git2/tree.h
+++ b/include/git2/tree.h
@@ -375,6 +375,19 @@ GIT_EXTERN(void) git_treebuilder_filter(
 GIT_EXTERN(int) git_treebuilder_write(
 	git_oid *id, git_treebuilder *bld);
 
+/**
+ * Write the contents of the tree builder as a tree object
+ * using a shared git_buf.
+ *
+ * @see git_treebuilder_write
+ *
+ * @param id Pointer to store the OID of the newly written tree
+ * @param bld Tree builder to write
+ * @param tree Shared buffer for writing the tree. Will be grown as necessary.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_treebuilder_write_with_buffer(
+	git_oid *oid, git_treebuilder *bld, git_buf *tree);
 
 /** Callback for the tree traversal method */
 typedef int (*git_treewalk_cb)(
diff --git a/src/tree.c b/src/tree.c
index 5db2446..0a155a9 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -515,7 +515,8 @@ static int write_tree(
 	git_repository *repo,
 	git_index *index,
 	const char *dirname,
-	size_t start)
+	size_t start,
+	git_buf *shared_buf)
 {
 	git_treebuilder *bld = NULL;
 	size_t i, entries = git_index_entrycount(index);
@@ -568,7 +569,7 @@ static int write_tree(
 			GITERR_CHECK_ALLOC(subdir);
 
 			/* Write out the subtree */
-			written = write_tree(&sub_oid, repo, index, subdir, i);
+			written = write_tree(&sub_oid, repo, index, subdir, i, shared_buf);
 			if (written < 0) {
 				git__free(subdir);
 				goto on_error;
@@ -600,7 +601,7 @@ static int write_tree(
 		}
 	}
 
-	if (git_treebuilder_write(oid, bld) < 0)
+	if (git_treebuilder_write_with_buffer(oid, bld, shared_buf) < 0)
 		goto on_error;
 
 	git_treebuilder_free(bld);
@@ -616,6 +617,7 @@ int git_tree__write_index(
 {
 	int ret;
 	git_tree *tree;
+	git_buf shared_buf = GIT_BUF_INIT;
 	bool old_ignore_case = false;
 
 	assert(oid && index && repo);
@@ -641,7 +643,8 @@ int git_tree__write_index(
 		git_index__set_ignore_case(index, false);
 	}
 
-	ret = write_tree(oid, repo, index, "", 0);
+	ret = write_tree(oid, repo, index, "", 0, &shared_buf);
+	git_buf_free(&shared_buf);
 
 	if (old_ignore_case)
 		git_index__set_ignore_case(index, true);
@@ -797,19 +800,36 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename)
 
 int git_treebuilder_write(git_oid *oid, git_treebuilder *bld)
 {
+	int error;
+	git_buf buffer = GIT_BUF_INIT;
+
+	error = git_treebuilder_write_with_buffer(oid, bld, &buffer);
+
+	git_buf_free(&buffer);
+	return error;
+}
+
+int git_treebuilder_write_with_buffer(git_oid *oid, git_treebuilder *bld, git_buf *tree)
+{
 	int error = 0;
 	size_t i, entrycount;
-	git_buf tree = GIT_BUF_INIT;
 	git_odb *odb;
 	git_tree_entry *entry;
 	git_vector entries;
 
 	assert(bld);
+	assert(tree);
+
+	git_buf_clear(tree);
 
 	entrycount = git_strmap_num_entries(bld->map);
 	if (git_vector_init(&entries, entrycount, entry_sort_cmp) < 0)
 		return -1;
 
+	if (tree->asize == 0 &&
+		(error = git_buf_grow(tree, entrycount * 72)) < 0)
+		return error;
+
 	git_strmap_foreach_value(bld->map, entry, {
 		if (git_vector_insert(&entries, entry) < 0)
 			return -1;
@@ -817,26 +837,21 @@ int git_treebuilder_write(git_oid *oid, git_treebuilder *bld)
 
 	git_vector_sort(&entries);
 
-	/* Grow the buffer beforehand to an estimated size */
-	error = git_buf_grow(&tree, entrycount * 72);
-
 	for (i = 0; i < entries.length && !error; ++i) {
 		git_tree_entry *entry = git_vector_get(&entries, i);
 
-		git_buf_printf(&tree, "%o ", entry->attr);
-		git_buf_put(&tree, entry->filename, entry->filename_len + 1);
-		git_buf_put(&tree, (char *)entry->oid->id, GIT_OID_RAWSZ);
+		git_buf_printf(tree, "%o ", entry->attr);
+		git_buf_put(tree, entry->filename, entry->filename_len + 1);
+		git_buf_put(tree, (char *)entry->oid->id, GIT_OID_RAWSZ);
 
-		if (git_buf_oom(&tree))
+		if (git_buf_oom(tree))
 			error = -1;
 	}
 
-
 	if (!error &&
 		!(error = git_repository_odb__weakptr(&odb, bld->repo)))
-		error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE);
+		error = git_odb_write(oid, odb, tree->ptr, tree->size, GIT_OBJ_TREE);
 
-	git_buf_free(&tree);
 	git_vector_free(&entries);
 
 	return error;