summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2026-05-21 22:34:55 +0900
committerJunio C Hamano <gitster@pobox.com>2026-05-21 22:34:55 +0900
commit9ebc19b76065f71a2f09d85b59e53615c5dc3633 (patch)
treeaafc4fc4186aeb166d57dab5941442549614dbe1
parentaec3f587505a472db67e9462d0702e7d463a449d (diff)
parentd2902a45498793f8dc69abc6448f517b69437eec (diff)
Merge branch 'ps/odb-in-memory' into ps/odb-source-loose
* ps/odb-in-memory: (24 commits) t/unit-tests: add tests for the in-memory object source odb: generic in-memory source odb/source-inmemory: stub out remaining functions odb/source-inmemory: implement `freshen_object()` callback odb/source-inmemory: implement `count_objects()` callback odb/source-inmemory: implement `find_abbrev_len()` callback odb/source-inmemory: implement `for_each_object()` callback odb/source-inmemory: convert to use oidtree oidtree: add ability to store data cbtree: allow using arbitrary wrapper structures for nodes odb/source-inmemory: implement `write_object_stream()` callback odb/source-inmemory: implement `write_object()` callback odb/source-inmemory: implement `read_object_stream()` callback odb/source-inmemory: implement `read_object_info()` callback odb: fix unnecessary call to `find_cached_object()` odb/source-inmemory: implement `free()` callback odb: introduce "in-memory" source odb/transaction: make `write_object_stream()` pluggable object-file: generalize packfile writes to use odb_write_stream object-file: avoid fd seekback by checking object size upfront ...
-rw-r--r--Makefile3
-rw-r--r--builtin/add.c1
-rw-r--r--builtin/unpack-objects.c21
-rw-r--r--builtin/update-index.c1
-rw-r--r--cache-tree.c1
-rw-r--r--cbtree.c25
-rw-r--r--cbtree.h17
-rw-r--r--loose.c2
-rw-r--r--meson.build2
-rw-r--r--object-file.c258
-rw-r--r--odb.c107
-rw-r--r--odb.h41
-rw-r--r--odb/source-inmemory.c382
-rw-r--r--odb/source-inmemory.h33
-rw-r--r--odb/source.h3
-rw-r--r--odb/streaming.c51
-rw-r--r--odb/streaming.h30
-rw-r--r--odb/transaction.c35
-rw-r--r--odb/transaction.h57
-rw-r--r--oidtree.c66
-rw-r--r--oidtree.h12
-rw-r--r--read-cache.c1
-rw-r--r--t/meson.build1
-rw-r--r--t/unit-tests/u-odb-inmemory.c313
-rw-r--r--t/unit-tests/u-oidtree.c26
25 files changed, 1158 insertions, 331 deletions
diff --git a/Makefile b/Makefile
index fb50c57e4f..a43b8ee067 100644
--- a/Makefile
+++ b/Makefile
@@ -1216,7 +1216,9 @@ LIB_OBJS += object.o
LIB_OBJS += odb.o
LIB_OBJS += odb/source.o
LIB_OBJS += odb/source-files.o
+LIB_OBJS += odb/source-inmemory.o
LIB_OBJS += odb/streaming.o
+LIB_OBJS += odb/transaction.o
LIB_OBJS += oid-array.o
LIB_OBJS += oidmap.o
LIB_OBJS += oidset.o
@@ -1525,6 +1527,7 @@ CLAR_TEST_SUITES += u-hash
CLAR_TEST_SUITES += u-hashmap
CLAR_TEST_SUITES += u-list-objects-filter-options
CLAR_TEST_SUITES += u-mem-pool
+CLAR_TEST_SUITES += u-odb-inmemory
CLAR_TEST_SUITES += u-oid-array
CLAR_TEST_SUITES += u-oidmap
CLAR_TEST_SUITES += u-oidtree
diff --git a/builtin/add.c b/builtin/add.c
index 7737ab878b..c859f66519 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -16,6 +16,7 @@
#include "run-command.h"
#include "object-file.h"
#include "odb.h"
+#include "odb/transaction.h"
#include "parse-options.h"
#include "path.h"
#include "preload-index.h"
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 12452aff60..59e9b8711e 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -9,6 +9,8 @@
#include "hex.h"
#include "object-file.h"
#include "odb.h"
+#include "odb/streaming.h"
+#include "odb/transaction.h"
#include "object.h"
#include "delta.h"
#include "pack.h"
@@ -358,24 +360,21 @@ static void unpack_non_delta_entry(enum object_type type, unsigned long size,
struct input_zstream_data {
git_zstream *zstream;
- unsigned char buf[8192];
int status;
};
-static const void *feed_input_zstream(struct odb_write_stream *in_stream,
- unsigned long *readlen)
+static ssize_t feed_input_zstream(struct odb_write_stream *in_stream,
+ unsigned char *buf, size_t buf_len)
{
struct input_zstream_data *data = in_stream->data;
git_zstream *zstream = data->zstream;
void *in = fill(1);
- if (in_stream->is_finished) {
- *readlen = 0;
- return NULL;
- }
+ if (in_stream->is_finished)
+ return 0;
- zstream->next_out = data->buf;
- zstream->avail_out = sizeof(data->buf);
+ zstream->next_out = buf;
+ zstream->avail_out = buf_len;
zstream->next_in = in;
zstream->avail_in = len;
@@ -383,9 +382,7 @@ static const void *feed_input_zstream(struct odb_write_stream *in_stream,
in_stream->is_finished = data->status != Z_OK;
use(len - zstream->avail_in);
- *readlen = sizeof(data->buf) - zstream->avail_out;
-
- return data->buf;
+ return buf_len - zstream->avail_out;
}
static void stream_blob(unsigned long size, unsigned nr)
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 8a5907767b..bcc43852ef 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -19,6 +19,7 @@
#include "tree-walk.h"
#include "object-file.h"
#include "odb.h"
+#include "odb/transaction.h"
#include "refs.h"
#include "resolve-undo.h"
#include "parse-options.h"
diff --git a/cache-tree.c b/cache-tree.c
index 2b636eb3f8..184f7e2635 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -10,6 +10,7 @@
#include "cache-tree.h"
#include "object-file.h"
#include "odb.h"
+#include "odb/transaction.h"
#include "read-cache-ll.h"
#include "replace-object.h"
#include "repository.h"
diff --git a/cbtree.c b/cbtree.c
index 4ab794bddc..8f5edbb80a 100644
--- a/cbtree.c
+++ b/cbtree.c
@@ -7,6 +7,11 @@
#include "git-compat-util.h"
#include "cbtree.h"
+static inline uint8_t *cb_node_key(struct cb_tree *t, struct cb_node *node)
+{
+ return (uint8_t *) node + t->key_offset;
+}
+
static struct cb_node *cb_node_of(const void *p)
{
return (struct cb_node *)((uintptr_t)p - 1);
@@ -33,6 +38,7 @@ struct cb_node *cb_insert(struct cb_tree *t, struct cb_node *node, size_t klen)
uint8_t c;
int newdirection;
struct cb_node **wherep, *p;
+ uint8_t *node_key, *p_key;
assert(!((uintptr_t)node & 1)); /* allocations must be aligned */
@@ -41,23 +47,26 @@ struct cb_node *cb_insert(struct cb_tree *t, struct cb_node *node, size_t klen)
return NULL; /* success */
}
+ node_key = cb_node_key(t, node);
+
/* see if a node already exists */
- p = cb_internal_best_match(t->root, node->k, klen);
+ p = cb_internal_best_match(t->root, node_key, klen);
+ p_key = cb_node_key(t, p);
/* find first differing byte */
for (newbyte = 0; newbyte < klen; newbyte++) {
- if (p->k[newbyte] != node->k[newbyte])
+ if (p_key[newbyte] != node_key[newbyte])
goto different_byte_found;
}
return p; /* element exists, let user deal with it */
different_byte_found:
- newotherbits = p->k[newbyte] ^ node->k[newbyte];
+ newotherbits = p_key[newbyte] ^ node_key[newbyte];
newotherbits |= newotherbits >> 1;
newotherbits |= newotherbits >> 2;
newotherbits |= newotherbits >> 4;
newotherbits = (newotherbits & ~(newotherbits >> 1)) ^ 255;
- c = p->k[newbyte];
+ c = p_key[newbyte];
newdirection = (1 + (newotherbits | c)) >> 8;
node->byte = newbyte;
@@ -78,7 +87,7 @@ different_byte_found:
break;
if (q->byte == newbyte && q->otherbits > newotherbits)
break;
- c = q->byte < klen ? node->k[q->byte] : 0;
+ c = q->byte < klen ? node_key[q->byte] : 0;
direction = (1 + (q->otherbits | c)) >> 8;
wherep = q->child + direction;
}
@@ -93,7 +102,7 @@ struct cb_node *cb_lookup(struct cb_tree *t, const uint8_t *k, size_t klen)
{
struct cb_node *p = cb_internal_best_match(t->root, k, klen);
- return p && !memcmp(p->k, k, klen) ? p : NULL;
+ return p && !memcmp(cb_node_key(t, p), k, klen) ? p : NULL;
}
static int cb_descend(struct cb_node *p, cb_iter fn, void *arg)
@@ -115,6 +124,7 @@ int cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen,
struct cb_node *p = t->root;
struct cb_node *top = p;
size_t i = 0;
+ uint8_t *p_key;
if (!p)
return 0; /* empty tree */
@@ -130,8 +140,9 @@ int cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen,
top = p;
}
+ p_key = cb_node_key(t, p);
for (i = 0; i < klen; i++) {
- if (p->k[i] != kpfx[i])
+ if (p_key[i] != kpfx[i])
return 0; /* "best" match failed */
}
diff --git a/cbtree.h b/cbtree.h
index c374b1b3db..4647d4a32f 100644
--- a/cbtree.h
+++ b/cbtree.h
@@ -6,9 +6,9 @@
*
* This is adapted to store arbitrary data (not just NUL-terminated C strings
* and allocates no memory internally. The user needs to allocate
- * "struct cb_node" and fill cb_node.k[] with arbitrary match data
- * for memcmp.
- * If "klen" is variable, then it should be embedded into "c_node.k[]"
+ * "struct cb_node" and provide `key_offset` to indicate where the key can be
+ * found relative to the `struct cb_node` for memcmp.
+ * If "klen" is variable, then it should be embedded into the key.
* Recursion is bound by the maximum value of "klen" used.
*/
#ifndef CBTREE_H
@@ -23,18 +23,19 @@ struct cb_node {
*/
uint32_t byte;
uint8_t otherbits;
- uint8_t k[FLEX_ARRAY]; /* arbitrary data, unaligned */
};
struct cb_tree {
struct cb_node *root;
+ ptrdiff_t key_offset;
};
-#define CBTREE_INIT { 0 }
-
-static inline void cb_init(struct cb_tree *t)
+static inline void cb_init(struct cb_tree *t,
+ ptrdiff_t key_offset)
{
- struct cb_tree blank = CBTREE_INIT;
+ struct cb_tree blank = {
+ .key_offset = key_offset,
+ };
memcpy(t, &blank, sizeof(*t));
}
diff --git a/loose.c b/loose.c
index 07333be696..f7a3dd1a72 100644
--- a/loose.c
+++ b/loose.c
@@ -57,7 +57,7 @@ static int insert_loose_map(struct odb_source *source,
inserted |= insert_oid_pair(map->to_compat, oid, compat_oid);
inserted |= insert_oid_pair(map->to_storage, compat_oid, oid);
if (inserted)
- oidtree_insert(files->loose->cache, compat_oid);
+ oidtree_insert(files->loose->cache, compat_oid, NULL);
return inserted;
}
diff --git a/meson.build b/meson.build
index 052c81f288..664d831329 100644
--- a/meson.build
+++ b/meson.build
@@ -404,7 +404,9 @@ libgit_sources = [
'odb.c',
'odb/source.c',
'odb/source-files.c',
+ 'odb/source-inmemory.c',
'odb/streaming.c',
+ 'odb/transaction.c',
'oid-array.c',
'oidmap.c',
'oidset.c',
diff --git a/object-file.c b/object-file.c
index 0be2981c7a..90f995d000 100644
--- a/object-file.c
+++ b/object-file.c
@@ -21,6 +21,7 @@
#include "object-file.h"
#include "odb.h"
#include "odb/streaming.h"
+#include "odb/transaction.h"
#include "oidtree.h"
#include "pack.h"
#include "packfile.h"
@@ -1068,6 +1069,7 @@ int odb_source_loose_write_stream(struct odb_source *source,
struct git_hash_ctx c, compat_c;
struct strbuf tmp_file = STRBUF_INIT;
struct strbuf filename = STRBUF_INIT;
+ unsigned char buf[8192];
int dirlen;
char hdr[MAX_HEADER_LEN];
int hdrlen;
@@ -1100,9 +1102,17 @@ int odb_source_loose_write_stream(struct odb_source *source,
unsigned char *in0 = stream.next_in;
if (!stream.avail_in && !in_stream->is_finished) {
- const void *in = in_stream->read(in_stream, &stream.avail_in);
- stream.next_in = (void *)in;
- in0 = (unsigned char *)in;
+ ssize_t read_len = odb_write_stream_read(in_stream, buf,
+ sizeof(buf));
+ if (read_len < 0) {
+ close(fd);
+ err = -1;
+ goto cleanup;
+ }
+
+ stream.avail_in = read_len;
+ stream.next_in = buf;
+ in0 = buf;
/* All data has been read. */
if (in_stream->is_finished)
flush = 1;
@@ -1392,11 +1402,10 @@ static int already_written(struct odb_transaction_files *transaction,
}
/* Lazily create backing packfile for the state */
-static void prepare_packfile_transaction(struct odb_transaction_files *transaction,
- unsigned flags)
+static void prepare_packfile_transaction(struct odb_transaction_files *transaction)
{
struct transaction_packfile *state = &transaction->packfile;
- if (!(flags & INDEX_WRITE_OBJECT) || state->f)
+ if (state->f)
return;
state->f = create_tmp_packfile(transaction->base.source->odb->repo,
@@ -1409,33 +1418,53 @@ static void prepare_packfile_transaction(struct odb_transaction_files *transacti
die_errno("unable to write pack header");
}
+static int hash_blob_stream(struct odb_write_stream *stream,
+ const struct git_hash_algo *hash_algo,
+ struct object_id *result_oid, size_t size)
+{
+ unsigned char buf[16384];
+ struct git_hash_ctx ctx;
+ unsigned header_len;
+ size_t bytes_hashed = 0;
+
+ header_len = format_object_header((char *)buf, sizeof(buf),
+ OBJ_BLOB, size);
+ hash_algo->init_fn(&ctx);
+ git_hash_update(&ctx, buf, header_len);
+
+ while (!stream->is_finished) {
+ ssize_t read_result = odb_write_stream_read(stream, buf,
+ sizeof(buf));
+
+ if (read_result < 0)
+ return -1;
+
+ git_hash_update(&ctx, buf, read_result);
+ bytes_hashed += read_result;
+ }
+
+ if (bytes_hashed != size)
+ return -1;
+
+ git_hash_final_oid(result_oid, &ctx);
+
+ return 0;
+}
+
/*
- * Read the contents from fd for size bytes, streaming it to the
- * packfile in state while updating the hash in ctx. Signal a failure
- * by returning a negative value when the resulting pack would exceed
- * the pack size limit and this is not the first object in the pack,
- * so that the caller can discard what we wrote from the current pack
- * by truncating it and opening a new one. The caller will then call
- * us again after rewinding the input fd.
- *
- * The already_hashed_to pointer is kept untouched by the caller to
- * make sure we do not hash the same byte when we are called
- * again. This way, the caller does not have to checkpoint its hash
- * status before calling us just in case we ask it to call us again
- * with a new pack.
+ * Read the contents from the stream provided, streaming it to the
+ * packfile in state while updating the hash in ctx.
*/
-static int stream_blob_to_pack(struct transaction_packfile *state,
- struct git_hash_ctx *ctx, off_t *already_hashed_to,
- int fd, size_t size, const char *path,
- unsigned flags)
+static void stream_blob_to_pack(struct transaction_packfile *state,
+ struct git_hash_ctx *ctx, size_t size,
+ struct odb_write_stream *stream)
{
git_zstream s;
unsigned char ibuf[16384];
unsigned char obuf[16384];
unsigned hdrlen;
int status = Z_OK;
- int write_object = (flags & INDEX_WRITE_OBJECT);
- off_t offset = 0;
+ size_t bytes_read = 0;
git_deflate_init(&s, pack_compression_level);
@@ -1444,45 +1473,27 @@ static int stream_blob_to_pack(struct transaction_packfile *state,
s.avail_out = sizeof(obuf) - hdrlen;
while (status != Z_STREAM_END) {
- if (size && !s.avail_in) {
- size_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
- ssize_t read_result = read_in_full(fd, ibuf, rsize);
- if (read_result < 0)
- die_errno("failed to read from '%s'", path);
- if ((size_t)read_result != rsize)
- die("failed to read %u bytes from '%s'",
- (unsigned)rsize, path);
- offset += rsize;
- if (*already_hashed_to < offset) {
- size_t hsize = offset - *already_hashed_to;
- if (rsize < hsize)
- hsize = rsize;
- if (hsize)
- git_hash_update(ctx, ibuf, hsize);
- *already_hashed_to = offset;
- }
+ if (!stream->is_finished && !s.avail_in) {
+ ssize_t rsize = odb_write_stream_read(stream, ibuf,
+ sizeof(ibuf));
+
+ if (rsize < 0)
+ die("failed to read blob data");
+
+ git_hash_update(ctx, ibuf, rsize);
+
s.next_in = ibuf;
s.avail_in = rsize;
- size -= rsize;
+ bytes_read += rsize;
}
- status = git_deflate(&s, size ? 0 : Z_FINISH);
+ status = git_deflate(&s, stream->is_finished ? Z_FINISH : 0);
if (!s.avail_out || status == Z_STREAM_END) {
- if (write_object) {
- size_t written = s.next_out - obuf;
-
- /* would we bust the size limit? */
- if (state->nr_written &&
- pack_size_limit_cfg &&
- pack_size_limit_cfg < state->offset + written) {
- git_deflate_abort(&s);
- return -1;
- }
+ size_t written = s.next_out - obuf;
- hashwrite(state->f, obuf, written);
- state->offset += written;
- }
+ hashwrite(state->f, obuf, written);
+ state->offset += written;
s.next_out = obuf;
s.avail_out = sizeof(obuf);
}
@@ -1496,8 +1507,12 @@ static int stream_blob_to_pack(struct transaction_packfile *state,
die("unexpected deflate failure: %d", status);
}
}
+
+ if (bytes_read != size)
+ die("read %" PRIuMAX " bytes of blob data, but expected %" PRIuMAX " bytes",
+ (uintmax_t)bytes_read, (uintmax_t)size);
+
git_deflate_end(&s);
- return 0;
}
static void flush_packfile_transaction(struct odb_transaction_files *transaction)
@@ -1568,64 +1583,49 @@ clear_exit:
* binary blobs, they generally do not want to get any conversion, and
* callers should avoid this code path when filters are requested.
*/
-static int index_blob_packfile_transaction(struct odb_transaction_files *transaction,
- struct object_id *result_oid, int fd,
- size_t size, const char *path,
- unsigned flags)
+static int odb_transaction_files_write_object_stream(struct odb_transaction *base,
+ struct odb_write_stream *stream,
+ size_t size,
+ struct object_id *result_oid)
{
+ struct odb_transaction_files *transaction = container_of(base,
+ struct odb_transaction_files,
+ base);
struct transaction_packfile *state = &transaction->packfile;
- off_t seekback, already_hashed_to;
struct git_hash_ctx ctx;
unsigned char obuf[16384];
unsigned header_len;
struct hashfile_checkpoint checkpoint;
- struct pack_idx_entry *idx = NULL;
-
- seekback = lseek(fd, 0, SEEK_CUR);
- if (seekback == (off_t)-1)
- return error("cannot find the current offset");
+ struct pack_idx_entry *idx;
header_len = format_object_header((char *)obuf, sizeof(obuf),
OBJ_BLOB, size);
transaction->base.source->odb->repo->hash_algo->init_fn(&ctx);
git_hash_update(&ctx, obuf, header_len);
- /* Note: idx is non-NULL when we are writing */
- if ((flags & INDEX_WRITE_OBJECT) != 0) {
- CALLOC_ARRAY(idx, 1);
-
- prepare_packfile_transaction(transaction, flags);
- hashfile_checkpoint_init(state->f, &checkpoint);
- }
+ /*
+ * If writing another object to the packfile could result in it
+ * exceeding the configured size limit, flush the current packfile
+ * transaction.
+ *
+ * Note that this uses the inflated object size as an approximation.
+ * Blob objects written in this manner are not delta-compressed, so
+ * the difference between the inflated and on-disk size is limited
+ * to zlib compression and is sufficient for this check.
+ */
+ if (state->nr_written && pack_size_limit_cfg &&
+ pack_size_limit_cfg < state->offset + size)
+ flush_packfile_transaction(transaction);
- already_hashed_to = 0;
+ CALLOC_ARRAY(idx, 1);
+ prepare_packfile_transaction(transaction);
+ hashfile_checkpoint_init(state->f, &checkpoint);
- while (1) {
- prepare_packfile_transaction(transaction, flags);
- if (idx) {
- hashfile_checkpoint(state->f, &checkpoint);
- idx->offset = state->offset;
- crc32_begin(state->f);
- }
- if (!stream_blob_to_pack(state, &ctx, &already_hashed_to,
- fd, size, path, flags))
- break;
- /*
- * Writing this object to the current pack will make
- * it too big; we need to truncate it, start a new
- * pack, and write into it.
- */
- if (!idx)
- BUG("should not happen");
- hashfile_truncate(state->f, &checkpoint);
- state->offset = checkpoint.offset;
- flush_packfile_transaction(transaction);
- if (lseek(fd, seekback, SEEK_SET) == (off_t)-1)
- return error("cannot seek back");
- }
+ hashfile_checkpoint(state->f, &checkpoint);
+ idx->offset = state->offset;
+ crc32_begin(state->f);
+ stream_blob_to_pack(state, &ctx, size, stream);
git_hash_final_oid(result_oid, &ctx);
- if (!idx)
- return 0;
idx->crc32 = crc32_end(state->f);
if (already_written(transaction, result_oid)) {
@@ -1642,34 +1642,6 @@ static int index_blob_packfile_transaction(struct odb_transaction_files *transac
return 0;
}
-static int hash_blob_stream(const struct git_hash_algo *hash_algo,
- struct object_id *result_oid, int fd, size_t size)
-{
- unsigned char buf[16384];
- struct git_hash_ctx ctx;
- unsigned header_len;
-
- header_len = format_object_header((char *)buf, sizeof(buf),
- OBJ_BLOB, size);
- hash_algo->init_fn(&ctx);
- git_hash_update(&ctx, buf, header_len);
-
- while (size) {
- size_t rsize = size < sizeof(buf) ? size : sizeof(buf);
- ssize_t read_result = read_in_full(fd, buf, rsize);
-
- if ((read_result < 0) || ((size_t)read_result != rsize))
- return -1;
-
- git_hash_update(&ctx, buf, rsize);
- size -= read_result;
- }
-
- git_hash_final_oid(result_oid, &ctx);
-
- return 0;
-}
-
int index_fd(struct index_state *istate, struct object_id *oid,
int fd, struct stat *st,
enum object_type type, const char *path, unsigned flags)
@@ -1691,23 +1663,25 @@ int index_fd(struct index_state *istate, struct object_id *oid,
ret = index_core(istate, oid, fd, xsize_t(st->st_size),
type, path, flags);
} else {
+ struct odb_write_stream stream;
+ odb_write_stream_from_fd(&stream, fd, xsize_t(st->st_size));
+
if (flags & INDEX_WRITE_OBJECT) {
struct object_database *odb = the_repository->objects;
- struct odb_transaction_files *files_transaction;
- struct odb_transaction *transaction;
+ struct odb_transaction *transaction = odb_transaction_begin(odb);
- transaction = odb_transaction_begin(odb);
- files_transaction = container_of(odb->transaction,
- struct odb_transaction_files,
- base);
- ret = index_blob_packfile_transaction(files_transaction, oid, fd,
- xsize_t(st->st_size),
- path, flags);
+ ret = odb_transaction_write_object_stream(odb->transaction,
+ &stream,
+ xsize_t(st->st_size),
+ oid);
odb_transaction_commit(transaction);
} else {
- ret = hash_blob_stream(the_repository->hash_algo, oid,
- fd, xsize_t(st->st_size));
+ ret = hash_blob_stream(&stream,
+ the_repository->hash_algo, oid,
+ xsize_t(st->st_size));
}
+
+ odb_write_stream_release(&stream);
}
close(fd);
@@ -1884,6 +1858,7 @@ static int for_each_object_wrapper_cb(const struct object_id *oid,
}
static int for_each_prefixed_object_wrapper_cb(const struct object_id *oid,
+ void *node_data UNUSED,
void *cb_data)
{
struct for_each_object_wrapper_data *data = cb_data;
@@ -2029,7 +2004,7 @@ static int append_loose_object(const struct object_id *oid,
const char *path UNUSED,
void *data)
{
- oidtree_insert(data, oid);
+ oidtree_insert(data, oid, NULL);
return 0;
}
@@ -2225,6 +2200,7 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
transaction = xcalloc(1, sizeof(*transaction));
transaction->base.source = source;
transaction->base.commit = odb_transaction_files_commit;
+ transaction->base.write_object_stream = odb_transaction_files_write_object_stream;
return &transaction->base;
}
diff --git a/odb.c b/odb.c
index 9b28fe25ef..965ef68e4e 100644
--- a/odb.c
+++ b/odb.c
@@ -14,6 +14,7 @@
#include "object-file.h"
#include "object-name.h"
#include "odb.h"
+#include "odb/source-inmemory.h"
#include "packfile.h"
#include "path.h"
#include "promisor-remote.h"
@@ -31,40 +32,6 @@
KHASH_INIT(odb_path_map, const char * /* key: odb_path */,
struct odb_source *, 1, fspathhash, fspatheq)
-/*
- * This is meant to hold a *small* number of objects that you would
- * want odb_read_object() to be able to return, but yet you do not want
- * to write them into the object store (e.g. a browse-only
- * application).
- */
-struct cached_object_entry {
- struct object_id oid;
- struct cached_object {
- enum object_type type;
- const void *buf;
- unsigned long size;
- } value;
-};
-
-static const struct cached_object *find_cached_object(struct object_database *object_store,
- const struct object_id *oid)
-{
- static const struct cached_object empty_tree = {
- .type = OBJ_TREE,
- .buf = "",
- };
- const struct cached_object_entry *co = object_store->cached_objects;
-
- for (size_t i = 0; i < object_store->cached_object_nr; i++, co++)
- if (oideq(&co->oid, oid))
- return &co->value;
-
- if (oid->algo && oideq(oid, hash_algos[oid->algo].empty_tree))
- return &empty_tree;
-
- return NULL;
-}
-
int odb_mkstemp(struct object_database *odb,
struct strbuf *temp_filename, const char *pattern)
{
@@ -584,7 +551,6 @@ static int do_oid_object_info_extended(struct object_database *odb,
const struct object_id *oid,
struct object_info *oi, unsigned flags)
{
- const struct cached_object *co;
const struct object_id *real = oid;
int already_retried = 0;
@@ -594,25 +560,8 @@ static int do_oid_object_info_extended(struct object_database *odb,
if (is_null_oid(real))
return -1;
- co = find_cached_object(odb, real);
- if (co) {
- if (oi) {
- if (oi->typep)
- *(oi->typep) = co->type;
- if (oi->sizep)
- *(oi->sizep) = co->size;
- if (oi->disk_sizep)
- *(oi->disk_sizep) = 0;
- if (oi->delta_base_oid)
- oidclr(oi->delta_base_oid, odb->repo->hash_algo);
- if (oi->contentp)
- *oi->contentp = xmemdupz(co->buf, co->size);
- if (oi->mtimep)
- *oi->mtimep = 0;
- oi->whence = OI_CACHED;
- }
+ if (!odb_source_read_object_info(odb->inmemory_objects, oid, oi, flags))
return 0;
- }
odb_prepare_alternates(odb);
@@ -784,24 +733,12 @@ int odb_pretend_object(struct object_database *odb,
void *buf, unsigned long len, enum object_type type,
struct object_id *oid)
{
- struct cached_object_entry *co;
- char *co_buf;
-
hash_object_file(odb->repo->hash_algo, buf, len, type, oid);
- if (odb_has_object(odb, oid, 0) ||
- find_cached_object(odb, oid))
+ if (odb_has_object(odb, oid, 0))
return 0;
- ALLOC_GROW(odb->cached_objects,
- odb->cached_object_nr + 1, odb->cached_object_alloc);
- co = &odb->cached_objects[odb->cached_object_nr++];
- co->value.size = len;
- co->value.type = type;
- co_buf = xmalloc(len);
- memcpy(co_buf, buf, len);
- co->value.buf = co_buf;
- oidcpy(&co->oid, oid);
- return 0;
+ return odb_source_write_object(odb->inmemory_objects,
+ buf, len, type, oid, NULL, 0);
}
void *odb_read_object(struct object_database *odb,
@@ -1083,6 +1020,7 @@ struct object_database *odb_new(struct repository *repo,
o->sources = odb_source_new(o, primary_source, true);
o->sources_tail = &o->sources->next;
o->alternate_db = xstrdup_or_null(secondary_sources);
+ o->inmemory_objects = &odb_source_inmemory_new(o)->base;
free(to_free);
@@ -1106,6 +1044,10 @@ static void odb_free_sources(struct object_database *o)
odb_source_free(o->sources);
o->sources = next;
}
+
+ odb_source_free(o->inmemory_objects);
+ o->inmemory_objects = NULL;
+
kh_destroy_odb_path_map(o->source_by_path);
o->source_by_path = NULL;
}
@@ -1123,10 +1065,6 @@ void odb_free(struct object_database *o)
odb_close(o);
odb_free_sources(o);
- for (size_t i = 0; i < o->cached_object_nr; i++)
- free((char *) o->cached_objects[i].value.buf);
- free(o->cached_objects);
-
string_list_clear(&o->submodule_source_paths, 0);
free(o);
@@ -1154,28 +1092,3 @@ void odb_reprepare(struct object_database *o)
obj_read_unlock();
}
-
-struct odb_transaction *odb_transaction_begin(struct object_database *odb)
-{
- if (odb->transaction)
- return NULL;
-
- odb->transaction = odb_transaction_files_begin(odb->sources);
-
- return odb->transaction;
-}
-
-void odb_transaction_commit(struct odb_transaction *transaction)
-{
- if (!transaction)
- return;
-
- /*
- * Ensure the transaction ending matches the pending transaction.
- */
- ASSERT(transaction == transaction->source->odb->transaction);
-
- transaction->commit(transaction);
- transaction->source->odb->transaction = NULL;
- free(transaction);
-}
diff --git a/odb.h b/odb.h
index 3a711f6547..73553ed5a7 100644
--- a/odb.h
+++ b/odb.h
@@ -8,6 +8,7 @@
#include "thread-utils.h"
struct cached_object_entry;
+struct odb_source_inmemory;
struct packed_git;
struct repository;
struct strbuf;
@@ -30,24 +31,6 @@ extern int fetch_if_missing;
char *compute_alternate_path(const char *path, struct strbuf *err);
/*
- * A transaction may be started for an object database prior to writing new
- * objects via odb_transaction_begin(). These objects are not committed until
- * odb_transaction_commit() is invoked. Only a single transaction may be pending
- * at a time.
- *
- * Each ODB source is expected to implement its own transaction handling.
- */
-struct odb_transaction;
-typedef void (*odb_transaction_commit_fn)(struct odb_transaction *transaction);
-struct odb_transaction {
- /* The ODB source the transaction is opened against. */
- struct odb_source *source;
-
- /* The ODB source specific callback invoked to commit a transaction. */
- odb_transaction_commit_fn commit;
-};
-
-/*
* The object database encapsulates access to objects in a repository. It
* manages one or more sources that store the actual objects which are
* configured via alternates.
@@ -98,8 +81,7 @@ struct object_database {
* to write them into the object store (e.g. a browse-only
* application).
*/
- struct cached_object_entry *cached_objects;
- size_t cached_object_nr, cached_object_alloc;
+ struct odb_source *inmemory_objects;
/*
* A fast, rough count of the number of objects in the repository.
@@ -149,19 +131,6 @@ void odb_close(struct object_database *o);
void odb_reprepare(struct object_database *o);
/*
- * Starts an ODB transaction. Subsequent objects are written to the transaction
- * and not committed until odb_transaction_commit() is invoked on the
- * transaction. If the ODB already has a pending transaction, NULL is returned.
- */
-struct odb_transaction *odb_transaction_begin(struct object_database *odb);
-
-/*
- * Commits an ODB transaction making the written objects visible. If the
- * specified transaction is NULL, the function is a no-op.
- */
-void odb_transaction_commit(struct odb_transaction *transaction);
-
-/*
* Find source by its object directory path. Returns a `NULL` pointer in case
* the source could not be found.
*/
@@ -593,11 +562,7 @@ static inline int odb_write_object(struct object_database *odb,
return odb_write_object_ext(odb, buf, len, type, oid, NULL, 0);
}
-struct odb_write_stream {
- const void *(*read)(struct odb_write_stream *, unsigned long *len);
- void *data;
- int is_finished;
-};
+struct odb_write_stream;
int odb_write_object_stream(struct object_database *odb,
struct odb_write_stream *stream, size_t len,
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
new file mode 100644
index 0000000000..e004566d76
--- /dev/null
+++ b/odb/source-inmemory.c
@@ -0,0 +1,382 @@
+#include "git-compat-util.h"
+#include "object-file.h"
+#include "odb.h"
+#include "odb/source-inmemory.h"
+#include "odb/streaming.h"
+#include "oidtree.h"
+#include "repository.h"
+
+struct inmemory_object {
+ enum object_type type;
+ const void *buf;
+ unsigned long size;
+};
+
+static const struct inmemory_object *find_cached_object(struct odb_source_inmemory *source,
+ const struct object_id *oid)
+{
+ static const struct inmemory_object empty_tree = {
+ .type = OBJ_TREE,
+ .buf = "",
+ };
+ const struct inmemory_object *object;
+
+ if (source->objects) {
+ object = oidtree_get(source->objects, oid);
+ if (object)
+ return object;
+ }
+
+ if (oid->algo && oideq(oid, hash_algos[oid->algo].empty_tree))
+ return &empty_tree;
+
+ return NULL;
+}
+
+static void populate_object_info(struct odb_source_inmemory *source,
+ struct object_info *oi,
+ const struct inmemory_object *object)
+{
+ if (!oi)
+ return;
+
+ if (oi->typep)
+ *(oi->typep) = object->type;
+ if (oi->sizep)
+ *(oi->sizep) = object->size;
+ if (oi->disk_sizep)
+ *(oi->disk_sizep) = 0;
+ if (oi->delta_base_oid)
+ oidclr(oi->delta_base_oid, source->base.odb->repo->hash_algo);
+ if (oi->contentp)
+ *oi->contentp = xmemdupz(object->buf, object->size);
+ if (oi->mtimep)
+ *oi->mtimep = 0;
+ oi->whence = OI_CACHED;
+}
+
+static int odb_source_inmemory_read_object_info(struct odb_source *source,
+ const struct object_id *oid,
+ struct object_info *oi,
+ enum object_info_flags flags UNUSED)
+{
+ struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
+ const struct inmemory_object *object;
+
+ object = find_cached_object(inmemory, oid);
+ if (!object)
+ return -1;
+
+ populate_object_info(inmemory, oi, object);
+ return 0;
+}
+
+struct odb_read_stream_inmemory {
+ struct odb_read_stream base;
+ const unsigned char *buf;
+ size_t offset;
+};
+
+static ssize_t odb_read_stream_inmemory_read(struct odb_read_stream *stream,
+ char *buf, size_t buf_len)
+{
+ struct odb_read_stream_inmemory *inmemory =
+ container_of(stream, struct odb_read_stream_inmemory, base);
+ size_t bytes = buf_len;
+
+ if (buf_len > inmemory->base.size - inmemory->offset)
+ bytes = inmemory->base.size - inmemory->offset;
+
+ memcpy(buf, inmemory->buf + inmemory->offset, bytes);
+ inmemory->offset += bytes;
+
+ return bytes;
+}
+
+static int odb_read_stream_inmemory_close(struct odb_read_stream *stream UNUSED)
+{
+ return 0;
+}
+
+static int odb_source_inmemory_read_object_stream(struct odb_read_stream **out,
+ struct odb_source *source,
+ const struct object_id *oid)
+{
+ struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
+ struct odb_read_stream_inmemory *stream;
+ const struct inmemory_object *object;
+
+ object = find_cached_object(inmemory, oid);
+ if (!object)
+ return -1;
+
+ CALLOC_ARRAY(stream, 1);
+ stream->base.read = odb_read_stream_inmemory_read;
+ stream->base.close = odb_read_stream_inmemory_close;
+ stream->base.size = object->size;
+ stream->base.type = object->type;
+ stream->buf = object->buf;
+
+ *out = &stream->base;
+ return 0;
+}
+
+struct odb_source_inmemory_for_each_object_data {
+ struct odb_source_inmemory *inmemory;
+ const struct object_info *request;
+ odb_for_each_object_cb cb;
+ void *cb_data;
+};
+
+static int odb_source_inmemory_for_each_object_cb(const struct object_id *oid,
+ void *node_data, void *cb_data)
+{
+ struct odb_source_inmemory_for_each_object_data *data = cb_data;
+ struct inmemory_object *object = node_data;
+
+ if (data->request) {
+ struct object_info oi = *data->request;
+ populate_object_info(data->inmemory, &oi, object);
+ return data->cb(oid, &oi, data->cb_data);
+ } else {
+ return data->cb(oid, NULL, data->cb_data);
+ }
+}
+
+static int odb_source_inmemory_for_each_object(struct odb_source *source,
+ const struct object_info *request,
+ odb_for_each_object_cb cb,
+ void *cb_data,
+ const struct odb_for_each_object_options *opts)
+{
+ struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
+ struct odb_source_inmemory_for_each_object_data payload = {
+ .inmemory = inmemory,
+ .request = request,
+ .cb = cb,
+ .cb_data = cb_data,
+ };
+ struct object_id null_oid = { 0 };
+
+ if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) ||
+ (opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local))
+ return 0;
+ if (!inmemory->objects)
+ return 0;
+
+ return oidtree_each(inmemory->objects,
+ opts->prefix ? opts->prefix : &null_oid, opts->prefix_hex_len,
+ odb_source_inmemory_for_each_object_cb, &payload);
+}
+
+struct find_abbrev_len_data {
+ const struct object_id *oid;
+ unsigned len;
+};
+
+static int find_abbrev_len_cb(const struct object_id *oid,
+ struct object_info *oi UNUSED,
+ void *cb_data)
+{
+ struct find_abbrev_len_data *data = cb_data;
+ unsigned len = oid_common_prefix_hexlen(oid, data->oid);
+ if (len != hash_algos[oid->algo].hexsz && len >= data->len)
+ data->len = len + 1;
+ return 0;
+}
+
+static int odb_source_inmemory_find_abbrev_len(struct odb_source *source,
+ const struct object_id *oid,
+ unsigned min_len,
+ unsigned *out)
+{
+ struct odb_for_each_object_options opts = {
+ .prefix = oid,
+ .prefix_hex_len = min_len,
+ };
+ struct find_abbrev_len_data data = {
+ .oid = oid,
+ .len = min_len,
+ };
+ int ret;
+
+ ret = odb_source_inmemory_for_each_object(source, NULL, find_abbrev_len_cb,
+ &data, &opts);
+ *out = data.len;
+
+ return ret;
+}
+
+static int count_objects_cb(const struct object_id *oid UNUSED,
+ struct object_info *oi UNUSED,
+ void *cb_data)
+{
+ unsigned long *counter = cb_data;
+ (*counter)++;
+ return 0;
+}
+
+static int odb_source_inmemory_count_objects(struct odb_source *source,
+ enum odb_count_objects_flags flags UNUSED,
+ unsigned long *out)
+{
+ struct odb_for_each_object_options opts = { 0 };
+ *out = 0;
+ return odb_source_inmemory_for_each_object(source, NULL, count_objects_cb,
+ out, &opts);
+}
+
+static int odb_source_inmemory_write_object(struct odb_source *source,
+ const void *buf, unsigned long len,
+ enum object_type type,
+ struct object_id *oid,
+ struct object_id *compat_oid UNUSED,
+ enum odb_write_object_flags flags UNUSED)
+{
+ struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
+ struct inmemory_object *object;
+
+ hash_object_file(source->odb->repo->hash_algo, buf, len, type, oid);
+
+ if (!inmemory->objects) {
+ CALLOC_ARRAY(inmemory->objects, 1);
+ oidtree_init(inmemory->objects);
+ } else if (oidtree_contains(inmemory->objects, oid)) {
+ return 0;
+ }
+
+ CALLOC_ARRAY(object, 1);
+ object->size = len;
+ object->type = type;
+ object->buf = xmemdupz(buf, len);
+
+ oidtree_insert(inmemory->objects, oid, object);
+
+ return 0;
+}
+
+static int odb_source_inmemory_write_object_stream(struct odb_source *source,
+ struct odb_write_stream *stream,
+ size_t len,
+ struct object_id *oid)
+{
+ char buf[16384];
+ size_t total_read = 0;
+ char *data;
+ int ret;
+
+ CALLOC_ARRAY(data, len);
+ while (!stream->is_finished) {
+ ssize_t bytes_read;
+
+ bytes_read = odb_write_stream_read(stream, buf, sizeof(buf));
+ if (total_read + bytes_read > len) {
+ ret = error("object stream yielded more bytes than expected");
+ goto out;
+ }
+
+ memcpy(data + total_read, buf, bytes_read);
+ total_read += bytes_read;
+ }
+
+ if (total_read != len) {
+ ret = error("object stream yielded less bytes than expected");
+ goto out;
+ }
+
+ ret = odb_source_inmemory_write_object(source, data, len, OBJ_BLOB, oid,
+ NULL, 0);
+ if (ret < 0)
+ goto out;
+
+out:
+ free(data);
+ return ret;
+}
+
+static int odb_source_inmemory_freshen_object(struct odb_source *source,
+ const struct object_id *oid)
+{
+ struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
+ if (find_cached_object(inmemory, oid))
+ return 1;
+ return 0;
+}
+
+static int odb_source_inmemory_begin_transaction(struct odb_source *source UNUSED,
+ struct odb_transaction **out UNUSED)
+{
+ return error("in-memory source does not support transactions");
+}
+
+static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
+ struct strvec *out UNUSED)
+{
+ return 0;
+}
+
+static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
+ const char *alternate UNUSED)
+{
+ return error("in-memory source does not support alternates");
+}
+
+static void odb_source_inmemory_close(struct odb_source *source UNUSED)
+{
+}
+
+static void odb_source_inmemory_reprepare(struct odb_source *source UNUSED)
+{
+}
+
+static int inmemory_object_free(const struct object_id *oid UNUSED,
+ void *node_data,
+ void *cb_data UNUSED)
+{
+ struct inmemory_object *object = node_data;
+ free((void *) object->buf);
+ free(object);
+ return 0;
+}
+
+static void odb_source_inmemory_free(struct odb_source *source)
+{
+ struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
+
+ if (inmemory->objects) {
+ struct object_id null_oid = { 0 };
+
+ oidtree_each(inmemory->objects, &null_oid, 0,
+ inmemory_object_free, NULL);
+ oidtree_clear(inmemory->objects);
+ free(inmemory->objects);
+ }
+
+ free(inmemory->base.path);
+ free(inmemory);
+}
+
+struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
+{
+ struct odb_source_inmemory *source;
+
+ CALLOC_ARRAY(source, 1);
+ odb_source_init(&source->base, odb, ODB_SOURCE_INMEMORY, "source", false);
+
+ source->base.free = odb_source_inmemory_free;
+ source->base.close = odb_source_inmemory_close;
+ source->base.reprepare = odb_source_inmemory_reprepare;
+ source->base.read_object_info = odb_source_inmemory_read_object_info;
+ source->base.read_object_stream = odb_source_inmemory_read_object_stream;
+ source->base.for_each_object = odb_source_inmemory_for_each_object;
+ source->base.find_abbrev_len = odb_source_inmemory_find_abbrev_len;
+ source->base.count_objects = odb_source_inmemory_count_objects;
+ source->base.write_object = odb_source_inmemory_write_object;
+ source->base.write_object_stream = odb_source_inmemory_write_object_stream;
+ source->base.freshen_object = odb_source_inmemory_freshen_object;
+ source->base.begin_transaction = odb_source_inmemory_begin_transaction;
+ source->base.read_alternates = odb_source_inmemory_read_alternates;
+ source->base.write_alternate = odb_source_inmemory_write_alternate;
+
+ return source;
+}
diff --git a/odb/source-inmemory.h b/odb/source-inmemory.h
new file mode 100644
index 0000000000..a88fc2e320
--- /dev/null
+++ b/odb/source-inmemory.h
@@ -0,0 +1,33 @@
+#ifndef ODB_SOURCE_INMEMORY_H
+#define ODB_SOURCE_INMEMORY_H
+
+#include "odb/source.h"
+
+struct oidtree;
+
+/*
+ * An in-memory source that you can write objects to that shall be made
+ * available for reading, but that shouldn't ever be persisted to disk. Note
+ * that any objects written to this source will be stored in memory, so the
+ * number of objects you can store is limited by available system memory.
+ */
+struct odb_source_inmemory {
+ struct odb_source base;
+ struct oidtree *objects;
+};
+
+/* Create a new in-memory object database source. */
+struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb);
+
+/*
+ * Cast the given object database source to the in-memory backend. This will
+ * cause a BUG in case the source doesn't use this backend.
+ */
+static inline struct odb_source_inmemory *odb_source_inmemory_downcast(struct odb_source *source)
+{
+ if (source->type != ODB_SOURCE_INMEMORY)
+ BUG("trying to downcast source of type '%d' to in-memory", source->type);
+ return container_of(source, struct odb_source_inmemory, base);
+}
+
+#endif
diff --git a/odb/source.h b/odb/source.h
index f706e0608a..0a440884e4 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -13,6 +13,9 @@ enum odb_source_type {
/* The "files" backend that uses loose objects and packfiles. */
ODB_SOURCE_FILES,
+
+ /* The "in-memory" backend that stores objects in memory. */
+ ODB_SOURCE_INMEMORY,
};
struct object_id;
diff --git a/odb/streaming.c b/odb/streaming.c
index af2adf5ce7..7602a8d5d8 100644
--- a/odb/streaming.c
+++ b/odb/streaming.c
@@ -243,6 +243,16 @@ struct odb_read_stream *odb_read_stream_open(struct object_database *odb,
return st;
}
+ssize_t odb_write_stream_read(struct odb_write_stream *st, void *buf, size_t sz)
+{
+ return st->read(st, buf, sz);
+}
+
+void odb_write_stream_release(struct odb_write_stream *st)
+{
+ free(st->data);
+}
+
int odb_stream_blob_to_fd(struct object_database *odb,
int fd,
const struct object_id *oid,
@@ -298,3 +308,44 @@ int odb_stream_blob_to_fd(struct object_database *odb,
odb_read_stream_close(st);
return result;
}
+
+struct read_object_fd_data {
+ int fd;
+ size_t remaining;
+};
+
+static ssize_t read_object_fd(struct odb_write_stream *stream,
+ unsigned char *buf, size_t len)
+{
+ struct read_object_fd_data *data = stream->data;
+ ssize_t read_result;
+ size_t count;
+
+ if (stream->is_finished)
+ return 0;
+
+ count = data->remaining < len ? data->remaining : len;
+ read_result = read_in_full(data->fd, buf, count);
+ if (read_result < 0 || (size_t)read_result != count)
+ return -1;
+
+ data->remaining -= count;
+ if (!data->remaining)
+ stream->is_finished = 1;
+
+ return read_result;
+}
+
+void odb_write_stream_from_fd(struct odb_write_stream *stream, int fd,
+ size_t size)
+{
+ struct read_object_fd_data *data;
+
+ CALLOC_ARRAY(data, 1);
+ data->fd = fd;
+ data->remaining = size;
+
+ stream->data = data;
+ stream->read = read_object_fd;
+ stream->is_finished = 0;
+}
diff --git a/odb/streaming.h b/odb/streaming.h
index 517e2ea2d3..c023671780 100644
--- a/odb/streaming.h
+++ b/odb/streaming.h
@@ -5,6 +5,7 @@
#define STREAMING_H 1
#include "object.h"
+#include "odb.h"
struct object_database;
struct odb_read_stream;
@@ -48,6 +49,29 @@ int odb_read_stream_close(struct odb_read_stream *stream);
ssize_t odb_read_stream_read(struct odb_read_stream *stream, void *buf, size_t len);
/*
+ * A stream that provides an object to be written to the object database without
+ * loading all of it into memory.
+ */
+struct odb_write_stream {
+ ssize_t (*read)(struct odb_write_stream *, unsigned char *, size_t);
+ void *data;
+ int is_finished;
+};
+
+/*
+ * Read data from the stream into the buffer. Returns 0 when finished and the
+ * number of bytes read on success. Returns a negative error code in case
+ * reading from the stream fails.
+ */
+ssize_t odb_write_stream_read(struct odb_write_stream *stream, void *buf,
+ size_t len);
+
+/*
+ * Releases memory allocated for underlying stream data.
+ */
+void odb_write_stream_release(struct odb_write_stream *stream);
+
+/*
* Look up the object by its ID and write the full contents to the file
* descriptor. The object must be a blob, or the function will fail. When
* provided, the filter is used to transform the blob contents.
@@ -64,4 +88,10 @@ int odb_stream_blob_to_fd(struct object_database *odb,
struct stream_filter *filter,
int can_seek);
+/*
+ * Sets up an ODB write stream that reads from an fd.
+ */
+void odb_write_stream_from_fd(struct odb_write_stream *stream, int fd,
+ size_t size);
+
#endif /* STREAMING_H */
diff --git a/odb/transaction.c b/odb/transaction.c
new file mode 100644
index 0000000000..b16e07aebf
--- /dev/null
+++ b/odb/transaction.c
@@ -0,0 +1,35 @@
+#include "git-compat-util.h"
+#include "odb/source.h"
+#include "odb/transaction.h"
+
+struct odb_transaction *odb_transaction_begin(struct object_database *odb)
+{
+ if (odb->transaction)
+ return NULL;
+
+ odb_source_begin_transaction(odb->sources, &odb->transaction);
+
+ return odb->transaction;
+}
+
+void odb_transaction_commit(struct odb_transaction *transaction)
+{
+ if (!transaction)
+ return;
+
+ /*
+ * Ensure the transaction ending matches the pending transaction.
+ */
+ ASSERT(transaction == transaction->source->odb->transaction);
+
+ transaction->commit(transaction);
+ transaction->source->odb->transaction = NULL;
+ free(transaction);
+}
+
+int odb_transaction_write_object_stream(struct odb_transaction *transaction,
+ struct odb_write_stream *stream,
+ size_t len, struct object_id *oid)
+{
+ return transaction->write_object_stream(transaction, stream, len, oid);
+}
diff --git a/odb/transaction.h b/odb/transaction.h
new file mode 100644
index 0000000000..854fda06f5
--- /dev/null
+++ b/odb/transaction.h
@@ -0,0 +1,57 @@
+#ifndef ODB_TRANSACTION_H
+#define ODB_TRANSACTION_H
+
+#include "odb.h"
+#include "odb/source.h"
+
+/*
+ * A transaction may be started for an object database prior to writing new
+ * objects via odb_transaction_begin(). These objects are not committed until
+ * odb_transaction_commit() is invoked. Only a single transaction may be pending
+ * at a time.
+ *
+ * Each ODB source is expected to implement its own transaction handling.
+ */
+struct odb_transaction {
+ /* The ODB source the transaction is opened against. */
+ struct odb_source *source;
+
+ /* The ODB source specific callback invoked to commit a transaction. */
+ void (*commit)(struct odb_transaction *transaction);
+
+ /*
+ * This callback is expected to write the given object stream into
+ * the ODB transaction. Note that for now, only blobs support streaming.
+ *
+ * The resulting object ID shall be written into the out pointer. The
+ * callback is expected to return 0 on success, a negative error code
+ * otherwise.
+ */
+ int (*write_object_stream)(struct odb_transaction *transaction,
+ struct odb_write_stream *stream, size_t len,
+ struct object_id *oid);
+};
+
+/*
+ * Starts an ODB transaction. Subsequent objects are written to the transaction
+ * and not committed until odb_transaction_commit() is invoked on the
+ * transaction. If the ODB already has a pending transaction, NULL is returned.
+ */
+struct odb_transaction *odb_transaction_begin(struct object_database *odb);
+
+/*
+ * Commits an ODB transaction making the written objects visible. If the
+ * specified transaction is NULL, the function is a no-op.
+ */
+void odb_transaction_commit(struct odb_transaction *transaction);
+
+/*
+ * Writes the object in the provided stream into the transaction. The resulting
+ * object ID is written into the out pointer. Returns 0 on success, a negative
+ * error code otherwise.
+ */
+int odb_transaction_write_object_stream(struct odb_transaction *transaction,
+ struct odb_write_stream *stream,
+ size_t len, struct object_id *oid);
+
+#endif
diff --git a/oidtree.c b/oidtree.c
index ab9fe7ec7a..e43f18026e 100644
--- a/oidtree.c
+++ b/oidtree.c
@@ -6,9 +6,15 @@
#include "oidtree.h"
#include "hash.h"
+struct oidtree_node {
+ struct cb_node base;
+ struct object_id key;
+ void *data;
+};
+
void oidtree_init(struct oidtree *ot)
{
- cb_init(&ot->tree);
+ cb_init(&ot->tree, offsetof(struct oidtree_node, key));
mem_pool_init(&ot->mem_pool, 0);
}
@@ -20,22 +26,22 @@ void oidtree_clear(struct oidtree *ot)
}
}
-void oidtree_insert(struct oidtree *ot, const struct object_id *oid)
+struct oidtree_data {
+ struct object_id oid;
+};
+
+void oidtree_insert(struct oidtree *ot, const struct object_id *oid,
+ void *data)
{
- struct cb_node *on;
- struct object_id k;
+ struct oidtree_node *on;
+ struct cb_node *node;
if (!oid->algo)
BUG("oidtree_insert requires oid->algo");
- on = mem_pool_alloc(&ot->mem_pool, sizeof(*on) + sizeof(*oid));
-
- /*
- * Clear the padding and copy the result in separate steps to
- * respect the 4-byte alignment needed by struct object_id.
- */
- oidcpy(&k, oid);
- memcpy(on->k, &k, sizeof(k));
+ on = mem_pool_alloc(&ot->mem_pool, sizeof(*on));
+ oidcpy(&on->key, oid);
+ on->data = data;
/*
* n.b. Current callers won't get us duplicates, here. If a
@@ -43,13 +49,19 @@ void oidtree_insert(struct oidtree *ot, const struct object_id *oid)
* that won't be freed until oidtree_clear. Currently it's not
* worth maintaining a free list
*/
- cb_insert(&ot->tree, on, sizeof(*oid));
+ node = cb_insert(&ot->tree, &on->base, sizeof(*oid));
+ if (node) {
+ struct oidtree_node *preexisting = container_of(node, struct oidtree_node, base);
+ preexisting->data = data;
+ }
}
-bool oidtree_contains(struct oidtree *ot, const struct object_id *oid)
+static struct oidtree_node *oidtree_lookup(struct oidtree *ot,
+ const struct object_id *oid)
{
struct object_id k;
size_t klen = sizeof(k);
+ struct cb_node *node;
oidcpy(&k, oid);
@@ -60,7 +72,20 @@ bool oidtree_contains(struct oidtree *ot, const struct object_id *oid)
klen += BUILD_ASSERT_OR_ZERO(offsetof(struct object_id, hash) <
offsetof(struct object_id, algo));
- return !!cb_lookup(&ot->tree, (const uint8_t *)&k, klen);
+ node = cb_lookup(&ot->tree, (const uint8_t *)&k, klen);
+ return node ? container_of(node, struct oidtree_node, base) : NULL;
+}
+
+bool oidtree_contains(struct oidtree *ot, const struct object_id *oid)
+{
+ struct oidtree_node *node = oidtree_lookup(ot, oid);
+ return node ? 1 : 0;
+}
+
+void *oidtree_get(struct oidtree *ot, const struct object_id *oid)
+{
+ struct oidtree_node *node = oidtree_lookup(ot, oid);
+ return node ? node->data : NULL;
}
struct oidtree_each_data {
@@ -73,21 +98,18 @@ struct oidtree_each_data {
static int iter(struct cb_node *n, void *cb_data)
{
+ struct oidtree_node *node = container_of(n, struct oidtree_node, base);
struct oidtree_each_data *data = cb_data;
- struct object_id k;
-
- /* Copy to provide 4-byte alignment needed by struct object_id. */
- memcpy(&k, n->k, sizeof(k));
- if (data->algo != GIT_HASH_UNKNOWN && data->algo != k.algo)
+ if (data->algo != GIT_HASH_UNKNOWN && data->algo != node->key.algo)
return 0;
if (data->last_nibble_at) {
- if ((k.hash[*data->last_nibble_at] ^ data->last_byte) & 0xf0)
+ if ((node->key.hash[*data->last_nibble_at] ^ data->last_byte) & 0xf0)
return 0;
}
- return data->cb(&k, data->cb_data);
+ return data->cb(&node->key, node->data, data->cb_data);
}
int oidtree_each(struct oidtree *ot, const struct object_id *prefix,
diff --git a/oidtree.h b/oidtree.h
index 2b7bad2e60..baa5a436ea 100644
--- a/oidtree.h
+++ b/oidtree.h
@@ -29,18 +29,26 @@ void oidtree_init(struct oidtree *ot);
*/
void oidtree_clear(struct oidtree *ot);
-/* Insert the object ID into the tree. */
-void oidtree_insert(struct oidtree *ot, const struct object_id *oid);
+/*
+ * Insert the object ID into the tree and store the given pointer alongside
+ * with it. The data pointer of any preexisting entry will be overwritten.
+ */
+void oidtree_insert(struct oidtree *ot, const struct object_id *oid,
+ void *data);
/* Check whether the tree contains the given object ID. */
bool oidtree_contains(struct oidtree *ot, const struct object_id *oid);
+/* Get the payload stored with the given object ID. */
+void *oidtree_get(struct oidtree *ot, const struct object_id *oid);
+
/*
* Callback function used for `oidtree_each()`. Returning a non-zero exit code
* will cause iteration to stop. The exit code will be propagated to the caller
* of `oidtree_each()`.
*/
typedef int (*oidtree_each_cb)(const struct object_id *oid,
+ void *node_data,
void *cb_data);
/*
diff --git a/read-cache.c b/read-cache.c
index 38a04b8de3..21829102ae 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -20,6 +20,7 @@
#include "dir.h"
#include "object-file.h"
#include "odb.h"
+#include "odb/transaction.h"
#include "oid-array.h"
#include "tree.h"
#include "commit.h"
diff --git a/t/meson.build b/t/meson.build
index fd955f44ef..2ad6a801cd 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -6,6 +6,7 @@ clar_test_suites = [
'unit-tests/u-hashmap.c',
'unit-tests/u-list-objects-filter-options.c',
'unit-tests/u-mem-pool.c',
+ 'unit-tests/u-odb-inmemory.c',
'unit-tests/u-oid-array.c',
'unit-tests/u-oidmap.c',
'unit-tests/u-oidtree.c',
diff --git a/t/unit-tests/u-odb-inmemory.c b/t/unit-tests/u-odb-inmemory.c
new file mode 100644
index 0000000000..482502ef4b
--- /dev/null
+++ b/t/unit-tests/u-odb-inmemory.c
@@ -0,0 +1,313 @@
+#include "unit-test.h"
+#include "hex.h"
+#include "odb/source-inmemory.h"
+#include "odb/streaming.h"
+#include "oidset.h"
+#include "repository.h"
+#include "strbuf.h"
+
+#define RANDOM_OID "da39a3ee5e6b4b0d3255bfef95601890afd80709"
+#define FOOBAR_OID "f6ea0495187600e7b2288c8ac19c5886383a4632"
+
+static struct repository repo = {
+ .hash_algo = &hash_algos[GIT_HASH_SHA1],
+};
+static struct object_database *odb;
+
+static void cl_assert_object_info(struct odb_source_inmemory *source,
+ const struct object_id *oid,
+ enum object_type expected_type,
+ const char *expected_content)
+{
+ enum object_type actual_type;
+ unsigned long actual_size;
+ void *actual_content;
+ struct object_info oi = {
+ .typep = &actual_type,
+ .sizep = &actual_size,
+ .contentp = &actual_content,
+ };
+
+ cl_must_pass(odb_source_read_object_info(&source->base, oid, &oi, 0));
+ cl_assert_equal_u(actual_size, strlen(expected_content));
+ cl_assert_equal_u(actual_type, expected_type);
+ cl_assert_equal_s((char *) actual_content, expected_content);
+
+ free(actual_content);
+}
+
+void test_odb_inmemory__initialize(void)
+{
+ odb = odb_new(&repo, "", "");
+}
+
+void test_odb_inmemory__cleanup(void)
+{
+ odb_free(odb);
+}
+
+void test_odb_inmemory__new(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ cl_assert_equal_i(source->base.type, ODB_SOURCE_INMEMORY);
+ odb_source_free(&source->base);
+}
+
+void test_odb_inmemory__read_missing_object(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ struct object_id oid;
+ const char *end;
+
+ cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo));
+ cl_must_fail(odb_source_read_object_info(&source->base, &oid, NULL, 0));
+
+ odb_source_free(&source->base);
+}
+
+void test_odb_inmemory__read_empty_tree(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ cl_assert_object_info(source, repo.hash_algo->empty_tree, OBJ_TREE, "");
+ odb_source_free(&source->base);
+}
+
+void test_odb_inmemory__read_written_object(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ const char data[] = "foobar";
+ struct object_id written_oid;
+
+ cl_must_pass(odb_source_write_object(&source->base, data, strlen(data),
+ OBJ_BLOB, &written_oid, NULL, 0));
+ cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID);
+ cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar");
+
+ odb_source_free(&source->base);
+}
+
+void test_odb_inmemory__read_stream_object(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ struct odb_read_stream *stream;
+ struct object_id written_oid;
+ const char data[] = "foobar";
+ char buf[3] = { 0 };
+
+ cl_must_pass(odb_source_write_object(&source->base, data, strlen(data),
+ OBJ_BLOB, &written_oid, NULL, 0));
+
+ cl_must_pass(odb_source_read_object_stream(&stream, &source->base,
+ &written_oid));
+ cl_assert_equal_i(stream->type, OBJ_BLOB);
+ cl_assert_equal_u(stream->size, 6);
+
+ cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 2);
+ cl_assert_equal_s(buf, "fo");
+ cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 2);
+ cl_assert_equal_s(buf, "ob");
+ cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 2);
+ cl_assert_equal_s(buf, "ar");
+ cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 0);
+
+ odb_read_stream_close(stream);
+ odb_source_free(&source->base);
+}
+
+static int add_one_object(const struct object_id *oid,
+ struct object_info *oi UNUSED,
+ void *payload)
+{
+ struct oidset *actual_oids = payload;
+ cl_must_pass(oidset_insert(actual_oids, oid));
+ return 0;
+}
+
+void test_odb_inmemory__for_each_object(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ struct odb_for_each_object_options opts = { 0 };
+ struct oidset expected_oids = OIDSET_INIT;
+ struct oidset actual_oids = OIDSET_INIT;
+ struct strbuf buf = STRBUF_INIT;
+
+ cl_must_pass(odb_source_for_each_object(&source->base, NULL,
+ add_one_object, &actual_oids, &opts));
+ cl_assert_equal_u(oidset_size(&actual_oids), 0);
+
+ for (int i = 0; i < 10; i++) {
+ struct object_id written_oid;
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%d", i);
+
+ cl_must_pass(odb_source_write_object(&source->base, buf.buf, buf.len,
+ OBJ_BLOB, &written_oid, NULL, 0));
+ cl_must_pass(oidset_insert(&expected_oids, &written_oid));
+ }
+
+ cl_must_pass(odb_source_for_each_object(&source->base, NULL,
+ add_one_object, &actual_oids, &opts));
+ cl_assert_equal_b(oidset_equal(&expected_oids, &actual_oids), true);
+
+ odb_source_free(&source->base);
+ oidset_clear(&expected_oids);
+ oidset_clear(&actual_oids);
+ strbuf_release(&buf);
+}
+
+static int abort_after_two_objects(const struct object_id *oid UNUSED,
+ struct object_info *oi UNUSED,
+ void *payload)
+{
+ unsigned *counter = payload;
+ (*counter)++;
+ if (*counter == 2)
+ return 123;
+ return 0;
+}
+
+void test_odb_inmemory__for_each_object_can_abort_iteration(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ struct odb_for_each_object_options opts = { 0 };
+ struct object_id written_oid;
+ unsigned counter = 0;
+
+ cl_must_pass(odb_source_write_object(&source->base, "1", 1,
+ OBJ_BLOB, &written_oid, NULL, 0));
+ cl_must_pass(odb_source_write_object(&source->base, "2", 1,
+ OBJ_BLOB, &written_oid, NULL, 0));
+ cl_must_pass(odb_source_write_object(&source->base, "3", 1,
+ OBJ_BLOB, &written_oid, NULL, 0));
+
+ cl_assert_equal_i(odb_source_for_each_object(&source->base, NULL,
+ abort_after_two_objects,
+ &counter, &opts),
+ 123);
+ cl_assert_equal_u(counter, 2);
+
+ odb_source_free(&source->base);
+}
+
+void test_odb_inmemory__count_objects(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ struct object_id written_oid;
+ unsigned long count;
+
+ cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
+ cl_assert_equal_u(count, 0);
+
+ cl_must_pass(odb_source_write_object(&source->base, "1", 1,
+ OBJ_BLOB, &written_oid, NULL, 0));
+ cl_must_pass(odb_source_write_object(&source->base, "2", 1,
+ OBJ_BLOB, &written_oid, NULL, 0));
+ cl_must_pass(odb_source_write_object(&source->base, "3", 1,
+ OBJ_BLOB, &written_oid, NULL, 0));
+
+ cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
+ cl_assert_equal_u(count, 3);
+
+ odb_source_free(&source->base);
+}
+
+void test_odb_inmemory__find_abbrev_len(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ struct object_id oid1, oid2;
+ unsigned abbrev_len;
+
+ /*
+ * The two blobs we're about to write share the first 10 hex characters
+ * of their object IDs ("a09f43dc45"), so at least 11 characters are
+ * needed to tell them apart:
+ *
+ * "368317" -> a09f43dc4562d45115583f5094640ae237df55f7
+ * "514796" -> a09f43dc45fef837235eb7e6b1a6ca5e169a3981
+ *
+ * With only one blob written we expect a length of 4.
+ */
+ cl_must_pass(odb_source_write_object(&source->base, "368317", strlen("368317"),
+ OBJ_BLOB, &oid1, NULL, 0));
+ cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
+ &abbrev_len));
+ cl_assert_equal_u(abbrev_len, 4);
+
+ /*
+ * With both objects present, the shared 10-character prefix means we
+ * need at least 11 characters to uniquely identify either object.
+ */
+ cl_must_pass(odb_source_write_object(&source->base, "514796", strlen("514796"),
+ OBJ_BLOB, &oid2, NULL, 0));
+ cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
+ &abbrev_len));
+ cl_assert_equal_u(abbrev_len, 11);
+
+ odb_source_free(&source->base);
+}
+
+void test_odb_inmemory__freshen_object(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ struct object_id written_oid;
+ struct object_id oid;
+ const char *end;
+
+ cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo));
+ cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid), 0);
+
+ cl_must_pass(odb_source_write_object(&source->base, "foobar",
+ strlen("foobar"), OBJ_BLOB,
+ &written_oid, NULL, 0));
+ cl_assert_equal_i(odb_source_freshen_object(&source->base,
+ &written_oid), 1);
+
+ odb_source_free(&source->base);
+}
+
+struct membuf_write_stream {
+ struct odb_write_stream base;
+ const char *buf;
+ size_t offset;
+ size_t size;
+};
+
+static ssize_t membuf_write_stream_read(struct odb_write_stream *stream,
+ unsigned char *buf, size_t len)
+{
+ struct membuf_write_stream *s = container_of(stream, struct membuf_write_stream, base);
+ size_t chunk_size = 2;
+
+ if (chunk_size > len)
+ chunk_size = len;
+ if (chunk_size > s->size - s->offset)
+ chunk_size = s->size - s->offset;
+
+ memcpy(buf, s->buf + s->offset, chunk_size);
+
+ s->offset += chunk_size;
+ if (s->offset == s->size)
+ s->base.is_finished = 1;
+
+ return chunk_size;
+}
+
+void test_odb_inmemory__write_object_stream(void)
+{
+ struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
+ const char data[] = "foobar";
+ struct membuf_write_stream stream = {
+ .base.read = membuf_write_stream_read,
+ .buf = data,
+ .size = strlen(data),
+ };
+ struct object_id written_oid;
+
+ cl_must_pass(odb_source_write_object_stream(&source->base, &stream.base,
+ strlen(data), &written_oid));
+ cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID);
+ cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar");
+
+ odb_source_free(&source->base);
+}
diff --git a/t/unit-tests/u-oidtree.c b/t/unit-tests/u-oidtree.c
index d4d05c7dc3..f0d5ebb733 100644
--- a/t/unit-tests/u-oidtree.c
+++ b/t/unit-tests/u-oidtree.c
@@ -19,7 +19,7 @@ static int fill_tree_loc(struct oidtree *ot, const char *hexes[], size_t n)
for (size_t i = 0; i < n; i++) {
struct object_id oid;
cl_parse_any_oid(hexes[i], &oid);
- oidtree_insert(ot, &oid);
+ oidtree_insert(ot, &oid, NULL);
}
return 0;
}
@@ -38,9 +38,9 @@ struct expected_hex_iter {
const char *query;
};
-static int check_each_cb(const struct object_id *oid, void *data)
+static int check_each_cb(const struct object_id *oid, void *node_data UNUSED, void *cb_data)
{
- struct expected_hex_iter *hex_iter = data;
+ struct expected_hex_iter *hex_iter = cb_data;
struct object_id expected;
cl_assert(hex_iter->i < hex_iter->expected_hexes.nr);
@@ -105,3 +105,23 @@ void test_oidtree__each(void)
check_each(&ot, "32100", "321", NULL);
check_each(&ot, "32", "320", "321", NULL);
}
+
+void test_oidtree__insert_overwrites_data(void)
+{
+ struct object_id oid;
+ struct oidtree ot;
+ int a, b;
+
+ cl_parse_any_oid("1", &oid);
+
+ oidtree_init(&ot);
+
+ oidtree_insert(&ot, &oid, NULL);
+ cl_assert_equal_p(oidtree_get(&ot, &oid), NULL);
+ oidtree_insert(&ot, &oid, &a);
+ cl_assert_equal_p(oidtree_get(&ot, &oid), &a);
+ oidtree_insert(&ot, &oid, &b);
+ cl_assert_equal_p(oidtree_get(&ot, &oid), &b);
+
+ oidtree_clear(&ot);
+}