odb/source-inmemory: convert to use oidtree

The in-memory source stores its objects in a simple array that we grow as needed. This has a couple of downsides: - The object lookup is O(n). This doesn't matter in practice because we only store a small number of objects. - We don't have an easy way to iterate over all objects in lexicographic order. - We don't have an easy way to compute unique object ID prefixes. Refactor the code to use an oidtree instead. This is the same data structure used by our loose object source, and thus it means we get a bunch of functionality for free. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Apr 10, 2026 at 14:12 UTC c04907694601556de0ce862ad4f80fc55ec38c62
2 files changed +54 -31
odb/source-inmemory.c
+52 -20
@@ -3,20 +3,29 @@
3 #include "odb.h"
4 #include "odb/source-inmemory.h"
5 #include "odb/streaming.h"
6 +#include "oidtree.h"
7 #include "repository.h"
8
8 -static const struct cached_object *find_cached_object(struct odb_source_inmemory *source,
9 - const struct object_id *oid)
9 +struct inmemory_object {
10 + enum object_type type;
11 + const void *buf;
12 + unsigned long size;
13 +};
14 +
15 +static const struct inmemory_object *find_cached_object(struct odb_source_inmemory *source,
16 + const struct object_id *oid)
17 {
11 - static const struct cached_object empty_tree = {
18 + static const struct inmemory_object empty_tree = {
19 .type = OBJ_TREE,
20 .buf = "",
21 };
15 - const struct cached_object_entry *co = source->objects;
22 + const struct inmemory_object *object;
23
17 - for (size_t i = 0; i < source->objects_nr; i++, co++)
18 - if (oideq(&co->oid, oid))
19 - return &co->value;
24 + if (source->objects) {
25 + object = oidtree_get(source->objects, oid);
26 + if (object)
27 + return object;
28 + }
29
30 if (oid->algo && oideq(oid, hash_algos[oid->algo].empty_tree))
31 return &empty_tree;
@@ -30,7 +39,7 @@ static int odb_source_inmemory_read_object_info(struct odb_source *source,
39 enum object_info_flags flags UNUSED)
40 {
41 struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
33 - const struct cached_object *object;
42 + const struct inmemory_object *object;
43
44 object = find_cached_object(inmemory, oid);
45 if (!object)
@@ -88,7 +97,7 @@ static int odb_source_inmemory_read_object_stream(struct odb_read_stream **out,
97 {
98 struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
99 struct odb_read_stream_inmemory *stream;
91 - const struct cached_object *object;
100 + const struct inmemory_object *object;
101
102 object = find_cached_object(inmemory, oid);
103 if (!object)
@@ -113,17 +122,23 @@ static int odb_source_inmemory_write_object(struct odb_source *source,
122 enum odb_write_object_flags flags UNUSED)
123 {
124 struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
116 - struct cached_object_entry *object;
125 + struct inmemory_object *object;
126
127 hash_object_file(source->odb->repo->hash_algo, buf, len, type, oid);
128
120 - ALLOC_GROW(inmemory->objects, inmemory->objects_nr + 1,
121 - inmemory->objects_alloc);
122 - object = &inmemory->objects[inmemory->objects_nr++];
123 - object->value.size = len;
124 - object->value.type = type;
125 - object->value.buf = xmemdupz(buf, len);
126 - oidcpy(&object->oid, oid);
129 + if (!inmemory->objects) {
130 + CALLOC_ARRAY(inmemory->objects, 1);
131 + oidtree_init(inmemory->objects);
132 + } else if (oidtree_contains(inmemory->objects, oid)) {
133 + return 0;
134 + }
135 +
136 + CALLOC_ARRAY(object, 1);
137 + object->size = len;
138 + object->type = type;
139 + object->buf = xmemdupz(buf, len);
140 +
141 + oidtree_insert(inmemory->objects, oid, object);
142
143 return 0;
144 }
@@ -167,12 +182,29 @@ out:
182 return ret;
183 }
184
185 +static int inmemory_object_free(const struct object_id *oid UNUSED,
186 + void *node_data,
187 + void *cb_data UNUSED)
188 +{
189 + struct inmemory_object *object = node_data;
190 + free((void *) object->buf);
191 + free(object);
192 + return 0;
193 +}
194 +
195 static void odb_source_inmemory_free(struct odb_source *source)
196 {
197 struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
173 - for (size_t i = 0; i < inmemory->objects_nr; i++)
174 - free((char *) inmemory->objects[i].value.buf);
175 - free(inmemory->objects);
198 +
199 + if (inmemory->objects) {
200 + struct object_id null_oid = { 0 };
201 +
202 + oidtree_each(inmemory->objects, &null_oid, 0,
203 + inmemory_object_free, NULL);
204 + oidtree_clear(inmemory->objects);
205 + free(inmemory->objects);
206 + }
207 +
208 free(inmemory->base.path);
209 free(inmemory);
210 }
odb/source-inmemory.h
+2 -11
@@ -3,14 +3,7 @@
3
4 #include "odb/source.h"
5
6 -struct cached_object_entry {
7 - struct object_id oid;
8 - struct cached_object {
9 - enum object_type type;
10 - const void *buf;
11 - unsigned long size;
12 - } value;
13 -};
6 +struct oidtree;
7
8 /*
9 * An in-memory source that you can write objects to that shall be made
@@ -20,9 +13,7 @@ struct cached_object_entry {
13 */
14 struct odb_source_inmemory {
15 struct odb_source base;
23 -
24 - struct cached_object_entry *objects;
25 - size_t objects_nr, objects_alloc;
16 + struct oidtree *objects;
17 };
18
19 /* Create a new in-memory object database source. */