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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
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;