odb: introduce `odb_write_object()`

We do not have a backend-agnostic way to write objects into an object database. While there is `write_object_file()`, this function is rather specific to the loose object format. Introduce `odb_write_object()` to plug this gap. For now, this function is a simple wrapper around `write_object_file()` and doesn't even use the passed-in object database yet. This will change in subsequent commits, where `write_object_file()` is converted so that it works on top of an `odb_source`. `odb_write_object()` will then become responsible for deciding which source an object shall be written to. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jul 17, 2025 at 06:56 UTC ab1c6e1d12e869cbca3ea7f8a4e767e45fd14c49
21 files changed +106 -67
apply.c
+7 -4
@@ -3621,7 +3621,7 @@ static int try_threeway(struct apply_state *state,
3621
3622 /* Preimage the patch was prepared for */
3623 if (patch->is_new)
3624 - write_object_file("", 0, OBJ_BLOB, &pre_oid);
3624 + odb_write_object(the_repository->objects, "", 0, OBJ_BLOB, &pre_oid);
3625 else if (repo_get_oid(the_repository, patch->old_oid_prefix, &pre_oid) ||
3626 read_blob_object(&buf, &pre_oid, patch->old_mode))
3627 return error(_("repository lacks the necessary blob to perform 3-way merge."));
@@ -3637,7 +3637,8 @@ static int try_threeway(struct apply_state *state,
3637 return -1;
3638 }
3639 /* post_oid is theirs */
3640 - write_object_file(tmp_image.buf.buf, tmp_image.buf.len, OBJ_BLOB, &post_oid);
3640 + odb_write_object(the_repository->objects, tmp_image.buf.buf,
3641 + tmp_image.buf.len, OBJ_BLOB, &post_oid);
3642 image_clear(&tmp_image);
3643
3644 /* our_oid is ours */
@@ -3650,7 +3651,8 @@ static int try_threeway(struct apply_state *state,
3651 return error(_("cannot read the current contents of '%s'"),
3652 patch->old_name);
3653 }
3653 - write_object_file(tmp_image.buf.buf, tmp_image.buf.len, OBJ_BLOB, &our_oid);
3654 + odb_write_object(the_repository->objects, tmp_image.buf.buf,
3655 + tmp_image.buf.len, OBJ_BLOB, &our_oid);
3656 image_clear(&tmp_image);
3657
3658 /* in-core three-way merge between post and our using pre as base */
@@ -4360,7 +4362,8 @@ static int add_index_file(struct apply_state *state,
4362 }
4363 fill_stat_cache_info(state->repo->index, ce, &st);
4364 }
4363 - if (write_object_file(buf, size, OBJ_BLOB, &ce->oid) < 0) {
4365 + if (odb_write_object(the_repository->objects, buf, size,
4366 + OBJ_BLOB, &ce->oid) < 0) {
4367 discard_cache_entry(ce);
4368 return error(_("unable to create backing store "
4369 "for newly created file %s"), path);
builtin/checkout.c
+1 -1
@@ -320,7 +320,7 @@ static int checkout_merged(int pos, const struct checkout *state,
320 * (it also writes the merge result to the object database even
321 * when it may contain conflicts).
322 */
323 - if (write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &oid))
323 + if (odb_write_object(the_repository->objects, result_buf.ptr, result_buf.size, OBJ_BLOB, &oid))
324 die(_("Unable to add merge result for '%s'"), path);
325 free(result_buf.ptr);
326 ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool);
builtin/merge-file.c
+2 -1
@@ -155,7 +155,8 @@ int cmd_merge_file(int argc,
155 if (object_id && !to_stdout) {
156 struct object_id oid;
157 if (result.size) {
158 - if (write_object_file(result.ptr, result.size, OBJ_BLOB, &oid) < 0)
158 + if (odb_write_object(the_repository->objects, result.ptr,
159 + result.size, OBJ_BLOB, &oid) < 0)
160 ret = error(_("Could not write object file"));
161 } else {
162 oidcpy(&oid, the_hash_algo->empty_blob);
builtin/mktag.c
+1 -1
@@ -106,7 +106,7 @@ int cmd_mktag(int argc,
106 if (verify_object_in_tag(&tagged_oid, &tagged_type) < 0)
107 die(_("tag on stdin did not refer to a valid object"));
108
109 - if (write_object_file(buf.buf, buf.len, OBJ_TAG, &result) < 0)
109 + if (odb_write_object(the_repository->objects, buf.buf, buf.len, OBJ_TAG, &result) < 0)
110 die(_("unable to write tag file"));
111
112 strbuf_release(&buf);
builtin/mktree.c
+1 -1
@@ -63,7 +63,7 @@ static void write_tree(struct object_id *oid)
63 strbuf_add(&buf, ent->oid.hash, the_hash_algo->rawsz);
64 }
65
66 - write_object_file(buf.buf, buf.len, OBJ_TREE, oid);
66 + odb_write_object(the_repository->objects, buf.buf, buf.len, OBJ_TREE, oid);
67 strbuf_release(&buf);
68 }
69
builtin/notes.c
+2 -1
@@ -229,7 +229,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *
229
230 static void write_note_data(struct note_data *d, struct object_id *oid)
231 {
232 - if (write_object_file(d->buf.buf, d->buf.len, OBJ_BLOB, oid)) {
232 + if (odb_write_object(the_repository->objects, d->buf.buf,
233 + d->buf.len, OBJ_BLOB, oid)) {
234 int status = die_message(_("unable to write note object"));
235
236 if (d->edit_path)
builtin/receive-pack.c
+2 -2
@@ -760,8 +760,8 @@ static void prepare_push_cert_sha1(struct child_process *proc)
760 int bogs /* beginning_of_gpg_sig */;
761
762 already_done = 1;
763 - if (write_object_file(push_cert.buf, push_cert.len, OBJ_BLOB,
764 - &push_cert_oid))
763 + if (odb_write_object(the_repository->objects, push_cert.buf,
764 + push_cert.len, OBJ_BLOB, &push_cert_oid))
765 oidclr(&push_cert_oid, the_repository->hash_algo);
766
767 memset(&sigcheck, '\0', sizeof(sigcheck));
builtin/replace.c
+2 -1
@@ -488,7 +488,8 @@ static int create_graft(int argc, const char **argv, int force, int gentle)
488 return -1;
489 }
490
491 - if (write_object_file(buf.buf, buf.len, OBJ_COMMIT, &new_oid)) {
491 + if (odb_write_object(the_repository->objects, buf.buf,
492 + buf.len, OBJ_COMMIT, &new_oid)) {
493 strbuf_release(&buf);
494 return error(_("could not write replacement commit for: '%s'"),
495 old_ref);
builtin/tag.c
+2 -2
@@ -271,8 +271,8 @@ static int build_tag_object(struct strbuf *buf, int sign, struct object_id *resu
271 struct object_id *compat_oid = NULL, compat_oid_buf;
272 if (sign && do_sign(buf, &compat_oid, &compat_oid_buf) < 0)
273 return error(_("unable to sign the tag"));
274 - if (write_object_file_flags(buf->buf, buf->len, OBJ_TAG, result,
275 - compat_oid, 0) < 0)
274 + if (odb_write_object_ext(the_repository->objects, buf->buf,
275 + buf->len, OBJ_TAG, result, compat_oid, 0) < 0)
276 return error(_("unable to write tag file"));
277 return 0;
278 }
builtin/unpack-objects.c
+6 -6
@@ -204,8 +204,8 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf)
204 {
205 struct object_id oid;
206
207 - if (write_object_file(obj_buf->buffer, obj_buf->size,
208 - obj->type, &oid) < 0)
207 + if (odb_write_object(the_repository->objects, obj_buf->buffer, obj_buf->size,
208 + obj->type, &oid) < 0)
209 die("failed to write object %s", oid_to_hex(&obj->oid));
210 obj->flags |= FLAG_WRITTEN;
211 }
@@ -272,16 +272,16 @@ static void write_object(unsigned nr, enum object_type type,
272 void *buf, unsigned long size)
273 {
274 if (!strict) {
275 - if (write_object_file(buf, size, type,
276 - &obj_list[nr].oid) < 0)
275 + if (odb_write_object(the_repository->objects, buf, size, type,
276 + &obj_list[nr].oid) < 0)
277 die("failed to write object");
278 added_object(nr, type, buf, size);
279 free(buf);
280 obj_list[nr].obj = NULL;
281 } else if (type == OBJ_BLOB) {
282 struct blob *blob;
283 - if (write_object_file(buf, size, type,
284 - &obj_list[nr].oid) < 0)
283 + if (odb_write_object(the_repository->objects, buf, size, type,
284 + &obj_list[nr].oid) < 0)
285 die("failed to write object");
286 added_object(nr, type, buf, size);
287 free(buf);
cache-tree.c
+2 -3
@@ -456,9 +456,8 @@ static int update_one(struct cache_tree *it,
456 } else if (dryrun) {
457 hash_object_file(the_hash_algo, buffer.buf, buffer.len,
458 OBJ_TREE, &it->oid);
459 - } else if (write_object_file_flags(buffer.buf, buffer.len, OBJ_TREE,
460 - &it->oid, NULL, flags & WRITE_TREE_SILENT
461 - ? WRITE_OBJECT_FILE_SILENT : 0)) {
459 + } else if (odb_write_object_ext(the_repository->objects, buffer.buf, buffer.len, OBJ_TREE,
460 + &it->oid, NULL, flags & WRITE_TREE_SILENT ? WRITE_OBJECT_SILENT : 0)) {
461 strbuf_release(&buffer);
462 return -1;
463 }
commit.c
+2 -2
@@ -1797,8 +1797,8 @@ int commit_tree_extended(const char *msg, size_t msg_len,
1797 compat_oid = &compat_oid_buf;
1798 }
1799
1800 - result = write_object_file_flags(buffer.buf, buffer.len, OBJ_COMMIT,
1801 - ret, compat_oid, 0);
1800 + result = odb_write_object_ext(the_repository->objects, buffer.buf, buffer.len,
1801 + OBJ_COMMIT, ret, compat_oid, 0);
1802 out:
1803 free(parent_buf);
1804 strbuf_release(&buffer);
match-trees.c
+1 -1
@@ -246,7 +246,7 @@ static int splice_tree(struct repository *r,
246 rewrite_with = oid2;
247 }
248 hashcpy(rewrite_here, rewrite_with->hash, r->hash_algo);
249 - status = write_object_file(buf, sz, OBJ_TREE, result);
249 + status = odb_write_object(r->objects, buf, sz, OBJ_TREE, result);
250 free(buf);
251 return status;
252 }
merge-ort.c
+4 -3
@@ -2216,8 +2216,8 @@ static int handle_content_merge(struct merge_options *opt,
2216 }
2217
2218 if (!ret && record_object &&
2219 - write_object_file(result_buf.ptr, result_buf.size,
2220 - OBJ_BLOB, &result->oid)) {
2219 + odb_write_object(the_repository->objects, result_buf.ptr, result_buf.size,
2220 + OBJ_BLOB, &result->oid)) {
2221 path_msg(opt, ERROR_OBJECT_WRITE_FAILED, 0,
2222 pathnames[0], pathnames[1], pathnames[2], NULL,
2223 _("error: unable to add %s to database"), path);
@@ -3772,7 +3772,8 @@ static int write_tree(struct object_id *result_oid,
3772 }
3773
3774 /* Write this object file out, and record in result_oid */
3775 - if (write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid))
3775 + if (odb_write_object(the_repository->objects, buf.buf,
3776 + buf.len, OBJ_TREE, result_oid))
3777 ret = -1;
3778 strbuf_release(&buf);
3779 return ret;
notes-cache.c
+2 -1
@@ -98,7 +98,8 @@ int notes_cache_put(struct notes_cache *c, struct object_id *key_oid,
98 {
99 struct object_id value_oid;
100
101 - if (write_object_file(data, size, OBJ_BLOB, &value_oid) < 0)
101 + if (odb_write_object(the_repository->objects, data,
102 + size, OBJ_BLOB, &value_oid) < 0)
103 return -1;
104 return add_note(&c->tree, key_oid, &value_oid, NULL);
105 }
notes.c
+8 -4
@@ -682,7 +682,8 @@ static int tree_write_stack_finish_subtree(struct tree_write_stack *tws)
682 ret = tree_write_stack_finish_subtree(n);
683 if (ret)
684 return ret;
685 - ret = write_object_file(n->buf.buf, n->buf.len, OBJ_TREE, &s);
685 + ret = odb_write_object(the_repository->objects, n->buf.buf,
686 + n->buf.len, OBJ_TREE, &s);
687 if (ret)
688 return ret;
689 strbuf_release(&n->buf);
@@ -847,7 +848,8 @@ int combine_notes_concatenate(struct object_id *cur_oid,
848 free(new_msg);
849
850 /* create a new blob object from buf */
850 - ret = write_object_file(buf, buf_len, OBJ_BLOB, cur_oid);
851 + ret = odb_write_object(the_repository->objects, buf,
852 + buf_len, OBJ_BLOB, cur_oid);
853 free(buf);
854 return ret;
855 }
@@ -927,7 +929,8 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,
929 string_list_join_lines_helper, &buf))
930 goto out;
931
930 - ret = write_object_file(buf.buf, buf.len, OBJ_BLOB, cur_oid);
932 + ret = odb_write_object(the_repository->objects, buf.buf,
933 + buf.len, OBJ_BLOB, cur_oid);
934
935 out:
936 strbuf_release(&buf);
@@ -1215,7 +1218,8 @@ int write_notes_tree(struct notes_tree *t, struct object_id *result)
1218 ret = for_each_note(t, flags, write_each_note, &cb_data) ||
1219 write_each_non_note_until(NULL, &cb_data) ||
1220 tree_write_stack_finish_subtree(&root) ||
1218 - write_object_file(root.buf.buf, root.buf.len, OBJ_TREE, result);
1221 + odb_write_object(the_repository->objects, root.buf.buf,
1222 + root.buf.len, OBJ_TREE, result);
1223 strbuf_release(&root.buf);
1224 return ret;
1225 }
object-file.c
+9 -9
@@ -755,7 +755,7 @@ static int start_loose_object_common(struct strbuf *tmp_file,
755
756 fd = create_tmpfile(tmp_file, filename);
757 if (fd < 0) {
758 - if (flags & WRITE_OBJECT_FILE_SILENT)
758 + if (flags & WRITE_OBJECT_SILENT)
759 return -1;
760 else if (errno == EACCES)
761 return error(_("insufficient permission for adding "
@@ -887,7 +887,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,
887 utb.actime = mtime;
888 utb.modtime = mtime;
889 if (utime(tmp_file.buf, &utb) < 0 &&
890 - !(flags & WRITE_OBJECT_FILE_SILENT))
890 + !(flags & WRITE_OBJECT_SILENT))
891 warning_errno(_("failed utime() on %s"), tmp_file.buf);
892 }
893
@@ -1032,9 +1032,9 @@ cleanup:
1032 return err;
1033 }
1034
1035 -int write_object_file_flags(const void *buf, unsigned long len,
1036 - enum object_type type, struct object_id *oid,
1037 - struct object_id *compat_oid_in, unsigned flags)
1035 +int write_object_file(const void *buf, unsigned long len,
1036 + enum object_type type, struct object_id *oid,
1037 + struct object_id *compat_oid_in, unsigned flags)
1038 {
1039 struct repository *repo = the_repository;
1040 const struct git_hash_algo *algo = repo->hash_algo;
@@ -1159,7 +1159,7 @@ static int index_mem(struct index_state *istate,
1159 }
1160
1161 if (write_object)
1162 - ret = write_object_file(buf, size, type, oid);
1162 + ret = odb_write_object(istate->repo->objects, buf, size, type, oid);
1163 else
1164 hash_object_file(istate->repo->hash_algo, buf, size, type, oid);
1165
@@ -1184,8 +1184,8 @@ static int index_stream_convert_blob(struct index_state *istate,
1184 get_conv_flags(flags));
1185
1186 if (write_object)
1187 - ret = write_object_file(sbuf.buf, sbuf.len, OBJ_BLOB,
1188 - oid);
1187 + ret = odb_write_object(istate->repo->objects, sbuf.buf, sbuf.len, OBJ_BLOB,
1188 + oid);
1189 else
1190 hash_object_file(istate->repo->hash_algo, sbuf.buf, sbuf.len, OBJ_BLOB,
1191 oid);
@@ -1287,7 +1287,7 @@ int index_path(struct index_state *istate, struct object_id *oid,
1287 if (!(flags & INDEX_WRITE_OBJECT))
1288 hash_object_file(istate->repo->hash_algo, sb.buf, sb.len,
1289 OBJ_BLOB, oid);
1290 - else if (write_object_file(sb.buf, sb.len, OBJ_BLOB, oid))
1290 + else if (odb_write_object(the_repository->objects, sb.buf, sb.len, OBJ_BLOB, oid))
1291 rc = error(_("%s: failed to insert into database"), path);
1292 strbuf_release(&sb);
1293 break;
object-file.h
+3 -23
@@ -157,29 +157,9 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
157 struct object_info;
158 int parse_loose_header(const char *hdr, struct object_info *oi);
159
160 -enum {
161 - /*
162 - * By default, `write_object_file()` does not actually write
163 - * anything into the object store, but only computes the object ID.
164 - * This flag changes that so that the object will be written as a loose
165 - * object and persisted.
166 - */
167 - WRITE_OBJECT_FILE_PERSIST = (1 << 0),
168 -
169 - /*
170 - * Do not print an error in case something gose wrong.
171 - */
172 - WRITE_OBJECT_FILE_SILENT = (1 << 1),
173 -};
174 -
175 -int write_object_file_flags(const void *buf, unsigned long len,
176 - enum object_type type, struct object_id *oid,
177 - struct object_id *compat_oid_in, unsigned flags);
178 -static inline int write_object_file(const void *buf, unsigned long len,
179 - enum object_type type, struct object_id *oid)
180 -{
181 - return write_object_file_flags(buf, len, type, oid, NULL, 0);
182 -}
160 +int write_object_file(const void *buf, unsigned long len,
161 + enum object_type type, struct object_id *oid,
162 + struct object_id *compat_oid_in, unsigned flags);
163
164 struct input_stream {
165 const void *(*read)(struct input_stream *, unsigned long *len);
odb.c
+10
@@ -980,6 +980,16 @@ void odb_assert_oid_type(struct object_database *odb,
980 type_name(expect));
981 }
982
983 +int odb_write_object_ext(struct object_database *odb UNUSED,
984 + const void *buf, unsigned long len,
985 + enum object_type type,
986 + struct object_id *oid,
987 + struct object_id *compat_oid,
988 + unsigned flags)
989 +{
990 + return write_object_file(buf, len, type, oid, compat_oid, flags);
991 +}
992 +
993 struct object_database *odb_new(struct repository *repo)
994 {
995 struct object_database *o = xmalloc(sizeof(*o));
odb.h
+38
@@ -437,6 +437,44 @@ enum for_each_object_flags {
437 FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),
438 };
439
440 +enum {
441 + /*
442 + * By default, `odb_write_object()` does not actually write anything
443 + * into the object store, but only computes the object ID. This flag
444 + * changes that so that the object will be written as a loose object
445 + * and persisted.
446 + */
447 + WRITE_OBJECT_PERSIST = (1 << 0),
448 +
449 + /*
450 + * Do not print an error in case something goes wrong.
451 + */
452 + WRITE_OBJECT_SILENT = (1 << 1),
453 +};
454 +
455 +/*
456 + * Write an object into the object database. The object is being written into
457 + * the local alternate of the repository. If provided, the converted object ID
458 + * as well as the compatibility object ID are written to the respective
459 + * pointers.
460 + *
461 + * Returns 0 on success, a negative error code otherwise.
462 + */
463 +int odb_write_object_ext(struct object_database *odb,
464 + const void *buf, unsigned long len,
465 + enum object_type type,
466 + struct object_id *oid,
467 + struct object_id *compat_oid,
468 + unsigned flags);
469 +
470 +static inline int odb_write_object(struct object_database *odb,
471 + const void *buf, unsigned long len,
472 + enum object_type type,
473 + struct object_id *oid)
474 +{
475 + return odb_write_object_ext(odb, buf, len, type, oid, NULL, 0);
476 +}
477 +
478 /* Compatibility wrappers, to be removed once Git 2.51 has been released. */
479 #include "repository.h"
480
read-cache.c
+1 -1
@@ -690,7 +690,7 @@ static struct cache_entry *create_alias_ce(struct index_state *istate,
690 void set_object_name_for_intent_to_add_entry(struct cache_entry *ce)
691 {
692 struct object_id oid;
693 - if (write_object_file("", 0, OBJ_BLOB, &oid))
693 + if (odb_write_object(the_repository->objects, "", 0, OBJ_BLOB, &oid))
694 die(_("cannot create an empty blob in the object database"));
695 oidcpy(&ce->oid, &oid);
696 }