| #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; |
| } |