{"thread":{"id":"63771","subject":"[PATCH 00/19] object-file: get rid of `the_repository`","startedAt":"2025-07-09T11:17:22Z","lastAt":"2026-04-05T06:56:04Z","messageCount":60,"participants":["Patrick Steinhardt","Phillip Wood","Toon Claes","Karthik Nayak","Junio C Hamano","Ayush Chandekar","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":19},"messages":[{"id":"521648","messageId":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","threadId":"63771","inReplyTo":null,"subject":"[PATCH 00/19] object-file: get rid of `the_repository`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:10Z","receivedAt":"2025-07-09T11:17:22Z","isPatch":true,"body":"Hi,\n\nthis patch series refactors \"object-file.c\" to get rid of the dependency\non `the_repository`. In many such cases this is done by passing in a\n`struct odb_source`, which prepares us for eventually converting this\ninto the \"loose\" object source with pluggable object databases.\n\nThe patch series is built on top of a30f80fde92 (The eighth batch,\n2025-07-08) with \"ps/object-store\" at 841a03b4046 (odb: rename\n`read_object_with_reference()`, 2025-07-01) merged into it.\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (19):\n      object-file: fix -Wsign-compare warnings\n      object-file: stop using `the_hash_algo`\n      object-file: get rid of `the_repository` in `has_loose_object()`\n      object-file: inline `check_and_freshen()` functions\n      object-file: get rid of `the_repository` when freshening objects\n      object-file: get rid of `the_repository` in `loose_object_info()`\n      object-file: get rid of `the_repository` in `finalize_object_file()`\n      loose: write loose objects map via their source\n      odb: introduce `odb_write_object()`\n      object-file: get rid of `the_repository` when writing objects\n      object-file: inline `for_each_loose_file_in_objdir_buf()`\n      object-file: remove declaration for `for_each_file_in_obj_subdir()`\n      object-file: get rid of `the_repository` in loose object iterators\n      object-file: get rid of `the_repository` in `read_loose_object()`\n      object-file: get rid of `the_repository` in `force_object_loose()`\n      object-file: get rid of `the_repository` in index-related functions\n      environment: move compression level into repo settings\n      environment: move object creation mode into repo settings\n      object-file: drop USE_THE_REPOSITORY_VARIABLE\n\n apply.c                  |  11 +-\n builtin/cat-file.c       |   2 +-\n builtin/checkout.c       |   2 +-\n builtin/count-objects.c  |   2 +-\n builtin/fast-import.c    |  12 +-\n builtin/fsck.c           |  16 +--\n builtin/gc.c             |  10 +-\n builtin/index-pack.c     |   5 +-\n builtin/merge-file.c     |   3 +-\n builtin/mktag.c          |   2 +-\n builtin/mktree.c         |   2 +-\n builtin/notes.c          |   3 +-\n builtin/pack-objects.c   |  55 ++++++---\n builtin/prune.c          |   2 +-\n builtin/receive-pack.c   |   4 +-\n builtin/replace.c        |   3 +-\n builtin/tag.c            |   4 +-\n builtin/unpack-objects.c |  15 +--\n bulk-checkin.c           |   5 +-\n cache-tree.c             |   5 +-\n commit.c                 |   4 +-\n config.c                 |  50 --------\n diff.c                   |   3 +-\n environment.c            |   7 --\n environment.h            |   8 --\n http-push.c              |   3 +-\n http.c                   |   4 +-\n loose.c                  |  16 +--\n loose.h                  |   4 +-\n match-trees.c            |   2 +-\n merge-ort.c              |   7 +-\n midx-write.c             |   2 +-\n notes-cache.c            |   3 +-\n notes.c                  |  12 +-\n object-file.c            | 314 ++++++++++++++++++++++-------------------------\n object-file.h            |  65 +++-------\n odb.c                    |  10 ++\n odb.h                    |  38 ++++++\n pack-write.c             |  16 +--\n pack.h                   |   3 +-\n prune-packed.c           |   2 +-\n reachable.c              |   2 +-\n read-cache.c             |   2 +-\n repo-settings.c          |  54 ++++++++\n repo-settings.h          |   8 ++\n tmp-objdir.c             |   2 +-\n 46 files changed, 426 insertions(+), 378 deletions(-)\n\n\n---\nbase-commit: f0228c39bf2fe539583cd594671039f05765bc9b\nchange-id: 20250709-pks-object-file-wo-the-repository-9f41234c4747\n\n"},{"id":"521652","messageId":"20250709-pks-object-file-wo-the-repository-v1-1-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 01/19] object-file: fix -Wsign-compare warnings","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:11Z","receivedAt":"2025-07-09T11:17:24Z","isPatch":true,"body":"There are some trivial -Wsign-compare warnings in \"object-file.c\". Fix\nthem and drop the preprocessor define that disables those warnings.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 15 ++++++---------\n 1 file changed, 6 insertions(+), 9 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 3d674d1093e..987cf289420 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -8,7 +8,6 @@\n  */\n \n #define USE_THE_REPOSITORY_VARIABLE\n-#define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\n #include \"bulk-checkin.h\"\n@@ -44,8 +43,7 @@ static int get_conv_flags(unsigned flags)\n \n static void fill_loose_path(struct strbuf *buf, const struct object_id *oid)\n {\n-\tint i;\n-\tfor (i = 0; i < the_hash_algo->rawsz; i++) {\n+\tfor (size_t i = 0; i < the_hash_algo->rawsz; i++) {\n \t\tstatic char hex[] = \"0123456789abcdef\";\n \t\tunsigned int val = oid->hash[i];\n \t\tstrbuf_addch(buf, hex[val >> 4]);\n@@ -327,9 +325,8 @@ static void *unpack_loose_rest(git_zstream *stream,\n \t\t\t       void *buffer, unsigned long size,\n \t\t\t       const struct object_id *oid)\n {\n-\tint bytes = strlen(buffer) + 1;\n+\tsize_t bytes = strlen(buffer) + 1, n;\n \tunsigned char *buf = xmallocz(size);\n-\tunsigned long n;\n \tint status = Z_OK;\n \n \tn = stream->total_out - bytes;\n@@ -596,7 +593,7 @@ static int check_collision(const char *source, const char *dest)\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (sz_a < sizeof(buf_source))\n+\t\tif ((size_t) sz_a < sizeof(buf_source))\n \t\t\tbreak;\n \t}\n \n@@ -1240,7 +1237,7 @@ static int index_core(struct index_state *istate,\n \t\tif (read_result < 0)\n \t\t\tret = error_errno(_(\"read error while indexing %s\"),\n \t\t\t\t\t  path ? path : \"<unknown>\");\n-\t\telse if (read_result != size)\n+\t\telse if ((size_t) read_result != size)\n \t\t\tret = error(_(\"short read while indexing %s\"),\n \t\t\t\t    path ? path : \"<unknown>\");\n \t\telse\n@@ -1268,7 +1265,7 @@ int index_fd(struct index_state *istate, struct object_id *oid,\n \t\tret = index_stream_convert_blob(istate, oid, fd, path, flags);\n \telse if (!S_ISREG(st->st_mode))\n \t\tret = index_pipe(istate, oid, fd, type, path, flags);\n-\telse if (st->st_size <= repo_settings_get_big_file_threshold(the_repository) ||\n+\telse if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(the_repository)) ||\n \t\t type != OBJ_BLOB ||\n \t\t (path && would_convert_to_git(istate, path)))\n \t\tret = index_core(istate, oid, fd, xsize_t(st->st_size),\n@@ -1472,7 +1469,7 @@ struct oidtree *odb_loose_cache(struct odb_source *source,\n \tuint32_t *bitmap;\n \n \tif (subdir_nr < 0 ||\n-\t    subdir_nr >= bitsizeof(source->loose_objects_subdir_seen))\n+\t    (size_t) subdir_nr >= bitsizeof(source->loose_objects_subdir_seen))\n \t\tBUG(\"subdir_nr out of range\");\n \n \tbitmap = &source->loose_objects_subdir_seen[word_index];\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521649","messageId":"20250709-pks-object-file-wo-the-repository-v1-2-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 02/19] object-file: stop using `the_hash_algo`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:12Z","receivedAt":"2025-07-09T11:17:26Z","isPatch":true,"body":"There are a couple of users of the `the_hash_algo` macro, which\nimplicitly depends on `the_repository`. Adapt these callers to not do so\nanymore, either by deriving it from already-available context or by\nusing `the_repository->hash_algo`. The latter variant doesn't yet help\nto remove the global dependency, but such users will be adapted in the\nfollowing commits to not use `the_repository` anymore, either.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 40 ++++++++++++++++++++++++----------------\n object-file.h |  1 +\n 2 files changed, 25 insertions(+), 16 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 987cf289420..bc395febc9d 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -25,6 +25,7 @@\n #include \"pack.h\"\n #include \"packfile.h\"\n #include \"path.h\"\n+#include \"read-cache-ll.h\"\n #include \"setup.h\"\n #include \"streaming.h\"\n \n@@ -41,9 +42,11 @@ static int get_conv_flags(unsigned flags)\n \t\treturn 0;\n }\n \n-static void fill_loose_path(struct strbuf *buf, const struct object_id *oid)\n+static void fill_loose_path(struct strbuf *buf,\n+\t\t\t    const struct object_id *oid,\n+\t\t\t    const struct git_hash_algo *algop)\n {\n-\tfor (size_t i = 0; i < the_hash_algo->rawsz; i++) {\n+\tfor (size_t i = 0; i < algop->rawsz; i++) {\n \t\tstatic char hex[] = \"0123456789abcdef\";\n \t\tunsigned int val = oid->hash[i];\n \t\tstrbuf_addch(buf, hex[val >> 4]);\n@@ -60,7 +63,7 @@ const char *odb_loose_path(struct odb_source *source,\n \tstrbuf_reset(buf);\n \tstrbuf_addstr(buf, source->path);\n \tstrbuf_addch(buf, '/');\n-\tfill_loose_path(buf, oid);\n+\tfill_loose_path(buf, oid, source->odb->repo->hash_algo);\n \treturn buf->buf;\n }\n \n@@ -1165,7 +1168,7 @@ static int index_mem(struct index_state *istate,\n \n \t\topts.strict = 1;\n \t\topts.error_func = hash_format_check_report;\n-\t\tif (fsck_buffer(null_oid(the_hash_algo), type, buf, size, &opts))\n+\t\tif (fsck_buffer(null_oid(istate->repo->hash_algo), type, buf, size, &opts))\n \t\t\tdie(_(\"refusing to create malformed object\"));\n \t\tfsck_finish(&opts);\n \t}\n@@ -1173,7 +1176,7 @@ static int index_mem(struct index_state *istate,\n \tif (write_object)\n \t\tret = write_object_file(buf, size, type, oid);\n \telse\n-\t\thash_object_file(the_hash_algo, buf, size, type, oid);\n+\t\thash_object_file(istate->repo->hash_algo, buf, size, type, oid);\n \n \tstrbuf_release(&nbuf);\n \treturn ret;\n@@ -1199,7 +1202,7 @@ static int index_stream_convert_blob(struct index_state *istate,\n \t\tret = write_object_file(sbuf.buf, sbuf.len, OBJ_BLOB,\n \t\t\t\t\toid);\n \telse\n-\t\thash_object_file(the_hash_algo, sbuf.buf, sbuf.len, OBJ_BLOB,\n+\t\thash_object_file(istate->repo->hash_algo, sbuf.buf, sbuf.len, OBJ_BLOB,\n \t\t\t\t oid);\n \tstrbuf_release(&sbuf);\n \treturn ret;\n@@ -1297,7 +1300,7 @@ int index_path(struct index_state *istate, struct object_id *oid,\n \t\tif (strbuf_readlink(&sb, path, st->st_size))\n \t\t\treturn error_errno(\"readlink(\\\"%s\\\")\", path);\n \t\tif (!(flags & INDEX_WRITE_OBJECT))\n-\t\t\thash_object_file(the_hash_algo, sb.buf, sb.len,\n+\t\t\thash_object_file(istate->repo->hash_algo, sb.buf, sb.len,\n \t\t\t\t\t OBJ_BLOB, oid);\n \t\telse if (write_object_file(sb.buf, sb.len, OBJ_BLOB, oid))\n \t\t\trc = error(_(\"%s: failed to insert into database\"), path);\n@@ -1328,6 +1331,7 @@ int read_pack_header(int fd, struct pack_header *header)\n \n int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \t\t\t\tstruct strbuf *path,\n+\t\t\t\tconst struct git_hash_algo *algop,\n \t\t\t\teach_loose_object_fn obj_cb,\n \t\t\t\teach_loose_cruft_fn cruft_cb,\n \t\t\t\teach_loose_subdir_fn subdir_cb,\n@@ -1364,12 +1368,12 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \t\tnamelen = strlen(de->d_name);\n \t\tstrbuf_setlen(path, baselen);\n \t\tstrbuf_add(path, de->d_name, namelen);\n-\t\tif (namelen == the_hash_algo->hexsz - 2 &&\n+\t\tif (namelen == algop->hexsz - 2 &&\n \t\t    !hex_to_bytes(oid.hash + 1, de->d_name,\n-\t\t\t\t  the_hash_algo->rawsz - 1)) {\n-\t\t\toid_set_algo(&oid, the_hash_algo);\n-\t\t\tmemset(oid.hash + the_hash_algo->rawsz, 0,\n-\t\t\t       GIT_MAX_RAWSZ - the_hash_algo->rawsz);\n+\t\t\t\t  algop->rawsz - 1)) {\n+\t\t\toid_set_algo(&oid, algop);\n+\t\t\tmemset(oid.hash + algop->rawsz, 0,\n+\t\t\t       GIT_MAX_RAWSZ - algop->rawsz);\n \t\t\tif (obj_cb) {\n \t\t\t\tr = obj_cb(&oid, path->buf, data);\n \t\t\t\tif (r)\n@@ -1405,7 +1409,8 @@ int for_each_loose_file_in_objdir_buf(struct strbuf *path,\n \tint i;\n \n \tfor (i = 0; i < 256; i++) {\n-\t\tr = for_each_file_in_obj_subdir(i, path, obj_cb, cruft_cb,\n+\t\tr = for_each_file_in_obj_subdir(i, path, the_repository->hash_algo,\n+\t\t\t\t\t\tobj_cb, cruft_cb,\n \t\t\t\t\t\tsubdir_cb, data);\n \t\tif (r)\n \t\t\tbreak;\n@@ -1481,6 +1486,7 @@ struct oidtree *odb_loose_cache(struct odb_source *source,\n \t}\n \tstrbuf_addstr(&buf, source->path);\n \tfor_each_file_in_obj_subdir(subdir_nr, &buf,\n+\t\t\t\t    source->odb->repo->hash_algo,\n \t\t\t\t    append_loose_object,\n \t\t\t\t    NULL, NULL,\n \t\t\t\t    source->loose_objects_cache);\n@@ -1501,7 +1507,8 @@ static int check_stream_oid(git_zstream *stream,\n \t\t\t    const char *hdr,\n \t\t\t    unsigned long size,\n \t\t\t    const char *path,\n-\t\t\t    const struct object_id *expected_oid)\n+\t\t\t    const struct object_id *expected_oid,\n+\t\t\t    const struct git_hash_algo *algop)\n {\n \tstruct git_hash_ctx c;\n \tstruct object_id real_oid;\n@@ -1509,7 +1516,7 @@ static int check_stream_oid(git_zstream *stream,\n \tunsigned long total_read;\n \tint status = Z_OK;\n \n-\tthe_hash_algo->init_fn(&c);\n+\talgop->init_fn(&c);\n \tgit_hash_update(&c, hdr, stream->total_out);\n \n \t/*\n@@ -1594,7 +1601,8 @@ int read_loose_object(const char *path,\n \n \tif (*oi->typep == OBJ_BLOB &&\n \t    *size > repo_settings_get_big_file_threshold(the_repository)) {\n-\t\tif (check_stream_oid(&stream, hdr, *size, path, expected_oid) < 0)\n+\t\tif (check_stream_oid(&stream, hdr, *size, path, expected_oid,\n+\t\t\t\t     the_repository->hash_algo) < 0)\n \t\t\tgoto out_inflate;\n \t} else {\n \t\t*contents = unpack_loose_rest(&stream, hdr, *size, expected_oid);\ndiff --git a/object-file.h b/object-file.h\nindex 67b4ffc4808..222ff2871a1 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -89,6 +89,7 @@ typedef int each_loose_subdir_fn(unsigned int nr,\n \t\t\t\t void *data);\n int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \t\t\t\tstruct strbuf *path,\n+\t\t\t\tconst struct git_hash_algo *algo,\n \t\t\t\teach_loose_object_fn obj_cb,\n \t\t\t\teach_loose_cruft_fn cruft_cb,\n \t\t\t\teach_loose_subdir_fn subdir_cb,\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521650","messageId":"20250709-pks-object-file-wo-the-repository-v1-3-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 03/19] object-file: get rid of `the_repository` in `has_loose_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:13Z","receivedAt":"2025-07-09T11:17:29Z","isPatch":true,"body":"We implicitly depend on `the_repository` in `has_loose_object()`.\nRefactor the function to accept an `odb_source` as input that should be\nchecked for such a loose object.\n\nThis refactoring changes semantics of the function to not check the\nwhole object database for such a loose object anymore, but instead we\nnow only check that single source. Existing callers thus need to loop\nthrough all sources manually now.\n\nWhile this change may seem illogical at first, whether or not an object\nexists in a specific format should be answered by the source using that\nformat. As such, we can eventually convert this into a generic function\n`odb_source_has_object()` that simply checks whether a given object\nexists in an object source. And as we will know about the format that\nany given source uses it allows us to derive whether the object exists\nin a given format.\n\nThis change also makes `has_loose_object_nonlocal()` obsolete. The only\ncaller of this function is adapted so that it skips the primary object\nsource.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/pack-objects.c | 24 ++++++++++++++++++++----\n object-file.c          | 16 +++++++---------\n object-file.h          |  7 +++----\n 3 files changed, 30 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 5781dec9808..a44f0ce1c78 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -1703,8 +1703,16 @@ static int want_object_in_pack_mtime(const struct object_id *oid,\n \tstruct list_head *pos;\n \tstruct multi_pack_index *m;\n \n-\tif (!exclude && local && has_loose_object_nonlocal(oid))\n-\t\treturn 0;\n+\tif (!exclude && local) {\n+\t\t/*\n+\t\t * Note that we start iterating at `sources->next` so that we\n+\t\t * skip the local object source.\n+\t\t */\n+\t\tstruct odb_source *source = the_repository->objects->sources->next;\n+\t\tfor (; source; source = source->next)\n+\t\t\tif (has_loose_object(source, oid))\n+\t\t\t\treturn 0;\n+\t}\n \n \t/*\n \t * If we already know the pack object lives in, start checks from that\n@@ -3928,7 +3936,14 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type\n \t} else {\n \t\tif (!want_object_in_pack_mtime(oid, 0, &pack, &offset, mtime))\n \t\t\treturn;\n-\t\tif (!pack && type == OBJ_BLOB && !has_loose_object(oid)) {\n+\t\tif (!pack && type == OBJ_BLOB) {\n+\t\t\tstruct odb_source *source = the_repository->objects->sources;\n+\t\t\tint found = 0;\n+\n+\t\t\tfor (; !found && source; source = source->next)\n+\t\t\t\tif (has_loose_object(source, oid))\n+\t\t\t\t\tfound = 1;\n+\n \t\t\t/*\n \t\t\t * If a traversed tree has a missing blob then we want\n \t\t\t * to avoid adding that missing object to our pack.\n@@ -3942,7 +3957,8 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type\n \t\t\t * limited to \"ensure non-tip blobs which don't exist in\n \t\t\t * packs do exist via loose objects\". Confused?\n \t\t\t */\n-\t\t\treturn;\n+\t\t\tif (!found)\n+\t\t\t\treturn;\n \t\t}\n \n \t\tentry = create_object_entry(oid, type, pack_name_hash_fn(name),\ndiff --git a/object-file.c b/object-file.c\nindex bc395febc9d..7aecaa3d2a0 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -121,14 +121,10 @@ static int check_and_freshen(const struct object_id *oid, int freshen)\n \t       check_and_freshen_nonlocal(oid, freshen);\n }\n \n-int has_loose_object_nonlocal(const struct object_id *oid)\n+int has_loose_object(struct odb_source *source,\n+\t\t     const struct object_id *oid)\n {\n-\treturn check_and_freshen_nonlocal(oid, 0);\n-}\n-\n-int has_loose_object(const struct object_id *oid)\n-{\n-\treturn check_and_freshen(oid, 0);\n+\treturn check_and_freshen_odb(source, oid, 0);\n }\n \n int format_object_header(char *str, size_t size, enum object_type type,\n@@ -1103,8 +1099,10 @@ int force_object_loose(const struct object_id *oid, time_t mtime)\n \tint hdrlen;\n \tint ret;\n \n-\tif (has_loose_object(oid))\n-\t\treturn 0;\n+\tfor (struct odb_source *source = repo->objects->sources; source; source = source->next)\n+\t\tif (has_loose_object(source, oid))\n+\t\t\treturn 0;\n+\n \toi.typep = &type;\n \toi.sizep = &len;\n \toi.contentp = &buf;\ndiff --git a/object-file.h b/object-file.h\nindex 222ff2871a1..5b63a05ab51 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -45,13 +45,12 @@ const char *odb_loose_path(struct odb_source *source,\n \t\t\t   const struct object_id *oid);\n \n /*\n- * Return true iff an alternate object database has a loose object\n+ * Return true iff an object database source has a loose object\n  * with the specified name.  This function does not respect replace\n  * references.\n  */\n-int has_loose_object_nonlocal(const struct object_id *);\n-\n-int has_loose_object(const struct object_id *);\n+int has_loose_object(struct odb_source *source,\n+\t\t     const struct object_id *oid);\n \n void *map_loose_object(struct repository *r, const struct object_id *oid,\n \t\t       unsigned long *size);\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521651","messageId":"20250709-pks-object-file-wo-the-repository-v1-4-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 04/19] object-file: inline `check_and_freshen()` functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:14Z","receivedAt":"2025-07-09T11:17:33Z","isPatch":true,"body":"The `check_and_freshen()` functions are only used by a single caller\nnow. Inline them into `freshen_loose_object()`.\n\nWhile at it, rename `check_and_freshen_odb()` to `_source()` to reflect\nthat it works on a single object source instead of on the whole database.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 41 +++++++++++++----------------------------\n 1 file changed, 13 insertions(+), 28 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 7aecaa3d2a0..9e17e608f78 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -89,42 +89,19 @@ int check_and_freshen_file(const char *fn, int freshen)\n \treturn 1;\n }\n \n-static int check_and_freshen_odb(struct odb_source *source,\n-\t\t\t\t const struct object_id *oid,\n-\t\t\t\t int freshen)\n+static int check_and_freshen_source(struct odb_source *source,\n+\t\t\t\t    const struct object_id *oid,\n+\t\t\t\t    int freshen)\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \todb_loose_path(source, &path, oid);\n \treturn check_and_freshen_file(path.buf, freshen);\n }\n \n-static int check_and_freshen_local(const struct object_id *oid, int freshen)\n-{\n-\treturn check_and_freshen_odb(the_repository->objects->sources, oid, freshen);\n-}\n-\n-static int check_and_freshen_nonlocal(const struct object_id *oid, int freshen)\n-{\n-\tstruct odb_source *source;\n-\n-\todb_prepare_alternates(the_repository->objects);\n-\tfor (source = the_repository->objects->sources->next; source; source = source->next) {\n-\t\tif (check_and_freshen_odb(source, oid, freshen))\n-\t\t\treturn 1;\n-\t}\n-\treturn 0;\n-}\n-\n-static int check_and_freshen(const struct object_id *oid, int freshen)\n-{\n-\treturn check_and_freshen_local(oid, freshen) ||\n-\t       check_and_freshen_nonlocal(oid, freshen);\n-}\n-\n int has_loose_object(struct odb_source *source,\n \t\t     const struct object_id *oid)\n {\n-\treturn check_and_freshen_odb(source, oid, 0);\n+\treturn check_and_freshen_source(source, oid, 0);\n }\n \n int format_object_header(char *str, size_t size, enum object_type type,\n@@ -918,7 +895,15 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \n static int freshen_loose_object(const struct object_id *oid)\n {\n-\treturn check_and_freshen(oid, 1);\n+\tstruct odb_source *source;\n+\n+\todb_prepare_alternates(the_repository->objects);\n+\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\tif (check_and_freshen_source(source, oid, 1))\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n }\n \n static int freshen_packed_object(const struct object_id *oid)\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521653","messageId":"20250709-pks-object-file-wo-the-repository-v1-5-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 05/19] object-file: get rid of `the_repository` when freshening objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:15Z","receivedAt":"2025-07-09T11:17:36Z","isPatch":true,"body":"We implicitly depend on `the_repository` when freshening either loose or\npacked objects. Refactor these functions to instead accept an object\ndatabase as input so that we can get rid of the global dependency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 9e17e608f78..3453989b7e3 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -893,23 +893,21 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\t\t\t\t  FOF_SKIP_COLLISION_CHECK);\n }\n \n-static int freshen_loose_object(const struct object_id *oid)\n+static int freshen_loose_object(struct object_database *odb,\n+\t\t\t\tconst struct object_id *oid)\n {\n-\tstruct odb_source *source;\n-\n-\todb_prepare_alternates(the_repository->objects);\n-\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\todb_prepare_alternates(odb);\n+\tfor (struct odb_source *source = odb->sources; source; source = source->next)\n \t\tif (check_and_freshen_source(source, oid, 1))\n \t\t\treturn 1;\n-\t}\n-\n \treturn 0;\n }\n \n-static int freshen_packed_object(const struct object_id *oid)\n+static int freshen_packed_object(struct object_database *odb,\n+\t\t\t\t const struct object_id *oid)\n {\n \tstruct pack_entry e;\n-\tif (!find_pack_entry(the_repository, oid, &e))\n+\tif (!find_pack_entry(odb->repo, oid, &e))\n \t\treturn 0;\n \tif (e.p->is_cruft)\n \t\treturn 0;\n@@ -999,7 +997,8 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tdie(_(\"deflateEnd on stream object failed (%d)\"), ret);\n \tclose_loose_object(fd, tmp_file.buf);\n \n-\tif (freshen_packed_object(oid) || freshen_loose_object(oid)) {\n+\tif (freshen_packed_object(the_repository->objects, oid) ||\n+\t    freshen_loose_object(the_repository->objects, oid)) {\n \t\tunlink_or_warn(tmp_file.buf);\n \t\tgoto cleanup;\n \t}\n@@ -1062,7 +1061,8 @@ int write_object_file_flags(const void *buf, unsigned long len,\n \t * it out into .git/objects/??/?{38} file.\n \t */\n \twrite_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);\n-\tif (freshen_packed_object(oid) || freshen_loose_object(oid))\n+\tif (freshen_packed_object(repo->objects, oid) ||\n+\t    freshen_loose_object(repo->objects, oid))\n \t\treturn 0;\n \tif (write_loose_object(oid, hdr, hdrlen, buf, len, 0, flags))\n \t\treturn -1;\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521654","messageId":"20250709-pks-object-file-wo-the-repository-v1-6-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 06/19] object-file: get rid of `the_repository` in `loose_object_info()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:16Z","receivedAt":"2025-07-09T11:17:38Z","isPatch":true,"body":"While `loose_object_info()` already accepts a repository as parameter we\nstill have one callsite in there where we use `the_repository` to figure\nout the hash algorithm. Use the passed-in repository instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 3453989b7e3..800eeae85af 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -421,7 +421,7 @@ int loose_object_info(struct repository *r,\n \tenum object_type type_scratch;\n \n \tif (oi->delta_base_oid)\n-\t\toidclr(oi->delta_base_oid, the_repository->hash_algo);\n+\t\toidclr(oi->delta_base_oid, r->hash_algo);\n \n \t/*\n \t * If we don't care about type or size, then we don't\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521655","messageId":"20250709-pks-object-file-wo-the-repository-v1-7-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 07/19] object-file: get rid of `the_repository` in `finalize_object_file()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:17Z","receivedAt":"2025-07-09T11:17:42Z","isPatch":true,"body":"We implicitly depend on `the_repository` when moving an object file into\nplace in `finalize_object_file()`. Get rid of this global dependency by\npassing in a repository.\n\nNote that one might be pressed to inject an object database instead of a\nrepository. But the function doesn't really care about the ODB at all.\nAll it does is to move a file into place while checking whether there is\nany collision. As such, the functionality it provides is independent of\nthe object database and only needs the repository as parameter so that\nit can adjust permissions of the file we are about to finalize.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-import.c  |  4 ++--\n builtin/index-pack.c   |  2 +-\n builtin/pack-objects.c |  2 +-\n bulk-checkin.c         |  2 +-\n http.c                 |  4 ++--\n midx-write.c           |  2 +-\n object-file.c          | 14 ++++++++------\n object-file.h          |  6 ++++--\n pack-write.c           | 16 +++++++++-------\n pack.h                 |  3 ++-\n tmp-objdir.c           |  2 +-\n 11 files changed, 32 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex b1389c59211..89f57898b15 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -821,11 +821,11 @@ static char *keep_pack(const char *curr_index_name)\n \t\tdie_errno(\"failed to write keep file\");\n \n \todb_pack_name(pack_data->repo, &name, pack_data->hash, \"pack\");\n-\tif (finalize_object_file(pack_data->pack_name, name.buf))\n+\tif (finalize_object_file(pack_data->repo, pack_data->pack_name, name.buf))\n \t\tdie(\"cannot store pack file\");\n \n \todb_pack_name(pack_data->repo, &name, pack_data->hash, \"idx\");\n-\tif (finalize_object_file(curr_index_name, name.buf))\n+\tif (finalize_object_file(pack_data->repo, curr_index_name, name.buf))\n \t\tdie(\"cannot store index file\");\n \tfree((void *)curr_index_name);\n \treturn strbuf_detach(&name, NULL);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 19c67a85344..dabeb825a6c 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1598,7 +1598,7 @@ static void rename_tmp_packfile(const char **final_name,\n \tif (!*final_name || strcmp(*final_name, curr_name)) {\n \t\tif (!*final_name)\n \t\t\t*final_name = odb_pack_name(the_repository, name, hash, ext);\n-\t\tif (finalize_object_file(curr_name, *final_name))\n+\t\tif (finalize_object_file(the_repository, curr_name, *final_name))\n \t\t\tdie(_(\"unable to rename temporary '*.%s' file to '%s'\"),\n \t\t\t    ext, *final_name);\n \t} else if (make_read_only_if_same) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex a44f0ce1c78..e8e85d8278b 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -1449,7 +1449,7 @@ static void write_pack_file(void)\n \t\t\t\tstrbuf_setlen(&tmpname, tmpname_len);\n \t\t\t}\n \n-\t\t\trename_tmp_packfile_idx(&tmpname, &idx_tmp_name);\n+\t\t\trename_tmp_packfile_idx(the_repository, &tmpname, &idx_tmp_name);\n \n \t\t\tfree(idx_tmp_name);\n \t\t\tstrbuf_release(&tmpname);\ndiff --git a/bulk-checkin.c b/bulk-checkin.c\nindex 16df86c0ba8..b2809ab0398 100644\n--- a/bulk-checkin.c\n+++ b/bulk-checkin.c\n@@ -46,7 +46,7 @@ static void finish_tmp_packfile(struct strbuf *basename,\n \tstage_tmp_packfiles(the_repository, basename, pack_tmp_name,\n \t\t\t    written_list, nr_written, NULL, pack_idx_opts, hash,\n \t\t\t    &idx_tmp_name);\n-\trename_tmp_packfile_idx(basename, &idx_tmp_name);\n+\trename_tmp_packfile_idx(the_repository, basename, &idx_tmp_name);\n \n \tfree(idx_tmp_name);\n }\ndiff --git a/http.c b/http.c\nindex 9b62f627dc5..7cc797116bb 100644\n--- a/http.c\n+++ b/http.c\n@@ -2331,7 +2331,7 @@ int http_get_file(const char *url, const char *filename,\n \tret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options);\n \tfclose(result);\n \n-\tif (ret == HTTP_OK && finalize_object_file(tmpfile.buf, filename))\n+\tif (ret == HTTP_OK && finalize_object_file(the_repository, tmpfile.buf, filename))\n \t\tret = HTTP_ERROR;\n cleanup:\n \tstrbuf_release(&tmpfile);\n@@ -2815,7 +2815,7 @@ int finish_http_object_request(struct http_object_request *freq)\n \t\treturn -1;\n \t}\n \todb_loose_path(the_repository->objects->sources, &filename, &freq->oid);\n-\tfreq->rename = finalize_object_file(freq->tmpfile.buf, filename.buf);\n+\tfreq->rename = finalize_object_file(the_repository, freq->tmpfile.buf, filename.buf);\n \tstrbuf_release(&filename);\n \n \treturn freq->rename;\ndiff --git a/midx-write.c b/midx-write.c\nindex f2cfb85476e..effacade2d3 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -667,7 +667,7 @@ static void write_midx_reverse_index(struct write_midx_context *ctx,\n \ttmp_file = write_rev_file_order(ctx->repo, NULL, ctx->pack_order,\n \t\t\t\t\tctx->entries_nr, midx_hash, WRITE_REV);\n \n-\tif (finalize_object_file(tmp_file, buf.buf))\n+\tif (finalize_object_file(ctx->repo, tmp_file, buf.buf))\n \t\tdie(_(\"cannot store reverse index file\"));\n \n \tstrbuf_release(&buf);\ndiff --git a/object-file.c b/object-file.c\nindex 800eeae85af..6a7049a9e98 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -584,12 +584,14 @@ static int check_collision(const char *source, const char *dest)\n /*\n  * Move the just written object into its final resting place.\n  */\n-int finalize_object_file(const char *tmpfile, const char *filename)\n+int finalize_object_file(struct repository *repo,\n+\t\t\t const char *tmpfile, const char *filename)\n {\n-\treturn finalize_object_file_flags(tmpfile, filename, 0);\n+\treturn finalize_object_file_flags(repo, tmpfile, filename, 0);\n }\n \n-int finalize_object_file_flags(const char *tmpfile, const char *filename,\n+int finalize_object_file_flags(struct repository *repo,\n+\t\t\t       const char *tmpfile, const char *filename,\n \t\t\t       enum finalize_object_file_flags flags)\n {\n \tunsigned retries = 0;\n@@ -649,7 +651,7 @@ int finalize_object_file_flags(const char *tmpfile, const char *filename,\n \t}\n \n out:\n-\tif (adjust_shared_perm(the_repository, filename))\n+\tif (adjust_shared_perm(repo, filename))\n \t\treturn error(_(\"unable to set permission to '%s'\"), filename);\n \treturn 0;\n }\n@@ -889,7 +891,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\t\twarning_errno(_(\"failed utime() on %s\"), tmp_file.buf);\n \t}\n \n-\treturn finalize_object_file_flags(tmp_file.buf, filename.buf,\n+\treturn finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n \t\t\t\t\t  FOF_SKIP_COLLISION_CHECK);\n }\n \n@@ -1020,7 +1022,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tstrbuf_release(&dir);\n \t}\n \n-\terr = finalize_object_file_flags(tmp_file.buf, filename.buf,\n+\terr = finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n \t\t\t\t\t FOF_SKIP_COLLISION_CHECK);\n \tif (!err && compat)\n \t\terr = repo_add_loose_object_map(the_repository, oid, &compat_oid);\ndiff --git a/object-file.h b/object-file.h\nindex 5b63a05ab51..370139e0762 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -218,8 +218,10 @@ enum finalize_object_file_flags {\n \tFOF_SKIP_COLLISION_CHECK = 1,\n };\n \n-int finalize_object_file(const char *tmpfile, const char *filename);\n-int finalize_object_file_flags(const char *tmpfile, const char *filename,\n+int finalize_object_file(struct repository *repo,\n+\t\t\t const char *tmpfile, const char *filename);\n+int finalize_object_file_flags(struct repository *repo,\n+\t\t\t       const char *tmpfile, const char *filename,\n \t\t\t       enum finalize_object_file_flags flags);\n \n void hash_object_file(const struct git_hash_algo *algo, const void *buf,\ndiff --git a/pack-write.c b/pack-write.c\nindex eccdc798e36..83eaf88541e 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -538,22 +538,24 @@ struct hashfile *create_tmp_packfile(struct repository *repo,\n \treturn hashfd(repo->hash_algo, fd, *pack_tmp_name);\n }\n \n-static void rename_tmp_packfile(struct strbuf *name_prefix, const char *source,\n+static void rename_tmp_packfile(struct repository *repo,\n+\t\t\t\tstruct strbuf *name_prefix, const char *source,\n \t\t\t\tconst char *ext)\n {\n \tsize_t name_prefix_len = name_prefix->len;\n \n \tstrbuf_addstr(name_prefix, ext);\n-\tif (finalize_object_file(source, name_prefix->buf))\n+\tif (finalize_object_file(repo, source, name_prefix->buf))\n \t\tdie(\"unable to rename temporary file to '%s'\",\n \t\t    name_prefix->buf);\n \tstrbuf_setlen(name_prefix, name_prefix_len);\n }\n \n-void rename_tmp_packfile_idx(struct strbuf *name_buffer,\n+void rename_tmp_packfile_idx(struct repository *repo,\n+\t\t\t     struct strbuf *name_buffer,\n \t\t\t     char **idx_tmp_name)\n {\n-\trename_tmp_packfile(name_buffer, *idx_tmp_name, \"idx\");\n+\trename_tmp_packfile(repo, name_buffer, *idx_tmp_name, \"idx\");\n }\n \n void stage_tmp_packfiles(struct repository *repo,\n@@ -586,11 +588,11 @@ void stage_tmp_packfiles(struct repository *repo,\n \t\t\t\t\t\t    hash);\n \t}\n \n-\trename_tmp_packfile(name_buffer, pack_tmp_name, \"pack\");\n+\trename_tmp_packfile(repo, name_buffer, pack_tmp_name, \"pack\");\n \tif (rev_tmp_name)\n-\t\trename_tmp_packfile(name_buffer, rev_tmp_name, \"rev\");\n+\t\trename_tmp_packfile(repo, name_buffer, rev_tmp_name, \"rev\");\n \tif (mtimes_tmp_name)\n-\t\trename_tmp_packfile(name_buffer, mtimes_tmp_name, \"mtimes\");\n+\t\trename_tmp_packfile(repo, name_buffer, mtimes_tmp_name, \"mtimes\");\n \n \tfree(rev_tmp_name);\n \tfree(mtimes_tmp_name);\ndiff --git a/pack.h b/pack.h\nindex 5d4393eaffe..ec76472e49b 100644\n--- a/pack.h\n+++ b/pack.h\n@@ -145,7 +145,8 @@ void stage_tmp_packfiles(struct repository *repo,\n \t\t\t struct pack_idx_option *pack_idx_opts,\n \t\t\t unsigned char hash[],\n \t\t\t char **idx_tmp_name);\n-void rename_tmp_packfile_idx(struct strbuf *basename,\n+void rename_tmp_packfile_idx(struct repository *repo,\n+\t\t\t     struct strbuf *basename,\n \t\t\t     char **idx_tmp_name);\n \n #endif\ndiff --git a/tmp-objdir.c b/tmp-objdir.c\nindex ae01eae9c41..9f5a1788cd7 100644\n--- a/tmp-objdir.c\n+++ b/tmp-objdir.c\n@@ -227,7 +227,7 @@ static int migrate_one(struct tmp_objdir *t,\n \t\t\treturn -1;\n \t\treturn migrate_paths(t, src, dst, flags);\n \t}\n-\treturn finalize_object_file_flags(src->buf, dst->buf, flags);\n+\treturn finalize_object_file_flags(t->repo, src->buf, dst->buf, flags);\n }\n \n static int is_loose_object_shard(const char *name)\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521656","messageId":"20250709-pks-object-file-wo-the-repository-v1-8-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 08/19] loose: write loose objects map via their source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:18Z","receivedAt":"2025-07-09T11:17:45Z","isPatch":true,"body":"When a repository is configured to have a compatibility hash algorithm\nwe keep track of object ID mappings for loose objects via the loose\nobject map. This map simply maps an object ID of the actual hash to the\nobject ID of the compatibility hash. This loose object map is an\ninherent property of the loose files backend and thus of one specific\nobject source.\n\nRefactor the interfaces to reflect this by requiring a `struct\nodb_source` as input instead of a repository. This prepares for\nsubsequent commits where we will refactor writing of loose objects to\nwork on a `struct odb_source`, as well.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n loose.c       | 16 +++++++++-------\n loose.h       |  4 +++-\n object-file.c |  6 +++---\n 3 files changed, 15 insertions(+), 11 deletions(-)\n\ndiff --git a/loose.c b/loose.c\nindex 519f5db7935..e8ea6e7e24b 100644\n--- a/loose.c\n+++ b/loose.c\n@@ -166,7 +166,8 @@ int repo_write_loose_object_map(struct repository *repo)\n \treturn -1;\n }\n \n-static int write_one_object(struct repository *repo, const struct object_id *oid,\n+static int write_one_object(struct odb_source *source,\n+\t\t\t    const struct object_id *oid,\n \t\t\t    const struct object_id *compat_oid)\n {\n \tstruct lock_file lock;\n@@ -174,7 +175,7 @@ static int write_one_object(struct repository *repo, const struct object_id *oid\n \tstruct stat st;\n \tstruct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;\n \n-\trepo_common_path_replace(repo, &path, \"objects/loose-object-idx\");\n+\tstrbuf_addf(&path, \"%s/loose-object-idx\", source->path);\n \thold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1);\n \n \tfd = open(path.buf, O_WRONLY | O_CREAT | O_APPEND, 0666);\n@@ -190,7 +191,7 @@ static int write_one_object(struct repository *repo, const struct object_id *oid\n \t\tgoto errout;\n \tif (close(fd))\n \t\tgoto errout;\n-\tadjust_shared_perm(repo, path.buf);\n+\tadjust_shared_perm(source->odb->repo, path.buf);\n \trollback_lock_file(&lock);\n \tstrbuf_release(&buf);\n \tstrbuf_release(&path);\n@@ -204,17 +205,18 @@ static int write_one_object(struct repository *repo, const struct object_id *oid\n \treturn -1;\n }\n \n-int repo_add_loose_object_map(struct repository *repo, const struct object_id *oid,\n+int repo_add_loose_object_map(struct odb_source *source,\n+\t\t\t      const struct object_id *oid,\n \t\t\t      const struct object_id *compat_oid)\n {\n \tint inserted = 0;\n \n-\tif (!should_use_loose_object_map(repo))\n+\tif (!should_use_loose_object_map(source->odb->repo))\n \t\treturn 0;\n \n-\tinserted = insert_loose_map(repo->objects->sources, oid, compat_oid);\n+\tinserted = insert_loose_map(source, oid, compat_oid);\n \tif (inserted)\n-\t\treturn write_one_object(repo, oid, compat_oid);\n+\t\treturn write_one_object(source, oid, compat_oid);\n \treturn 0;\n }\n \ndiff --git a/loose.h b/loose.h\nindex 28512306e5f..6af1702973c 100644\n--- a/loose.h\n+++ b/loose.h\n@@ -4,6 +4,7 @@\n #include \"khash.h\"\n \n struct repository;\n+struct odb_source;\n \n struct loose_object_map {\n \tkh_oid_map_t *to_compat;\n@@ -16,7 +17,8 @@ int repo_loose_object_map_oid(struct repository *repo,\n \t\t\t      const struct object_id *src,\n \t\t\t      const struct git_hash_algo *dest_algo,\n \t\t\t      struct object_id *dest);\n-int repo_add_loose_object_map(struct repository *repo, const struct object_id *oid,\n+int repo_add_loose_object_map(struct odb_source *source,\n+\t\t\t      const struct object_id *oid,\n \t\t\t      const struct object_id *compat_oid);\n int repo_read_loose_object_map(struct repository *repo);\n int repo_write_loose_object_map(struct repository *repo);\ndiff --git a/object-file.c b/object-file.c\nindex 6a7049a9e98..a9248760a26 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1025,7 +1025,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \terr = finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n \t\t\t\t\t FOF_SKIP_COLLISION_CHECK);\n \tif (!err && compat)\n-\t\terr = repo_add_loose_object_map(the_repository, oid, &compat_oid);\n+\t\terr = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n cleanup:\n \tstrbuf_release(&tmp_file);\n \tstrbuf_release(&filename);\n@@ -1069,7 +1069,7 @@ int write_object_file_flags(const void *buf, unsigned long len,\n \tif (write_loose_object(oid, hdr, hdrlen, buf, len, 0, flags))\n \t\treturn -1;\n \tif (compat)\n-\t\treturn repo_add_loose_object_map(repo, oid, &compat_oid);\n+\t\treturn repo_add_loose_object_map(repo->objects->sources, oid, &compat_oid);\n \treturn 0;\n }\n \n@@ -1103,7 +1103,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime)\n \thdrlen = format_object_header(hdr, sizeof(hdr), type, len);\n \tret = write_loose_object(oid, hdr, hdrlen, buf, len, mtime, 0);\n \tif (!ret && compat)\n-\t\tret = repo_add_loose_object_map(the_repository, oid, &compat_oid);\n+\t\tret = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n \tfree(buf);\n \n \treturn ret;\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521657","messageId":"20250709-pks-object-file-wo-the-repository-v1-9-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 09/19] odb: introduce `odb_write_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:19Z","receivedAt":"2025-07-09T11:17:48Z","isPatch":true,"body":"We do not have a backend-agnostic way to write objects into an object\ndatabase. While there is `write_object_file()`, this function is rather\nspecific to the loose object format.\n\nIntroduce `odb_write_object()` to plug this gap. For now, this function\nis a simple wrapper around `write_object_file()` and doesn't even use\nthe passed-in object database yet. This will change in subsequent\ncommits, where `write_object_file()` is converted so that it works on\ntop of an `odb_source`. `odb_write_object()` will then become\nresponsible for deciding which source an object shall be written to.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n apply.c                  | 11 +++++++----\n builtin/checkout.c       |  2 +-\n builtin/merge-file.c     |  3 ++-\n builtin/mktag.c          |  2 +-\n builtin/mktree.c         |  2 +-\n builtin/notes.c          |  3 ++-\n builtin/receive-pack.c   |  4 ++--\n builtin/replace.c        |  3 ++-\n builtin/tag.c            |  4 ++--\n builtin/unpack-objects.c | 12 ++++++------\n cache-tree.c             |  5 ++---\n commit.c                 |  4 ++--\n match-trees.c            |  2 +-\n merge-ort.c              |  7 ++++---\n notes-cache.c            |  3 ++-\n notes.c                  | 12 ++++++++----\n object-file.c            | 18 +++++++++---------\n object-file.h            | 26 +++-----------------------\n odb.c                    | 10 ++++++++++\n odb.h                    | 38 ++++++++++++++++++++++++++++++++++++++\n read-cache.c             |  2 +-\n 21 files changed, 106 insertions(+), 67 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex a6836692d0c..ffb9d9f76d6 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3621,7 +3621,7 @@ static int try_threeway(struct apply_state *state,\n \n \t/* Preimage the patch was prepared for */\n \tif (patch->is_new)\n-\t\twrite_object_file(\"\", 0, OBJ_BLOB, &pre_oid);\n+\t\todb_write_object(the_repository->objects, \"\", 0, OBJ_BLOB, &pre_oid);\n \telse if (repo_get_oid(the_repository, patch->old_oid_prefix, &pre_oid) ||\n \t\t read_blob_object(&buf, &pre_oid, patch->old_mode))\n \t\treturn error(_(\"repository lacks the necessary blob to perform 3-way merge.\"));\n@@ -3637,7 +3637,8 @@ static int try_threeway(struct apply_state *state,\n \t\treturn -1;\n \t}\n \t/* post_oid is theirs */\n-\twrite_object_file(tmp_image.buf.buf, tmp_image.buf.len, OBJ_BLOB, &post_oid);\n+\todb_write_object(the_repository->objects, tmp_image.buf.buf,\n+\t\t\t tmp_image.buf.len, OBJ_BLOB, &post_oid);\n \timage_clear(&tmp_image);\n \n \t/* our_oid is ours */\n@@ -3650,7 +3651,8 @@ static int try_threeway(struct apply_state *state,\n \t\t\treturn error(_(\"cannot read the current contents of '%s'\"),\n \t\t\t\t     patch->old_name);\n \t}\n-\twrite_object_file(tmp_image.buf.buf, tmp_image.buf.len, OBJ_BLOB, &our_oid);\n+\todb_write_object(the_repository->objects, tmp_image.buf.buf,\n+\t\t\t tmp_image.buf.len, OBJ_BLOB, &our_oid);\n \timage_clear(&tmp_image);\n \n \t/* in-core three-way merge between post and our using pre as base */\n@@ -4360,7 +4362,8 @@ static int add_index_file(struct apply_state *state,\n \t\t\t}\n \t\t\tfill_stat_cache_info(state->repo->index, ce, &st);\n \t\t}\n-\t\tif (write_object_file(buf, size, OBJ_BLOB, &ce->oid) < 0) {\n+\t\tif (odb_write_object(the_repository->objects, buf, size,\n+\t\t\t\t     OBJ_BLOB, &ce->oid) < 0) {\n \t\t\tdiscard_cache_entry(ce);\n \t\t\treturn error(_(\"unable to create backing store \"\n \t\t\t\t       \"for newly created file %s\"), path);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 0a90b86a729..f95eb64ffb3 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -320,7 +320,7 @@ static int checkout_merged(int pos, const struct checkout *state,\n \t * (it also writes the merge result to the object database even\n \t * when it may contain conflicts).\n \t */\n-\tif (write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &oid))\n+\tif (odb_write_object(the_repository->objects, result_buf.ptr, result_buf.size, OBJ_BLOB, &oid))\n \t\tdie(_(\"Unable to add merge result for '%s'\"), path);\n \tfree(result_buf.ptr);\n \tce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool);\ndiff --git a/builtin/merge-file.c b/builtin/merge-file.c\nindex 9464f275629..b8b25a14e6d 100644\n--- a/builtin/merge-file.c\n+++ b/builtin/merge-file.c\n@@ -155,7 +155,8 @@ int cmd_merge_file(int argc,\n \t\tif (object_id && !to_stdout) {\n \t\t\tstruct object_id oid;\n \t\t\tif (result.size) {\n-\t\t\t\tif (write_object_file(result.ptr, result.size, OBJ_BLOB, &oid) < 0)\n+\t\t\t\tif (odb_write_object(the_repository->objects, result.ptr,\n+\t\t\t\t\t\t     result.size, OBJ_BLOB, &oid) < 0)\n \t\t\t\t\tret = error(_(\"Could not write object file\"));\n \t\t\t} else {\n \t\t\t\toidcpy(&oid, the_hash_algo->empty_blob);\ndiff --git a/builtin/mktag.c b/builtin/mktag.c\nindex 27e649736cf..12552bbb217 100644\n--- a/builtin/mktag.c\n+++ b/builtin/mktag.c\n@@ -106,7 +106,7 @@ int cmd_mktag(int argc,\n \tif (verify_object_in_tag(&tagged_oid, &tagged_type) < 0)\n \t\tdie(_(\"tag on stdin did not refer to a valid object\"));\n \n-\tif (write_object_file(buf.buf, buf.len, OBJ_TAG, &result) < 0)\n+\tif (odb_write_object(the_repository->objects, buf.buf, buf.len, OBJ_TAG, &result) < 0)\n \t\tdie(_(\"unable to write tag file\"));\n \n \tstrbuf_release(&buf);\ndiff --git a/builtin/mktree.c b/builtin/mktree.c\nindex 81df7f6099f..12772303f50 100644\n--- a/builtin/mktree.c\n+++ b/builtin/mktree.c\n@@ -63,7 +63,7 @@ static void write_tree(struct object_id *oid)\n \t\tstrbuf_add(&buf, ent->oid.hash, the_hash_algo->rawsz);\n \t}\n \n-\twrite_object_file(buf.buf, buf.len, OBJ_TREE, oid);\n+\todb_write_object(the_repository->objects, buf.buf, buf.len, OBJ_TREE, oid);\n \tstrbuf_release(&buf);\n }\n \ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex a9529b1696a..a3580b4aa3d 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -229,7 +229,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *\n \n static void write_note_data(struct note_data *d, struct object_id *oid)\n {\n-\tif (write_object_file(d->buf.buf, d->buf.len, OBJ_BLOB, oid)) {\n+\tif (odb_write_object(the_repository->objects, d->buf.buf,\n+\t\t\t     d->buf.len, OBJ_BLOB, oid)) {\n \t\tint status = die_message(_(\"unable to write note object\"));\n \n \t\tif (d->edit_path)\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex dd1d1446e75..bd9baf81e56 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -760,8 +760,8 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tint bogs /* beginning_of_gpg_sig */;\n \n \t\talready_done = 1;\n-\t\tif (write_object_file(push_cert.buf, push_cert.len, OBJ_BLOB,\n-\t\t\t\t      &push_cert_oid))\n+\t\tif (odb_write_object(the_repository->objects, push_cert.buf,\n+\t\t\t\t     push_cert.len, OBJ_BLOB, &push_cert_oid))\n \t\t\toidclr(&push_cert_oid, the_repository->hash_algo);\n \n \t\tmemset(&sigcheck, '\\0', sizeof(sigcheck));\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 5ff2ab723cb..7c46d05ec15 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -488,7 +488,8 @@ static int create_graft(int argc, const char **argv, int force, int gentle)\n \t\treturn -1;\n \t}\n \n-\tif (write_object_file(buf.buf, buf.len, OBJ_COMMIT, &new_oid)) {\n+\tif (odb_write_object(the_repository->objects, buf.buf,\n+\t\t\t     buf.len, OBJ_COMMIT, &new_oid)) {\n \t\tstrbuf_release(&buf);\n \t\treturn error(_(\"could not write replacement commit for: '%s'\"),\n \t\t\t     old_ref);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 46cbf892e34..8fbe9e7be04 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -271,8 +271,8 @@ static int build_tag_object(struct strbuf *buf, int sign, struct object_id *resu\n \tstruct object_id *compat_oid = NULL, compat_oid_buf;\n \tif (sign && do_sign(buf, &compat_oid, &compat_oid_buf) < 0)\n \t\treturn error(_(\"unable to sign the tag\"));\n-\tif (write_object_file_flags(buf->buf, buf->len, OBJ_TAG, result,\n-\t\t\t\t    compat_oid, 0) < 0)\n+\tif (odb_write_object_ext(the_repository->objects, buf->buf,\n+\t\t\t\t buf->len, OBJ_TAG, result, compat_oid, 0) < 0)\n \t\treturn error(_(\"unable to write tag file\"));\n \treturn 0;\n }\ndiff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c\nindex a69d59eb50c..1a4fbef36f8 100644\n--- a/builtin/unpack-objects.c\n+++ b/builtin/unpack-objects.c\n@@ -204,8 +204,8 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf)\n {\n \tstruct object_id oid;\n \n-\tif (write_object_file(obj_buf->buffer, obj_buf->size,\n-\t\t\t      obj->type, &oid) < 0)\n+\tif (odb_write_object(the_repository->objects, obj_buf->buffer, obj_buf->size,\n+\t\t\t     obj->type, &oid) < 0)\n \t\tdie(\"failed to write object %s\", oid_to_hex(&obj->oid));\n \tobj->flags |= FLAG_WRITTEN;\n }\n@@ -272,16 +272,16 @@ static void write_object(unsigned nr, enum object_type type,\n \t\t\t void *buf, unsigned long size)\n {\n \tif (!strict) {\n-\t\tif (write_object_file(buf, size, type,\n-\t\t\t\t      &obj_list[nr].oid) < 0)\n+\t\tif (odb_write_object(the_repository->objects, buf, size, type,\n+\t\t\t\t     &obj_list[nr].oid) < 0)\n \t\t\tdie(\"failed to write object\");\n \t\tadded_object(nr, type, buf, size);\n \t\tfree(buf);\n \t\tobj_list[nr].obj = NULL;\n \t} else if (type == OBJ_BLOB) {\n \t\tstruct blob *blob;\n-\t\tif (write_object_file(buf, size, type,\n-\t\t\t\t      &obj_list[nr].oid) < 0)\n+\t\tif (odb_write_object(the_repository->objects, buf, size, type,\n+\t\t\t\t     &obj_list[nr].oid) < 0)\n \t\t\tdie(\"failed to write object\");\n \t\tadded_object(nr, type, buf, size);\n \t\tfree(buf);\ndiff --git a/cache-tree.c b/cache-tree.c\nindex a4bc14ad15c..66ef2becbe0 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -456,9 +456,8 @@ static int update_one(struct cache_tree *it,\n \t} else if (dryrun) {\n \t\thash_object_file(the_hash_algo, buffer.buf, buffer.len,\n \t\t\t\t OBJ_TREE, &it->oid);\n-\t} else if (write_object_file_flags(buffer.buf, buffer.len, OBJ_TREE,\n-\t\t\t\t\t   &it->oid, NULL, flags & WRITE_TREE_SILENT\n-\t\t\t\t\t   ? WRITE_OBJECT_FILE_SILENT : 0)) {\n+\t} else if (odb_write_object_ext(the_repository->objects, buffer.buf, buffer.len, OBJ_TREE,\n+\t\t\t\t\t&it->oid, NULL, flags & WRITE_TREE_SILENT ? WRITE_OBJECT_SILENT : 0)) {\n \t\tstrbuf_release(&buffer);\n \t\treturn -1;\n \t}\ndiff --git a/commit.c b/commit.c\nindex 15115125c36..bcc9aea55f6 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1797,8 +1797,8 @@ int commit_tree_extended(const char *msg, size_t msg_len,\n \t\tcompat_oid = &compat_oid_buf;\n \t}\n \n-\tresult = write_object_file_flags(buffer.buf, buffer.len, OBJ_COMMIT,\n-\t\t\t\t\t ret, compat_oid, 0);\n+\tresult = odb_write_object_ext(the_repository->objects, buffer.buf, buffer.len,\n+\t\t\t\t      OBJ_COMMIT, ret, compat_oid, 0);\n out:\n \tfree(parent_buf);\n \tstrbuf_release(&buffer);\ndiff --git a/match-trees.c b/match-trees.c\nindex 5a8a5c39b04..4216933d06b 100644\n--- a/match-trees.c\n+++ b/match-trees.c\n@@ -246,7 +246,7 @@ static int splice_tree(struct repository *r,\n \t\trewrite_with = oid2;\n \t}\n \thashcpy(rewrite_here, rewrite_with->hash, r->hash_algo);\n-\tstatus = write_object_file(buf, sz, OBJ_TREE, result);\n+\tstatus = odb_write_object(r->objects, buf, sz, OBJ_TREE, result);\n \tfree(buf);\n \treturn status;\n }\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 473ff61e36e..535ef3efc6f 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -2216,8 +2216,8 @@ static int handle_content_merge(struct merge_options *opt,\n \t\t}\n \n \t\tif (!ret && record_object &&\n-\t\t    write_object_file(result_buf.ptr, result_buf.size,\n-\t\t\t\t      OBJ_BLOB, &result->oid)) {\n+\t\t    odb_write_object(the_repository->objects, result_buf.ptr, result_buf.size,\n+\t\t\t\t     OBJ_BLOB, &result->oid)) {\n \t\t\tpath_msg(opt, ERROR_OBJECT_WRITE_FAILED, 0,\n \t\t\t\t pathnames[0], pathnames[1], pathnames[2], NULL,\n \t\t\t\t _(\"error: unable to add %s to database\"), path);\n@@ -3772,7 +3772,8 @@ static int write_tree(struct object_id *result_oid,\n \t}\n \n \t/* Write this object file out, and record in result_oid */\n-\tif (write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid))\n+\tif (odb_write_object(the_repository->objects, buf.buf,\n+\t\t\t     buf.len, OBJ_TREE, result_oid))\n \t\tret = -1;\n \tstrbuf_release(&buf);\n \treturn ret;\ndiff --git a/notes-cache.c b/notes-cache.c\nindex dd56feed6e8..bf5bb1f6c13 100644\n--- a/notes-cache.c\n+++ b/notes-cache.c\n@@ -98,7 +98,8 @@ int notes_cache_put(struct notes_cache *c, struct object_id *key_oid,\n {\n \tstruct object_id value_oid;\n \n-\tif (write_object_file(data, size, OBJ_BLOB, &value_oid) < 0)\n+\tif (odb_write_object(the_repository->objects, data,\n+\t\t\t     size, OBJ_BLOB, &value_oid) < 0)\n \t\treturn -1;\n \treturn add_note(&c->tree, key_oid, &value_oid, NULL);\n }\ndiff --git a/notes.c b/notes.c\nindex 97b995f3f2d..7596c0df9a1 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -682,7 +682,8 @@ static int tree_write_stack_finish_subtree(struct tree_write_stack *tws)\n \t\tret = tree_write_stack_finish_subtree(n);\n \t\tif (ret)\n \t\t\treturn ret;\n-\t\tret = write_object_file(n->buf.buf, n->buf.len, OBJ_TREE, &s);\n+\t\tret = odb_write_object(the_repository->objects, n->buf.buf,\n+\t\t\t\t       n->buf.len, OBJ_TREE, &s);\n \t\tif (ret)\n \t\t\treturn ret;\n \t\tstrbuf_release(&n->buf);\n@@ -847,7 +848,8 @@ int combine_notes_concatenate(struct object_id *cur_oid,\n \tfree(new_msg);\n \n \t/* create a new blob object from buf */\n-\tret = write_object_file(buf, buf_len, OBJ_BLOB, cur_oid);\n+\tret = odb_write_object(the_repository->objects, buf,\n+\t\t\t       buf_len, OBJ_BLOB, cur_oid);\n \tfree(buf);\n \treturn ret;\n }\n@@ -927,7 +929,8 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,\n \t\t\t\t string_list_join_lines_helper, &buf))\n \t\tgoto out;\n \n-\tret = write_object_file(buf.buf, buf.len, OBJ_BLOB, cur_oid);\n+\tret = odb_write_object(the_repository->objects, buf.buf,\n+\t\t\t       buf.len, OBJ_BLOB, cur_oid);\n \n out:\n \tstrbuf_release(&buf);\n@@ -1215,7 +1218,8 @@ int write_notes_tree(struct notes_tree *t, struct object_id *result)\n \tret = for_each_note(t, flags, write_each_note, &cb_data) ||\n \t      write_each_non_note_until(NULL, &cb_data) ||\n \t      tree_write_stack_finish_subtree(&root) ||\n-\t      write_object_file(root.buf.buf, root.buf.len, OBJ_TREE, result);\n+\t      odb_write_object(the_repository->objects, root.buf.buf,\n+\t\t\t       root.buf.len, OBJ_TREE, result);\n \tstrbuf_release(&root.buf);\n \treturn ret;\n }\ndiff --git a/object-file.c b/object-file.c\nindex a9248760a26..84ece01337e 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -755,7 +755,7 @@ static int start_loose_object_common(struct strbuf *tmp_file,\n \n \tfd = create_tmpfile(tmp_file, filename);\n \tif (fd < 0) {\n-\t\tif (flags & WRITE_OBJECT_FILE_SILENT)\n+\t\tif (flags & WRITE_OBJECT_SILENT)\n \t\t\treturn -1;\n \t\telse if (errno == EACCES)\n \t\t\treturn error(_(\"insufficient permission for adding \"\n@@ -887,7 +887,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\tutb.actime = mtime;\n \t\tutb.modtime = mtime;\n \t\tif (utime(tmp_file.buf, &utb) < 0 &&\n-\t\t    !(flags & WRITE_OBJECT_FILE_SILENT))\n+\t\t    !(flags & WRITE_OBJECT_SILENT))\n \t\t\twarning_errno(_(\"failed utime() on %s\"), tmp_file.buf);\n \t}\n \n@@ -1032,9 +1032,9 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \treturn err;\n }\n \n-int write_object_file_flags(const void *buf, unsigned long len,\n-\t\t\t    enum object_type type, struct object_id *oid,\n-\t\t\t    struct object_id *compat_oid_in, unsigned flags)\n+int write_object_file(const void *buf, unsigned long len,\n+\t\t      enum object_type type, struct object_id *oid,\n+\t\t      struct object_id *compat_oid_in, unsigned flags)\n {\n \tstruct repository *repo = the_repository;\n \tconst struct git_hash_algo *algo = repo->hash_algo;\n@@ -1159,7 +1159,7 @@ static int index_mem(struct index_state *istate,\n \t}\n \n \tif (write_object)\n-\t\tret = write_object_file(buf, size, type, oid);\n+\t\tret = odb_write_object(istate->repo->objects, buf, size, type, oid);\n \telse\n \t\thash_object_file(istate->repo->hash_algo, buf, size, type, oid);\n \n@@ -1184,8 +1184,8 @@ static int index_stream_convert_blob(struct index_state *istate,\n \t\t\t\t get_conv_flags(flags));\n \n \tif (write_object)\n-\t\tret = write_object_file(sbuf.buf, sbuf.len, OBJ_BLOB,\n-\t\t\t\t\toid);\n+\t\tret = odb_write_object(istate->repo->objects, sbuf.buf, sbuf.len, OBJ_BLOB,\n+\t\t\t\t       oid);\n \telse\n \t\thash_object_file(istate->repo->hash_algo, sbuf.buf, sbuf.len, OBJ_BLOB,\n \t\t\t\t oid);\n@@ -1287,7 +1287,7 @@ int index_path(struct index_state *istate, struct object_id *oid,\n \t\tif (!(flags & INDEX_WRITE_OBJECT))\n \t\t\thash_object_file(istate->repo->hash_algo, sb.buf, sb.len,\n \t\t\t\t\t OBJ_BLOB, oid);\n-\t\telse if (write_object_file(sb.buf, sb.len, OBJ_BLOB, oid))\n+\t\telse if (odb_write_object(the_repository->objects, sb.buf, sb.len, OBJ_BLOB, oid))\n \t\t\trc = error(_(\"%s: failed to insert into database\"), path);\n \t\tstrbuf_release(&sb);\n \t\tbreak;\ndiff --git a/object-file.h b/object-file.h\nindex 370139e0762..8ee24b7d8f3 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -157,29 +157,9 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,\n struct object_info;\n int parse_loose_header(const char *hdr, struct object_info *oi);\n \n-enum {\n-\t/*\n-\t * By default, `write_object_file()` does not actually write\n-\t * anything into the object store, but only computes the object ID.\n-\t * This flag changes that so that the object will be written as a loose\n-\t * object and persisted.\n-\t */\n-\tWRITE_OBJECT_FILE_PERSIST = (1 << 0),\n-\n-\t/*\n-\t * Do not print an error in case something gose wrong.\n-\t */\n-\tWRITE_OBJECT_FILE_SILENT = (1 << 1),\n-};\n-\n-int write_object_file_flags(const void *buf, unsigned long len,\n-\t\t\t    enum object_type type, struct object_id *oid,\n-\t\t\t    struct object_id *compat_oid_in, unsigned flags);\n-static inline int write_object_file(const void *buf, unsigned long len,\n-\t\t\t\t    enum object_type type, struct object_id *oid)\n-{\n-\treturn write_object_file_flags(buf, len, type, oid, NULL, 0);\n-}\n+int write_object_file(const void *buf, unsigned long len,\n+\t\t      enum object_type type, struct object_id *oid,\n+\t\t      struct object_id *compat_oid_in, unsigned flags);\n \n struct input_stream {\n \tconst void *(*read)(struct input_stream *, unsigned long *len);\ndiff --git a/odb.c b/odb.c\nindex 1f48a0448e3..519df2fa497 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -980,6 +980,16 @@ void odb_assert_oid_type(struct object_database *odb,\n \t\t    type_name(expect));\n }\n \n+int odb_write_object_ext(struct object_database *odb UNUSED,\n+\t\t\t const void *buf, unsigned long len,\n+\t\t\t enum object_type type,\n+\t\t\t struct object_id *oid,\n+\t\t\t struct object_id *compat_oid,\n+\t\t\t unsigned flags)\n+{\n+\treturn write_object_file(buf, len, type, oid, compat_oid, flags);\n+}\n+\n struct object_database *odb_new(struct repository *repo)\n {\n \tstruct object_database *o = xmalloc(sizeof(*o));\ndiff --git a/odb.h b/odb.h\nindex e922f256802..c96d2c29e9f 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -437,6 +437,44 @@ enum for_each_object_flags {\n \tFOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n+enum {\n+\t/*\n+\t * By default, `odb_write_object()` does not actually write anything\n+\t * into the object store, but only computes the object ID. This flag\n+\t * changes that so that the object will be written as a loose object\n+\t * and persisted.\n+\t */\n+\tWRITE_OBJECT_PERSIST = (1 << 0),\n+\n+\t/*\n+\t * Do not print an error in case something gose wrong.\n+\t */\n+\tWRITE_OBJECT_SILENT = (1 << 1),\n+};\n+\n+/*\n+ * Write an object into the object database. The object is being written into\n+ * the local alternate of the repository. If provided, the converted object ID\n+ * as well as the compatibility object ID are written to the respective\n+ * pointers.\n+ *\n+ * Returns 0 on success, a negative error code otherwise.\n+ */\n+int odb_write_object_ext(struct object_database *odb,\n+\t\t\t const void *buf, unsigned long len,\n+\t\t\t enum object_type type,\n+\t\t\t struct object_id *oid,\n+\t\t\t struct object_id *compat_oid,\n+\t\t\t unsigned flags);\n+\n+static inline int odb_write_object(struct object_database *odb,\n+\t\t\t\t   const void *buf, unsigned long len,\n+\t\t\t\t   enum object_type type,\n+\t\t\t\t   struct object_id *oid)\n+{\n+\treturn odb_write_object_ext(odb, buf, len, type, oid, NULL, 0);\n+}\n+\n /* Compatibility wrappers, to be removed once Git 2.51 has been released. */\n #include \"repository.h\"\n \ndiff --git a/read-cache.c b/read-cache.c\nindex 531d87e7905..be17ca7f586 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -690,7 +690,7 @@ static struct cache_entry *create_alias_ce(struct index_state *istate,\n void set_object_name_for_intent_to_add_entry(struct cache_entry *ce)\n {\n \tstruct object_id oid;\n-\tif (write_object_file(\"\", 0, OBJ_BLOB, &oid))\n+\tif (odb_write_object(the_repository->objects, \"\", 0, OBJ_BLOB, &oid))\n \t\tdie(_(\"cannot create an empty blob in the object database\"));\n \toidcpy(&ce->oid, &oid);\n }\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521658","messageId":"20250709-pks-object-file-wo-the-repository-v1-10-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 10/19] object-file: get rid of `the_repository` when writing objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:20Z","receivedAt":"2025-07-09T11:17:51Z","isPatch":true,"body":"The logic that writes loose objects still relies on `the_repository` to\ndecide where exactly the object shall be written to. Refactor it so that\nthe logic instead operates on a `struct odb_source` so that we can get\nrid of this global dependency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/unpack-objects.c |  3 +-\n object-file.c            | 96 +++++++++++++++++++++++++-----------------------\n object-file.h            |  6 ++-\n odb.c                    |  4 +-\n 4 files changed, 58 insertions(+), 51 deletions(-)\n\ndiff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c\nindex 1a4fbef36f8..1d405d156e4 100644\n--- a/builtin/unpack-objects.c\n+++ b/builtin/unpack-objects.c\n@@ -403,7 +403,8 @@ static void stream_blob(unsigned long size, unsigned nr)\n \tdata.zstream = &zstream;\n \tgit_inflate_init(&zstream);\n \n-\tif (stream_loose_object(&in_stream, size, &info->oid))\n+\tif (stream_loose_object(the_repository->objects->sources,\n+\t\t\t\t&in_stream, size, &info->oid))\n \t\tdie(_(\"failed to write object in stream\"));\n \n \tif (data.status != Z_STREAM_END)\ndiff --git a/object-file.c b/object-file.c\nindex 84ece01337e..fc061c37bb5 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -667,9 +667,10 @@ void hash_object_file(const struct git_hash_algo *algo, const void *buf,\n }\n \n /* Finalize a file on disk, and close it. */\n-static void close_loose_object(int fd, const char *filename)\n+static void close_loose_object(struct odb_source *source,\n+\t\t\t       int fd, const char *filename)\n {\n-\tif (the_repository->objects->sources->will_destroy)\n+\tif (source->will_destroy)\n \t\tgoto out;\n \n \tif (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))\n@@ -701,7 +702,8 @@ static inline int directory_size(const char *filename)\n  * We want to avoid cross-directory filename renames, because those\n  * can have problems on various filesystems (FAT, NFS, Coda).\n  */\n-static int create_tmpfile(struct strbuf *tmp, const char *filename)\n+static int create_tmpfile(struct repository *repo,\n+\t\t\t  struct strbuf *tmp, const char *filename)\n {\n \tint fd, dirlen = directory_size(filename);\n \n@@ -720,7 +722,7 @@ static int create_tmpfile(struct strbuf *tmp, const char *filename)\n \t\tstrbuf_add(tmp, filename, dirlen - 1);\n \t\tif (mkdir(tmp->buf, 0777) && errno != EEXIST)\n \t\t\treturn -1;\n-\t\tif (adjust_shared_perm(the_repository, tmp->buf))\n+\t\tif (adjust_shared_perm(repo, tmp->buf))\n \t\t\treturn -1;\n \n \t\t/* Try again */\n@@ -741,26 +743,26 @@ static int create_tmpfile(struct strbuf *tmp, const char *filename)\n  * Returns a \"fd\", which should later be provided to\n  * end_loose_object_common().\n  */\n-static int start_loose_object_common(struct strbuf *tmp_file,\n+static int start_loose_object_common(struct odb_source *source,\n+\t\t\t\t     struct strbuf *tmp_file,\n \t\t\t\t     const char *filename, unsigned flags,\n \t\t\t\t     git_zstream *stream,\n \t\t\t\t     unsigned char *buf, size_t buflen,\n \t\t\t\t     struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n \t\t\t\t     char *hdr, int hdrlen)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *algo = repo->hash_algo;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *algo = source->odb->repo->hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tint fd;\n \n-\tfd = create_tmpfile(tmp_file, filename);\n+\tfd = create_tmpfile(source->odb->repo, tmp_file, filename);\n \tif (fd < 0) {\n \t\tif (flags & WRITE_OBJECT_SILENT)\n \t\t\treturn -1;\n \t\telse if (errno == EACCES)\n \t\t\treturn error(_(\"insufficient permission for adding \"\n \t\t\t\t       \"an object to repository database %s\"),\n-\t\t\t\t     repo_get_object_directory(the_repository));\n+\t\t\t\t     source->path);\n \t\telse\n \t\t\treturn error_errno(\n \t\t\t\t_(\"unable to create temporary file\"));\n@@ -790,14 +792,14 @@ static int start_loose_object_common(struct strbuf *tmp_file,\n  * Common steps for the inner git_deflate() loop for writing loose\n  * objects. Returns what git_deflate() returns.\n  */\n-static int write_loose_object_common(struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n+static int write_loose_object_common(struct odb_source *source,\n+\t\t\t\t     struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n \t\t\t\t     git_zstream *stream, const int flush,\n \t\t\t\t     unsigned char *in0, const int fd,\n \t\t\t\t     unsigned char *compressed,\n \t\t\t\t     const size_t compressed_len)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tint ret;\n \n \tret = git_deflate(stream, flush ? Z_FINISH : 0);\n@@ -818,12 +820,12 @@ static int write_loose_object_common(struct git_hash_ctx *c, struct git_hash_ctx\n  * - End the compression of zlib stream.\n  * - Get the calculated oid to \"oid\".\n  */\n-static int end_loose_object_common(struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n+static int end_loose_object_common(struct odb_source *source,\n+\t\t\t\t   struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n \t\t\t\t   git_zstream *stream, struct object_id *oid,\n \t\t\t\t   struct object_id *compat_oid)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tint ret;\n \n \tret = git_deflate_end_gently(stream);\n@@ -836,7 +838,8 @@ static int end_loose_object_common(struct git_hash_ctx *c, struct git_hash_ctx *\n \treturn Z_OK;\n }\n \n-static int write_loose_object(const struct object_id *oid, char *hdr,\n+static int write_loose_object(struct odb_source *source,\n+\t\t\t      const struct object_id *oid, char *hdr,\n \t\t\t      int hdrlen, const void *buf, unsigned long len,\n \t\t\t      time_t mtime, unsigned flags)\n {\n@@ -851,9 +854,9 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \tif (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))\n \t\tprepare_loose_object_bulk_checkin();\n \n-\todb_loose_path(the_repository->objects->sources, &filename, oid);\n+\todb_loose_path(source, &filename, oid);\n \n-\tfd = start_loose_object_common(&tmp_file, filename.buf, flags,\n+\tfd = start_loose_object_common(source, &tmp_file, filename.buf, flags,\n \t\t\t\t       &stream, compressed, sizeof(compressed),\n \t\t\t\t       &c, NULL, hdr, hdrlen);\n \tif (fd < 0)\n@@ -865,14 +868,14 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \tdo {\n \t\tunsigned char *in0 = stream.next_in;\n \n-\t\tret = write_loose_object_common(&c, NULL, &stream, 1, in0, fd,\n+\t\tret = write_loose_object_common(source, &c, NULL, &stream, 1, in0, fd,\n \t\t\t\t\t\tcompressed, sizeof(compressed));\n \t} while (ret == Z_OK);\n \n \tif (ret != Z_STREAM_END)\n \t\tdie(_(\"unable to deflate new object %s (%d)\"), oid_to_hex(oid),\n \t\t    ret);\n-\tret = end_loose_object_common(&c, NULL, &stream, &parano_oid, NULL);\n+\tret = end_loose_object_common(source, &c, NULL, &stream, &parano_oid, NULL);\n \tif (ret != Z_OK)\n \t\tdie(_(\"deflateEnd on object %s failed (%d)\"), oid_to_hex(oid),\n \t\t    ret);\n@@ -880,7 +883,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\tdie(_(\"confused by unstable object source data for %s\"),\n \t\t    oid_to_hex(oid));\n \n-\tclose_loose_object(fd, tmp_file.buf);\n+\tclose_loose_object(source, fd, tmp_file.buf);\n \n \tif (mtime) {\n \t\tstruct utimbuf utb;\n@@ -891,7 +894,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\t\twarning_errno(_(\"failed utime() on %s\"), tmp_file.buf);\n \t}\n \n-\treturn finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n+\treturn finalize_object_file_flags(source->odb->repo, tmp_file.buf, filename.buf,\n \t\t\t\t\t  FOF_SKIP_COLLISION_CHECK);\n }\n \n@@ -921,10 +924,11 @@ static int freshen_packed_object(struct object_database *odb,\n \treturn 1;\n }\n \n-int stream_loose_object(struct input_stream *in_stream, size_t len,\n+int stream_loose_object(struct odb_source *source,\n+\t\t\tstruct input_stream *in_stream, size_t len,\n \t\t\tstruct object_id *oid)\n {\n-\tconst struct git_hash_algo *compat = the_repository->compat_hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tstruct object_id compat_oid;\n \tint fd, ret, err = 0, flush = 0;\n \tunsigned char compressed[4096];\n@@ -940,7 +944,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tprepare_loose_object_bulk_checkin();\n \n \t/* Since oid is not determined, save tmp file to odb path. */\n-\tstrbuf_addf(&filename, \"%s/\", repo_get_object_directory(the_repository));\n+\tstrbuf_addf(&filename, \"%s/\", source->path);\n \thdrlen = format_object_header(hdr, sizeof(hdr), OBJ_BLOB, len);\n \n \t/*\n@@ -951,7 +955,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t *  - Setup zlib stream for compression.\n \t *  - Start to feed header to zlib stream.\n \t */\n-\tfd = start_loose_object_common(&tmp_file, filename.buf, 0,\n+\tfd = start_loose_object_common(source, &tmp_file, filename.buf, 0,\n \t\t\t\t       &stream, compressed, sizeof(compressed),\n \t\t\t\t       &c, &compat_c, hdr, hdrlen);\n \tif (fd < 0) {\n@@ -971,7 +975,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\t\tif (in_stream->is_finished)\n \t\t\t\tflush = 1;\n \t\t}\n-\t\tret = write_loose_object_common(&c, &compat_c, &stream, flush, in0, fd,\n+\t\tret = write_loose_object_common(source, &c, &compat_c, &stream, flush, in0, fd,\n \t\t\t\t\t\tcompressed, sizeof(compressed));\n \t\t/*\n \t\t * Unlike write_loose_object(), we do not have the entire\n@@ -994,18 +998,18 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t */\n \tif (ret != Z_STREAM_END)\n \t\tdie(_(\"unable to stream deflate new object (%d)\"), ret);\n-\tret = end_loose_object_common(&c, &compat_c, &stream, oid, &compat_oid);\n+\tret = end_loose_object_common(source, &c, &compat_c, &stream, oid, &compat_oid);\n \tif (ret != Z_OK)\n \t\tdie(_(\"deflateEnd on stream object failed (%d)\"), ret);\n-\tclose_loose_object(fd, tmp_file.buf);\n+\tclose_loose_object(source, fd, tmp_file.buf);\n \n-\tif (freshen_packed_object(the_repository->objects, oid) ||\n-\t    freshen_loose_object(the_repository->objects, oid)) {\n+\tif (freshen_packed_object(source->odb, oid) ||\n+\t    freshen_loose_object(source->odb, oid)) {\n \t\tunlink_or_warn(tmp_file.buf);\n \t\tgoto cleanup;\n \t}\n \n-\todb_loose_path(the_repository->objects->sources, &filename, oid);\n+\todb_loose_path(source, &filename, oid);\n \n \t/* We finally know the object path, and create the missing dir. */\n \tdirlen = directory_size(filename.buf);\n@@ -1013,7 +1017,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tstruct strbuf dir = STRBUF_INIT;\n \t\tstrbuf_add(&dir, filename.buf, dirlen);\n \n-\t\tif (safe_create_dir_in_gitdir(the_repository, dir.buf) &&\n+\t\tif (safe_create_dir_in_gitdir(source->odb->repo, dir.buf) &&\n \t\t    errno != EEXIST) {\n \t\t\terr = error_errno(_(\"unable to create directory %s\"), dir.buf);\n \t\t\tstrbuf_release(&dir);\n@@ -1022,23 +1026,23 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tstrbuf_release(&dir);\n \t}\n \n-\terr = finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n+\terr = finalize_object_file_flags(source->odb->repo, tmp_file.buf, filename.buf,\n \t\t\t\t\t FOF_SKIP_COLLISION_CHECK);\n \tif (!err && compat)\n-\t\terr = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n+\t\terr = repo_add_loose_object_map(source, oid, &compat_oid);\n cleanup:\n \tstrbuf_release(&tmp_file);\n \tstrbuf_release(&filename);\n \treturn err;\n }\n \n-int write_object_file(const void *buf, unsigned long len,\n+int write_object_file(struct odb_source *source,\n+\t\t      const void *buf, unsigned long len,\n \t\t      enum object_type type, struct object_id *oid,\n \t\t      struct object_id *compat_oid_in, unsigned flags)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *algo = repo->hash_algo;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *algo = source->odb->repo->hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tstruct object_id compat_oid;\n \tchar hdr[MAX_HEADER_LEN];\n \tint hdrlen = sizeof(hdr);\n@@ -1051,7 +1055,7 @@ int write_object_file(const void *buf, unsigned long len,\n \t\t\thash_object_file(compat, buf, len, type, &compat_oid);\n \t\telse {\n \t\t\tstruct strbuf converted = STRBUF_INIT;\n-\t\t\tconvert_object_file(the_repository, &converted, algo, compat,\n+\t\t\tconvert_object_file(source->odb->repo, &converted, algo, compat,\n \t\t\t\t\t    buf, len, type, 0);\n \t\t\thash_object_file(compat, converted.buf, converted.len,\n \t\t\t\t\t type, &compat_oid);\n@@ -1063,13 +1067,13 @@ int write_object_file(const void *buf, unsigned long len,\n \t * it out into .git/objects/??/?{38} file.\n \t */\n \twrite_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);\n-\tif (freshen_packed_object(repo->objects, oid) ||\n-\t    freshen_loose_object(repo->objects, oid))\n+\tif (freshen_packed_object(source->odb, oid) ||\n+\t    freshen_loose_object(source->odb, oid))\n \t\treturn 0;\n-\tif (write_loose_object(oid, hdr, hdrlen, buf, len, 0, flags))\n+\tif (write_loose_object(source, oid, hdr, hdrlen, buf, len, 0, flags))\n \t\treturn -1;\n \tif (compat)\n-\t\treturn repo_add_loose_object_map(repo->objects->sources, oid, &compat_oid);\n+\t\treturn repo_add_loose_object_map(source, oid, &compat_oid);\n \treturn 0;\n }\n \n@@ -1101,7 +1105,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime)\n \t\t\t\t     oid_to_hex(oid), compat->name);\n \t}\n \thdrlen = format_object_header(hdr, sizeof(hdr), type, len);\n-\tret = write_loose_object(oid, hdr, hdrlen, buf, len, mtime, 0);\n+\tret = write_loose_object(repo->objects->sources, oid, hdr, hdrlen, buf, len, mtime, 0);\n \tif (!ret && compat)\n \t\tret = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n \tfree(buf);\ndiff --git a/object-file.h b/object-file.h\nindex 8ee24b7d8f3..622e2b2bb7d 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -157,7 +157,8 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,\n struct object_info;\n int parse_loose_header(const char *hdr, struct object_info *oi);\n \n-int write_object_file(const void *buf, unsigned long len,\n+int write_object_file(struct odb_source *source,\n+\t\t      const void *buf, unsigned long len,\n \t\t      enum object_type type, struct object_id *oid,\n \t\t      struct object_id *compat_oid_in, unsigned flags);\n \n@@ -167,7 +168,8 @@ struct input_stream {\n \tint is_finished;\n };\n \n-int stream_loose_object(struct input_stream *in_stream, size_t len,\n+int stream_loose_object(struct odb_source *source,\n+\t\t\tstruct input_stream *in_stream, size_t len,\n \t\t\tstruct object_id *oid);\n \n int force_object_loose(const struct object_id *oid, time_t mtime);\ndiff --git a/odb.c b/odb.c\nindex 519df2fa497..2a92a018c42 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -980,14 +980,14 @@ void odb_assert_oid_type(struct object_database *odb,\n \t\t    type_name(expect));\n }\n \n-int odb_write_object_ext(struct object_database *odb UNUSED,\n+int odb_write_object_ext(struct object_database *odb,\n \t\t\t const void *buf, unsigned long len,\n \t\t\t enum object_type type,\n \t\t\t struct object_id *oid,\n \t\t\t struct object_id *compat_oid,\n \t\t\t unsigned flags)\n {\n-\treturn write_object_file(buf, len, type, oid, compat_oid, flags);\n+\treturn write_object_file(odb->sources, buf, len, type, oid, compat_oid, flags);\n }\n \n struct object_database *odb_new(struct repository *repo)\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521659","messageId":"20250709-pks-object-file-wo-the-repository-v1-11-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 11/19] object-file: inline `for_each_loose_file_in_objdir_buf()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:21Z","receivedAt":"2025-07-09T11:17:53Z","isPatch":true,"body":"The function `for_each_loose_file_in_objdir_buf()` is declared in our\nheaders, but it is not used anywhere else than in the corresponding code\nfile itself. Drop the declaration and inline the function into its only\ncaller.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 31 ++++++++-----------------------\n object-file.h |  5 -----\n 2 files changed, 8 insertions(+), 28 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex fc061c37bb5..5a936f17148 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1388,26 +1388,6 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \treturn r;\n }\n \n-int for_each_loose_file_in_objdir_buf(struct strbuf *path,\n-\t\t\t    each_loose_object_fn obj_cb,\n-\t\t\t    each_loose_cruft_fn cruft_cb,\n-\t\t\t    each_loose_subdir_fn subdir_cb,\n-\t\t\t    void *data)\n-{\n-\tint r = 0;\n-\tint i;\n-\n-\tfor (i = 0; i < 256; i++) {\n-\t\tr = for_each_file_in_obj_subdir(i, path, the_repository->hash_algo,\n-\t\t\t\t\t\tobj_cb, cruft_cb,\n-\t\t\t\t\t\tsubdir_cb, data);\n-\t\tif (r)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn r;\n-}\n-\n int for_each_loose_file_in_objdir(const char *path,\n \t\t\t\t  each_loose_object_fn obj_cb,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n@@ -1418,10 +1398,15 @@ int for_each_loose_file_in_objdir(const char *path,\n \tint r;\n \n \tstrbuf_addstr(&buf, path);\n-\tr = for_each_loose_file_in_objdir_buf(&buf, obj_cb, cruft_cb,\n-\t\t\t\t\t      subdir_cb, data);\n-\tstrbuf_release(&buf);\n+\tfor (int i = 0; i < 256; i++) {\n+\t\tr = for_each_file_in_obj_subdir(i, &buf, the_repository->hash_algo,\n+\t\t\t\t\t\tobj_cb, cruft_cb,\n+\t\t\t\t\t\tsubdir_cb, data);\n+\t\tif (r)\n+\t\t\tbreak;\n+\t}\n \n+\tstrbuf_release(&buf);\n \treturn r;\n }\n \ndiff --git a/object-file.h b/object-file.h\nindex 622e2b2bb7d..eca323f9736 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -98,11 +98,6 @@ int for_each_loose_file_in_objdir(const char *path,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n \t\t\t\t  void *data);\n-int for_each_loose_file_in_objdir_buf(struct strbuf *path,\n-\t\t\t\t      each_loose_object_fn obj_cb,\n-\t\t\t\t      each_loose_cruft_fn cruft_cb,\n-\t\t\t\t      each_loose_subdir_fn subdir_cb,\n-\t\t\t\t      void *data);\n \n /*\n  * Iterate over all accessible loose objects without respect to\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521660","messageId":"20250709-pks-object-file-wo-the-repository-v1-12-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 12/19] object-file: remove declaration for `for_each_file_in_obj_subdir()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:22Z","receivedAt":"2025-07-09T11:17:56Z","isPatch":true,"body":"The function `for_each_file_in_obj_subdir()` is declared in our headers,\nbut it is not used anywhere else than in the corresponding code file\nitself. Drop the declaration and mark the function as file-local.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 14 +++++++-------\n object-file.h |  7 -------\n 2 files changed, 7 insertions(+), 14 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 5a936f17148..bd93f17dcfe 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1318,13 +1318,13 @@ int read_pack_header(int fd, struct pack_header *header)\n \treturn 0;\n }\n \n-int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n-\t\t\t\tstruct strbuf *path,\n-\t\t\t\tconst struct git_hash_algo *algop,\n-\t\t\t\teach_loose_object_fn obj_cb,\n-\t\t\t\teach_loose_cruft_fn cruft_cb,\n-\t\t\t\teach_loose_subdir_fn subdir_cb,\n-\t\t\t\tvoid *data)\n+static int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n+\t\t\t\t       struct strbuf *path,\n+\t\t\t\t       const struct git_hash_algo *algop,\n+\t\t\t\t       each_loose_object_fn obj_cb,\n+\t\t\t\t       each_loose_cruft_fn cruft_cb,\n+\t\t\t\t       each_loose_subdir_fn subdir_cb,\n+\t\t\t\t       void *data)\n {\n \tsize_t origlen, baselen;\n \tDIR *dir;\ndiff --git a/object-file.h b/object-file.h\nindex eca323f9736..d52b335e85b 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -86,13 +86,6 @@ typedef int each_loose_cruft_fn(const char *basename,\n typedef int each_loose_subdir_fn(unsigned int nr,\n \t\t\t\t const char *path,\n \t\t\t\t void *data);\n-int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n-\t\t\t\tstruct strbuf *path,\n-\t\t\t\tconst struct git_hash_algo *algo,\n-\t\t\t\teach_loose_object_fn obj_cb,\n-\t\t\t\teach_loose_cruft_fn cruft_cb,\n-\t\t\t\teach_loose_subdir_fn subdir_cb,\n-\t\t\t\tvoid *data);\n int for_each_loose_file_in_objdir(const char *path,\n \t\t\t\t  each_loose_object_fn obj_cb,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521662","messageId":"20250709-pks-object-file-wo-the-repository-v1-13-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 13/19] object-file: get rid of `the_repository` in loose object iterators","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:23Z","receivedAt":"2025-07-09T11:18:00Z","isPatch":true,"body":"The iterators for loose objects still rely on `the_repository`. Refactor\nthem:\n\n  - `for_each_loose_file_in_objdir()` is refactored so that the caller\n    is now expected to pass an `odb_source` as parameter instead of the\n    path to that source. Furthermore, it is renamed accordingly to\n    `for_each_loose_file_in_source()`.\n\n  - `for_each_loose_object()` is refactored to take in an object\n    database now and calls the above function in a loop.\n\nThis allows us to get rid of the global dependency.\n\nAdjust callers accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c      |  2 +-\n builtin/count-objects.c |  2 +-\n builtin/fsck.c          | 14 ++++++++------\n builtin/gc.c            | 10 ++++------\n builtin/pack-objects.c  |  5 ++---\n builtin/prune.c         |  2 +-\n object-file.c           | 18 +++++++++---------\n object-file.h           |  5 +++--\n prune-packed.c          |  2 +-\n reachable.c             |  2 +-\n 10 files changed, 31 insertions(+), 31 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 2492a0b6f39..aa1498aa60f 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -848,7 +848,7 @@ static void batch_each_object(struct batch_options *opt,\n \t};\n \tstruct bitmap_index *bitmap = prepare_bitmap_git(the_repository);\n \n-\tfor_each_loose_object(batch_one_object_loose, &payload, 0);\n+\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n \n \tif (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,\n \t\t\t\t\t\t batch_one_object_bitmapped, &payload)) {\ndiff --git a/builtin/count-objects.c b/builtin/count-objects.c\nindex f687647931e..e70a01c628e 100644\n--- a/builtin/count-objects.c\n+++ b/builtin/count-objects.c\n@@ -117,7 +117,7 @@ int cmd_count_objects(int argc,\n \t\treport_linked_checkout_garbage(the_repository);\n \t}\n \n-\tfor_each_loose_file_in_objdir(repo_get_object_directory(the_repository),\n+\tfor_each_loose_file_in_source(the_repository->objects->sources,\n \t\t\t\t      count_loose, count_cruft, NULL, NULL);\n \n \tif (verbose) {\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 0084cf7400b..f0854ce5d84 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -393,7 +393,8 @@ static void check_connectivity(void)\n \t\t * and ignore any that weren't present in our earlier\n \t\t * traversal.\n \t\t */\n-\t\tfor_each_loose_object(mark_loose_unreachable_referents, NULL, 0);\n+\t\tfor_each_loose_object(the_repository->objects,\n+\t\t\t\t      mark_loose_unreachable_referents, NULL, 0);\n \t\tfor_each_packed_object(the_repository,\n \t\t\t\t       mark_packed_unreachable_referents,\n \t\t\t\t       NULL,\n@@ -687,7 +688,7 @@ static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *data)\n \treturn 0;\n }\n \n-static void fsck_object_dir(const char *path)\n+static void fsck_source(struct odb_source *source)\n {\n \tstruct progress *progress = NULL;\n \tstruct for_each_loose_cb cb_data = {\n@@ -701,8 +702,8 @@ static void fsck_object_dir(const char *path)\n \t\tprogress = start_progress(the_repository,\n \t\t\t\t\t  _(\"Checking object directories\"), 256);\n \n-\tfor_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir,\n-\t\t\t\t      &cb_data);\n+\tfor_each_loose_file_in_source(source, fsck_loose,\n+\t\t\t\t      fsck_cruft, fsck_subdir, &cb_data);\n \tdisplay_progress(progress, 256);\n \tstop_progress(&progress);\n }\n@@ -994,13 +995,14 @@ int cmd_fsck(int argc,\n \t\tfsck_refs(the_repository);\n \n \tif (connectivity_only) {\n-\t\tfor_each_loose_object(mark_loose_for_connectivity, NULL, 0);\n+\t\tfor_each_loose_object(the_repository->objects,\n+\t\t\t\t      mark_loose_for_connectivity, NULL, 0);\n \t\tfor_each_packed_object(the_repository,\n \t\t\t\t       mark_packed_for_connectivity, NULL, 0);\n \t} else {\n \t\todb_prepare_alternates(the_repository->objects);\n \t\tfor (source = the_repository->objects->sources; source; source = source->next)\n-\t\t\tfsck_object_dir(source->path);\n+\t\t\tfsck_source(source);\n \n \t\tif (check_full) {\n \t\t\tstruct packed_git *p;\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 21bd44e1645..6eefefc63d2 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1301,7 +1301,7 @@ static int loose_object_auto_condition(struct gc_config *cfg UNUSED)\n \tif (loose_object_auto_limit < 0)\n \t\treturn 1;\n \n-\treturn for_each_loose_file_in_objdir(the_repository->objects->sources->path,\n+\treturn for_each_loose_file_in_source(the_repository->objects->sources,\n \t\t\t\t\t     loose_object_count,\n \t\t\t\t\t     NULL, NULL, &count);\n }\n@@ -1336,7 +1336,7 @@ static int pack_loose(struct maintenance_run_opts *opts)\n \t * Do not start pack-objects process\n \t * if there are no loose objects.\n \t */\n-\tif (!for_each_loose_file_in_objdir(r->objects->sources->path,\n+\tif (!for_each_loose_file_in_source(r->objects->sources,\n \t\t\t\t\t   bail_on_loose,\n \t\t\t\t\t   NULL, NULL, NULL))\n \t\treturn 0;\n@@ -1376,11 +1376,9 @@ static int pack_loose(struct maintenance_run_opts *opts)\n \telse if (data.batch_size > 0)\n \t\tdata.batch_size--; /* Decrease for equality on limit. */\n \n-\tfor_each_loose_file_in_objdir(r->objects->sources->path,\n+\tfor_each_loose_file_in_source(r->objects->sources,\n \t\t\t\t      write_loose_object_to_stdin,\n-\t\t\t\t      NULL,\n-\t\t\t\t      NULL,\n-\t\t\t\t      &data);\n+\t\t\t\t      NULL, NULL, &data);\n \n \tfclose(data.in);\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex e8e85d8278b..9e85293730b 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4342,9 +4342,8 @@ static int add_loose_object(const struct object_id *oid, const char *path,\n  */\n static void add_unreachable_loose_objects(void)\n {\n-\tfor_each_loose_file_in_objdir(repo_get_object_directory(the_repository),\n-\t\t\t\t      add_loose_object,\n-\t\t\t\t      NULL, NULL, NULL);\n+\tfor_each_loose_file_in_source(the_repository->objects->sources,\n+\t\t\t\t      add_loose_object, NULL, NULL, NULL);\n }\n \n static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid)\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 339017c7ccf..bf5d3bb152c 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -200,7 +200,7 @@ int cmd_prune(int argc,\n \t\trevs.exclude_promisor_objects = 1;\n \t}\n \n-\tfor_each_loose_file_in_objdir(repo_get_object_directory(the_repository),\n+\tfor_each_loose_file_in_source(the_repository->objects->sources,\n \t\t\t\t      prune_object, prune_cruft, prune_subdir, &revs);\n \n \tprune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);\ndiff --git a/object-file.c b/object-file.c\nindex bd93f17dcfe..b894379d22c 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1388,7 +1388,7 @@ static int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \treturn r;\n }\n \n-int for_each_loose_file_in_objdir(const char *path,\n+int for_each_loose_file_in_source(struct odb_source *source,\n \t\t\t\t  each_loose_object_fn obj_cb,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n@@ -1397,11 +1397,10 @@ int for_each_loose_file_in_objdir(const char *path,\n \tstruct strbuf buf = STRBUF_INIT;\n \tint r;\n \n-\tstrbuf_addstr(&buf, path);\n+\tstrbuf_addstr(&buf, source->path);\n \tfor (int i = 0; i < 256; i++) {\n-\t\tr = for_each_file_in_obj_subdir(i, &buf, the_repository->hash_algo,\n-\t\t\t\t\t\tobj_cb, cruft_cb,\n-\t\t\t\t\t\tsubdir_cb, data);\n+\t\tr = for_each_file_in_obj_subdir(i, &buf, source->odb->repo->hash_algo,\n+\t\t\t\t\t\tobj_cb, cruft_cb, subdir_cb, data);\n \t\tif (r)\n \t\t\tbreak;\n \t}\n@@ -1410,14 +1409,15 @@ int for_each_loose_file_in_objdir(const char *path,\n \treturn r;\n }\n \n-int for_each_loose_object(each_loose_object_fn cb, void *data,\n+int for_each_loose_object(struct object_database *odb,\n+\t\t\t  each_loose_object_fn cb, void *data,\n \t\t\t  enum for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \n-\todb_prepare_alternates(the_repository->objects);\n-\tfor (source = the_repository->objects->sources; source; source = source->next) {\n-\t\tint r = for_each_loose_file_in_objdir(source->path, cb, NULL,\n+\todb_prepare_alternates(odb);\n+\tfor (source = odb->sources; source; source = source->next) {\n+\t\tint r = for_each_loose_file_in_source(source, cb, NULL,\n \t\t\t\t\t\t      NULL, data);\n \t\tif (r)\n \t\t\treturn r;\ndiff --git a/object-file.h b/object-file.h\nindex d52b335e85b..1b1ab95423d 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -86,7 +86,7 @@ typedef int each_loose_cruft_fn(const char *basename,\n typedef int each_loose_subdir_fn(unsigned int nr,\n \t\t\t\t const char *path,\n \t\t\t\t void *data);\n-int for_each_loose_file_in_objdir(const char *path,\n+int for_each_loose_file_in_source(struct odb_source *source,\n \t\t\t\t  each_loose_object_fn obj_cb,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n@@ -99,7 +99,8 @@ int for_each_loose_file_in_objdir(const char *path,\n  *\n  * Any flags specific to packs are ignored.\n  */\n-int for_each_loose_object(each_loose_object_fn, void *,\n+int for_each_loose_object(struct object_database *odb,\n+\t\t\t  each_loose_object_fn, void *,\n \t\t\t  enum for_each_object_flags flags);\n \n \ndiff --git a/prune-packed.c b/prune-packed.c\nindex 92fb4fbb0ed..d49dc11957c 100644\n--- a/prune-packed.c\n+++ b/prune-packed.c\n@@ -40,7 +40,7 @@ void prune_packed_objects(int opts)\n \t\tprogress = start_delayed_progress(the_repository,\n \t\t\t\t\t\t  _(\"Removing duplicate objects\"), 256);\n \n-\tfor_each_loose_file_in_objdir(repo_get_object_directory(the_repository),\n+\tfor_each_loose_file_in_source(the_repository->objects->sources,\n \t\t\t\t      prune_object, NULL, prune_subdir, &opts);\n \n \t/* Ensure we show 100% before finishing progress */\ndiff --git a/reachable.c b/reachable.c\nindex e984b68a0c4..5706ccaede3 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -319,7 +319,7 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \toidset_init(&data.extra_recent_oids, 0);\n \tdata.extra_recent_oids_loaded = 0;\n \n-\tr = for_each_loose_object(add_recent_loose, &data,\n+\tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n \t\t\t\t  FOR_EACH_OBJECT_LOCAL_ONLY);\n \tif (r)\n \t\tgoto done;\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521661","messageId":"20250709-pks-object-file-wo-the-repository-v1-14-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 14/19] object-file: get rid of `the_repository` in `read_loose_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:24Z","receivedAt":"2025-07-09T11:18:03Z","isPatch":true,"body":"The function `read_loose_object()` takes a path to an object file and\ntries to parse it. As such, the function does not depend on any specific\nobject database but instead acts as an ODB-independent way to read a\nspecific file. As such, all it needs as input is a repository so that we\ncan derive repo settings and the hash algorithm.\n\nThat repository isn't passed in as a parameter though, as we implicitly\ndepend on the global `the_repository`. Refactor the function so that we\npass in the repository as a parameter.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 2 +-\n object-file.c  | 9 +++++----\n object-file.h  | 3 ++-\n 3 files changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex f0854ce5d84..e9112d884f0 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -633,7 +633,7 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n \toi.sizep = &size;\n \toi.typep = &type;\n \n-\tif (read_loose_object(path, oid, &real_oid, &contents, &oi) < 0) {\n+\tif (read_loose_object(the_repository, path, oid, &real_oid, &contents, &oi) < 0) {\n \t\tif (contents && !oideq(&real_oid, oid))\n \t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n \t\t\t\t    oid_to_hex(&real_oid), path);\ndiff --git a/object-file.c b/object-file.c\nindex b894379d22c..f7c07acadc9 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1535,7 +1535,8 @@ static int check_stream_oid(git_zstream *stream,\n \treturn 0;\n }\n \n-int read_loose_object(const char *path,\n+int read_loose_object(struct repository *repo,\n+\t\t      const char *path,\n \t\t      const struct object_id *expected_oid,\n \t\t      struct object_id *real_oid,\n \t\t      void **contents,\n@@ -1574,9 +1575,9 @@ int read_loose_object(const char *path,\n \t}\n \n \tif (*oi->typep == OBJ_BLOB &&\n-\t    *size > repo_settings_get_big_file_threshold(the_repository)) {\n+\t    *size > repo_settings_get_big_file_threshold(repo)) {\n \t\tif (check_stream_oid(&stream, hdr, *size, path, expected_oid,\n-\t\t\t\t     the_repository->hash_algo) < 0)\n+\t\t\t\t     repo->hash_algo) < 0)\n \t\t\tgoto out_inflate;\n \t} else {\n \t\t*contents = unpack_loose_rest(&stream, hdr, *size, expected_oid);\n@@ -1584,7 +1585,7 @@ int read_loose_object(const char *path,\n \t\t\terror(_(\"unable to unpack contents of %s\"), path);\n \t\t\tgoto out_inflate;\n \t\t}\n-\t\thash_object_file(the_repository->hash_algo,\n+\t\thash_object_file(repo->hash_algo,\n \t\t\t\t *contents, *size,\n \t\t\t\t *oi->typep, real_oid);\n \t\tif (!oideq(expected_oid, real_oid))\ndiff --git a/object-file.h b/object-file.h\nindex 1b1ab95423d..52f7979267d 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -210,7 +210,8 @@ int check_and_freshen_file(const char *fn, int freshen);\n  *\n  * Returns 0 on success, negative on error (details may be written to stderr).\n  */\n-int read_loose_object(const char *path,\n+int read_loose_object(struct repository *repo,\n+\t\t      const char *path,\n \t\t      const struct object_id *expected_oid,\n \t\t      struct object_id *real_oid,\n \t\t      void **contents,\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521663","messageId":"20250709-pks-object-file-wo-the-repository-v1-15-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 15/19] object-file: get rid of `the_repository` in `force_object_loose()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:25Z","receivedAt":"2025-07-09T11:18:06Z","isPatch":true,"body":"The function `force_object_loose()` forces an object to become a loose\nobject in case it only exists in its packed form. To do so it implicitly\nrelies on `the_repository`.\n\nRefactor the function by passing a `struct odb_source` as parameter.\nWhile the check whether any such loose object exists already acts on the\nwhole object database, writing the loose object happens in one specific\nsource.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/pack-objects.c |  3 ++-\n object-file.c          | 18 +++++++++---------\n object-file.h          |  3 ++-\n 3 files changed, 13 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 9e85293730b..7ff79d6b376 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4411,7 +4411,8 @@ static void loosen_unused_packed_objects(void)\n \t\t\tif (!packlist_find(&to_pack, &oid) &&\n \t\t\t    !has_sha1_pack_kept_or_nonlocal(&oid) &&\n \t\t\t    !loosened_object_can_be_discarded(&oid, p->mtime)) {\n-\t\t\t\tif (force_object_loose(&oid, p->mtime))\n+\t\t\t\tif (force_object_loose(the_repository->objects->sources,\n+\t\t\t\t\t\t       &oid, p->mtime))\n \t\t\t\t\tdie(_(\"unable to force loose object\"));\n \t\t\t\tloosened_objects_nr++;\n \t\t\t}\ndiff --git a/object-file.c b/object-file.c\nindex f7c07acadc9..e9152d9e04c 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1077,10 +1077,10 @@ int write_object_file(struct odb_source *source,\n \treturn 0;\n }\n \n-int force_object_loose(const struct object_id *oid, time_t mtime)\n+int force_object_loose(struct odb_source *source,\n+\t\t       const struct object_id *oid, time_t mtime)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tvoid *buf;\n \tunsigned long len;\n \tstruct object_info oi = OBJECT_INFO_INIT;\n@@ -1090,24 +1090,24 @@ int force_object_loose(const struct object_id *oid, time_t mtime)\n \tint hdrlen;\n \tint ret;\n \n-\tfor (struct odb_source *source = repo->objects->sources; source; source = source->next)\n-\t\tif (has_loose_object(source, oid))\n+\tfor (struct odb_source *s = source->odb->sources; s; s = s->next)\n+\t\tif (has_loose_object(s, oid))\n \t\t\treturn 0;\n \n \toi.typep = &type;\n \toi.sizep = &len;\n \toi.contentp = &buf;\n-\tif (odb_read_object_info_extended(the_repository->objects, oid, &oi, 0))\n+\tif (odb_read_object_info_extended(source->odb, oid, &oi, 0))\n \t\treturn error(_(\"cannot read object for %s\"), oid_to_hex(oid));\n \tif (compat) {\n-\t\tif (repo_oid_to_algop(repo, oid, compat, &compat_oid))\n+\t\tif (repo_oid_to_algop(source->odb->repo, oid, compat, &compat_oid))\n \t\t\treturn error(_(\"cannot map object %s to %s\"),\n \t\t\t\t     oid_to_hex(oid), compat->name);\n \t}\n \thdrlen = format_object_header(hdr, sizeof(hdr), type, len);\n-\tret = write_loose_object(repo->objects->sources, oid, hdr, hdrlen, buf, len, mtime, 0);\n+\tret = write_loose_object(source, oid, hdr, hdrlen, buf, len, mtime, 0);\n \tif (!ret && compat)\n-\t\tret = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n+\t\tret = repo_add_loose_object_map(source, oid, &compat_oid);\n \tfree(buf);\n \n \treturn ret;\ndiff --git a/object-file.h b/object-file.h\nindex 52f7979267d..15d97630d3b 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -161,7 +161,8 @@ int stream_loose_object(struct odb_source *source,\n \t\t\tstruct input_stream *in_stream, size_t len,\n \t\t\tstruct object_id *oid);\n \n-int force_object_loose(const struct object_id *oid, time_t mtime);\n+int force_object_loose(struct odb_source *source,\n+\t\t       const struct object_id *oid, time_t mtime);\n \n /**\n  * With in-core object data in \"buf\", rehash it to make sure the\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521664","messageId":"20250709-pks-object-file-wo-the-repository-v1-16-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 16/19] object-file: get rid of `the_repository` in index-related functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:26Z","receivedAt":"2025-07-09T11:18:09Z","isPatch":true,"body":"Both `index_fd()` and `index_path()` still use `the_repository` even\nthough they have a repository available via `struct index_state`. Adapt\nthem so that they use the index' repository instead to get rid of this\nglobal dependency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex e9152d9e04c..2bc36ab3ee8 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1257,7 +1257,7 @@ int index_fd(struct index_state *istate, struct object_id *oid,\n \t\tret = index_stream_convert_blob(istate, oid, fd, path, flags);\n \telse if (!S_ISREG(st->st_mode))\n \t\tret = index_pipe(istate, oid, fd, type, path, flags);\n-\telse if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(the_repository)) ||\n+\telse if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) ||\n \t\t type != OBJ_BLOB ||\n \t\t (path && would_convert_to_git(istate, path)))\n \t\tret = index_core(istate, oid, fd, xsize_t(st->st_size),\n@@ -1291,12 +1291,12 @@ int index_path(struct index_state *istate, struct object_id *oid,\n \t\tif (!(flags & INDEX_WRITE_OBJECT))\n \t\t\thash_object_file(istate->repo->hash_algo, sb.buf, sb.len,\n \t\t\t\t\t OBJ_BLOB, oid);\n-\t\telse if (odb_write_object(the_repository->objects, sb.buf, sb.len, OBJ_BLOB, oid))\n+\t\telse if (odb_write_object(istate->repo->objects, sb.buf, sb.len, OBJ_BLOB, oid))\n \t\t\trc = error(_(\"%s: failed to insert into database\"), path);\n \t\tstrbuf_release(&sb);\n \t\tbreak;\n \tcase S_IFDIR:\n-\t\treturn repo_resolve_gitlink_ref(the_repository, path, \"HEAD\", oid);\n+\t\treturn repo_resolve_gitlink_ref(istate->repo, path, \"HEAD\", oid);\n \tdefault:\n \t\treturn error(_(\"%s: unsupported file type\"), path);\n \t}\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521665","messageId":"20250709-pks-object-file-wo-the-repository-v1-17-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 17/19] environment: move compression level into repo settings","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:27Z","receivedAt":"2025-07-09T11:18:12Z","isPatch":true,"body":"The compression level for loose objects and packfiles is tracked via a\ncouple of global variables. Refactor these so that the values for them\nare tracked via repo settings, which allows us to drop this global\ndependency.\n\nNote that the refactoring is mostly straight-forward, except in\ngit-pack-objects(1). Here it is possible to change the compression level\nvia a command line option, and that option of course should override\nwhatever the user has configured. This creates the problem that we need\nto be able to see whether the option has been given in the first place.\n\nThis is done by using `INT_MIN` as default value. Any value smaller than\n-1 is an invalid compression level, so it's quite unlikely that any user\never passed that sentinel value. And if they did we would have died\nanyway.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-import.c  |  8 +++++---\n builtin/index-pack.c   |  3 ++-\n builtin/pack-objects.c | 21 ++++++++++++++-------\n bulk-checkin.c         |  3 ++-\n config.c               | 38 --------------------------------------\n diff.c                 |  3 ++-\n environment.c          |  3 ---\n environment.h          |  2 --\n http-push.c            |  3 ++-\n object-file.c          |  3 ++-\n repo-settings.c        | 38 ++++++++++++++++++++++++++++++++++++++\n repo-settings.h        |  2 ++\n 12 files changed, 69 insertions(+), 58 deletions(-)\n\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 89f57898b15..2733c6ed7fc 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -992,7 +992,8 @@ static int store_object(\n \t} else\n \t\tdelta = NULL;\n \n-\tgit_deflate_init(&s, pack_compression_level);\n+\tprepare_repo_settings(the_repository);\n+\tgit_deflate_init(&s, the_repository->settings.pack_compression_level);\n \tif (delta) {\n \t\ts.next_in = delta;\n \t\ts.avail_in = deltalen;\n@@ -1019,7 +1020,7 @@ static int store_object(\n \t\tif (delta) {\n \t\t\tFREE_AND_NULL(delta);\n \n-\t\t\tgit_deflate_init(&s, pack_compression_level);\n+\t\t\tgit_deflate_init(&s, the_repository->settings.pack_compression_level);\n \t\t\ts.next_in = (void *)dat->buf;\n \t\t\ts.avail_in = dat->len;\n \t\t\ts.avail_out = git_deflate_bound(&s, s.avail_in);\n@@ -1120,7 +1121,8 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)\n \n \tcrc32_begin(pack_file);\n \n-\tgit_deflate_init(&s, pack_compression_level);\n+\tprepare_repo_settings(the_repository);\n+\tgit_deflate_init(&s, the_repository->settings.pack_compression_level);\n \n \thdrlen = encode_in_pack_object_header(out_buf, out_sz, OBJ_BLOB, len);\n \ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex dabeb825a6c..d302bab9de9 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1420,7 +1420,8 @@ static int write_compressed(struct hashfile *f, void *in, unsigned int size)\n \tint status;\n \tunsigned char outbuf[4096];\n \n-\tgit_deflate_init(&stream, zlib_compression_level);\n+\tprepare_repo_settings(the_repository);\n+\tgit_deflate_init(&stream, the_repository->settings.zlib_compression_level);\n \tstream.next_in = in;\n \tstream.avail_in = size;\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 7ff79d6b376..62096c1fe03 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -379,7 +379,8 @@ static unsigned long do_compress(void **pptr, unsigned long size)\n \tvoid *in, *out;\n \tunsigned long maxsize;\n \n-\tgit_deflate_init(&stream, pack_compression_level);\n+\tprepare_repo_settings(the_repository);\n+\tgit_deflate_init(&stream, the_repository->settings.pack_compression_level);\n \tmaxsize = git_deflate_bound(&stream, size);\n \n \tin = *pptr;\n@@ -406,7 +407,8 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct hashfi\n \tunsigned char obuf[1024 * 16];\n \tunsigned long olen = 0;\n \n-\tgit_deflate_init(&stream, pack_compression_level);\n+\tprepare_repo_settings(the_repository);\n+\tgit_deflate_init(&stream, the_repository->settings.pack_compression_level);\n \n \tfor (;;) {\n \t\tssize_t readlen;\n@@ -4803,6 +4805,7 @@ int cmd_pack_objects(int argc,\n \t\t     const char *prefix,\n \t\t     struct repository *repo UNUSED)\n {\n+\tint compression_level = INT_MIN;\n \tint use_internal_rev_list = 0;\n \tint all_progress_implied = 0;\n \tstruct strvec rp = STRVEC_INIT;\n@@ -4892,7 +4895,7 @@ int cmd_pack_objects(int argc,\n \t\t\t N_(\"ignore packs that have companion .keep file\")),\n \t\tOPT_STRING_LIST(0, \"keep-pack\", &keep_pack_list, N_(\"name\"),\n \t\t\t\tN_(\"ignore this pack\")),\n-\t\tOPT_INTEGER(0, \"compression\", &pack_compression_level,\n+\t\tOPT_INTEGER(0, \"compression\", &compression_level,\n \t\t\t    N_(\"pack compression level\")),\n \t\tOPT_BOOL(0, \"keep-true-parents\", &grafts_keep_true_parents,\n \t\t\t N_(\"do not hide commits by grafts\")),\n@@ -5046,10 +5049,14 @@ int cmd_pack_objects(int argc,\n \n \tif (!reuse_object)\n \t\treuse_delta = 0;\n-\tif (pack_compression_level == -1)\n-\t\tpack_compression_level = Z_DEFAULT_COMPRESSION;\n-\telse if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION)\n-\t\tdie(_(\"bad pack compression level %d\"), pack_compression_level);\n+\tif (compression_level != INT_MIN) {\n+\t\tif (compression_level == -1)\n+\t\t\tcompression_level = Z_DEFAULT_COMPRESSION;\n+\t\telse if (compression_level < 0 || compression_level > Z_BEST_COMPRESSION)\n+\t\t\tdie(_(\"bad pack compression level %d\"), compression_level);\n+\t\tprepare_repo_settings(the_repository);\n+\t\tthe_repository->settings.pack_compression_level = compression_level;\n+\t}\n \n \tif (!delta_search_threads)\t/* --threads=0 means autodetect */\n \t\tdelta_search_threads = online_cpus();\ndiff --git a/bulk-checkin.c b/bulk-checkin.c\nindex b2809ab0398..3ea181baf93 100644\n--- a/bulk-checkin.c\n+++ b/bulk-checkin.c\n@@ -171,7 +171,8 @@ static int stream_blob_to_pack(struct bulk_checkin_packfile *state,\n \tint write_object = (flags & INDEX_WRITE_OBJECT);\n \toff_t offset = 0;\n \n-\tgit_deflate_init(&s, pack_compression_level);\n+\tprepare_repo_settings(the_repository);\n+\tgit_deflate_init(&s, the_repository->settings.pack_compression_level);\n \n \thdrlen = encode_in_pack_object_header(obuf, sizeof(obuf), OBJ_BLOB, size);\n \ts.next_out = obuf + hdrlen;\ndiff --git a/config.c b/config.c\nindex 095a17bd429..b7d1fa90fbf 100644\n--- a/config.c\n+++ b/config.c\n@@ -71,9 +71,6 @@ struct config_source {\n };\n #define CONFIG_SOURCE_INIT { 0 }\n \n-static int pack_compression_seen;\n-static int zlib_compression_seen;\n-\n /*\n  * Config that comes from trusted scopes, namely:\n  * - CONFIG_SCOPE_SYSTEM (e.g. /etc/gitconfig)\n@@ -1466,30 +1463,6 @@ static int git_default_core_config(const char *var, const char *value,\n \tif (!strcmp(var, \"core.disambiguate\"))\n \t\treturn set_disambiguate_hint_config(var, value);\n \n-\tif (!strcmp(var, \"core.loosecompression\")) {\n-\t\tint level = git_config_int(var, value, ctx->kvi);\n-\t\tif (level == -1)\n-\t\t\tlevel = Z_DEFAULT_COMPRESSION;\n-\t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n-\t\t\tdie(_(\"bad zlib compression level %d\"), level);\n-\t\tzlib_compression_level = level;\n-\t\tzlib_compression_seen = 1;\n-\t\treturn 0;\n-\t}\n-\n-\tif (!strcmp(var, \"core.compression\")) {\n-\t\tint level = git_config_int(var, value, ctx->kvi);\n-\t\tif (level == -1)\n-\t\t\tlevel = Z_DEFAULT_COMPRESSION;\n-\t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n-\t\t\tdie(_(\"bad zlib compression level %d\"), level);\n-\t\tif (!zlib_compression_seen)\n-\t\t\tzlib_compression_level = level;\n-\t\tif (!pack_compression_seen)\n-\t\t\tpack_compression_level = level;\n-\t\treturn 0;\n-\t}\n-\n \tif (!strcmp(var, \"core.autocrlf\")) {\n \t\tif (value && !strcasecmp(value, \"input\")) {\n \t\t\tauto_crlf = AUTO_CRLF_INPUT;\n@@ -1802,17 +1775,6 @@ int git_default_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(var, \"pack.compression\")) {\n-\t\tint level = git_config_int(var, value, ctx->kvi);\n-\t\tif (level == -1)\n-\t\t\tlevel = Z_DEFAULT_COMPRESSION;\n-\t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n-\t\t\tdie(_(\"bad pack compression level %d\"), level);\n-\t\tpack_compression_level = level;\n-\t\tpack_compression_seen = 1;\n-\t\treturn 0;\n-\t}\n-\n \tif (starts_with(var, \"sparse.\"))\n \t\treturn git_default_sparse_config(var, value);\n \ndiff --git a/diff.c b/diff.c\nindex dca87e164fb..45c0bcd2bde 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3307,7 +3307,8 @@ static unsigned char *deflate_it(char *data,\n \tunsigned char *deflated;\n \tgit_zstream stream;\n \n-\tgit_deflate_init(&stream, zlib_compression_level);\n+\tprepare_repo_settings(the_repository);\n+\tgit_deflate_init(&stream, the_repository->settings.zlib_compression_level);\n \tbound = git_deflate_bound(&stream, size);\n \tdeflated = xmalloc(bound);\n \tstream.next_out = deflated;\ndiff --git a/environment.c b/environment.c\nindex 7bf0390a335..dbb186b56d0 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -16,7 +16,6 @@\n #include \"convert.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n-#include \"git-zlib.h\"\n #include \"repository.h\"\n #include \"config.h\"\n #include \"refs.h\"\n@@ -43,8 +42,6 @@ char *git_log_output_encoding;\n char *apply_default_whitespace;\n char *apply_default_ignorewhitespace;\n char *git_attributes_file;\n-int zlib_compression_level = Z_BEST_SPEED;\n-int pack_compression_level = Z_DEFAULT_COMPRESSION;\n int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\ndiff --git a/environment.h b/environment.h\nindex 9a3d05d414a..4245b58af6e 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -150,8 +150,6 @@ extern int warn_on_object_refname_ambiguity;\n extern char *apply_default_whitespace;\n extern char *apply_default_ignorewhitespace;\n extern char *git_attributes_file;\n-extern int zlib_compression_level;\n-extern int pack_compression_level;\n extern unsigned long pack_size_limit_cfg;\n extern int max_allowed_tree_depth;\n \ndiff --git a/http-push.c b/http-push.c\nindex 91a5465afb1..77670774713 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -374,7 +374,8 @@ static void start_put(struct transfer_request *request)\n \thdrlen = format_object_header(hdr, sizeof(hdr), type, len);\n \n \t/* Set it up */\n-\tgit_deflate_init(&stream, zlib_compression_level);\n+\tprepare_repo_settings(the_repository);\n+\tgit_deflate_init(&stream, the_repository->settings.zlib_compression_level);\n \tsize = git_deflate_bound(&stream, len + hdrlen);\n \tstrbuf_grow(&request->buffer.buf, size);\n \trequest->buffer.posn = 0;\ndiff --git a/object-file.c b/object-file.c\nindex 2bc36ab3ee8..0afd39dd346 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -769,7 +769,8 @@ static int start_loose_object_common(struct odb_source *source,\n \t}\n \n \t/*  Setup zlib stream for compression */\n-\tgit_deflate_init(stream, zlib_compression_level);\n+\tprepare_repo_settings(source->odb->repo);\n+\tgit_deflate_init(stream, source->odb->repo->settings.zlib_compression_level);\n \tstream->next_out = buf;\n \tstream->avail_out = buflen;\n \talgo->init_fn(c);\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 195c24e9c07..1d3626018a0 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -1,5 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"config.h\"\n+#include \"git-zlib.h\"\n+#include \"gettext.h\"\n #include \"repo-settings.h\"\n #include \"repository.h\"\n #include \"midx.h\"\n@@ -29,6 +31,8 @@ static void repo_cfg_ulong(struct repository *r, const char *key, unsigned long\n \n void prepare_repo_settings(struct repository *r)\n {\n+\tint pack_compression_seen = 0;\n+\tint zlib_compression_seen = 0;\n \tint experimental;\n \tint value;\n \tconst char *strval;\n@@ -151,6 +155,40 @@ void prepare_repo_settings(struct repository *r)\n \n \tif (!repo_config_get_ulong(r, \"core.packedgitlimit\", &ulongval))\n \t\tr->settings.packed_git_limit = ulongval;\n+\n+\tif (!repo_config_get_int(r, \"core.loosecompression\", &value)) {\n+\t\tif (value == -1)\n+\t\t\tvalue = Z_DEFAULT_COMPRESSION;\n+\t\telse if (value < 0 || value > Z_BEST_COMPRESSION)\n+\t\t\tdie(_(\"bad zlib compression level %d\"), value);\n+\t\tr->settings.zlib_compression_level = value;\n+\t\tzlib_compression_seen = 1;\n+\t}\n+\n+\tif (!repo_config_get_int(r, \"pack.compression\", &value)) {\n+\t\tif (value == -1)\n+\t\t\tvalue = Z_DEFAULT_COMPRESSION;\n+\t\telse if (value < 0 || value > Z_BEST_COMPRESSION)\n+\t\t\tdie(_(\"bad pack compression level %d\"), value);\n+\t\tr->settings.pack_compression_level = value;\n+\t\tpack_compression_seen = 1;\n+\t}\n+\n+\tif (!repo_config_get_int(r, \"core.compression\", &value)) {\n+\t\tif (value == -1)\n+\t\t\tvalue = Z_DEFAULT_COMPRESSION;\n+\t\telse if (value < 0 || value > Z_BEST_COMPRESSION)\n+\t\t\tdie(_(\"bad zlib compression level %d\"), value);\n+\t\tif (!zlib_compression_seen)\n+\t\t\tr->settings.zlib_compression_level = value;\n+\t\tif (!pack_compression_seen)\n+\t\t\tr->settings.pack_compression_level = value;\n+\t} else {\n+\t\tif (!zlib_compression_seen)\n+\t\t\tr->settings.zlib_compression_level = Z_BEST_SPEED;\n+\t\tif (!pack_compression_seen)\n+\t\t\tr->settings.pack_compression_level = Z_DEFAULT_COMPRESSION;\n+\t}\n }\n \n void repo_settings_clear(struct repository *r)\ndiff --git a/repo-settings.h b/repo-settings.h\nindex d4778855614..f60900317cf 100644\n--- a/repo-settings.h\n+++ b/repo-settings.h\n@@ -36,6 +36,8 @@ struct repo_settings {\n \tint pack_read_reverse_index;\n \tint pack_use_bitmap_boundary_traversal;\n \tint pack_use_multi_pack_reuse;\n+\tint pack_compression_level;\n+\tint zlib_compression_level;\n \n \tint shared_repository;\n \tint shared_repository_initialized;\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521666","messageId":"20250709-pks-object-file-wo-the-repository-v1-18-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 18/19] environment: move object creation mode into repo settings","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:28Z","receivedAt":"2025-07-09T11:18:15Z","isPatch":true,"body":"The object creation mode controls whether we use hardlinks or renames to\nmove objects into place. The value for that config is stored in a global\nvariable, which is bad practice nowadays.\n\nRefactor the config value so that it is instead tracked via our repo\nsettings.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c        | 12 ------------\n environment.c   |  4 ----\n environment.h   |  6 ------\n object-file.c   |  3 ++-\n repo-settings.c | 16 ++++++++++++++++\n repo-settings.h |  6 ++++++\n 6 files changed, 24 insertions(+), 23 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex b7d1fa90fbf..81a29e37010 100644\n--- a/config.c\n+++ b/config.c\n@@ -1568,18 +1568,6 @@ static int git_default_core_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n-\tif (!strcmp(var, \"core.createobject\")) {\n-\t\tif (!value)\n-\t\t\treturn config_error_nonbool(var);\n-\t\tif (!strcmp(value, \"rename\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_RENAMES;\n-\t\telse if (!strcmp(value, \"link\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n-\t\telse\n-\t\t\tdie(_(\"invalid mode for object creation: %s\"), value);\n-\t\treturn 0;\n-\t}\n-\n \tif (!strcmp(var, \"core.sparsecheckout\")) {\n \t\tcore_apply_sparse_checkout = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/environment.c b/environment.c\nindex dbb186b56d0..ed0e9c62346 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -56,10 +56,6 @@ char *check_roundtrip_encoding;\n enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;\n enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;\n-#ifndef OBJECT_CREATION_MODE\n-#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n-#endif\n-enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;\n int grafts_keep_true_parents;\n int core_apply_sparse_checkout;\n int core_sparse_checkout_cone;\ndiff --git a/environment.h b/environment.h\nindex 4245b58af6e..7c5ddc1da8f 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -179,12 +179,6 @@ enum push_default_type {\n };\n extern enum push_default_type push_default;\n \n-enum object_creation_mode {\n-\tOBJECT_CREATION_USES_HARDLINKS = 0,\n-\tOBJECT_CREATION_USES_RENAMES = 1\n-};\n-extern enum object_creation_mode object_creation_mode;\n-\n extern int grafts_keep_true_parents;\n \n extern int repository_format_precious_objects;\ndiff --git a/object-file.c b/object-file.c\nindex 0afd39dd346..55396d4eaeb 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -600,7 +600,8 @@ int finalize_object_file_flags(struct repository *repo,\n retry:\n \tret = 0;\n \n-\tif (object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n+\tprepare_repo_settings(repo);\n+\tif (repo->settings.object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n \t\tgoto try_rename;\n \telse if (link(tmpfile, filename))\n \t\tret = errno;\ndiff --git a/repo-settings.c b/repo-settings.c\nindex 1d3626018a0..38a4145c3eb 100644\n--- a/repo-settings.c\n+++ b/repo-settings.c\n@@ -189,6 +189,22 @@ void prepare_repo_settings(struct repository *r)\n \t\tif (!pack_compression_seen)\n \t\t\tr->settings.pack_compression_level = Z_DEFAULT_COMPRESSION;\n \t}\n+\n+\tif (!repo_config_get_string_tmp(r, \"core.createobject\", &strval)) {\n+\t\tif (!strval)\n+\t\t\tdie(_(\"missing value for '%s'\"), strval);\n+\t\tif (!strcmp(strval, \"rename\"))\n+\t\t\tr->settings.object_creation_mode = OBJECT_CREATION_USES_RENAMES;\n+\t\telse if (!strcmp(strval, \"link\"))\n+\t\t\tr->settings.object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n+\t\telse\n+\t\t\tdie(_(\"invalid mode for object creation: %s\"), strval);\n+\t} else {\n+#ifndef OBJECT_CREATION_MODE\n+# define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n+#endif\n+\t\tr->settings.object_creation_mode = OBJECT_CREATION_MODE;\n+\t}\n }\n \n void repo_settings_clear(struct repository *r)\ndiff --git a/repo-settings.h b/repo-settings.h\nindex f60900317cf..18074115145 100644\n--- a/repo-settings.h\n+++ b/repo-settings.h\n@@ -23,6 +23,11 @@ enum log_refs_config {\n \tLOG_REFS_ALWAYS\n };\n \n+enum object_creation_mode {\n+\tOBJECT_CREATION_USES_HARDLINKS = 0,\n+\tOBJECT_CREATION_USES_RENAMES = 1\n+};\n+\n struct repo_settings {\n \tint initialized;\n \n@@ -60,6 +65,7 @@ struct repo_settings {\n \tint pack_use_sparse;\n \tint pack_use_path_walk;\n \tenum fetch_negotiation_setting fetch_negotiation_algorithm;\n+\tenum object_creation_mode object_creation_mode;\n \n \tint core_multi_pack_index;\n \tint warn_ambiguous_refs; /* lazily loaded via accessor */\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521667","messageId":"20250709-pks-object-file-wo-the-repository-v1-19-62627b55707f@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH 19/19] object-file: drop USE_THE_REPOSITORY_VARIABLE","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-09T11:17:29Z","receivedAt":"2025-07-09T11:18:17Z","isPatch":true,"body":"We do not depend on any global state anymore in \"object-file.c\", so we\ncan now get rid of the USE_THE_REPOSITORY_VARIABLE preprocessor macro.\nRemove it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 55396d4eaeb..9b4ae1bc82b 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -7,8 +7,6 @@\n  * creation etc.\n  */\n \n-#define USE_THE_REPOSITORY_VARIABLE\n-\n #include \"git-compat-util.h\"\n #include \"bulk-checkin.h\"\n #include \"convert.h\"\n\n-- \n2.50.1.327.g047016eb4a.dirty\n\n"},{"id":"521690","messageId":"32fceddc-c867-4a47-bde8-c873279edbc1@gmail.com","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-17-62627b55707f@pks.im","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-09T15:26:54Z","receivedAt":"2025-07-09T15:26:57Z","isPatch":true,"body":"Hi Patrick\n\nOn 09/07/2025 12:17, Patrick Steinhardt wrote:\n> The compression level for loose objects and packfiles is tracked via a\n> couple of global variables. Refactor these so that the values for them\n> are tracked via repo settings, which allows us to drop this global\n> dependency.\n\nThe delayed config parsing causes a user visible regression in \nfast-import. If I run\n\n$ git fast-export HEAD^ | git -c core.compression=500 fast-import\n\nit dies with\n\nfatal: bad zlib compression level 500\n\nWith this series applied I see\n\nfatal: bad zlib compression level 500\nfast-import: dumping crash report to \n/home/phil/src/git/.git/worktrees/tmp2/fast_import_crash_13462\n\nI do not think adding prepare_repo_settings() calls all over the place \nis a good way forward as it makes it very easy to introduce regressions \nlike this. Our builtin commands parse the config at startup for good \nreasons if we're going to move settings out of git_default_core_config() \nwe should ensure that they are still parsed at startup.\n\nThanks\n\nPhillip\n\n\n> Note that the refactoring is mostly straight-forward, except in\n> git-pack-objects(1). Here it is possible to change the compression level\n> via a command line option, and that option of course should override\n> whatever the user has configured. This creates the problem that we need\n> to be able to see whether the option has been given in the first place.\n> \n> This is done by using `INT_MIN` as default value. Any value smaller than\n> -1 is an invalid compression level, so it's quite unlikely that any user\n> ever passed that sentinel value. And if they did we would have died\n> anyway.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>   builtin/fast-import.c  |  8 +++++---\n>   builtin/index-pack.c   |  3 ++-\n>   builtin/pack-objects.c | 21 ++++++++++++++-------\n>   bulk-checkin.c         |  3 ++-\n>   config.c               | 38 --------------------------------------\n>   diff.c                 |  3 ++-\n>   environment.c          |  3 ---\n>   environment.h          |  2 --\n>   http-push.c            |  3 ++-\n>   object-file.c          |  3 ++-\n>   repo-settings.c        | 38 ++++++++++++++++++++++++++++++++++++++\n>   repo-settings.h        |  2 ++\n>   12 files changed, 69 insertions(+), 58 deletions(-)\n> \n> diff --git a/builtin/fast-import.c b/builtin/fast-import.c\n> index 89f57898b15..2733c6ed7fc 100644\n> --- a/builtin/fast-import.c\n> +++ b/builtin/fast-import.c\n> @@ -992,7 +992,8 @@ static int store_object(\n>   \t} else\n>   \t\tdelta = NULL;\n>   \n> -\tgit_deflate_init(&s, pack_compression_level);\n> +\tprepare_repo_settings(the_repository);\n> +\tgit_deflate_init(&s, the_repository->settings.pack_compression_level);\n>   \tif (delta) {\n>   \t\ts.next_in = delta;\n>   \t\ts.avail_in = deltalen;\n> @@ -1019,7 +1020,7 @@ static int store_object(\n>   \t\tif (delta) {\n>   \t\t\tFREE_AND_NULL(delta);\n>   \n> -\t\t\tgit_deflate_init(&s, pack_compression_level);\n> +\t\t\tgit_deflate_init(&s, the_repository->settings.pack_compression_level);\n>   \t\t\ts.next_in = (void *)dat->buf;\n>   \t\t\ts.avail_in = dat->len;\n>   \t\t\ts.avail_out = git_deflate_bound(&s, s.avail_in);\n> @@ -1120,7 +1121,8 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)\n>   \n>   \tcrc32_begin(pack_file);\n>   \n> -\tgit_deflate_init(&s, pack_compression_level);\n> +\tprepare_repo_settings(the_repository);\n> +\tgit_deflate_init(&s, the_repository->settings.pack_compression_level);\n>   \n>   \thdrlen = encode_in_pack_object_header(out_buf, out_sz, OBJ_BLOB, len);\n>   \n> diff --git a/builtin/index-pack.c b/builtin/index-pack.c\n> index dabeb825a6c..d302bab9de9 100644\n> --- a/builtin/index-pack.c\n> +++ b/builtin/index-pack.c\n> @@ -1420,7 +1420,8 @@ static int write_compressed(struct hashfile *f, void *in, unsigned int size)\n>   \tint status;\n>   \tunsigned char outbuf[4096];\n>   \n> -\tgit_deflate_init(&stream, zlib_compression_level);\n> +\tprepare_repo_settings(the_repository);\n> +\tgit_deflate_init(&stream, the_repository->settings.zlib_compression_level);\n>   \tstream.next_in = in;\n>   \tstream.avail_in = size;\n>   \n> diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\n> index 7ff79d6b376..62096c1fe03 100644\n> --- a/builtin/pack-objects.c\n> +++ b/builtin/pack-objects.c\n> @@ -379,7 +379,8 @@ static unsigned long do_compress(void **pptr, unsigned long size)\n>   \tvoid *in, *out;\n>   \tunsigned long maxsize;\n>   \n> -\tgit_deflate_init(&stream, pack_compression_level);\n> +\tprepare_repo_settings(the_repository);\n> +\tgit_deflate_init(&stream, the_repository->settings.pack_compression_level);\n>   \tmaxsize = git_deflate_bound(&stream, size);\n>   \n>   \tin = *pptr;\n> @@ -406,7 +407,8 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct hashfi\n>   \tunsigned char obuf[1024 * 16];\n>   \tunsigned long olen = 0;\n>   \n> -\tgit_deflate_init(&stream, pack_compression_level);\n> +\tprepare_repo_settings(the_repository);\n> +\tgit_deflate_init(&stream, the_repository->settings.pack_compression_level);\n>   \n>   \tfor (;;) {\n>   \t\tssize_t readlen;\n> @@ -4803,6 +4805,7 @@ int cmd_pack_objects(int argc,\n>   \t\t     const char *prefix,\n>   \t\t     struct repository *repo UNUSED)\n>   {\n> +\tint compression_level = INT_MIN;\n>   \tint use_internal_rev_list = 0;\n>   \tint all_progress_implied = 0;\n>   \tstruct strvec rp = STRVEC_INIT;\n> @@ -4892,7 +4895,7 @@ int cmd_pack_objects(int argc,\n>   \t\t\t N_(\"ignore packs that have companion .keep file\")),\n>   \t\tOPT_STRING_LIST(0, \"keep-pack\", &keep_pack_list, N_(\"name\"),\n>   \t\t\t\tN_(\"ignore this pack\")),\n> -\t\tOPT_INTEGER(0, \"compression\", &pack_compression_level,\n> +\t\tOPT_INTEGER(0, \"compression\", &compression_level,\n>   \t\t\t    N_(\"pack compression level\")),\n>   \t\tOPT_BOOL(0, \"keep-true-parents\", &grafts_keep_true_parents,\n>   \t\t\t N_(\"do not hide commits by grafts\")),\n> @@ -5046,10 +5049,14 @@ int cmd_pack_objects(int argc,\n>   \n>   \tif (!reuse_object)\n>   \t\treuse_delta = 0;\n> -\tif (pack_compression_level == -1)\n> -\t\tpack_compression_level = Z_DEFAULT_COMPRESSION;\n> -\telse if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION)\n> -\t\tdie(_(\"bad pack compression level %d\"), pack_compression_level);\n> +\tif (compression_level != INT_MIN) {\n> +\t\tif (compression_level == -1)\n> +\t\t\tcompression_level = Z_DEFAULT_COMPRESSION;\n> +\t\telse if (compression_level < 0 || compression_level > Z_BEST_COMPRESSION)\n> +\t\t\tdie(_(\"bad pack compression level %d\"), compression_level);\n> +\t\tprepare_repo_settings(the_repository);\n> +\t\tthe_repository->settings.pack_compression_level = compression_level;\n> +\t}\n>   \n>   \tif (!delta_search_threads)\t/* --threads=0 means autodetect */\n>   \t\tdelta_search_threads = online_cpus();\n> diff --git a/bulk-checkin.c b/bulk-checkin.c\n> index b2809ab0398..3ea181baf93 100644\n> --- a/bulk-checkin.c\n> +++ b/bulk-checkin.c\n> @@ -171,7 +171,8 @@ static int stream_blob_to_pack(struct bulk_checkin_packfile *state,\n>   \tint write_object = (flags & INDEX_WRITE_OBJECT);\n>   \toff_t offset = 0;\n>   \n> -\tgit_deflate_init(&s, pack_compression_level);\n> +\tprepare_repo_settings(the_repository);\n> +\tgit_deflate_init(&s, the_repository->settings.pack_compression_level);\n>   \n>   \thdrlen = encode_in_pack_object_header(obuf, sizeof(obuf), OBJ_BLOB, size);\n>   \ts.next_out = obuf + hdrlen;\n> diff --git a/config.c b/config.c\n> index 095a17bd429..b7d1fa90fbf 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -71,9 +71,6 @@ struct config_source {\n>   };\n>   #define CONFIG_SOURCE_INIT { 0 }\n>   \n> -static int pack_compression_seen;\n> -static int zlib_compression_seen;\n> -\n>   /*\n>    * Config that comes from trusted scopes, namely:\n>    * - CONFIG_SCOPE_SYSTEM (e.g. /etc/gitconfig)\n> @@ -1466,30 +1463,6 @@ static int git_default_core_config(const char *var, const char *value,\n>   \tif (!strcmp(var, \"core.disambiguate\"))\n>   \t\treturn set_disambiguate_hint_config(var, value);\n>   \n> -\tif (!strcmp(var, \"core.loosecompression\")) {\n> -\t\tint level = git_config_int(var, value, ctx->kvi);\n> -\t\tif (level == -1)\n> -\t\t\tlevel = Z_DEFAULT_COMPRESSION;\n> -\t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n> -\t\t\tdie(_(\"bad zlib compression level %d\"), level);\n> -\t\tzlib_compression_level = level;\n> -\t\tzlib_compression_seen = 1;\n> -\t\treturn 0;\n> -\t}\n> -\n> -\tif (!strcmp(var, \"core.compression\")) {\n> -\t\tint level = git_config_int(var, value, ctx->kvi);\n> -\t\tif (level == -1)\n> -\t\t\tlevel = Z_DEFAULT_COMPRESSION;\n> -\t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n> -\t\t\tdie(_(\"bad zlib compression level %d\"), level);\n> -\t\tif (!zlib_compression_seen)\n> -\t\t\tzlib_compression_level = level;\n> -\t\tif (!pack_compression_seen)\n> -\t\t\tpack_compression_level = level;\n> -\t\treturn 0;\n> -\t}\n> -\n>   \tif (!strcmp(var, \"core.autocrlf\")) {\n>   \t\tif (value && !strcasecmp(value, \"input\")) {\n>   \t\t\tauto_crlf = AUTO_CRLF_INPUT;\n> @@ -1802,17 +1775,6 @@ int git_default_config(const char *var, const char *value,\n>   \t\treturn 0;\n>   \t}\n>   \n> -\tif (!strcmp(var, \"pack.compression\")) {\n> -\t\tint level = git_config_int(var, value, ctx->kvi);\n> -\t\tif (level == -1)\n> -\t\t\tlevel = Z_DEFAULT_COMPRESSION;\n> -\t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n> -\t\t\tdie(_(\"bad pack compression level %d\"), level);\n> -\t\tpack_compression_level = level;\n> -\t\tpack_compression_seen = 1;\n> -\t\treturn 0;\n> -\t}\n> -\n>   \tif (starts_with(var, \"sparse.\"))\n>   \t\treturn git_default_sparse_config(var, value);\n>   \n> diff --git a/diff.c b/diff.c\n> index dca87e164fb..45c0bcd2bde 100644\n> --- a/diff.c\n> +++ b/diff.c\n> @@ -3307,7 +3307,8 @@ static unsigned char *deflate_it(char *data,\n>   \tunsigned char *deflated;\n>   \tgit_zstream stream;\n>   \n> -\tgit_deflate_init(&stream, zlib_compression_level);\n> +\tprepare_repo_settings(the_repository);\n> +\tgit_deflate_init(&stream, the_repository->settings.zlib_compression_level);\n>   \tbound = git_deflate_bound(&stream, size);\n>   \tdeflated = xmalloc(bound);\n>   \tstream.next_out = deflated;\n> diff --git a/environment.c b/environment.c\n> index 7bf0390a335..dbb186b56d0 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -16,7 +16,6 @@\n>   #include \"convert.h\"\n>   #include \"environment.h\"\n>   #include \"gettext.h\"\n> -#include \"git-zlib.h\"\n>   #include \"repository.h\"\n>   #include \"config.h\"\n>   #include \"refs.h\"\n> @@ -43,8 +42,6 @@ char *git_log_output_encoding;\n>   char *apply_default_whitespace;\n>   char *apply_default_ignorewhitespace;\n>   char *git_attributes_file;\n> -int zlib_compression_level = Z_BEST_SPEED;\n> -int pack_compression_level = Z_DEFAULT_COMPRESSION;\n>   int fsync_object_files = -1;\n>   int use_fsync = -1;\n>   enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n> diff --git a/environment.h b/environment.h\n> index 9a3d05d414a..4245b58af6e 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -150,8 +150,6 @@ extern int warn_on_object_refname_ambiguity;\n>   extern char *apply_default_whitespace;\n>   extern char *apply_default_ignorewhitespace;\n>   extern char *git_attributes_file;\n> -extern int zlib_compression_level;\n> -extern int pack_compression_level;\n>   extern unsigned long pack_size_limit_cfg;\n>   extern int max_allowed_tree_depth;\n>   \n> diff --git a/http-push.c b/http-push.c\n> index 91a5465afb1..77670774713 100644\n> --- a/http-push.c\n> +++ b/http-push.c\n> @@ -374,7 +374,8 @@ static void start_put(struct transfer_request *request)\n>   \thdrlen = format_object_header(hdr, sizeof(hdr), type, len);\n>   \n>   \t/* Set it up */\n> -\tgit_deflate_init(&stream, zlib_compression_level);\n> +\tprepare_repo_settings(the_repository);\n> +\tgit_deflate_init(&stream, the_repository->settings.zlib_compression_level);\n>   \tsize = git_deflate_bound(&stream, len + hdrlen);\n>   \tstrbuf_grow(&request->buffer.buf, size);\n>   \trequest->buffer.posn = 0;\n> diff --git a/object-file.c b/object-file.c\n> index 2bc36ab3ee8..0afd39dd346 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -769,7 +769,8 @@ static int start_loose_object_common(struct odb_source *source,\n>   \t}\n>   \n>   \t/*  Setup zlib stream for compression */\n> -\tgit_deflate_init(stream, zlib_compression_level);\n> +\tprepare_repo_settings(source->odb->repo);\n> +\tgit_deflate_init(stream, source->odb->repo->settings.zlib_compression_level);\n>   \tstream->next_out = buf;\n>   \tstream->avail_out = buflen;\n>   \talgo->init_fn(c);\n> diff --git a/repo-settings.c b/repo-settings.c\n> index 195c24e9c07..1d3626018a0 100644\n> --- a/repo-settings.c\n> +++ b/repo-settings.c\n> @@ -1,5 +1,7 @@\n>   #include \"git-compat-util.h\"\n>   #include \"config.h\"\n> +#include \"git-zlib.h\"\n> +#include \"gettext.h\"\n>   #include \"repo-settings.h\"\n>   #include \"repository.h\"\n>   #include \"midx.h\"\n> @@ -29,6 +31,8 @@ static void repo_cfg_ulong(struct repository *r, const char *key, unsigned long\n>   \n>   void prepare_repo_settings(struct repository *r)\n>   {\n> +\tint pack_compression_seen = 0;\n> +\tint zlib_compression_seen = 0;\n>   \tint experimental;\n>   \tint value;\n>   \tconst char *strval;\n> @@ -151,6 +155,40 @@ void prepare_repo_settings(struct repository *r)\n>   \n>   \tif (!repo_config_get_ulong(r, \"core.packedgitlimit\", &ulongval))\n>   \t\tr->settings.packed_git_limit = ulongval;\n> +\n> +\tif (!repo_config_get_int(r, \"core.loosecompression\", &value)) {\n> +\t\tif (value == -1)\n> +\t\t\tvalue = Z_DEFAULT_COMPRESSION;\n> +\t\telse if (value < 0 || value > Z_BEST_COMPRESSION)\n> +\t\t\tdie(_(\"bad zlib compression level %d\"), value);\n> +\t\tr->settings.zlib_compression_level = value;\n> +\t\tzlib_compression_seen = 1;\n> +\t}\n> +\n> +\tif (!repo_config_get_int(r, \"pack.compression\", &value)) {\n> +\t\tif (value == -1)\n> +\t\t\tvalue = Z_DEFAULT_COMPRESSION;\n> +\t\telse if (value < 0 || value > Z_BEST_COMPRESSION)\n> +\t\t\tdie(_(\"bad pack compression level %d\"), value);\n> +\t\tr->settings.pack_compression_level = value;\n> +\t\tpack_compression_seen = 1;\n> +\t}\n> +\n> +\tif (!repo_config_get_int(r, \"core.compression\", &value)) {\n> +\t\tif (value == -1)\n> +\t\t\tvalue = Z_DEFAULT_COMPRESSION;\n> +\t\telse if (value < 0 || value > Z_BEST_COMPRESSION)\n> +\t\t\tdie(_(\"bad zlib compression level %d\"), value);\n> +\t\tif (!zlib_compression_seen)\n> +\t\t\tr->settings.zlib_compression_level = value;\n> +\t\tif (!pack_compression_seen)\n> +\t\t\tr->settings.pack_compression_level = value;\n> +\t} else {\n> +\t\tif (!zlib_compression_seen)\n> +\t\t\tr->settings.zlib_compression_level = Z_BEST_SPEED;\n> +\t\tif (!pack_compression_seen)\n> +\t\t\tr->settings.pack_compression_level = Z_DEFAULT_COMPRESSION;\n> +\t}\n>   }\n>   \n>   void repo_settings_clear(struct repository *r)\n> diff --git a/repo-settings.h b/repo-settings.h\n> index d4778855614..f60900317cf 100644\n> --- a/repo-settings.h\n> +++ b/repo-settings.h\n> @@ -36,6 +36,8 @@ struct repo_settings {\n>   \tint pack_read_reverse_index;\n>   \tint pack_use_bitmap_boundary_traversal;\n>   \tint pack_use_multi_pack_reuse;\n> +\tint pack_compression_level;\n> +\tint zlib_compression_level;\n>   \n>   \tint shared_repository;\n>   \tint shared_repository_initialized;\n> \n"},{"id":"521767","messageId":"878qkvdh4z.fsf@iotcl.com","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-9-62627b55707f@pks.im","subject":"Re: [PATCH 09/19] odb: introduce `odb_write_object()`","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2025-07-10T18:39:56Z","receivedAt":"2025-07-10T18:40:11Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We do not have a backend-agnostic way to write objects into an object\n> database. While there is `write_object_file()`, this function is rather\n> specific to the loose object format.\n>\n> Introduce `odb_write_object()` to plug this gap. For now, this function\n> is a simple wrapper around `write_object_file()` and doesn't even use\n> the passed-in object database yet. This will change in subsequent\n> commits, where `write_object_file()` is converted so that it works on\n> top of an `odb_source`. `odb_write_object()` will then become\n> responsible for deciding which source an object shall be written to.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  apply.c                  | 11 +++++++----\n>  builtin/checkout.c       |  2 +-\n>  builtin/merge-file.c     |  3 ++-\n>  builtin/mktag.c          |  2 +-\n>  builtin/mktree.c         |  2 +-\n>  builtin/notes.c          |  3 ++-\n>  builtin/receive-pack.c   |  4 ++--\n>  builtin/replace.c        |  3 ++-\n>  builtin/tag.c            |  4 ++--\n>  builtin/unpack-objects.c | 12 ++++++------\n>  cache-tree.c             |  5 ++---\n>  commit.c                 |  4 ++--\n>  match-trees.c            |  2 +-\n>  merge-ort.c              |  7 ++++---\n>  notes-cache.c            |  3 ++-\n>  notes.c                  | 12 ++++++++----\n>  object-file.c            | 18 +++++++++---------\n>  object-file.h            | 26 +++-----------------------\n>  odb.c                    | 10 ++++++++++\n>  odb.h                    | 38 ++++++++++++++++++++++++++++++++++++++\n>  read-cache.c             |  2 +-\n>  21 files changed, 106 insertions(+), 67 deletions(-)\n>\n\n[snip]\n\n> diff --git a/odb.h b/odb.h\n> index e922f256802..c96d2c29e9f 100644\n> --- a/odb.h\n> +++ b/odb.h\n> @@ -437,6 +437,44 @@ enum for_each_object_flags {\n>  \tFOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n>  };\n>  \n> +enum {\n> +\t/*\n> +\t * By default, `odb_write_object()` does not actually write anything\n> +\t * into the object store, but only computes the object ID. This flag\n> +\t * changes that so that the object will be written as a loose object\n> +\t * and persisted.\n> +\t */\n> +\tWRITE_OBJECT_PERSIST = (1 << 0),\n> +\n> +\t/*\n> +\t * Do not print an error in case something gose wrong.\n\nWhile at it, shall we fix this typo?: s/gose/goes\n\n-- \nCheers,\nToon\n"},{"id":"521768","messageId":"875xfzdh2f.fsf@iotcl.com","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-13-62627b55707f@pks.im","subject":"Re: [PATCH 13/19] object-file: get rid of `the_repository` in loose object iterators","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2025-07-10T18:41:28Z","receivedAt":"2025-07-10T18:41:46Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The iterators for loose objects still rely on `the_repository`. Refactor\n> them:\n>\n>   - `for_each_loose_file_in_objdir()` is refactored so that the caller\n>     is now expected to pass an `odb_source` as parameter instead of the\n>     path to that source. Furthermore, it is renamed accordingly to\n>     `for_each_loose_file_in_source()`.\n>\n>   - `for_each_loose_object()` is refactored to take in an object\n>     database now and calls the above function in a loop.\n>\n> This allows us to get rid of the global dependency.\n>\n> Adjust callers accordingly.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/cat-file.c      |  2 +-\n>  builtin/count-objects.c |  2 +-\n>  builtin/fsck.c          | 14 ++++++++------\n>  builtin/gc.c            | 10 ++++------\n>  builtin/pack-objects.c  |  5 ++---\n>  builtin/prune.c         |  2 +-\n>  object-file.c           | 18 +++++++++---------\n>  object-file.h           |  5 +++--\n>  prune-packed.c          |  2 +-\n>  reachable.c             |  2 +-\n>  10 files changed, 31 insertions(+), 31 deletions(-)\n>\n\n[snip]\n\n> diff --git a/object-file.c b/object-file.c\n> index bd93f17dcfe..b894379d22c 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -1388,7 +1388,7 @@ static int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n>  \treturn r;\n>  }\n>  \n> -int for_each_loose_file_in_objdir(const char *path,\n> +int for_each_loose_file_in_source(struct odb_source *source,\n\nI really enjoy seeing how your plan comes together here. So much nicer\nto not pass in the path directly.\n\n-- \nCheers,\nToon\n"},{"id":"521769","messageId":"874ivjdh0l.fsf@iotcl.com","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-15-62627b55707f@pks.im","subject":"Re: [PATCH 15/19] object-file: get rid of `the_repository` in `force_object_loose()`","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2025-07-10T18:42:34Z","receivedAt":"2025-07-10T18:42:45Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> While the check whether any such loose object exists already acts on the\n> whole object database, writing the loose object happens in one specific\n> source.\n\nThis sounds weird and like unwanted behavior, but you're not changing\nthat. Makes sense to me.\n\n-- \nCheers,\nToon\n"},{"id":"521811","messageId":"CAOLa=ZQhuaXV_XqQ6ekqVq0hA5bu8EUyt5P6vVG321eU6brcHQ@mail.gmail.com","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-2-62627b55707f@pks.im","subject":"Re: [PATCH 02/19] object-file: stop using `the_hash_algo`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-07-11T09:52:07Z","receivedAt":"2025-07-11T09:52:09Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> There are a couple of users of the `the_hash_algo` macro, which\n> implicitly depends on `the_repository`. Adapt these callers to not do so\n> anymore, either by deriving it from already-available context or by\n> using `the_repository->hash_algo`. The latter variant doesn't yet help\n> to remove the global dependency, but such users will be adapted in the\n> following commits to not use `the_repository` anymore, either.\n>\n\nThe 'either' doesn't make sense here.\n\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  object-file.c | 40 ++++++++++++++++++++++++----------------\n>  object-file.h |  1 +\n>  2 files changed, 25 insertions(+), 16 deletions(-)\n>\n> diff --git a/object-file.c b/object-file.c\n> index 987cf289420..bc395febc9d 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -25,6 +25,7 @@\n>  #include \"pack.h\"\n>  #include \"packfile.h\"\n>  #include \"path.h\"\n> +#include \"read-cache-ll.h\"\n\nI wonder why we add this header.\n\nThe rest of the patch looks good.\n\n[snip]\n"},{"id":"521812","messageId":"CAOLa=ZTKLf5EsYGRckxWF1MLARYRdiPZh6ZJ8gYKaMxwBLkqUw@mail.gmail.com","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-5-62627b55707f@pks.im","subject":"Re: [PATCH 05/19] object-file: get rid of `the_repository` when freshening objects","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-07-11T09:59:15Z","receivedAt":"2025-07-11T09:59:17Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We implicitly depend on `the_repository` when freshening either loose or\n> packed objects. Refactor these functions to instead accept an object\n> database as input so that we can get rid of the global dependency.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  object-file.c | 22 +++++++++++-----------\n>  1 file changed, 11 insertions(+), 11 deletions(-)\n>\n> diff --git a/object-file.c b/object-file.c\n> index 9e17e608f78..3453989b7e3 100644\n> --- a/object-file.c\n> +++ b/object-file.c\n> @@ -893,23 +893,21 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n>  \t\t\t\t\t  FOF_SKIP_COLLISION_CHECK);\n>  }\n>\n> -static int freshen_loose_object(const struct object_id *oid)\n> +static int freshen_loose_object(struct object_database *odb,\n> +\t\t\t\tconst struct object_id *oid)\n>  {\n\nSo for functions which only work on object database source, we add a\n'_source' suffix. For others, it is expected to work on the database\nlevel. Ok.\n\n[snip]\n"},{"id":"521813","messageId":"CAOLa=ZQhWh9OKapT2=BB1kJNghYYPF+_133Qs_q8ZkyU1ONzew@mail.gmail.com","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-8-62627b55707f@pks.im","subject":"Re: [PATCH 08/19] loose: write loose objects map via their source","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-07-11T10:25:51Z","receivedAt":"2025-07-11T10:25:53Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> When a repository is configured to have a compatibility hash algorithm\n> we keep track of object ID mappings for loose objects via the loose\n> object map. This map simply maps an object ID of the actual hash to the\n> object ID of the compatibility hash. This loose object map is an\n> inherent property of the loose files backend and thus of one specific\n> object source.\n>\n> Refactor the interfaces to reflect this by requiring a `struct\n> odb_source` as input instead of a repository. This prepares for\n> subsequent commits where we will refactor writing of loose objects to\n> work on a `struct odb_source`, as well.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  loose.c       | 16 +++++++++-------\n>  loose.h       |  4 +++-\n>  object-file.c |  6 +++---\n>  3 files changed, 15 insertions(+), 11 deletions(-)\n>\n> diff --git a/loose.c b/loose.c\n> index 519f5db7935..e8ea6e7e24b 100644\n> --- a/loose.c\n> +++ b/loose.c\n> @@ -166,7 +166,8 @@ int repo_write_loose_object_map(struct repository *repo)\n>  \treturn -1;\n>  }\n>\n> -static int write_one_object(struct repository *repo, const struct object_id *oid,\n> +static int write_one_object(struct odb_source *source,\n\nNit: In one of the earlier commits, we renamed a function working on a\nparticular source to have the '_source' suffix. Should we do the same\nhere?\n\nI understand that this is related to a specific source (loose files) and\nprobably would move into its own file under the objects namespace. But\nperhaps something to think about.\n\n[snip]\n"},{"id":"521814","messageId":"CAOLa=ZQ2msNYfURDXe1eNBtbFxDjmc4dJ-u1s0Fo-mqyjnnQHA@mail.gmail.com","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-15-62627b55707f@pks.im","subject":"Re: [PATCH 15/19] object-file: get rid of `the_repository` in `force_object_loose()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-07-11T10:38:35Z","receivedAt":"2025-07-11T10:38:37Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The function `force_object_loose()` forces an object to become a loose\n> object in case it only exists in its packed form. To do so it implicitly\n> relies on `the_repository`.\n>\n> Refactor the function by passing a `struct odb_source` as parameter.\n> While the check whether any such loose object exists already acts on the\n> whole object database, writing the loose object happens in one specific\n> source.\n>\n\nQ: Since it exists in the packed form, won't the check always return\ntrue?\n\n[snip]\n"},{"id":"521840","messageId":"xmqqbjpq1rs0.fsf@gitster.g","threadId":"63771","inReplyTo":"32fceddc-c867-4a47-bde8-c873279edbc1@gmail.com","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-11T18:55:27Z","receivedAt":"2025-07-11T18:55:30Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> I do not think adding prepare_repo_settings() calls all over the place\n> is a good way forward as it makes it very easy to introduce\n> regressions like this. Our builtin commands parse the config at\n> startup for good reasons if we're going to move settings out of\n> git_default_core_config() we should ensure that they are still parsed\n> at startup.\n\nI think that is a good guideline that applies not just to this\nseries but to other topics that attempt to move globals to a member\nin struct repository (or repository_settings)\n\nThanks.\n.\n"},{"id":"521961","messageId":"aHYyXoRcJGqac9td@pks.im","threadId":"63771","inReplyTo":"CAOLa=ZQhWh9OKapT2=BB1kJNghYYPF+_133Qs_q8ZkyU1ONzew@mail.gmail.com","subject":"Re: [PATCH 08/19] loose: write loose objects map via their source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-15T10:50:06Z","receivedAt":"2025-07-15T10:50:13Z","isPatch":true,"body":"On Fri, Jul 11, 2025 at 05:25:51AM -0500, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > When a repository is configured to have a compatibility hash algorithm\n> > we keep track of object ID mappings for loose objects via the loose\n> > object map. This map simply maps an object ID of the actual hash to the\n> > object ID of the compatibility hash. This loose object map is an\n> > inherent property of the loose files backend and thus of one specific\n> > object source.\n> >\n> > Refactor the interfaces to reflect this by requiring a `struct\n> > odb_source` as input instead of a repository. This prepares for\n> > subsequent commits where we will refactor writing of loose objects to\n> > work on a `struct odb_source`, as well.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> >  loose.c       | 16 +++++++++-------\n> >  loose.h       |  4 +++-\n> >  object-file.c |  6 +++---\n> >  3 files changed, 15 insertions(+), 11 deletions(-)\n> >\n> > diff --git a/loose.c b/loose.c\n> > index 519f5db7935..e8ea6e7e24b 100644\n> > --- a/loose.c\n> > +++ b/loose.c\n> > @@ -166,7 +166,8 @@ int repo_write_loose_object_map(struct repository *repo)\n> >  \treturn -1;\n> >  }\n> >\n> > -static int write_one_object(struct repository *repo, const struct object_id *oid,\n> > +static int write_one_object(struct odb_source *source,\n> \n> Nit: In one of the earlier commits, we renamed a function working on a\n> particular source to have the '_source' suffix. Should we do the same\n> here?\n> \n> I understand that this is related to a specific source (loose files) and\n> probably would move into its own file under the objects namespace. But\n> perhaps something to think about.\n\nYeah, things are still wildly inconsistent right now. This will change\nonce we can finally carve out the actual object source backends, at\nwhich point we'll have to move around a bunch of functions anyway. So\nI'm not yet polishing up every function.\n\nPatrick\n"},{"id":"521962","messageId":"aHYyZJnQqKCVdFwK@pks.im","threadId":"63771","inReplyTo":"878qkvdh4z.fsf@iotcl.com","subject":"Re: [PATCH 09/19] odb: introduce `odb_write_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-15T10:50:12Z","receivedAt":"2025-07-15T10:50:18Z","isPatch":true,"body":"On Thu, Jul 10, 2025 at 08:39:56PM +0200, Toon Claes wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb.h b/odb.h\n> > index e922f256802..c96d2c29e9f 100644\n> > --- a/odb.h\n> > +++ b/odb.h\n> > @@ -437,6 +437,44 @@ enum for_each_object_flags {\n> >  \tFOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n> >  };\n> >  \n> > +enum {\n> > +\t/*\n> > +\t * By default, `odb_write_object()` does not actually write anything\n> > +\t * into the object store, but only computes the object ID. This flag\n> > +\t * changes that so that the object will be written as a loose object\n> > +\t * and persisted.\n> > +\t */\n> > +\tWRITE_OBJECT_PERSIST = (1 << 0),\n> > +\n> > +\t/*\n> > +\t * Do not print an error in case something gose wrong.\n> \n> While at it, shall we fix this typo?: s/gose/goes\n\nGood eyes, will do.\n\nPatrick\n"},{"id":"521963","messageId":"aHYya4PKoWT3-wPQ@pks.im","threadId":"63771","inReplyTo":"CAOLa=ZQ2msNYfURDXe1eNBtbFxDjmc4dJ-u1s0Fo-mqyjnnQHA@mail.gmail.com","subject":"Re: [PATCH 15/19] object-file: get rid of `the_repository` in `force_object_loose()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-15T10:50:19Z","receivedAt":"2025-07-15T10:50:25Z","isPatch":true,"body":"On Fri, Jul 11, 2025 at 05:38:35AM -0500, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The function `force_object_loose()` forces an object to become a loose\n> > object in case it only exists in its packed form. To do so it implicitly\n> > relies on `the_repository`.\n> >\n> > Refactor the function by passing a `struct odb_source` as parameter.\n> > While the check whether any such loose object exists already acts on the\n> > whole object database, writing the loose object happens in one specific\n> > source.\n> >\n> \n> Q: Since it exists in the packed form, won't the check always return\n> true?\n\nI'm not quite sure I understand the question. This function is about\n_ensuring_ that the object exists in its loose format. So if it only\nexists in a packfile, it will be written in its loose format. If it\nalready exists as a loose object, nothing happens.\n\nPatrick\n"},{"id":"521964","messageId":"aHYyeilUeXqP2IIB@pks.im","threadId":"63771","inReplyTo":"xmqqbjpq1rs0.fsf@gitster.g","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-15T10:50:34Z","receivedAt":"2025-07-15T10:50:39Z","isPatch":true,"body":"On Fri, Jul 11, 2025 at 11:55:27AM -0700, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n> > I do not think adding prepare_repo_settings() calls all over the place\n> > is a good way forward as it makes it very easy to introduce\n> > regressions like this. Our builtin commands parse the config at\n> > startup for good reasons if we're going to move settings out of\n> > git_default_core_config() we should ensure that they are still parsed\n> > at startup.\n> \n> I think that is a good guideline that applies not just to this\n> series but to other topics that attempt to move globals to a member\n> in struct repository (or repository_settings)\n\nFair enough. One thing we could do is to call `prepare_repo_settings()`\nat the point in time where any repository is opened. I'll have to think\nabout it and will try to come up with a solution.\n\nPatrick\n"},{"id":"521965","messageId":"aHY7LYHqVj-ECf_z@pks.im","threadId":"63771","inReplyTo":"xmqqbjpq1rs0.fsf@gitster.g","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-15T11:27:58Z","receivedAt":"2025-07-15T11:28:10Z","isPatch":true,"body":"On Fri, Jul 11, 2025 at 11:55:27AM -0700, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n> > I do not think adding prepare_repo_settings() calls all over the place\n> > is a good way forward as it makes it very easy to introduce\n> > regressions like this. Our builtin commands parse the config at\n> > startup for good reasons if we're going to move settings out of\n> > git_default_core_config() we should ensure that they are still parsed\n> > at startup.\n> \n> I think that is a good guideline that applies not just to this\n> series but to other topics that attempt to move globals to a member\n> in struct repository (or repository_settings)\n\nSo... the only real solution that I can think about right now is to\nstart parsing the repository configuration whenever we instantiate any\nrepository. E.g., something like the below patch. This has the effect\nthat the repo settings would always be populated when we have a\nrepository at hand. Consequently, we wouldn't need to clutter those\n`prepare_repo_settings()` calls everywhere anymore.\n\nBut there is a big question: what do we do with invalid configuration\nthen? Do we want to die immediately when we see such command? The answer\nis probably going to be a solid \"sometimes\":\n\n  - Some commands must function even with an invalid configuration. At\n    the very least git-config(1) needs to handle this alright, as\n    otherwise it might be impossible to unset/change invalid\n    configuration. There may be other such examples.\n\n  - Not all configuration is equal. It may be perfectly fine to ignore\n    some configuration, but other configuration may very much be mission\n    critical. And whether or not configuration is important isn't really\n    something we can decide, as it will depend on the specific use case.\n\nSo I'm afraid that there just isn't a perfect solution here. Does it\nmake sense to die due to a config key that isn't even used by a specific\ncommand? Maybe. And if not, which config keys _should_ make us die in\ncase they are invalid?\n\nThe overall situation right now is a proper mess: we have config parsing\ncluttered everywhere, and the behaviour is just plain inconsistent. Some\nparsing is delayed, some isn't. Some is per-repo, some is last-one-wins.\nSome config keys will cause us to die in case they are misconfigured,\nsome will just be ignored.\n\nSo where do we want to end up?\n\nMy dream would be that all configuration were to be defined in one\ncentral place. The configuration should be typed, there should be\nverification for each value configured by the user. All configuration\ngets parsed into a structure, and it can be parsed either via a\nrepository (in which case we take into account its local config), or\nonly via the global- and system-wide configuration. The whole config\nneeds to be parsed at startup so that issues like the reported one don't\nhappen where a subprocess that uses more config keys than the parent\nprocess dies because one of the extra keys is misconfigured.\n\nBut I very much feel like this is a pipe dream right now. We already are\nworking on multiple fronts to modernize the code base, and I don't quite\nfeel like opening up _another_ large transformation right now.\n\nSo I don't quite know what to do while we're not there yet. Without this\nlarge refactoring, all approaches feel like they aren't a perfect fit to\naddress the bigger issue.\n\nPatrick\n\ndif\n"},{"id":"521981","messageId":"87wm89bs91.fsf@iotcl.com","threadId":"63771","inReplyTo":"aHYya4PKoWT3-wPQ@pks.im","subject":"Re: [PATCH 15/19] object-file: get rid of `the_repository` in `force_object_loose()`","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2025-07-15T11:36:26Z","receivedAt":"2025-07-15T11:36:40Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Fri, Jul 11, 2025 at 05:38:35AM -0500, Karthik Nayak wrote:\n>> \n>> Q: Since it exists in the packed form, won't the check always return\n>> true?\n>\n> I'm not quite sure I understand the question. This function is about\n> _ensuring_ that the object exists in its loose format. So if it only\n> exists in a packfile, it will be written in its loose format. If it\n> already exists as a loose object, nothing happens.\n\nThe way I understand Karthik's question: We check all the odb->sources\nfor the object, so Karthik assumes (rightfully) one of the sources will\nhave the object, and thus the function early returns.\n\nBut when I look at the implementation of has_loose_object() it\neventually calls odb_loose_path() to find the object. So we check all\nsources, but check if it exists in loose form only.\n\nFrom the commit message:\n\n> While the check whether any such loose object exists already acts on the\n> whole object database, writing the loose object happens in one specific\n> source.\n\nI must admit this last sentence from commit message now also makes more\nsense to me.\n\n-- \nCheers,\nToon\n"},{"id":"522007","messageId":"f6479d6a-32a4-4a49-a75c-589978cb9a57@gmail.com","threadId":"63771","inReplyTo":"aHY7LYHqVj-ECf_z@pks.im","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-15T15:51:32Z","receivedAt":"2025-07-15T15:51:40Z","isPatch":true,"body":"Hi Patrick\n\nOn 15/07/2025 12:27, Patrick Steinhardt wrote:\n> On Fri, Jul 11, 2025 at 11:55:27AM -0700, Junio C Hamano wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>\n>>> I do not think adding prepare_repo_settings() calls all over the place\n>>> is a good way forward as it makes it very easy to introduce\n>>> regressions like this. Our builtin commands parse the config at\n>>> startup for good reasons if we're going to move settings out of\n>>> git_default_core_config() we should ensure that they are still parsed\n>>> at startup.\n>>\n>> I think that is a good guideline that applies not just to this\n>> series but to other topics that attempt to move globals to a member\n>> in struct repository (or repository_settings)\n> \n> So... the only real solution that I can think about right now is to\n> start parsing the repository configuration whenever we instantiate any\n> repository. E.g., something like the below patch. This has the effect\n> that the repo settings would always be populated when we have a\n> repository at hand. Consequently, we wouldn't need to clutter those\n> `prepare_repo_settings()` calls everywhere anymore.\n> \n> But there is a big question: what do we do with invalid configuration\n> then? Do we want to die immediately when we see such command? The answer\n> is probably going to be a solid \"sometimes\":\n> \n>    - Some commands must function even with an invalid configuration. At\n>      the very least git-config(1) needs to handle this alright, as\n>      otherwise it might be impossible to unset/change invalid\n>      configuration. There may be other such examples.\n\nThat's a good point.\n\n>    - Not all configuration is equal. It may be perfectly fine to ignore\n>      some configuration, but other configuration may very much be mission\n>      critical. And whether or not configuration is important isn't really\n>      something we can decide, as it will depend on the specific use case.\n> \n> So I'm afraid that there just isn't a perfect solution here. Does it\n> make sense to die due to a config key that isn't even used by a specific\n> command? Maybe. And if not, which config keys _should_ make us die in\n> case they are invalid?\n> \n> The overall situation right now is a proper mess: we have config parsing\n> cluttered everywhere, and the behaviour is just plain inconsistent. Some\n> parsing is delayed, some isn't. \n\nIndeed. My objection here was that we were delaying the parsing when it \nwasn't delayed before. Is it feasible to call prepare_repo_settings() in \nrepo_config()? That would at least avoid the problem that moving config \nsettings into `struct repo_settings` changes when the settings are \nparsed unless the command calls prepare_repo_settings() at start up. As \nfar as I remember `git config` uses config_with_options() so that would \nnot be adversely affected by such a change.\n\n> Some is per-repo, some is last-one-wins.\n> Some config keys will cause us to die in case they are misconfigured,\n> some will just be ignored.\n> \n> So where do we want to end up?\n> \n> My dream would be that all configuration were to be defined in one\n> central place. The configuration should be typed, there should be\n> verification for each value configured by the user.\n\nBeing able to verify config settings when they're set would be a great \nimprovement but we're a long way from being able to do that.\n\n> All configuration\n> gets parsed into a structure, and it can be parsed either via a\n> repository (in which case we take into account its local config), or\n> only via the global- and system-wide configuration. The whole config\n> needs to be parsed at startup so that issues like the reported one don't\n> happen where a subprocess that uses more config keys than the parent\n> process dies because one of the extra keys is misconfigured.\n> \n> But I very much feel like this is a pipe dream right now. We already are\n> working on multiple fronts to modernize the code base, and I don't quite\n> feel like opening up _another_ large transformation right now.\n\nI agree with this\n\n> So I don't quite know what to do while we're not there yet. Without this\n> large refactoring, all approaches feel like they aren't a perfect fit to\n> address the bigger issue.\n\nI agree addressing all the shortcomings you've outlined would require a \nlot of refactoring. If we can find a way to avoid introducing anymore \nshortcomings as we migrate away from global variables that would be a \ngood start.\n\nThanks\n\nPhillip\n\n"},{"id":"522008","messageId":"aHZ94u-xULDDBb7C@pks.im","threadId":"63771","inReplyTo":"f6479d6a-32a4-4a49-a75c-589978cb9a57@gmail.com","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-15T16:12:18Z","receivedAt":"2025-07-15T16:12:26Z","isPatch":true,"body":"On Tue, Jul 15, 2025 at 04:51:32PM +0100, Phillip Wood wrote:\n> On 15/07/2025 12:27, Patrick Steinhardt wrote:\n> > On Fri, Jul 11, 2025 at 11:55:27AM -0700, Junio C Hamano wrote:\n> > > Phillip Wood <phillip.wood123@gmail.com> writes:\n[snip]\n> >    - Not all configuration is equal. It may be perfectly fine to ignore\n> >      some configuration, but other configuration may very much be mission\n> >      critical. And whether or not configuration is important isn't really\n> >      something we can decide, as it will depend on the specific use case.\n> > \n> > So I'm afraid that there just isn't a perfect solution here. Does it\n> > make sense to die due to a config key that isn't even used by a specific\n> > command? Maybe. And if not, which config keys _should_ make us die in\n> > case they are invalid?\n> > \n> > The overall situation right now is a proper mess: we have config parsing\n> > cluttered everywhere, and the behaviour is just plain inconsistent. Some\n> > parsing is delayed, some isn't.\n> \n> Indeed. My objection here was that we were delaying the parsing when it\n> wasn't delayed before. Is it feasible to call prepare_repo_settings() in\n> repo_config()? That would at least avoid the problem that moving config\n> settings into `struct repo_settings` changes when the settings are parsed\n> unless the command calls prepare_repo_settings() at start up. As far as I\n> remember `git config` uses config_with_options() so that would not be\n> adversely affected by such a change.\n\nHm, yeah, I think adding it to `repo_config()` might be a viable\napproach. I'll give it a try tomorrow and see what breaks :)\n\nThanks!\n\nPatrick\n"},{"id":"522022","messageId":"xmqqy0spgufw.fsf@gitster.g","threadId":"63771","inReplyTo":"f6479d6a-32a4-4a49-a75c-589978cb9a57@gmail.com","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-15T18:50:11Z","receivedAt":"2025-07-15T18:50:15Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Indeed. My objection here was that we were delaying the parsing when\n> it wasn't delayed before. Is it feasible to call\n> prepare_repo_settings() in repo_config()? That would at least avoid\n> the problem that moving config settings into `struct repo_settings`\n> changes when the settings are parsed unless the command calls\n> prepare_repo_settings() at start up. As far as I remember `git config`\n> uses config_with_options() so that would not be adversely affected by\n> such a change.\n\nExcellent point.\n\n>> My dream would be that all configuration were to be defined in one\n>> central place. The configuration should be typed, there should be\n>> verification for each value configured by the user.\n>\n> Being able to verify config settings when they're set would be a great\n> improvement but we're a long way from being able to do that.\n\nYes, and there always are end-user or third-party defined keys that\nare not known to us, and we cannot tell if an unknown variable is\nsuch a end-user defined one or a typo.  I do not know if it is\nfeasible to aim for that.\n\n>> But I very much feel like this is a pipe dream right now. We already\n>> are\n>> working on multiple fronts to modernize the code base, and I don't quite\n>> feel like opening up _another_ large transformation right now.\n>\n> I agree with this\n\nAgreed.\n\n> I agree addressing all the shortcomings you've outlined would require\n> a lot of refactoring. If we can find a way to avoid introducing\n> anymore shortcomings as we migrate away from global variables that\n> would be a good start.\n\n;-).\n"},{"id":"522097","messageId":"aHehaghOW16vPee7@pks.im","threadId":"63771","inReplyTo":"aHZ94u-xULDDBb7C@pks.im","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-16T12:56:10Z","receivedAt":"2025-07-16T12:56:18Z","isPatch":true,"body":"On Tue, Jul 15, 2025 at 06:12:18PM +0200, Patrick Steinhardt wrote:\n> On Tue, Jul 15, 2025 at 04:51:32PM +0100, Phillip Wood wrote:\n> > On 15/07/2025 12:27, Patrick Steinhardt wrote:\n> > > On Fri, Jul 11, 2025 at 11:55:27AM -0700, Junio C Hamano wrote:\n> > > > Phillip Wood <phillip.wood123@gmail.com> writes:\n> [snip]\n> > >    - Not all configuration is equal. It may be perfectly fine to ignore\n> > >      some configuration, but other configuration may very much be mission\n> > >      critical. And whether or not configuration is important isn't really\n> > >      something we can decide, as it will depend on the specific use case.\n> > > \n> > > So I'm afraid that there just isn't a perfect solution here. Does it\n> > > make sense to die due to a config key that isn't even used by a specific\n> > > command? Maybe. And if not, which config keys _should_ make us die in\n> > > case they are invalid?\n> > > \n> > > The overall situation right now is a proper mess: we have config parsing\n> > > cluttered everywhere, and the behaviour is just plain inconsistent. Some\n> > > parsing is delayed, some isn't.\n> > \n> > Indeed. My objection here was that we were delaying the parsing when it\n> > wasn't delayed before. Is it feasible to call prepare_repo_settings() in\n> > repo_config()? That would at least avoid the problem that moving config\n> > settings into `struct repo_settings` changes when the settings are parsed\n> > unless the command calls prepare_repo_settings() at start up. As far as I\n> > remember `git config` uses config_with_options() so that would not be\n> > adversely affected by such a change.\n> \n> Hm, yeah, I think adding it to `repo_config()` might be a viable\n> approach. I'll give it a try tomorrow and see what breaks :)\n\nThe answer is \"quite a lot\". I'm now 15 patches deep to try and fix\nthis and am nowhere close to a working state yet. The single biggest\nissue is `core.shared_repository`, which is used in a ton of places and\nwhich causes all kinds of pain.\n\nI think I'll stop working on this for now, and would rather like to drop\nthe last three patches from this series so that we can move forward with\nit.\n\nPatrick\n"},{"id":"522152","messageId":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im","subject":"[PATCH v2 00/16] object-file: get rid of `the_repository`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:26Z","receivedAt":"2025-07-17T04:56:37Z","isPatch":true,"body":"Hi,\n\nthis patch series refactors \"object-file.c\" to get rid of the dependency\non `the_repository`. In many such cases this is done by passing in a\n`struct odb_source`, which prepares us for eventually converting this\ninto the \"loose\" object source with pluggable object databases.\n\nThe patch series is built on top of a30f80fde92 (The eighth batch,\n2025-07-08) with \"ps/object-store\" at 841a03b4046 (odb: rename\n`read_object_with_reference()`, 2025-07-01) merged into it.\n\nChanges in v2:\n  - Two small typo improvements.\n  - Drop the last three patches from this series that move some global\n    config into repo settings. Those cause a change in behaviour, and\n    fixing that is a lot of effort that falls outside of the scope of\n    this patch series.\n  - Link to v1: https://lore.kernel.org/r/20250709-pks-object-file-wo-the-repository-v1-0-62627b55707f@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (16):\n      object-file: fix -Wsign-compare warnings\n      object-file: stop using `the_hash_algo`\n      object-file: get rid of `the_repository` in `has_loose_object()`\n      object-file: inline `check_and_freshen()` functions\n      object-file: get rid of `the_repository` when freshening objects\n      object-file: get rid of `the_repository` in `loose_object_info()`\n      object-file: get rid of `the_repository` in `finalize_object_file()`\n      loose: write loose objects map via their source\n      odb: introduce `odb_write_object()`\n      object-file: get rid of `the_repository` when writing objects\n      object-file: inline `for_each_loose_file_in_objdir_buf()`\n      object-file: remove declaration for `for_each_file_in_obj_subdir()`\n      object-file: get rid of `the_repository` in loose object iterators\n      object-file: get rid of `the_repository` in `read_loose_object()`\n      object-file: get rid of `the_repository` in `force_object_loose()`\n      object-file: get rid of `the_repository` in index-related functions\n\n apply.c                  |  11 +-\n builtin/cat-file.c       |   2 +-\n builtin/checkout.c       |   2 +-\n builtin/count-objects.c  |   2 +-\n builtin/fast-import.c    |   4 +-\n builtin/fsck.c           |  16 +--\n builtin/gc.c             |  10 +-\n builtin/index-pack.c     |   2 +-\n builtin/merge-file.c     |   3 +-\n builtin/mktag.c          |   2 +-\n builtin/mktree.c         |   2 +-\n builtin/notes.c          |   3 +-\n builtin/pack-objects.c   |  34 ++++--\n builtin/prune.c          |   2 +-\n builtin/receive-pack.c   |   4 +-\n builtin/replace.c        |   3 +-\n builtin/tag.c            |   4 +-\n builtin/unpack-objects.c |  15 +--\n bulk-checkin.c           |   2 +-\n cache-tree.c             |   5 +-\n commit.c                 |   4 +-\n http.c                   |   4 +-\n loose.c                  |  16 +--\n loose.h                  |   4 +-\n match-trees.c            |   2 +-\n merge-ort.c              |   7 +-\n midx-write.c             |   2 +-\n notes-cache.c            |   3 +-\n notes.c                  |  12 +-\n object-file.c            | 306 ++++++++++++++++++++++-------------------------\n object-file.h            |  65 ++++------\n odb.c                    |  10 ++\n odb.h                    |  38 ++++++\n pack-write.c             |  16 +--\n pack.h                   |   3 +-\n prune-packed.c           |   2 +-\n reachable.c              |   2 +-\n read-cache.c             |   2 +-\n tmp-objdir.c             |   2 +-\n 39 files changed, 333 insertions(+), 295 deletions(-)\n\nRange-diff versus v1:\n\n 1:  c150744a648 =  1:  4fc36ad30ae object-file: fix -Wsign-compare warnings\n 2:  5cdc43d3d27 !  2:  c5ad1d12618 object-file: stop using `the_hash_algo`\n    @@ Commit message\n         anymore, either by deriving it from already-available context or by\n         using `the_repository->hash_algo`. The latter variant doesn't yet help\n         to remove the global dependency, but such users will be adapted in the\n    -    following commits to not use `the_repository` anymore, either.\n    +    following commits to not use `the_repository` anymore.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n 3:  8e63fb2d760 =  3:  76478623aa7 object-file: get rid of `the_repository` in `has_loose_object()`\n 4:  14153d37df4 =  4:  ee7b31c95cd object-file: inline `check_and_freshen()` functions\n 5:  70abad2d817 =  5:  4b7407f17b7 object-file: get rid of `the_repository` when freshening objects\n 6:  5fc03ab39da =  6:  40a7c009c7b object-file: get rid of `the_repository` in `loose_object_info()`\n 7:  9a07f6a27df =  7:  9568d7e996e object-file: get rid of `the_repository` in `finalize_object_file()`\n 8:  739008ad578 =  8:  cf974b8b48d loose: write loose objects map via their source\n 9:  f2f00d6f566 !  9:  fec51b64457 odb: introduce `odb_write_object()`\n    @@ odb.h: enum for_each_object_flags {\n     +\tWRITE_OBJECT_PERSIST = (1 << 0),\n     +\n     +\t/*\n    -+\t * Do not print an error in case something gose wrong.\n    ++\t * Do not print an error in case something goes wrong.\n     +\t */\n     +\tWRITE_OBJECT_SILENT = (1 << 1),\n     +};\n10:  84411e2a8fc = 10:  ece58da8182 object-file: get rid of `the_repository` when writing objects\n11:  f051bfbada1 = 11:  a76d5f24040 object-file: inline `for_each_loose_file_in_objdir_buf()`\n12:  3754b37207a = 12:  70e60db13f6 object-file: remove declaration for `for_each_file_in_obj_subdir()`\n13:  be855be5c0e = 13:  77ec9b765dc object-file: get rid of `the_repository` in loose object iterators\n14:  6cb7f6bc040 = 14:  b091f019d01 object-file: get rid of `the_repository` in `read_loose_object()`\n15:  8ddb96c9f30 = 15:  3982420285b object-file: get rid of `the_repository` in `force_object_loose()`\n16:  d885f70f58d = 16:  21272b2644b object-file: get rid of `the_repository` in index-related functions\n17:  1640212fe06 <  -:  ----------- environment: move compression level into repo settings\n18:  36496f2c42c <  -:  ----------- environment: move object creation mode into repo settings\n19:  8760509c0bb <  -:  ----------- object-file: drop USE_THE_REPOSITORY_VARIABLE\n\n---\nbase-commit: f0228c39bf2fe539583cd594671039f05765bc9b\nchange-id: 20250709-pks-object-file-wo-the-repository-9f41234c4747\n\n"},{"id":"522153","messageId":"20250717-pks-object-file-wo-the-repository-v2-1-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 01/16] object-file: fix -Wsign-compare warnings","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:27Z","receivedAt":"2025-07-17T04:56:39Z","isPatch":true,"body":"There are some trivial -Wsign-compare warnings in \"object-file.c\". Fix\nthem and drop the preprocessor define that disables those warnings.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 15 ++++++---------\n 1 file changed, 6 insertions(+), 9 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 3d674d1093e..987cf289420 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -8,7 +8,6 @@\n  */\n \n #define USE_THE_REPOSITORY_VARIABLE\n-#define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"git-compat-util.h\"\n #include \"bulk-checkin.h\"\n@@ -44,8 +43,7 @@ static int get_conv_flags(unsigned flags)\n \n static void fill_loose_path(struct strbuf *buf, const struct object_id *oid)\n {\n-\tint i;\n-\tfor (i = 0; i < the_hash_algo->rawsz; i++) {\n+\tfor (size_t i = 0; i < the_hash_algo->rawsz; i++) {\n \t\tstatic char hex[] = \"0123456789abcdef\";\n \t\tunsigned int val = oid->hash[i];\n \t\tstrbuf_addch(buf, hex[val >> 4]);\n@@ -327,9 +325,8 @@ static void *unpack_loose_rest(git_zstream *stream,\n \t\t\t       void *buffer, unsigned long size,\n \t\t\t       const struct object_id *oid)\n {\n-\tint bytes = strlen(buffer) + 1;\n+\tsize_t bytes = strlen(buffer) + 1, n;\n \tunsigned char *buf = xmallocz(size);\n-\tunsigned long n;\n \tint status = Z_OK;\n \n \tn = stream->total_out - bytes;\n@@ -596,7 +593,7 @@ static int check_collision(const char *source, const char *dest)\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (sz_a < sizeof(buf_source))\n+\t\tif ((size_t) sz_a < sizeof(buf_source))\n \t\t\tbreak;\n \t}\n \n@@ -1240,7 +1237,7 @@ static int index_core(struct index_state *istate,\n \t\tif (read_result < 0)\n \t\t\tret = error_errno(_(\"read error while indexing %s\"),\n \t\t\t\t\t  path ? path : \"<unknown>\");\n-\t\telse if (read_result != size)\n+\t\telse if ((size_t) read_result != size)\n \t\t\tret = error(_(\"short read while indexing %s\"),\n \t\t\t\t    path ? path : \"<unknown>\");\n \t\telse\n@@ -1268,7 +1265,7 @@ int index_fd(struct index_state *istate, struct object_id *oid,\n \t\tret = index_stream_convert_blob(istate, oid, fd, path, flags);\n \telse if (!S_ISREG(st->st_mode))\n \t\tret = index_pipe(istate, oid, fd, type, path, flags);\n-\telse if (st->st_size <= repo_settings_get_big_file_threshold(the_repository) ||\n+\telse if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(the_repository)) ||\n \t\t type != OBJ_BLOB ||\n \t\t (path && would_convert_to_git(istate, path)))\n \t\tret = index_core(istate, oid, fd, xsize_t(st->st_size),\n@@ -1472,7 +1469,7 @@ struct oidtree *odb_loose_cache(struct odb_source *source,\n \tuint32_t *bitmap;\n \n \tif (subdir_nr < 0 ||\n-\t    subdir_nr >= bitsizeof(source->loose_objects_subdir_seen))\n+\t    (size_t) subdir_nr >= bitsizeof(source->loose_objects_subdir_seen))\n \t\tBUG(\"subdir_nr out of range\");\n \n \tbitmap = &source->loose_objects_subdir_seen[word_index];\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522154","messageId":"20250717-pks-object-file-wo-the-repository-v2-2-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 02/16] object-file: stop using `the_hash_algo`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:28Z","receivedAt":"2025-07-17T04:56:42Z","isPatch":true,"body":"There are a couple of users of the `the_hash_algo` macro, which\nimplicitly depends on `the_repository`. Adapt these callers to not do so\nanymore, either by deriving it from already-available context or by\nusing `the_repository->hash_algo`. The latter variant doesn't yet help\nto remove the global dependency, but such users will be adapted in the\nfollowing commits to not use `the_repository` anymore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 40 ++++++++++++++++++++++++----------------\n object-file.h |  1 +\n 2 files changed, 25 insertions(+), 16 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 987cf289420..bc395febc9d 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -25,6 +25,7 @@\n #include \"pack.h\"\n #include \"packfile.h\"\n #include \"path.h\"\n+#include \"read-cache-ll.h\"\n #include \"setup.h\"\n #include \"streaming.h\"\n \n@@ -41,9 +42,11 @@ static int get_conv_flags(unsigned flags)\n \t\treturn 0;\n }\n \n-static void fill_loose_path(struct strbuf *buf, const struct object_id *oid)\n+static void fill_loose_path(struct strbuf *buf,\n+\t\t\t    const struct object_id *oid,\n+\t\t\t    const struct git_hash_algo *algop)\n {\n-\tfor (size_t i = 0; i < the_hash_algo->rawsz; i++) {\n+\tfor (size_t i = 0; i < algop->rawsz; i++) {\n \t\tstatic char hex[] = \"0123456789abcdef\";\n \t\tunsigned int val = oid->hash[i];\n \t\tstrbuf_addch(buf, hex[val >> 4]);\n@@ -60,7 +63,7 @@ const char *odb_loose_path(struct odb_source *source,\n \tstrbuf_reset(buf);\n \tstrbuf_addstr(buf, source->path);\n \tstrbuf_addch(buf, '/');\n-\tfill_loose_path(buf, oid);\n+\tfill_loose_path(buf, oid, source->odb->repo->hash_algo);\n \treturn buf->buf;\n }\n \n@@ -1165,7 +1168,7 @@ static int index_mem(struct index_state *istate,\n \n \t\topts.strict = 1;\n \t\topts.error_func = hash_format_check_report;\n-\t\tif (fsck_buffer(null_oid(the_hash_algo), type, buf, size, &opts))\n+\t\tif (fsck_buffer(null_oid(istate->repo->hash_algo), type, buf, size, &opts))\n \t\t\tdie(_(\"refusing to create malformed object\"));\n \t\tfsck_finish(&opts);\n \t}\n@@ -1173,7 +1176,7 @@ static int index_mem(struct index_state *istate,\n \tif (write_object)\n \t\tret = write_object_file(buf, size, type, oid);\n \telse\n-\t\thash_object_file(the_hash_algo, buf, size, type, oid);\n+\t\thash_object_file(istate->repo->hash_algo, buf, size, type, oid);\n \n \tstrbuf_release(&nbuf);\n \treturn ret;\n@@ -1199,7 +1202,7 @@ static int index_stream_convert_blob(struct index_state *istate,\n \t\tret = write_object_file(sbuf.buf, sbuf.len, OBJ_BLOB,\n \t\t\t\t\toid);\n \telse\n-\t\thash_object_file(the_hash_algo, sbuf.buf, sbuf.len, OBJ_BLOB,\n+\t\thash_object_file(istate->repo->hash_algo, sbuf.buf, sbuf.len, OBJ_BLOB,\n \t\t\t\t oid);\n \tstrbuf_release(&sbuf);\n \treturn ret;\n@@ -1297,7 +1300,7 @@ int index_path(struct index_state *istate, struct object_id *oid,\n \t\tif (strbuf_readlink(&sb, path, st->st_size))\n \t\t\treturn error_errno(\"readlink(\\\"%s\\\")\", path);\n \t\tif (!(flags & INDEX_WRITE_OBJECT))\n-\t\t\thash_object_file(the_hash_algo, sb.buf, sb.len,\n+\t\t\thash_object_file(istate->repo->hash_algo, sb.buf, sb.len,\n \t\t\t\t\t OBJ_BLOB, oid);\n \t\telse if (write_object_file(sb.buf, sb.len, OBJ_BLOB, oid))\n \t\t\trc = error(_(\"%s: failed to insert into database\"), path);\n@@ -1328,6 +1331,7 @@ int read_pack_header(int fd, struct pack_header *header)\n \n int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \t\t\t\tstruct strbuf *path,\n+\t\t\t\tconst struct git_hash_algo *algop,\n \t\t\t\teach_loose_object_fn obj_cb,\n \t\t\t\teach_loose_cruft_fn cruft_cb,\n \t\t\t\teach_loose_subdir_fn subdir_cb,\n@@ -1364,12 +1368,12 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \t\tnamelen = strlen(de->d_name);\n \t\tstrbuf_setlen(path, baselen);\n \t\tstrbuf_add(path, de->d_name, namelen);\n-\t\tif (namelen == the_hash_algo->hexsz - 2 &&\n+\t\tif (namelen == algop->hexsz - 2 &&\n \t\t    !hex_to_bytes(oid.hash + 1, de->d_name,\n-\t\t\t\t  the_hash_algo->rawsz - 1)) {\n-\t\t\toid_set_algo(&oid, the_hash_algo);\n-\t\t\tmemset(oid.hash + the_hash_algo->rawsz, 0,\n-\t\t\t       GIT_MAX_RAWSZ - the_hash_algo->rawsz);\n+\t\t\t\t  algop->rawsz - 1)) {\n+\t\t\toid_set_algo(&oid, algop);\n+\t\t\tmemset(oid.hash + algop->rawsz, 0,\n+\t\t\t       GIT_MAX_RAWSZ - algop->rawsz);\n \t\t\tif (obj_cb) {\n \t\t\t\tr = obj_cb(&oid, path->buf, data);\n \t\t\t\tif (r)\n@@ -1405,7 +1409,8 @@ int for_each_loose_file_in_objdir_buf(struct strbuf *path,\n \tint i;\n \n \tfor (i = 0; i < 256; i++) {\n-\t\tr = for_each_file_in_obj_subdir(i, path, obj_cb, cruft_cb,\n+\t\tr = for_each_file_in_obj_subdir(i, path, the_repository->hash_algo,\n+\t\t\t\t\t\tobj_cb, cruft_cb,\n \t\t\t\t\t\tsubdir_cb, data);\n \t\tif (r)\n \t\t\tbreak;\n@@ -1481,6 +1486,7 @@ struct oidtree *odb_loose_cache(struct odb_source *source,\n \t}\n \tstrbuf_addstr(&buf, source->path);\n \tfor_each_file_in_obj_subdir(subdir_nr, &buf,\n+\t\t\t\t    source->odb->repo->hash_algo,\n \t\t\t\t    append_loose_object,\n \t\t\t\t    NULL, NULL,\n \t\t\t\t    source->loose_objects_cache);\n@@ -1501,7 +1507,8 @@ static int check_stream_oid(git_zstream *stream,\n \t\t\t    const char *hdr,\n \t\t\t    unsigned long size,\n \t\t\t    const char *path,\n-\t\t\t    const struct object_id *expected_oid)\n+\t\t\t    const struct object_id *expected_oid,\n+\t\t\t    const struct git_hash_algo *algop)\n {\n \tstruct git_hash_ctx c;\n \tstruct object_id real_oid;\n@@ -1509,7 +1516,7 @@ static int check_stream_oid(git_zstream *stream,\n \tunsigned long total_read;\n \tint status = Z_OK;\n \n-\tthe_hash_algo->init_fn(&c);\n+\talgop->init_fn(&c);\n \tgit_hash_update(&c, hdr, stream->total_out);\n \n \t/*\n@@ -1594,7 +1601,8 @@ int read_loose_object(const char *path,\n \n \tif (*oi->typep == OBJ_BLOB &&\n \t    *size > repo_settings_get_big_file_threshold(the_repository)) {\n-\t\tif (check_stream_oid(&stream, hdr, *size, path, expected_oid) < 0)\n+\t\tif (check_stream_oid(&stream, hdr, *size, path, expected_oid,\n+\t\t\t\t     the_repository->hash_algo) < 0)\n \t\t\tgoto out_inflate;\n \t} else {\n \t\t*contents = unpack_loose_rest(&stream, hdr, *size, expected_oid);\ndiff --git a/object-file.h b/object-file.h\nindex 67b4ffc4808..222ff2871a1 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -89,6 +89,7 @@ typedef int each_loose_subdir_fn(unsigned int nr,\n \t\t\t\t void *data);\n int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \t\t\t\tstruct strbuf *path,\n+\t\t\t\tconst struct git_hash_algo *algo,\n \t\t\t\teach_loose_object_fn obj_cb,\n \t\t\t\teach_loose_cruft_fn cruft_cb,\n \t\t\t\teach_loose_subdir_fn subdir_cb,\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522155","messageId":"20250717-pks-object-file-wo-the-repository-v2-3-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 03/16] object-file: get rid of `the_repository` in `has_loose_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:29Z","receivedAt":"2025-07-17T04:56:46Z","isPatch":true,"body":"We implicitly depend on `the_repository` in `has_loose_object()`.\nRefactor the function to accept an `odb_source` as input that should be\nchecked for such a loose object.\n\nThis refactoring changes semantics of the function to not check the\nwhole object database for such a loose object anymore, but instead we\nnow only check that single source. Existing callers thus need to loop\nthrough all sources manually now.\n\nWhile this change may seem illogical at first, whether or not an object\nexists in a specific format should be answered by the source using that\nformat. As such, we can eventually convert this into a generic function\n`odb_source_has_object()` that simply checks whether a given object\nexists in an object source. And as we will know about the format that\nany given source uses it allows us to derive whether the object exists\nin a given format.\n\nThis change also makes `has_loose_object_nonlocal()` obsolete. The only\ncaller of this function is adapted so that it skips the primary object\nsource.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/pack-objects.c | 24 ++++++++++++++++++++----\n object-file.c          | 16 +++++++---------\n object-file.h          |  7 +++----\n 3 files changed, 30 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 5781dec9808..a44f0ce1c78 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -1703,8 +1703,16 @@ static int want_object_in_pack_mtime(const struct object_id *oid,\n \tstruct list_head *pos;\n \tstruct multi_pack_index *m;\n \n-\tif (!exclude && local && has_loose_object_nonlocal(oid))\n-\t\treturn 0;\n+\tif (!exclude && local) {\n+\t\t/*\n+\t\t * Note that we start iterating at `sources->next` so that we\n+\t\t * skip the local object source.\n+\t\t */\n+\t\tstruct odb_source *source = the_repository->objects->sources->next;\n+\t\tfor (; source; source = source->next)\n+\t\t\tif (has_loose_object(source, oid))\n+\t\t\t\treturn 0;\n+\t}\n \n \t/*\n \t * If we already know the pack object lives in, start checks from that\n@@ -3928,7 +3936,14 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type\n \t} else {\n \t\tif (!want_object_in_pack_mtime(oid, 0, &pack, &offset, mtime))\n \t\t\treturn;\n-\t\tif (!pack && type == OBJ_BLOB && !has_loose_object(oid)) {\n+\t\tif (!pack && type == OBJ_BLOB) {\n+\t\t\tstruct odb_source *source = the_repository->objects->sources;\n+\t\t\tint found = 0;\n+\n+\t\t\tfor (; !found && source; source = source->next)\n+\t\t\t\tif (has_loose_object(source, oid))\n+\t\t\t\t\tfound = 1;\n+\n \t\t\t/*\n \t\t\t * If a traversed tree has a missing blob then we want\n \t\t\t * to avoid adding that missing object to our pack.\n@@ -3942,7 +3957,8 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type\n \t\t\t * limited to \"ensure non-tip blobs which don't exist in\n \t\t\t * packs do exist via loose objects\". Confused?\n \t\t\t */\n-\t\t\treturn;\n+\t\t\tif (!found)\n+\t\t\t\treturn;\n \t\t}\n \n \t\tentry = create_object_entry(oid, type, pack_name_hash_fn(name),\ndiff --git a/object-file.c b/object-file.c\nindex bc395febc9d..7aecaa3d2a0 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -121,14 +121,10 @@ static int check_and_freshen(const struct object_id *oid, int freshen)\n \t       check_and_freshen_nonlocal(oid, freshen);\n }\n \n-int has_loose_object_nonlocal(const struct object_id *oid)\n+int has_loose_object(struct odb_source *source,\n+\t\t     const struct object_id *oid)\n {\n-\treturn check_and_freshen_nonlocal(oid, 0);\n-}\n-\n-int has_loose_object(const struct object_id *oid)\n-{\n-\treturn check_and_freshen(oid, 0);\n+\treturn check_and_freshen_odb(source, oid, 0);\n }\n \n int format_object_header(char *str, size_t size, enum object_type type,\n@@ -1103,8 +1099,10 @@ int force_object_loose(const struct object_id *oid, time_t mtime)\n \tint hdrlen;\n \tint ret;\n \n-\tif (has_loose_object(oid))\n-\t\treturn 0;\n+\tfor (struct odb_source *source = repo->objects->sources; source; source = source->next)\n+\t\tif (has_loose_object(source, oid))\n+\t\t\treturn 0;\n+\n \toi.typep = &type;\n \toi.sizep = &len;\n \toi.contentp = &buf;\ndiff --git a/object-file.h b/object-file.h\nindex 222ff2871a1..5b63a05ab51 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -45,13 +45,12 @@ const char *odb_loose_path(struct odb_source *source,\n \t\t\t   const struct object_id *oid);\n \n /*\n- * Return true iff an alternate object database has a loose object\n+ * Return true iff an object database source has a loose object\n  * with the specified name.  This function does not respect replace\n  * references.\n  */\n-int has_loose_object_nonlocal(const struct object_id *);\n-\n-int has_loose_object(const struct object_id *);\n+int has_loose_object(struct odb_source *source,\n+\t\t     const struct object_id *oid);\n \n void *map_loose_object(struct repository *r, const struct object_id *oid,\n \t\t       unsigned long *size);\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522156","messageId":"20250717-pks-object-file-wo-the-repository-v2-4-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 04/16] object-file: inline `check_and_freshen()` functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:30Z","receivedAt":"2025-07-17T04:56:48Z","isPatch":true,"body":"The `check_and_freshen()` functions are only used by a single caller\nnow. Inline them into `freshen_loose_object()`.\n\nWhile at it, rename `check_and_freshen_odb()` to `_source()` to reflect\nthat it works on a single object source instead of on the whole database.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 41 +++++++++++++----------------------------\n 1 file changed, 13 insertions(+), 28 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 7aecaa3d2a0..9e17e608f78 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -89,42 +89,19 @@ int check_and_freshen_file(const char *fn, int freshen)\n \treturn 1;\n }\n \n-static int check_and_freshen_odb(struct odb_source *source,\n-\t\t\t\t const struct object_id *oid,\n-\t\t\t\t int freshen)\n+static int check_and_freshen_source(struct odb_source *source,\n+\t\t\t\t    const struct object_id *oid,\n+\t\t\t\t    int freshen)\n {\n \tstatic struct strbuf path = STRBUF_INIT;\n \todb_loose_path(source, &path, oid);\n \treturn check_and_freshen_file(path.buf, freshen);\n }\n \n-static int check_and_freshen_local(const struct object_id *oid, int freshen)\n-{\n-\treturn check_and_freshen_odb(the_repository->objects->sources, oid, freshen);\n-}\n-\n-static int check_and_freshen_nonlocal(const struct object_id *oid, int freshen)\n-{\n-\tstruct odb_source *source;\n-\n-\todb_prepare_alternates(the_repository->objects);\n-\tfor (source = the_repository->objects->sources->next; source; source = source->next) {\n-\t\tif (check_and_freshen_odb(source, oid, freshen))\n-\t\t\treturn 1;\n-\t}\n-\treturn 0;\n-}\n-\n-static int check_and_freshen(const struct object_id *oid, int freshen)\n-{\n-\treturn check_and_freshen_local(oid, freshen) ||\n-\t       check_and_freshen_nonlocal(oid, freshen);\n-}\n-\n int has_loose_object(struct odb_source *source,\n \t\t     const struct object_id *oid)\n {\n-\treturn check_and_freshen_odb(source, oid, 0);\n+\treturn check_and_freshen_source(source, oid, 0);\n }\n \n int format_object_header(char *str, size_t size, enum object_type type,\n@@ -918,7 +895,15 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \n static int freshen_loose_object(const struct object_id *oid)\n {\n-\treturn check_and_freshen(oid, 1);\n+\tstruct odb_source *source;\n+\n+\todb_prepare_alternates(the_repository->objects);\n+\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\t\tif (check_and_freshen_source(source, oid, 1))\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n }\n \n static int freshen_packed_object(const struct object_id *oid)\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522157","messageId":"20250717-pks-object-file-wo-the-repository-v2-5-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 05/16] object-file: get rid of `the_repository` when freshening objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:31Z","receivedAt":"2025-07-17T04:56:52Z","isPatch":true,"body":"We implicitly depend on `the_repository` when freshening either loose or\npacked objects. Refactor these functions to instead accept an object\ndatabase as input so that we can get rid of the global dependency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 9e17e608f78..3453989b7e3 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -893,23 +893,21 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\t\t\t\t  FOF_SKIP_COLLISION_CHECK);\n }\n \n-static int freshen_loose_object(const struct object_id *oid)\n+static int freshen_loose_object(struct object_database *odb,\n+\t\t\t\tconst struct object_id *oid)\n {\n-\tstruct odb_source *source;\n-\n-\todb_prepare_alternates(the_repository->objects);\n-\tfor (source = the_repository->objects->sources; source; source = source->next) {\n+\todb_prepare_alternates(odb);\n+\tfor (struct odb_source *source = odb->sources; source; source = source->next)\n \t\tif (check_and_freshen_source(source, oid, 1))\n \t\t\treturn 1;\n-\t}\n-\n \treturn 0;\n }\n \n-static int freshen_packed_object(const struct object_id *oid)\n+static int freshen_packed_object(struct object_database *odb,\n+\t\t\t\t const struct object_id *oid)\n {\n \tstruct pack_entry e;\n-\tif (!find_pack_entry(the_repository, oid, &e))\n+\tif (!find_pack_entry(odb->repo, oid, &e))\n \t\treturn 0;\n \tif (e.p->is_cruft)\n \t\treturn 0;\n@@ -999,7 +997,8 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tdie(_(\"deflateEnd on stream object failed (%d)\"), ret);\n \tclose_loose_object(fd, tmp_file.buf);\n \n-\tif (freshen_packed_object(oid) || freshen_loose_object(oid)) {\n+\tif (freshen_packed_object(the_repository->objects, oid) ||\n+\t    freshen_loose_object(the_repository->objects, oid)) {\n \t\tunlink_or_warn(tmp_file.buf);\n \t\tgoto cleanup;\n \t}\n@@ -1062,7 +1061,8 @@ int write_object_file_flags(const void *buf, unsigned long len,\n \t * it out into .git/objects/??/?{38} file.\n \t */\n \twrite_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);\n-\tif (freshen_packed_object(oid) || freshen_loose_object(oid))\n+\tif (freshen_packed_object(repo->objects, oid) ||\n+\t    freshen_loose_object(repo->objects, oid))\n \t\treturn 0;\n \tif (write_loose_object(oid, hdr, hdrlen, buf, len, 0, flags))\n \t\treturn -1;\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522158","messageId":"20250717-pks-object-file-wo-the-repository-v2-6-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 06/16] object-file: get rid of `the_repository` in `loose_object_info()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:32Z","receivedAt":"2025-07-17T04:56:55Z","isPatch":true,"body":"While `loose_object_info()` already accepts a repository as parameter we\nstill have one callsite in there where we use `the_repository` to figure\nout the hash algorithm. Use the passed-in repository instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 3453989b7e3..800eeae85af 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -421,7 +421,7 @@ int loose_object_info(struct repository *r,\n \tenum object_type type_scratch;\n \n \tif (oi->delta_base_oid)\n-\t\toidclr(oi->delta_base_oid, the_repository->hash_algo);\n+\t\toidclr(oi->delta_base_oid, r->hash_algo);\n \n \t/*\n \t * If we don't care about type or size, then we don't\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522159","messageId":"20250717-pks-object-file-wo-the-repository-v2-7-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 07/16] object-file: get rid of `the_repository` in `finalize_object_file()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:33Z","receivedAt":"2025-07-17T04:56:59Z","isPatch":true,"body":"We implicitly depend on `the_repository` when moving an object file into\nplace in `finalize_object_file()`. Get rid of this global dependency by\npassing in a repository.\n\nNote that one might be pressed to inject an object database instead of a\nrepository. But the function doesn't really care about the ODB at all.\nAll it does is to move a file into place while checking whether there is\nany collision. As such, the functionality it provides is independent of\nthe object database and only needs the repository as parameter so that\nit can adjust permissions of the file we are about to finalize.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fast-import.c  |  4 ++--\n builtin/index-pack.c   |  2 +-\n builtin/pack-objects.c |  2 +-\n bulk-checkin.c         |  2 +-\n http.c                 |  4 ++--\n midx-write.c           |  2 +-\n object-file.c          | 14 ++++++++------\n object-file.h          |  6 ++++--\n pack-write.c           | 16 +++++++++-------\n pack.h                 |  3 ++-\n tmp-objdir.c           |  2 +-\n 11 files changed, 32 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex b1389c59211..89f57898b15 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -821,11 +821,11 @@ static char *keep_pack(const char *curr_index_name)\n \t\tdie_errno(\"failed to write keep file\");\n \n \todb_pack_name(pack_data->repo, &name, pack_data->hash, \"pack\");\n-\tif (finalize_object_file(pack_data->pack_name, name.buf))\n+\tif (finalize_object_file(pack_data->repo, pack_data->pack_name, name.buf))\n \t\tdie(\"cannot store pack file\");\n \n \todb_pack_name(pack_data->repo, &name, pack_data->hash, \"idx\");\n-\tif (finalize_object_file(curr_index_name, name.buf))\n+\tif (finalize_object_file(pack_data->repo, curr_index_name, name.buf))\n \t\tdie(\"cannot store index file\");\n \tfree((void *)curr_index_name);\n \treturn strbuf_detach(&name, NULL);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 19c67a85344..dabeb825a6c 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1598,7 +1598,7 @@ static void rename_tmp_packfile(const char **final_name,\n \tif (!*final_name || strcmp(*final_name, curr_name)) {\n \t\tif (!*final_name)\n \t\t\t*final_name = odb_pack_name(the_repository, name, hash, ext);\n-\t\tif (finalize_object_file(curr_name, *final_name))\n+\t\tif (finalize_object_file(the_repository, curr_name, *final_name))\n \t\t\tdie(_(\"unable to rename temporary '*.%s' file to '%s'\"),\n \t\t\t    ext, *final_name);\n \t} else if (make_read_only_if_same) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex a44f0ce1c78..e8e85d8278b 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -1449,7 +1449,7 @@ static void write_pack_file(void)\n \t\t\t\tstrbuf_setlen(&tmpname, tmpname_len);\n \t\t\t}\n \n-\t\t\trename_tmp_packfile_idx(&tmpname, &idx_tmp_name);\n+\t\t\trename_tmp_packfile_idx(the_repository, &tmpname, &idx_tmp_name);\n \n \t\t\tfree(idx_tmp_name);\n \t\t\tstrbuf_release(&tmpname);\ndiff --git a/bulk-checkin.c b/bulk-checkin.c\nindex 16df86c0ba8..b2809ab0398 100644\n--- a/bulk-checkin.c\n+++ b/bulk-checkin.c\n@@ -46,7 +46,7 @@ static void finish_tmp_packfile(struct strbuf *basename,\n \tstage_tmp_packfiles(the_repository, basename, pack_tmp_name,\n \t\t\t    written_list, nr_written, NULL, pack_idx_opts, hash,\n \t\t\t    &idx_tmp_name);\n-\trename_tmp_packfile_idx(basename, &idx_tmp_name);\n+\trename_tmp_packfile_idx(the_repository, basename, &idx_tmp_name);\n \n \tfree(idx_tmp_name);\n }\ndiff --git a/http.c b/http.c\nindex 9b62f627dc5..7cc797116bb 100644\n--- a/http.c\n+++ b/http.c\n@@ -2331,7 +2331,7 @@ int http_get_file(const char *url, const char *filename,\n \tret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options);\n \tfclose(result);\n \n-\tif (ret == HTTP_OK && finalize_object_file(tmpfile.buf, filename))\n+\tif (ret == HTTP_OK && finalize_object_file(the_repository, tmpfile.buf, filename))\n \t\tret = HTTP_ERROR;\n cleanup:\n \tstrbuf_release(&tmpfile);\n@@ -2815,7 +2815,7 @@ int finish_http_object_request(struct http_object_request *freq)\n \t\treturn -1;\n \t}\n \todb_loose_path(the_repository->objects->sources, &filename, &freq->oid);\n-\tfreq->rename = finalize_object_file(freq->tmpfile.buf, filename.buf);\n+\tfreq->rename = finalize_object_file(the_repository, freq->tmpfile.buf, filename.buf);\n \tstrbuf_release(&filename);\n \n \treturn freq->rename;\ndiff --git a/midx-write.c b/midx-write.c\nindex f2cfb85476e..effacade2d3 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -667,7 +667,7 @@ static void write_midx_reverse_index(struct write_midx_context *ctx,\n \ttmp_file = write_rev_file_order(ctx->repo, NULL, ctx->pack_order,\n \t\t\t\t\tctx->entries_nr, midx_hash, WRITE_REV);\n \n-\tif (finalize_object_file(tmp_file, buf.buf))\n+\tif (finalize_object_file(ctx->repo, tmp_file, buf.buf))\n \t\tdie(_(\"cannot store reverse index file\"));\n \n \tstrbuf_release(&buf);\ndiff --git a/object-file.c b/object-file.c\nindex 800eeae85af..6a7049a9e98 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -584,12 +584,14 @@ static int check_collision(const char *source, const char *dest)\n /*\n  * Move the just written object into its final resting place.\n  */\n-int finalize_object_file(const char *tmpfile, const char *filename)\n+int finalize_object_file(struct repository *repo,\n+\t\t\t const char *tmpfile, const char *filename)\n {\n-\treturn finalize_object_file_flags(tmpfile, filename, 0);\n+\treturn finalize_object_file_flags(repo, tmpfile, filename, 0);\n }\n \n-int finalize_object_file_flags(const char *tmpfile, const char *filename,\n+int finalize_object_file_flags(struct repository *repo,\n+\t\t\t       const char *tmpfile, const char *filename,\n \t\t\t       enum finalize_object_file_flags flags)\n {\n \tunsigned retries = 0;\n@@ -649,7 +651,7 @@ int finalize_object_file_flags(const char *tmpfile, const char *filename,\n \t}\n \n out:\n-\tif (adjust_shared_perm(the_repository, filename))\n+\tif (adjust_shared_perm(repo, filename))\n \t\treturn error(_(\"unable to set permission to '%s'\"), filename);\n \treturn 0;\n }\n@@ -889,7 +891,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\t\twarning_errno(_(\"failed utime() on %s\"), tmp_file.buf);\n \t}\n \n-\treturn finalize_object_file_flags(tmp_file.buf, filename.buf,\n+\treturn finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n \t\t\t\t\t  FOF_SKIP_COLLISION_CHECK);\n }\n \n@@ -1020,7 +1022,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tstrbuf_release(&dir);\n \t}\n \n-\terr = finalize_object_file_flags(tmp_file.buf, filename.buf,\n+\terr = finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n \t\t\t\t\t FOF_SKIP_COLLISION_CHECK);\n \tif (!err && compat)\n \t\terr = repo_add_loose_object_map(the_repository, oid, &compat_oid);\ndiff --git a/object-file.h b/object-file.h\nindex 5b63a05ab51..370139e0762 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -218,8 +218,10 @@ enum finalize_object_file_flags {\n \tFOF_SKIP_COLLISION_CHECK = 1,\n };\n \n-int finalize_object_file(const char *tmpfile, const char *filename);\n-int finalize_object_file_flags(const char *tmpfile, const char *filename,\n+int finalize_object_file(struct repository *repo,\n+\t\t\t const char *tmpfile, const char *filename);\n+int finalize_object_file_flags(struct repository *repo,\n+\t\t\t       const char *tmpfile, const char *filename,\n \t\t\t       enum finalize_object_file_flags flags);\n \n void hash_object_file(const struct git_hash_algo *algo, const void *buf,\ndiff --git a/pack-write.c b/pack-write.c\nindex eccdc798e36..83eaf88541e 100644\n--- a/pack-write.c\n+++ b/pack-write.c\n@@ -538,22 +538,24 @@ struct hashfile *create_tmp_packfile(struct repository *repo,\n \treturn hashfd(repo->hash_algo, fd, *pack_tmp_name);\n }\n \n-static void rename_tmp_packfile(struct strbuf *name_prefix, const char *source,\n+static void rename_tmp_packfile(struct repository *repo,\n+\t\t\t\tstruct strbuf *name_prefix, const char *source,\n \t\t\t\tconst char *ext)\n {\n \tsize_t name_prefix_len = name_prefix->len;\n \n \tstrbuf_addstr(name_prefix, ext);\n-\tif (finalize_object_file(source, name_prefix->buf))\n+\tif (finalize_object_file(repo, source, name_prefix->buf))\n \t\tdie(\"unable to rename temporary file to '%s'\",\n \t\t    name_prefix->buf);\n \tstrbuf_setlen(name_prefix, name_prefix_len);\n }\n \n-void rename_tmp_packfile_idx(struct strbuf *name_buffer,\n+void rename_tmp_packfile_idx(struct repository *repo,\n+\t\t\t     struct strbuf *name_buffer,\n \t\t\t     char **idx_tmp_name)\n {\n-\trename_tmp_packfile(name_buffer, *idx_tmp_name, \"idx\");\n+\trename_tmp_packfile(repo, name_buffer, *idx_tmp_name, \"idx\");\n }\n \n void stage_tmp_packfiles(struct repository *repo,\n@@ -586,11 +588,11 @@ void stage_tmp_packfiles(struct repository *repo,\n \t\t\t\t\t\t    hash);\n \t}\n \n-\trename_tmp_packfile(name_buffer, pack_tmp_name, \"pack\");\n+\trename_tmp_packfile(repo, name_buffer, pack_tmp_name, \"pack\");\n \tif (rev_tmp_name)\n-\t\trename_tmp_packfile(name_buffer, rev_tmp_name, \"rev\");\n+\t\trename_tmp_packfile(repo, name_buffer, rev_tmp_name, \"rev\");\n \tif (mtimes_tmp_name)\n-\t\trename_tmp_packfile(name_buffer, mtimes_tmp_name, \"mtimes\");\n+\t\trename_tmp_packfile(repo, name_buffer, mtimes_tmp_name, \"mtimes\");\n \n \tfree(rev_tmp_name);\n \tfree(mtimes_tmp_name);\ndiff --git a/pack.h b/pack.h\nindex 5d4393eaffe..ec76472e49b 100644\n--- a/pack.h\n+++ b/pack.h\n@@ -145,7 +145,8 @@ void stage_tmp_packfiles(struct repository *repo,\n \t\t\t struct pack_idx_option *pack_idx_opts,\n \t\t\t unsigned char hash[],\n \t\t\t char **idx_tmp_name);\n-void rename_tmp_packfile_idx(struct strbuf *basename,\n+void rename_tmp_packfile_idx(struct repository *repo,\n+\t\t\t     struct strbuf *basename,\n \t\t\t     char **idx_tmp_name);\n \n #endif\ndiff --git a/tmp-objdir.c b/tmp-objdir.c\nindex ae01eae9c41..9f5a1788cd7 100644\n--- a/tmp-objdir.c\n+++ b/tmp-objdir.c\n@@ -227,7 +227,7 @@ static int migrate_one(struct tmp_objdir *t,\n \t\t\treturn -1;\n \t\treturn migrate_paths(t, src, dst, flags);\n \t}\n-\treturn finalize_object_file_flags(src->buf, dst->buf, flags);\n+\treturn finalize_object_file_flags(t->repo, src->buf, dst->buf, flags);\n }\n \n static int is_loose_object_shard(const char *name)\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522160","messageId":"20250717-pks-object-file-wo-the-repository-v2-8-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 08/16] loose: write loose objects map via their source","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:34Z","receivedAt":"2025-07-17T04:57:03Z","isPatch":true,"body":"When a repository is configured to have a compatibility hash algorithm\nwe keep track of object ID mappings for loose objects via the loose\nobject map. This map simply maps an object ID of the actual hash to the\nobject ID of the compatibility hash. This loose object map is an\ninherent property of the loose files backend and thus of one specific\nobject source.\n\nRefactor the interfaces to reflect this by requiring a `struct\nodb_source` as input instead of a repository. This prepares for\nsubsequent commits where we will refactor writing of loose objects to\nwork on a `struct odb_source`, as well.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n loose.c       | 16 +++++++++-------\n loose.h       |  4 +++-\n object-file.c |  6 +++---\n 3 files changed, 15 insertions(+), 11 deletions(-)\n\ndiff --git a/loose.c b/loose.c\nindex 519f5db7935..e8ea6e7e24b 100644\n--- a/loose.c\n+++ b/loose.c\n@@ -166,7 +166,8 @@ int repo_write_loose_object_map(struct repository *repo)\n \treturn -1;\n }\n \n-static int write_one_object(struct repository *repo, const struct object_id *oid,\n+static int write_one_object(struct odb_source *source,\n+\t\t\t    const struct object_id *oid,\n \t\t\t    const struct object_id *compat_oid)\n {\n \tstruct lock_file lock;\n@@ -174,7 +175,7 @@ static int write_one_object(struct repository *repo, const struct object_id *oid\n \tstruct stat st;\n \tstruct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;\n \n-\trepo_common_path_replace(repo, &path, \"objects/loose-object-idx\");\n+\tstrbuf_addf(&path, \"%s/loose-object-idx\", source->path);\n \thold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1);\n \n \tfd = open(path.buf, O_WRONLY | O_CREAT | O_APPEND, 0666);\n@@ -190,7 +191,7 @@ static int write_one_object(struct repository *repo, const struct object_id *oid\n \t\tgoto errout;\n \tif (close(fd))\n \t\tgoto errout;\n-\tadjust_shared_perm(repo, path.buf);\n+\tadjust_shared_perm(source->odb->repo, path.buf);\n \trollback_lock_file(&lock);\n \tstrbuf_release(&buf);\n \tstrbuf_release(&path);\n@@ -204,17 +205,18 @@ static int write_one_object(struct repository *repo, const struct object_id *oid\n \treturn -1;\n }\n \n-int repo_add_loose_object_map(struct repository *repo, const struct object_id *oid,\n+int repo_add_loose_object_map(struct odb_source *source,\n+\t\t\t      const struct object_id *oid,\n \t\t\t      const struct object_id *compat_oid)\n {\n \tint inserted = 0;\n \n-\tif (!should_use_loose_object_map(repo))\n+\tif (!should_use_loose_object_map(source->odb->repo))\n \t\treturn 0;\n \n-\tinserted = insert_loose_map(repo->objects->sources, oid, compat_oid);\n+\tinserted = insert_loose_map(source, oid, compat_oid);\n \tif (inserted)\n-\t\treturn write_one_object(repo, oid, compat_oid);\n+\t\treturn write_one_object(source, oid, compat_oid);\n \treturn 0;\n }\n \ndiff --git a/loose.h b/loose.h\nindex 28512306e5f..6af1702973c 100644\n--- a/loose.h\n+++ b/loose.h\n@@ -4,6 +4,7 @@\n #include \"khash.h\"\n \n struct repository;\n+struct odb_source;\n \n struct loose_object_map {\n \tkh_oid_map_t *to_compat;\n@@ -16,7 +17,8 @@ int repo_loose_object_map_oid(struct repository *repo,\n \t\t\t      const struct object_id *src,\n \t\t\t      const struct git_hash_algo *dest_algo,\n \t\t\t      struct object_id *dest);\n-int repo_add_loose_object_map(struct repository *repo, const struct object_id *oid,\n+int repo_add_loose_object_map(struct odb_source *source,\n+\t\t\t      const struct object_id *oid,\n \t\t\t      const struct object_id *compat_oid);\n int repo_read_loose_object_map(struct repository *repo);\n int repo_write_loose_object_map(struct repository *repo);\ndiff --git a/object-file.c b/object-file.c\nindex 6a7049a9e98..a9248760a26 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1025,7 +1025,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \terr = finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n \t\t\t\t\t FOF_SKIP_COLLISION_CHECK);\n \tif (!err && compat)\n-\t\terr = repo_add_loose_object_map(the_repository, oid, &compat_oid);\n+\t\terr = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n cleanup:\n \tstrbuf_release(&tmp_file);\n \tstrbuf_release(&filename);\n@@ -1069,7 +1069,7 @@ int write_object_file_flags(const void *buf, unsigned long len,\n \tif (write_loose_object(oid, hdr, hdrlen, buf, len, 0, flags))\n \t\treturn -1;\n \tif (compat)\n-\t\treturn repo_add_loose_object_map(repo, oid, &compat_oid);\n+\t\treturn repo_add_loose_object_map(repo->objects->sources, oid, &compat_oid);\n \treturn 0;\n }\n \n@@ -1103,7 +1103,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime)\n \thdrlen = format_object_header(hdr, sizeof(hdr), type, len);\n \tret = write_loose_object(oid, hdr, hdrlen, buf, len, mtime, 0);\n \tif (!ret && compat)\n-\t\tret = repo_add_loose_object_map(the_repository, oid, &compat_oid);\n+\t\tret = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n \tfree(buf);\n \n \treturn ret;\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522161","messageId":"20250717-pks-object-file-wo-the-repository-v2-9-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 09/16] odb: introduce `odb_write_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:35Z","receivedAt":"2025-07-17T04:57:05Z","isPatch":true,"body":"We do not have a backend-agnostic way to write objects into an object\ndatabase. While there is `write_object_file()`, this function is rather\nspecific to the loose object format.\n\nIntroduce `odb_write_object()` to plug this gap. For now, this function\nis a simple wrapper around `write_object_file()` and doesn't even use\nthe passed-in object database yet. This will change in subsequent\ncommits, where `write_object_file()` is converted so that it works on\ntop of an `odb_source`. `odb_write_object()` will then become\nresponsible for deciding which source an object shall be written to.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n apply.c                  | 11 +++++++----\n builtin/checkout.c       |  2 +-\n builtin/merge-file.c     |  3 ++-\n builtin/mktag.c          |  2 +-\n builtin/mktree.c         |  2 +-\n builtin/notes.c          |  3 ++-\n builtin/receive-pack.c   |  4 ++--\n builtin/replace.c        |  3 ++-\n builtin/tag.c            |  4 ++--\n builtin/unpack-objects.c | 12 ++++++------\n cache-tree.c             |  5 ++---\n commit.c                 |  4 ++--\n match-trees.c            |  2 +-\n merge-ort.c              |  7 ++++---\n notes-cache.c            |  3 ++-\n notes.c                  | 12 ++++++++----\n object-file.c            | 18 +++++++++---------\n object-file.h            | 26 +++-----------------------\n odb.c                    | 10 ++++++++++\n odb.h                    | 38 ++++++++++++++++++++++++++++++++++++++\n read-cache.c             |  2 +-\n 21 files changed, 106 insertions(+), 67 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex a6836692d0c..ffb9d9f76d6 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3621,7 +3621,7 @@ static int try_threeway(struct apply_state *state,\n \n \t/* Preimage the patch was prepared for */\n \tif (patch->is_new)\n-\t\twrite_object_file(\"\", 0, OBJ_BLOB, &pre_oid);\n+\t\todb_write_object(the_repository->objects, \"\", 0, OBJ_BLOB, &pre_oid);\n \telse if (repo_get_oid(the_repository, patch->old_oid_prefix, &pre_oid) ||\n \t\t read_blob_object(&buf, &pre_oid, patch->old_mode))\n \t\treturn error(_(\"repository lacks the necessary blob to perform 3-way merge.\"));\n@@ -3637,7 +3637,8 @@ static int try_threeway(struct apply_state *state,\n \t\treturn -1;\n \t}\n \t/* post_oid is theirs */\n-\twrite_object_file(tmp_image.buf.buf, tmp_image.buf.len, OBJ_BLOB, &post_oid);\n+\todb_write_object(the_repository->objects, tmp_image.buf.buf,\n+\t\t\t tmp_image.buf.len, OBJ_BLOB, &post_oid);\n \timage_clear(&tmp_image);\n \n \t/* our_oid is ours */\n@@ -3650,7 +3651,8 @@ static int try_threeway(struct apply_state *state,\n \t\t\treturn error(_(\"cannot read the current contents of '%s'\"),\n \t\t\t\t     patch->old_name);\n \t}\n-\twrite_object_file(tmp_image.buf.buf, tmp_image.buf.len, OBJ_BLOB, &our_oid);\n+\todb_write_object(the_repository->objects, tmp_image.buf.buf,\n+\t\t\t tmp_image.buf.len, OBJ_BLOB, &our_oid);\n \timage_clear(&tmp_image);\n \n \t/* in-core three-way merge between post and our using pre as base */\n@@ -4360,7 +4362,8 @@ static int add_index_file(struct apply_state *state,\n \t\t\t}\n \t\t\tfill_stat_cache_info(state->repo->index, ce, &st);\n \t\t}\n-\t\tif (write_object_file(buf, size, OBJ_BLOB, &ce->oid) < 0) {\n+\t\tif (odb_write_object(the_repository->objects, buf, size,\n+\t\t\t\t     OBJ_BLOB, &ce->oid) < 0) {\n \t\t\tdiscard_cache_entry(ce);\n \t\t\treturn error(_(\"unable to create backing store \"\n \t\t\t\t       \"for newly created file %s\"), path);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 0a90b86a729..f95eb64ffb3 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -320,7 +320,7 @@ static int checkout_merged(int pos, const struct checkout *state,\n \t * (it also writes the merge result to the object database even\n \t * when it may contain conflicts).\n \t */\n-\tif (write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &oid))\n+\tif (odb_write_object(the_repository->objects, result_buf.ptr, result_buf.size, OBJ_BLOB, &oid))\n \t\tdie(_(\"Unable to add merge result for '%s'\"), path);\n \tfree(result_buf.ptr);\n \tce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool);\ndiff --git a/builtin/merge-file.c b/builtin/merge-file.c\nindex 9464f275629..b8b25a14e6d 100644\n--- a/builtin/merge-file.c\n+++ b/builtin/merge-file.c\n@@ -155,7 +155,8 @@ int cmd_merge_file(int argc,\n \t\tif (object_id && !to_stdout) {\n \t\t\tstruct object_id oid;\n \t\t\tif (result.size) {\n-\t\t\t\tif (write_object_file(result.ptr, result.size, OBJ_BLOB, &oid) < 0)\n+\t\t\t\tif (odb_write_object(the_repository->objects, result.ptr,\n+\t\t\t\t\t\t     result.size, OBJ_BLOB, &oid) < 0)\n \t\t\t\t\tret = error(_(\"Could not write object file\"));\n \t\t\t} else {\n \t\t\t\toidcpy(&oid, the_hash_algo->empty_blob);\ndiff --git a/builtin/mktag.c b/builtin/mktag.c\nindex 27e649736cf..12552bbb217 100644\n--- a/builtin/mktag.c\n+++ b/builtin/mktag.c\n@@ -106,7 +106,7 @@ int cmd_mktag(int argc,\n \tif (verify_object_in_tag(&tagged_oid, &tagged_type) < 0)\n \t\tdie(_(\"tag on stdin did not refer to a valid object\"));\n \n-\tif (write_object_file(buf.buf, buf.len, OBJ_TAG, &result) < 0)\n+\tif (odb_write_object(the_repository->objects, buf.buf, buf.len, OBJ_TAG, &result) < 0)\n \t\tdie(_(\"unable to write tag file\"));\n \n \tstrbuf_release(&buf);\ndiff --git a/builtin/mktree.c b/builtin/mktree.c\nindex 81df7f6099f..12772303f50 100644\n--- a/builtin/mktree.c\n+++ b/builtin/mktree.c\n@@ -63,7 +63,7 @@ static void write_tree(struct object_id *oid)\n \t\tstrbuf_add(&buf, ent->oid.hash, the_hash_algo->rawsz);\n \t}\n \n-\twrite_object_file(buf.buf, buf.len, OBJ_TREE, oid);\n+\todb_write_object(the_repository->objects, buf.buf, buf.len, OBJ_TREE, oid);\n \tstrbuf_release(&buf);\n }\n \ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex a9529b1696a..a3580b4aa3d 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -229,7 +229,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data *\n \n static void write_note_data(struct note_data *d, struct object_id *oid)\n {\n-\tif (write_object_file(d->buf.buf, d->buf.len, OBJ_BLOB, oid)) {\n+\tif (odb_write_object(the_repository->objects, d->buf.buf,\n+\t\t\t     d->buf.len, OBJ_BLOB, oid)) {\n \t\tint status = die_message(_(\"unable to write note object\"));\n \n \t\tif (d->edit_path)\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex dd1d1446e75..bd9baf81e56 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -760,8 +760,8 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tint bogs /* beginning_of_gpg_sig */;\n \n \t\talready_done = 1;\n-\t\tif (write_object_file(push_cert.buf, push_cert.len, OBJ_BLOB,\n-\t\t\t\t      &push_cert_oid))\n+\t\tif (odb_write_object(the_repository->objects, push_cert.buf,\n+\t\t\t\t     push_cert.len, OBJ_BLOB, &push_cert_oid))\n \t\t\toidclr(&push_cert_oid, the_repository->hash_algo);\n \n \t\tmemset(&sigcheck, '\\0', sizeof(sigcheck));\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 5ff2ab723cb..7c46d05ec15 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -488,7 +488,8 @@ static int create_graft(int argc, const char **argv, int force, int gentle)\n \t\treturn -1;\n \t}\n \n-\tif (write_object_file(buf.buf, buf.len, OBJ_COMMIT, &new_oid)) {\n+\tif (odb_write_object(the_repository->objects, buf.buf,\n+\t\t\t     buf.len, OBJ_COMMIT, &new_oid)) {\n \t\tstrbuf_release(&buf);\n \t\treturn error(_(\"could not write replacement commit for: '%s'\"),\n \t\t\t     old_ref);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 46cbf892e34..8fbe9e7be04 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -271,8 +271,8 @@ static int build_tag_object(struct strbuf *buf, int sign, struct object_id *resu\n \tstruct object_id *compat_oid = NULL, compat_oid_buf;\n \tif (sign && do_sign(buf, &compat_oid, &compat_oid_buf) < 0)\n \t\treturn error(_(\"unable to sign the tag\"));\n-\tif (write_object_file_flags(buf->buf, buf->len, OBJ_TAG, result,\n-\t\t\t\t    compat_oid, 0) < 0)\n+\tif (odb_write_object_ext(the_repository->objects, buf->buf,\n+\t\t\t\t buf->len, OBJ_TAG, result, compat_oid, 0) < 0)\n \t\treturn error(_(\"unable to write tag file\"));\n \treturn 0;\n }\ndiff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c\nindex a69d59eb50c..1a4fbef36f8 100644\n--- a/builtin/unpack-objects.c\n+++ b/builtin/unpack-objects.c\n@@ -204,8 +204,8 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf)\n {\n \tstruct object_id oid;\n \n-\tif (write_object_file(obj_buf->buffer, obj_buf->size,\n-\t\t\t      obj->type, &oid) < 0)\n+\tif (odb_write_object(the_repository->objects, obj_buf->buffer, obj_buf->size,\n+\t\t\t     obj->type, &oid) < 0)\n \t\tdie(\"failed to write object %s\", oid_to_hex(&obj->oid));\n \tobj->flags |= FLAG_WRITTEN;\n }\n@@ -272,16 +272,16 @@ static void write_object(unsigned nr, enum object_type type,\n \t\t\t void *buf, unsigned long size)\n {\n \tif (!strict) {\n-\t\tif (write_object_file(buf, size, type,\n-\t\t\t\t      &obj_list[nr].oid) < 0)\n+\t\tif (odb_write_object(the_repository->objects, buf, size, type,\n+\t\t\t\t     &obj_list[nr].oid) < 0)\n \t\t\tdie(\"failed to write object\");\n \t\tadded_object(nr, type, buf, size);\n \t\tfree(buf);\n \t\tobj_list[nr].obj = NULL;\n \t} else if (type == OBJ_BLOB) {\n \t\tstruct blob *blob;\n-\t\tif (write_object_file(buf, size, type,\n-\t\t\t\t      &obj_list[nr].oid) < 0)\n+\t\tif (odb_write_object(the_repository->objects, buf, size, type,\n+\t\t\t\t     &obj_list[nr].oid) < 0)\n \t\t\tdie(\"failed to write object\");\n \t\tadded_object(nr, type, buf, size);\n \t\tfree(buf);\ndiff --git a/cache-tree.c b/cache-tree.c\nindex a4bc14ad15c..66ef2becbe0 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -456,9 +456,8 @@ static int update_one(struct cache_tree *it,\n \t} else if (dryrun) {\n \t\thash_object_file(the_hash_algo, buffer.buf, buffer.len,\n \t\t\t\t OBJ_TREE, &it->oid);\n-\t} else if (write_object_file_flags(buffer.buf, buffer.len, OBJ_TREE,\n-\t\t\t\t\t   &it->oid, NULL, flags & WRITE_TREE_SILENT\n-\t\t\t\t\t   ? WRITE_OBJECT_FILE_SILENT : 0)) {\n+\t} else if (odb_write_object_ext(the_repository->objects, buffer.buf, buffer.len, OBJ_TREE,\n+\t\t\t\t\t&it->oid, NULL, flags & WRITE_TREE_SILENT ? WRITE_OBJECT_SILENT : 0)) {\n \t\tstrbuf_release(&buffer);\n \t\treturn -1;\n \t}\ndiff --git a/commit.c b/commit.c\nindex 15115125c36..bcc9aea55f6 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1797,8 +1797,8 @@ int commit_tree_extended(const char *msg, size_t msg_len,\n \t\tcompat_oid = &compat_oid_buf;\n \t}\n \n-\tresult = write_object_file_flags(buffer.buf, buffer.len, OBJ_COMMIT,\n-\t\t\t\t\t ret, compat_oid, 0);\n+\tresult = odb_write_object_ext(the_repository->objects, buffer.buf, buffer.len,\n+\t\t\t\t      OBJ_COMMIT, ret, compat_oid, 0);\n out:\n \tfree(parent_buf);\n \tstrbuf_release(&buffer);\ndiff --git a/match-trees.c b/match-trees.c\nindex 5a8a5c39b04..4216933d06b 100644\n--- a/match-trees.c\n+++ b/match-trees.c\n@@ -246,7 +246,7 @@ static int splice_tree(struct repository *r,\n \t\trewrite_with = oid2;\n \t}\n \thashcpy(rewrite_here, rewrite_with->hash, r->hash_algo);\n-\tstatus = write_object_file(buf, sz, OBJ_TREE, result);\n+\tstatus = odb_write_object(r->objects, buf, sz, OBJ_TREE, result);\n \tfree(buf);\n \treturn status;\n }\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 473ff61e36e..535ef3efc6f 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -2216,8 +2216,8 @@ static int handle_content_merge(struct merge_options *opt,\n \t\t}\n \n \t\tif (!ret && record_object &&\n-\t\t    write_object_file(result_buf.ptr, result_buf.size,\n-\t\t\t\t      OBJ_BLOB, &result->oid)) {\n+\t\t    odb_write_object(the_repository->objects, result_buf.ptr, result_buf.size,\n+\t\t\t\t     OBJ_BLOB, &result->oid)) {\n \t\t\tpath_msg(opt, ERROR_OBJECT_WRITE_FAILED, 0,\n \t\t\t\t pathnames[0], pathnames[1], pathnames[2], NULL,\n \t\t\t\t _(\"error: unable to add %s to database\"), path);\n@@ -3772,7 +3772,8 @@ static int write_tree(struct object_id *result_oid,\n \t}\n \n \t/* Write this object file out, and record in result_oid */\n-\tif (write_object_file(buf.buf, buf.len, OBJ_TREE, result_oid))\n+\tif (odb_write_object(the_repository->objects, buf.buf,\n+\t\t\t     buf.len, OBJ_TREE, result_oid))\n \t\tret = -1;\n \tstrbuf_release(&buf);\n \treturn ret;\ndiff --git a/notes-cache.c b/notes-cache.c\nindex dd56feed6e8..bf5bb1f6c13 100644\n--- a/notes-cache.c\n+++ b/notes-cache.c\n@@ -98,7 +98,8 @@ int notes_cache_put(struct notes_cache *c, struct object_id *key_oid,\n {\n \tstruct object_id value_oid;\n \n-\tif (write_object_file(data, size, OBJ_BLOB, &value_oid) < 0)\n+\tif (odb_write_object(the_repository->objects, data,\n+\t\t\t     size, OBJ_BLOB, &value_oid) < 0)\n \t\treturn -1;\n \treturn add_note(&c->tree, key_oid, &value_oid, NULL);\n }\ndiff --git a/notes.c b/notes.c\nindex 97b995f3f2d..7596c0df9a1 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -682,7 +682,8 @@ static int tree_write_stack_finish_subtree(struct tree_write_stack *tws)\n \t\tret = tree_write_stack_finish_subtree(n);\n \t\tif (ret)\n \t\t\treturn ret;\n-\t\tret = write_object_file(n->buf.buf, n->buf.len, OBJ_TREE, &s);\n+\t\tret = odb_write_object(the_repository->objects, n->buf.buf,\n+\t\t\t\t       n->buf.len, OBJ_TREE, &s);\n \t\tif (ret)\n \t\t\treturn ret;\n \t\tstrbuf_release(&n->buf);\n@@ -847,7 +848,8 @@ int combine_notes_concatenate(struct object_id *cur_oid,\n \tfree(new_msg);\n \n \t/* create a new blob object from buf */\n-\tret = write_object_file(buf, buf_len, OBJ_BLOB, cur_oid);\n+\tret = odb_write_object(the_repository->objects, buf,\n+\t\t\t       buf_len, OBJ_BLOB, cur_oid);\n \tfree(buf);\n \treturn ret;\n }\n@@ -927,7 +929,8 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,\n \t\t\t\t string_list_join_lines_helper, &buf))\n \t\tgoto out;\n \n-\tret = write_object_file(buf.buf, buf.len, OBJ_BLOB, cur_oid);\n+\tret = odb_write_object(the_repository->objects, buf.buf,\n+\t\t\t       buf.len, OBJ_BLOB, cur_oid);\n \n out:\n \tstrbuf_release(&buf);\n@@ -1215,7 +1218,8 @@ int write_notes_tree(struct notes_tree *t, struct object_id *result)\n \tret = for_each_note(t, flags, write_each_note, &cb_data) ||\n \t      write_each_non_note_until(NULL, &cb_data) ||\n \t      tree_write_stack_finish_subtree(&root) ||\n-\t      write_object_file(root.buf.buf, root.buf.len, OBJ_TREE, result);\n+\t      odb_write_object(the_repository->objects, root.buf.buf,\n+\t\t\t       root.buf.len, OBJ_TREE, result);\n \tstrbuf_release(&root.buf);\n \treturn ret;\n }\ndiff --git a/object-file.c b/object-file.c\nindex a9248760a26..84ece01337e 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -755,7 +755,7 @@ static int start_loose_object_common(struct strbuf *tmp_file,\n \n \tfd = create_tmpfile(tmp_file, filename);\n \tif (fd < 0) {\n-\t\tif (flags & WRITE_OBJECT_FILE_SILENT)\n+\t\tif (flags & WRITE_OBJECT_SILENT)\n \t\t\treturn -1;\n \t\telse if (errno == EACCES)\n \t\t\treturn error(_(\"insufficient permission for adding \"\n@@ -887,7 +887,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\tutb.actime = mtime;\n \t\tutb.modtime = mtime;\n \t\tif (utime(tmp_file.buf, &utb) < 0 &&\n-\t\t    !(flags & WRITE_OBJECT_FILE_SILENT))\n+\t\t    !(flags & WRITE_OBJECT_SILENT))\n \t\t\twarning_errno(_(\"failed utime() on %s\"), tmp_file.buf);\n \t}\n \n@@ -1032,9 +1032,9 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \treturn err;\n }\n \n-int write_object_file_flags(const void *buf, unsigned long len,\n-\t\t\t    enum object_type type, struct object_id *oid,\n-\t\t\t    struct object_id *compat_oid_in, unsigned flags)\n+int write_object_file(const void *buf, unsigned long len,\n+\t\t      enum object_type type, struct object_id *oid,\n+\t\t      struct object_id *compat_oid_in, unsigned flags)\n {\n \tstruct repository *repo = the_repository;\n \tconst struct git_hash_algo *algo = repo->hash_algo;\n@@ -1159,7 +1159,7 @@ static int index_mem(struct index_state *istate,\n \t}\n \n \tif (write_object)\n-\t\tret = write_object_file(buf, size, type, oid);\n+\t\tret = odb_write_object(istate->repo->objects, buf, size, type, oid);\n \telse\n \t\thash_object_file(istate->repo->hash_algo, buf, size, type, oid);\n \n@@ -1184,8 +1184,8 @@ static int index_stream_convert_blob(struct index_state *istate,\n \t\t\t\t get_conv_flags(flags));\n \n \tif (write_object)\n-\t\tret = write_object_file(sbuf.buf, sbuf.len, OBJ_BLOB,\n-\t\t\t\t\toid);\n+\t\tret = odb_write_object(istate->repo->objects, sbuf.buf, sbuf.len, OBJ_BLOB,\n+\t\t\t\t       oid);\n \telse\n \t\thash_object_file(istate->repo->hash_algo, sbuf.buf, sbuf.len, OBJ_BLOB,\n \t\t\t\t oid);\n@@ -1287,7 +1287,7 @@ int index_path(struct index_state *istate, struct object_id *oid,\n \t\tif (!(flags & INDEX_WRITE_OBJECT))\n \t\t\thash_object_file(istate->repo->hash_algo, sb.buf, sb.len,\n \t\t\t\t\t OBJ_BLOB, oid);\n-\t\telse if (write_object_file(sb.buf, sb.len, OBJ_BLOB, oid))\n+\t\telse if (odb_write_object(the_repository->objects, sb.buf, sb.len, OBJ_BLOB, oid))\n \t\t\trc = error(_(\"%s: failed to insert into database\"), path);\n \t\tstrbuf_release(&sb);\n \t\tbreak;\ndiff --git a/object-file.h b/object-file.h\nindex 370139e0762..8ee24b7d8f3 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -157,29 +157,9 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,\n struct object_info;\n int parse_loose_header(const char *hdr, struct object_info *oi);\n \n-enum {\n-\t/*\n-\t * By default, `write_object_file()` does not actually write\n-\t * anything into the object store, but only computes the object ID.\n-\t * This flag changes that so that the object will be written as a loose\n-\t * object and persisted.\n-\t */\n-\tWRITE_OBJECT_FILE_PERSIST = (1 << 0),\n-\n-\t/*\n-\t * Do not print an error in case something gose wrong.\n-\t */\n-\tWRITE_OBJECT_FILE_SILENT = (1 << 1),\n-};\n-\n-int write_object_file_flags(const void *buf, unsigned long len,\n-\t\t\t    enum object_type type, struct object_id *oid,\n-\t\t\t    struct object_id *compat_oid_in, unsigned flags);\n-static inline int write_object_file(const void *buf, unsigned long len,\n-\t\t\t\t    enum object_type type, struct object_id *oid)\n-{\n-\treturn write_object_file_flags(buf, len, type, oid, NULL, 0);\n-}\n+int write_object_file(const void *buf, unsigned long len,\n+\t\t      enum object_type type, struct object_id *oid,\n+\t\t      struct object_id *compat_oid_in, unsigned flags);\n \n struct input_stream {\n \tconst void *(*read)(struct input_stream *, unsigned long *len);\ndiff --git a/odb.c b/odb.c\nindex 1f48a0448e3..519df2fa497 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -980,6 +980,16 @@ void odb_assert_oid_type(struct object_database *odb,\n \t\t    type_name(expect));\n }\n \n+int odb_write_object_ext(struct object_database *odb UNUSED,\n+\t\t\t const void *buf, unsigned long len,\n+\t\t\t enum object_type type,\n+\t\t\t struct object_id *oid,\n+\t\t\t struct object_id *compat_oid,\n+\t\t\t unsigned flags)\n+{\n+\treturn write_object_file(buf, len, type, oid, compat_oid, flags);\n+}\n+\n struct object_database *odb_new(struct repository *repo)\n {\n \tstruct object_database *o = xmalloc(sizeof(*o));\ndiff --git a/odb.h b/odb.h\nindex e922f256802..03422068888 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -437,6 +437,44 @@ enum for_each_object_flags {\n \tFOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4),\n };\n \n+enum {\n+\t/*\n+\t * By default, `odb_write_object()` does not actually write anything\n+\t * into the object store, but only computes the object ID. This flag\n+\t * changes that so that the object will be written as a loose object\n+\t * and persisted.\n+\t */\n+\tWRITE_OBJECT_PERSIST = (1 << 0),\n+\n+\t/*\n+\t * Do not print an error in case something goes wrong.\n+\t */\n+\tWRITE_OBJECT_SILENT = (1 << 1),\n+};\n+\n+/*\n+ * Write an object into the object database. The object is being written into\n+ * the local alternate of the repository. If provided, the converted object ID\n+ * as well as the compatibility object ID are written to the respective\n+ * pointers.\n+ *\n+ * Returns 0 on success, a negative error code otherwise.\n+ */\n+int odb_write_object_ext(struct object_database *odb,\n+\t\t\t const void *buf, unsigned long len,\n+\t\t\t enum object_type type,\n+\t\t\t struct object_id *oid,\n+\t\t\t struct object_id *compat_oid,\n+\t\t\t unsigned flags);\n+\n+static inline int odb_write_object(struct object_database *odb,\n+\t\t\t\t   const void *buf, unsigned long len,\n+\t\t\t\t   enum object_type type,\n+\t\t\t\t   struct object_id *oid)\n+{\n+\treturn odb_write_object_ext(odb, buf, len, type, oid, NULL, 0);\n+}\n+\n /* Compatibility wrappers, to be removed once Git 2.51 has been released. */\n #include \"repository.h\"\n \ndiff --git a/read-cache.c b/read-cache.c\nindex 531d87e7905..be17ca7f586 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -690,7 +690,7 @@ static struct cache_entry *create_alias_ce(struct index_state *istate,\n void set_object_name_for_intent_to_add_entry(struct cache_entry *ce)\n {\n \tstruct object_id oid;\n-\tif (write_object_file(\"\", 0, OBJ_BLOB, &oid))\n+\tif (odb_write_object(the_repository->objects, \"\", 0, OBJ_BLOB, &oid))\n \t\tdie(_(\"cannot create an empty blob in the object database\"));\n \toidcpy(&ce->oid, &oid);\n }\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522162","messageId":"20250717-pks-object-file-wo-the-repository-v2-10-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 10/16] object-file: get rid of `the_repository` when writing objects","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:36Z","receivedAt":"2025-07-17T04:57:09Z","isPatch":true,"body":"The logic that writes loose objects still relies on `the_repository` to\ndecide where exactly the object shall be written to. Refactor it so that\nthe logic instead operates on a `struct odb_source` so that we can get\nrid of this global dependency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/unpack-objects.c |  3 +-\n object-file.c            | 96 +++++++++++++++++++++++++-----------------------\n object-file.h            |  6 ++-\n odb.c                    |  4 +-\n 4 files changed, 58 insertions(+), 51 deletions(-)\n\ndiff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c\nindex 1a4fbef36f8..1d405d156e4 100644\n--- a/builtin/unpack-objects.c\n+++ b/builtin/unpack-objects.c\n@@ -403,7 +403,8 @@ static void stream_blob(unsigned long size, unsigned nr)\n \tdata.zstream = &zstream;\n \tgit_inflate_init(&zstream);\n \n-\tif (stream_loose_object(&in_stream, size, &info->oid))\n+\tif (stream_loose_object(the_repository->objects->sources,\n+\t\t\t\t&in_stream, size, &info->oid))\n \t\tdie(_(\"failed to write object in stream\"));\n \n \tif (data.status != Z_STREAM_END)\ndiff --git a/object-file.c b/object-file.c\nindex 84ece01337e..fc061c37bb5 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -667,9 +667,10 @@ void hash_object_file(const struct git_hash_algo *algo, const void *buf,\n }\n \n /* Finalize a file on disk, and close it. */\n-static void close_loose_object(int fd, const char *filename)\n+static void close_loose_object(struct odb_source *source,\n+\t\t\t       int fd, const char *filename)\n {\n-\tif (the_repository->objects->sources->will_destroy)\n+\tif (source->will_destroy)\n \t\tgoto out;\n \n \tif (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))\n@@ -701,7 +702,8 @@ static inline int directory_size(const char *filename)\n  * We want to avoid cross-directory filename renames, because those\n  * can have problems on various filesystems (FAT, NFS, Coda).\n  */\n-static int create_tmpfile(struct strbuf *tmp, const char *filename)\n+static int create_tmpfile(struct repository *repo,\n+\t\t\t  struct strbuf *tmp, const char *filename)\n {\n \tint fd, dirlen = directory_size(filename);\n \n@@ -720,7 +722,7 @@ static int create_tmpfile(struct strbuf *tmp, const char *filename)\n \t\tstrbuf_add(tmp, filename, dirlen - 1);\n \t\tif (mkdir(tmp->buf, 0777) && errno != EEXIST)\n \t\t\treturn -1;\n-\t\tif (adjust_shared_perm(the_repository, tmp->buf))\n+\t\tif (adjust_shared_perm(repo, tmp->buf))\n \t\t\treturn -1;\n \n \t\t/* Try again */\n@@ -741,26 +743,26 @@ static int create_tmpfile(struct strbuf *tmp, const char *filename)\n  * Returns a \"fd\", which should later be provided to\n  * end_loose_object_common().\n  */\n-static int start_loose_object_common(struct strbuf *tmp_file,\n+static int start_loose_object_common(struct odb_source *source,\n+\t\t\t\t     struct strbuf *tmp_file,\n \t\t\t\t     const char *filename, unsigned flags,\n \t\t\t\t     git_zstream *stream,\n \t\t\t\t     unsigned char *buf, size_t buflen,\n \t\t\t\t     struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n \t\t\t\t     char *hdr, int hdrlen)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *algo = repo->hash_algo;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *algo = source->odb->repo->hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tint fd;\n \n-\tfd = create_tmpfile(tmp_file, filename);\n+\tfd = create_tmpfile(source->odb->repo, tmp_file, filename);\n \tif (fd < 0) {\n \t\tif (flags & WRITE_OBJECT_SILENT)\n \t\t\treturn -1;\n \t\telse if (errno == EACCES)\n \t\t\treturn error(_(\"insufficient permission for adding \"\n \t\t\t\t       \"an object to repository database %s\"),\n-\t\t\t\t     repo_get_object_directory(the_repository));\n+\t\t\t\t     source->path);\n \t\telse\n \t\t\treturn error_errno(\n \t\t\t\t_(\"unable to create temporary file\"));\n@@ -790,14 +792,14 @@ static int start_loose_object_common(struct strbuf *tmp_file,\n  * Common steps for the inner git_deflate() loop for writing loose\n  * objects. Returns what git_deflate() returns.\n  */\n-static int write_loose_object_common(struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n+static int write_loose_object_common(struct odb_source *source,\n+\t\t\t\t     struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n \t\t\t\t     git_zstream *stream, const int flush,\n \t\t\t\t     unsigned char *in0, const int fd,\n \t\t\t\t     unsigned char *compressed,\n \t\t\t\t     const size_t compressed_len)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tint ret;\n \n \tret = git_deflate(stream, flush ? Z_FINISH : 0);\n@@ -818,12 +820,12 @@ static int write_loose_object_common(struct git_hash_ctx *c, struct git_hash_ctx\n  * - End the compression of zlib stream.\n  * - Get the calculated oid to \"oid\".\n  */\n-static int end_loose_object_common(struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n+static int end_loose_object_common(struct odb_source *source,\n+\t\t\t\t   struct git_hash_ctx *c, struct git_hash_ctx *compat_c,\n \t\t\t\t   git_zstream *stream, struct object_id *oid,\n \t\t\t\t   struct object_id *compat_oid)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tint ret;\n \n \tret = git_deflate_end_gently(stream);\n@@ -836,7 +838,8 @@ static int end_loose_object_common(struct git_hash_ctx *c, struct git_hash_ctx *\n \treturn Z_OK;\n }\n \n-static int write_loose_object(const struct object_id *oid, char *hdr,\n+static int write_loose_object(struct odb_source *source,\n+\t\t\t      const struct object_id *oid, char *hdr,\n \t\t\t      int hdrlen, const void *buf, unsigned long len,\n \t\t\t      time_t mtime, unsigned flags)\n {\n@@ -851,9 +854,9 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \tif (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT))\n \t\tprepare_loose_object_bulk_checkin();\n \n-\todb_loose_path(the_repository->objects->sources, &filename, oid);\n+\todb_loose_path(source, &filename, oid);\n \n-\tfd = start_loose_object_common(&tmp_file, filename.buf, flags,\n+\tfd = start_loose_object_common(source, &tmp_file, filename.buf, flags,\n \t\t\t\t       &stream, compressed, sizeof(compressed),\n \t\t\t\t       &c, NULL, hdr, hdrlen);\n \tif (fd < 0)\n@@ -865,14 +868,14 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \tdo {\n \t\tunsigned char *in0 = stream.next_in;\n \n-\t\tret = write_loose_object_common(&c, NULL, &stream, 1, in0, fd,\n+\t\tret = write_loose_object_common(source, &c, NULL, &stream, 1, in0, fd,\n \t\t\t\t\t\tcompressed, sizeof(compressed));\n \t} while (ret == Z_OK);\n \n \tif (ret != Z_STREAM_END)\n \t\tdie(_(\"unable to deflate new object %s (%d)\"), oid_to_hex(oid),\n \t\t    ret);\n-\tret = end_loose_object_common(&c, NULL, &stream, &parano_oid, NULL);\n+\tret = end_loose_object_common(source, &c, NULL, &stream, &parano_oid, NULL);\n \tif (ret != Z_OK)\n \t\tdie(_(\"deflateEnd on object %s failed (%d)\"), oid_to_hex(oid),\n \t\t    ret);\n@@ -880,7 +883,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\tdie(_(\"confused by unstable object source data for %s\"),\n \t\t    oid_to_hex(oid));\n \n-\tclose_loose_object(fd, tmp_file.buf);\n+\tclose_loose_object(source, fd, tmp_file.buf);\n \n \tif (mtime) {\n \t\tstruct utimbuf utb;\n@@ -891,7 +894,7 @@ static int write_loose_object(const struct object_id *oid, char *hdr,\n \t\t\twarning_errno(_(\"failed utime() on %s\"), tmp_file.buf);\n \t}\n \n-\treturn finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n+\treturn finalize_object_file_flags(source->odb->repo, tmp_file.buf, filename.buf,\n \t\t\t\t\t  FOF_SKIP_COLLISION_CHECK);\n }\n \n@@ -921,10 +924,11 @@ static int freshen_packed_object(struct object_database *odb,\n \treturn 1;\n }\n \n-int stream_loose_object(struct input_stream *in_stream, size_t len,\n+int stream_loose_object(struct odb_source *source,\n+\t\t\tstruct input_stream *in_stream, size_t len,\n \t\t\tstruct object_id *oid)\n {\n-\tconst struct git_hash_algo *compat = the_repository->compat_hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tstruct object_id compat_oid;\n \tint fd, ret, err = 0, flush = 0;\n \tunsigned char compressed[4096];\n@@ -940,7 +944,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tprepare_loose_object_bulk_checkin();\n \n \t/* Since oid is not determined, save tmp file to odb path. */\n-\tstrbuf_addf(&filename, \"%s/\", repo_get_object_directory(the_repository));\n+\tstrbuf_addf(&filename, \"%s/\", source->path);\n \thdrlen = format_object_header(hdr, sizeof(hdr), OBJ_BLOB, len);\n \n \t/*\n@@ -951,7 +955,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t *  - Setup zlib stream for compression.\n \t *  - Start to feed header to zlib stream.\n \t */\n-\tfd = start_loose_object_common(&tmp_file, filename.buf, 0,\n+\tfd = start_loose_object_common(source, &tmp_file, filename.buf, 0,\n \t\t\t\t       &stream, compressed, sizeof(compressed),\n \t\t\t\t       &c, &compat_c, hdr, hdrlen);\n \tif (fd < 0) {\n@@ -971,7 +975,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\t\tif (in_stream->is_finished)\n \t\t\t\tflush = 1;\n \t\t}\n-\t\tret = write_loose_object_common(&c, &compat_c, &stream, flush, in0, fd,\n+\t\tret = write_loose_object_common(source, &c, &compat_c, &stream, flush, in0, fd,\n \t\t\t\t\t\tcompressed, sizeof(compressed));\n \t\t/*\n \t\t * Unlike write_loose_object(), we do not have the entire\n@@ -994,18 +998,18 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t */\n \tif (ret != Z_STREAM_END)\n \t\tdie(_(\"unable to stream deflate new object (%d)\"), ret);\n-\tret = end_loose_object_common(&c, &compat_c, &stream, oid, &compat_oid);\n+\tret = end_loose_object_common(source, &c, &compat_c, &stream, oid, &compat_oid);\n \tif (ret != Z_OK)\n \t\tdie(_(\"deflateEnd on stream object failed (%d)\"), ret);\n-\tclose_loose_object(fd, tmp_file.buf);\n+\tclose_loose_object(source, fd, tmp_file.buf);\n \n-\tif (freshen_packed_object(the_repository->objects, oid) ||\n-\t    freshen_loose_object(the_repository->objects, oid)) {\n+\tif (freshen_packed_object(source->odb, oid) ||\n+\t    freshen_loose_object(source->odb, oid)) {\n \t\tunlink_or_warn(tmp_file.buf);\n \t\tgoto cleanup;\n \t}\n \n-\todb_loose_path(the_repository->objects->sources, &filename, oid);\n+\todb_loose_path(source, &filename, oid);\n \n \t/* We finally know the object path, and create the missing dir. */\n \tdirlen = directory_size(filename.buf);\n@@ -1013,7 +1017,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tstruct strbuf dir = STRBUF_INIT;\n \t\tstrbuf_add(&dir, filename.buf, dirlen);\n \n-\t\tif (safe_create_dir_in_gitdir(the_repository, dir.buf) &&\n+\t\tif (safe_create_dir_in_gitdir(source->odb->repo, dir.buf) &&\n \t\t    errno != EEXIST) {\n \t\t\terr = error_errno(_(\"unable to create directory %s\"), dir.buf);\n \t\t\tstrbuf_release(&dir);\n@@ -1022,23 +1026,23 @@ int stream_loose_object(struct input_stream *in_stream, size_t len,\n \t\tstrbuf_release(&dir);\n \t}\n \n-\terr = finalize_object_file_flags(the_repository, tmp_file.buf, filename.buf,\n+\terr = finalize_object_file_flags(source->odb->repo, tmp_file.buf, filename.buf,\n \t\t\t\t\t FOF_SKIP_COLLISION_CHECK);\n \tif (!err && compat)\n-\t\terr = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n+\t\terr = repo_add_loose_object_map(source, oid, &compat_oid);\n cleanup:\n \tstrbuf_release(&tmp_file);\n \tstrbuf_release(&filename);\n \treturn err;\n }\n \n-int write_object_file(const void *buf, unsigned long len,\n+int write_object_file(struct odb_source *source,\n+\t\t      const void *buf, unsigned long len,\n \t\t      enum object_type type, struct object_id *oid,\n \t\t      struct object_id *compat_oid_in, unsigned flags)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *algo = repo->hash_algo;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *algo = source->odb->repo->hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tstruct object_id compat_oid;\n \tchar hdr[MAX_HEADER_LEN];\n \tint hdrlen = sizeof(hdr);\n@@ -1051,7 +1055,7 @@ int write_object_file(const void *buf, unsigned long len,\n \t\t\thash_object_file(compat, buf, len, type, &compat_oid);\n \t\telse {\n \t\t\tstruct strbuf converted = STRBUF_INIT;\n-\t\t\tconvert_object_file(the_repository, &converted, algo, compat,\n+\t\t\tconvert_object_file(source->odb->repo, &converted, algo, compat,\n \t\t\t\t\t    buf, len, type, 0);\n \t\t\thash_object_file(compat, converted.buf, converted.len,\n \t\t\t\t\t type, &compat_oid);\n@@ -1063,13 +1067,13 @@ int write_object_file(const void *buf, unsigned long len,\n \t * it out into .git/objects/??/?{38} file.\n \t */\n \twrite_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);\n-\tif (freshen_packed_object(repo->objects, oid) ||\n-\t    freshen_loose_object(repo->objects, oid))\n+\tif (freshen_packed_object(source->odb, oid) ||\n+\t    freshen_loose_object(source->odb, oid))\n \t\treturn 0;\n-\tif (write_loose_object(oid, hdr, hdrlen, buf, len, 0, flags))\n+\tif (write_loose_object(source, oid, hdr, hdrlen, buf, len, 0, flags))\n \t\treturn -1;\n \tif (compat)\n-\t\treturn repo_add_loose_object_map(repo->objects->sources, oid, &compat_oid);\n+\t\treturn repo_add_loose_object_map(source, oid, &compat_oid);\n \treturn 0;\n }\n \n@@ -1101,7 +1105,7 @@ int force_object_loose(const struct object_id *oid, time_t mtime)\n \t\t\t\t     oid_to_hex(oid), compat->name);\n \t}\n \thdrlen = format_object_header(hdr, sizeof(hdr), type, len);\n-\tret = write_loose_object(oid, hdr, hdrlen, buf, len, mtime, 0);\n+\tret = write_loose_object(repo->objects->sources, oid, hdr, hdrlen, buf, len, mtime, 0);\n \tif (!ret && compat)\n \t\tret = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n \tfree(buf);\ndiff --git a/object-file.h b/object-file.h\nindex 8ee24b7d8f3..622e2b2bb7d 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -157,7 +157,8 @@ enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,\n struct object_info;\n int parse_loose_header(const char *hdr, struct object_info *oi);\n \n-int write_object_file(const void *buf, unsigned long len,\n+int write_object_file(struct odb_source *source,\n+\t\t      const void *buf, unsigned long len,\n \t\t      enum object_type type, struct object_id *oid,\n \t\t      struct object_id *compat_oid_in, unsigned flags);\n \n@@ -167,7 +168,8 @@ struct input_stream {\n \tint is_finished;\n };\n \n-int stream_loose_object(struct input_stream *in_stream, size_t len,\n+int stream_loose_object(struct odb_source *source,\n+\t\t\tstruct input_stream *in_stream, size_t len,\n \t\t\tstruct object_id *oid);\n \n int force_object_loose(const struct object_id *oid, time_t mtime);\ndiff --git a/odb.c b/odb.c\nindex 519df2fa497..2a92a018c42 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -980,14 +980,14 @@ void odb_assert_oid_type(struct object_database *odb,\n \t\t    type_name(expect));\n }\n \n-int odb_write_object_ext(struct object_database *odb UNUSED,\n+int odb_write_object_ext(struct object_database *odb,\n \t\t\t const void *buf, unsigned long len,\n \t\t\t enum object_type type,\n \t\t\t struct object_id *oid,\n \t\t\t struct object_id *compat_oid,\n \t\t\t unsigned flags)\n {\n-\treturn write_object_file(buf, len, type, oid, compat_oid, flags);\n+\treturn write_object_file(odb->sources, buf, len, type, oid, compat_oid, flags);\n }\n \n struct object_database *odb_new(struct repository *repo)\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522163","messageId":"20250717-pks-object-file-wo-the-repository-v2-11-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 11/16] object-file: inline `for_each_loose_file_in_objdir_buf()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:37Z","receivedAt":"2025-07-17T04:57:11Z","isPatch":true,"body":"The function `for_each_loose_file_in_objdir_buf()` is declared in our\nheaders, but it is not used anywhere else than in the corresponding code\nfile itself. Drop the declaration and inline the function into its only\ncaller.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 31 ++++++++-----------------------\n object-file.h |  5 -----\n 2 files changed, 8 insertions(+), 28 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex fc061c37bb5..5a936f17148 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1388,26 +1388,6 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \treturn r;\n }\n \n-int for_each_loose_file_in_objdir_buf(struct strbuf *path,\n-\t\t\t    each_loose_object_fn obj_cb,\n-\t\t\t    each_loose_cruft_fn cruft_cb,\n-\t\t\t    each_loose_subdir_fn subdir_cb,\n-\t\t\t    void *data)\n-{\n-\tint r = 0;\n-\tint i;\n-\n-\tfor (i = 0; i < 256; i++) {\n-\t\tr = for_each_file_in_obj_subdir(i, path, the_repository->hash_algo,\n-\t\t\t\t\t\tobj_cb, cruft_cb,\n-\t\t\t\t\t\tsubdir_cb, data);\n-\t\tif (r)\n-\t\t\tbreak;\n-\t}\n-\n-\treturn r;\n-}\n-\n int for_each_loose_file_in_objdir(const char *path,\n \t\t\t\t  each_loose_object_fn obj_cb,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n@@ -1418,10 +1398,15 @@ int for_each_loose_file_in_objdir(const char *path,\n \tint r;\n \n \tstrbuf_addstr(&buf, path);\n-\tr = for_each_loose_file_in_objdir_buf(&buf, obj_cb, cruft_cb,\n-\t\t\t\t\t      subdir_cb, data);\n-\tstrbuf_release(&buf);\n+\tfor (int i = 0; i < 256; i++) {\n+\t\tr = for_each_file_in_obj_subdir(i, &buf, the_repository->hash_algo,\n+\t\t\t\t\t\tobj_cb, cruft_cb,\n+\t\t\t\t\t\tsubdir_cb, data);\n+\t\tif (r)\n+\t\t\tbreak;\n+\t}\n \n+\tstrbuf_release(&buf);\n \treturn r;\n }\n \ndiff --git a/object-file.h b/object-file.h\nindex 622e2b2bb7d..eca323f9736 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -98,11 +98,6 @@ int for_each_loose_file_in_objdir(const char *path,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n \t\t\t\t  void *data);\n-int for_each_loose_file_in_objdir_buf(struct strbuf *path,\n-\t\t\t\t      each_loose_object_fn obj_cb,\n-\t\t\t\t      each_loose_cruft_fn cruft_cb,\n-\t\t\t\t      each_loose_subdir_fn subdir_cb,\n-\t\t\t\t      void *data);\n \n /*\n  * Iterate over all accessible loose objects without respect to\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522164","messageId":"20250717-pks-object-file-wo-the-repository-v2-12-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 12/16] object-file: remove declaration for `for_each_file_in_obj_subdir()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:38Z","receivedAt":"2025-07-17T04:57:15Z","isPatch":true,"body":"The function `for_each_file_in_obj_subdir()` is declared in our headers,\nbut it is not used anywhere else than in the corresponding code file\nitself. Drop the declaration and mark the function as file-local.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 14 +++++++-------\n object-file.h |  7 -------\n 2 files changed, 7 insertions(+), 14 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex 5a936f17148..bd93f17dcfe 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1318,13 +1318,13 @@ int read_pack_header(int fd, struct pack_header *header)\n \treturn 0;\n }\n \n-int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n-\t\t\t\tstruct strbuf *path,\n-\t\t\t\tconst struct git_hash_algo *algop,\n-\t\t\t\teach_loose_object_fn obj_cb,\n-\t\t\t\teach_loose_cruft_fn cruft_cb,\n-\t\t\t\teach_loose_subdir_fn subdir_cb,\n-\t\t\t\tvoid *data)\n+static int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n+\t\t\t\t       struct strbuf *path,\n+\t\t\t\t       const struct git_hash_algo *algop,\n+\t\t\t\t       each_loose_object_fn obj_cb,\n+\t\t\t\t       each_loose_cruft_fn cruft_cb,\n+\t\t\t\t       each_loose_subdir_fn subdir_cb,\n+\t\t\t\t       void *data)\n {\n \tsize_t origlen, baselen;\n \tDIR *dir;\ndiff --git a/object-file.h b/object-file.h\nindex eca323f9736..d52b335e85b 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -86,13 +86,6 @@ typedef int each_loose_cruft_fn(const char *basename,\n typedef int each_loose_subdir_fn(unsigned int nr,\n \t\t\t\t const char *path,\n \t\t\t\t void *data);\n-int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n-\t\t\t\tstruct strbuf *path,\n-\t\t\t\tconst struct git_hash_algo *algo,\n-\t\t\t\teach_loose_object_fn obj_cb,\n-\t\t\t\teach_loose_cruft_fn cruft_cb,\n-\t\t\t\teach_loose_subdir_fn subdir_cb,\n-\t\t\t\tvoid *data);\n int for_each_loose_file_in_objdir(const char *path,\n \t\t\t\t  each_loose_object_fn obj_cb,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522165","messageId":"20250717-pks-object-file-wo-the-repository-v2-13-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 13/16] object-file: get rid of `the_repository` in loose object iterators","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:39Z","receivedAt":"2025-07-17T04:57:18Z","isPatch":true,"body":"The iterators for loose objects still rely on `the_repository`. Refactor\nthem:\n\n  - `for_each_loose_file_in_objdir()` is refactored so that the caller\n    is now expected to pass an `odb_source` as parameter instead of the\n    path to that source. Furthermore, it is renamed accordingly to\n    `for_each_loose_file_in_source()`.\n\n  - `for_each_loose_object()` is refactored to take in an object\n    database now and calls the above function in a loop.\n\nThis allows us to get rid of the global dependency.\n\nAdjust callers accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c      |  2 +-\n builtin/count-objects.c |  2 +-\n builtin/fsck.c          | 14 ++++++++------\n builtin/gc.c            | 10 ++++------\n builtin/pack-objects.c  |  5 ++---\n builtin/prune.c         |  2 +-\n object-file.c           | 18 +++++++++---------\n object-file.h           |  5 +++--\n prune-packed.c          |  2 +-\n reachable.c             |  2 +-\n 10 files changed, 31 insertions(+), 31 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 2492a0b6f39..aa1498aa60f 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -848,7 +848,7 @@ static void batch_each_object(struct batch_options *opt,\n \t};\n \tstruct bitmap_index *bitmap = prepare_bitmap_git(the_repository);\n \n-\tfor_each_loose_object(batch_one_object_loose, &payload, 0);\n+\tfor_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);\n \n \tif (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,\n \t\t\t\t\t\t batch_one_object_bitmapped, &payload)) {\ndiff --git a/builtin/count-objects.c b/builtin/count-objects.c\nindex f687647931e..e70a01c628e 100644\n--- a/builtin/count-objects.c\n+++ b/builtin/count-objects.c\n@@ -117,7 +117,7 @@ int cmd_count_objects(int argc,\n \t\treport_linked_checkout_garbage(the_repository);\n \t}\n \n-\tfor_each_loose_file_in_objdir(repo_get_object_directory(the_repository),\n+\tfor_each_loose_file_in_source(the_repository->objects->sources,\n \t\t\t\t      count_loose, count_cruft, NULL, NULL);\n \n \tif (verbose) {\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 0084cf7400b..f0854ce5d84 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -393,7 +393,8 @@ static void check_connectivity(void)\n \t\t * and ignore any that weren't present in our earlier\n \t\t * traversal.\n \t\t */\n-\t\tfor_each_loose_object(mark_loose_unreachable_referents, NULL, 0);\n+\t\tfor_each_loose_object(the_repository->objects,\n+\t\t\t\t      mark_loose_unreachable_referents, NULL, 0);\n \t\tfor_each_packed_object(the_repository,\n \t\t\t\t       mark_packed_unreachable_referents,\n \t\t\t\t       NULL,\n@@ -687,7 +688,7 @@ static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *data)\n \treturn 0;\n }\n \n-static void fsck_object_dir(const char *path)\n+static void fsck_source(struct odb_source *source)\n {\n \tstruct progress *progress = NULL;\n \tstruct for_each_loose_cb cb_data = {\n@@ -701,8 +702,8 @@ static void fsck_object_dir(const char *path)\n \t\tprogress = start_progress(the_repository,\n \t\t\t\t\t  _(\"Checking object directories\"), 256);\n \n-\tfor_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir,\n-\t\t\t\t      &cb_data);\n+\tfor_each_loose_file_in_source(source, fsck_loose,\n+\t\t\t\t      fsck_cruft, fsck_subdir, &cb_data);\n \tdisplay_progress(progress, 256);\n \tstop_progress(&progress);\n }\n@@ -994,13 +995,14 @@ int cmd_fsck(int argc,\n \t\tfsck_refs(the_repository);\n \n \tif (connectivity_only) {\n-\t\tfor_each_loose_object(mark_loose_for_connectivity, NULL, 0);\n+\t\tfor_each_loose_object(the_repository->objects,\n+\t\t\t\t      mark_loose_for_connectivity, NULL, 0);\n \t\tfor_each_packed_object(the_repository,\n \t\t\t\t       mark_packed_for_connectivity, NULL, 0);\n \t} else {\n \t\todb_prepare_alternates(the_repository->objects);\n \t\tfor (source = the_repository->objects->sources; source; source = source->next)\n-\t\t\tfsck_object_dir(source->path);\n+\t\t\tfsck_source(source);\n \n \t\tif (check_full) {\n \t\t\tstruct packed_git *p;\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 21bd44e1645..6eefefc63d2 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1301,7 +1301,7 @@ static int loose_object_auto_condition(struct gc_config *cfg UNUSED)\n \tif (loose_object_auto_limit < 0)\n \t\treturn 1;\n \n-\treturn for_each_loose_file_in_objdir(the_repository->objects->sources->path,\n+\treturn for_each_loose_file_in_source(the_repository->objects->sources,\n \t\t\t\t\t     loose_object_count,\n \t\t\t\t\t     NULL, NULL, &count);\n }\n@@ -1336,7 +1336,7 @@ static int pack_loose(struct maintenance_run_opts *opts)\n \t * Do not start pack-objects process\n \t * if there are no loose objects.\n \t */\n-\tif (!for_each_loose_file_in_objdir(r->objects->sources->path,\n+\tif (!for_each_loose_file_in_source(r->objects->sources,\n \t\t\t\t\t   bail_on_loose,\n \t\t\t\t\t   NULL, NULL, NULL))\n \t\treturn 0;\n@@ -1376,11 +1376,9 @@ static int pack_loose(struct maintenance_run_opts *opts)\n \telse if (data.batch_size > 0)\n \t\tdata.batch_size--; /* Decrease for equality on limit. */\n \n-\tfor_each_loose_file_in_objdir(r->objects->sources->path,\n+\tfor_each_loose_file_in_source(r->objects->sources,\n \t\t\t\t      write_loose_object_to_stdin,\n-\t\t\t\t      NULL,\n-\t\t\t\t      NULL,\n-\t\t\t\t      &data);\n+\t\t\t\t      NULL, NULL, &data);\n \n \tfclose(data.in);\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex e8e85d8278b..9e85293730b 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4342,9 +4342,8 @@ static int add_loose_object(const struct object_id *oid, const char *path,\n  */\n static void add_unreachable_loose_objects(void)\n {\n-\tfor_each_loose_file_in_objdir(repo_get_object_directory(the_repository),\n-\t\t\t\t      add_loose_object,\n-\t\t\t\t      NULL, NULL, NULL);\n+\tfor_each_loose_file_in_source(the_repository->objects->sources,\n+\t\t\t\t      add_loose_object, NULL, NULL, NULL);\n }\n \n static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid)\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 339017c7ccf..bf5d3bb152c 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -200,7 +200,7 @@ int cmd_prune(int argc,\n \t\trevs.exclude_promisor_objects = 1;\n \t}\n \n-\tfor_each_loose_file_in_objdir(repo_get_object_directory(the_repository),\n+\tfor_each_loose_file_in_source(the_repository->objects->sources,\n \t\t\t\t      prune_object, prune_cruft, prune_subdir, &revs);\n \n \tprune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);\ndiff --git a/object-file.c b/object-file.c\nindex bd93f17dcfe..b894379d22c 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1388,7 +1388,7 @@ static int for_each_file_in_obj_subdir(unsigned int subdir_nr,\n \treturn r;\n }\n \n-int for_each_loose_file_in_objdir(const char *path,\n+int for_each_loose_file_in_source(struct odb_source *source,\n \t\t\t\t  each_loose_object_fn obj_cb,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n@@ -1397,11 +1397,10 @@ int for_each_loose_file_in_objdir(const char *path,\n \tstruct strbuf buf = STRBUF_INIT;\n \tint r;\n \n-\tstrbuf_addstr(&buf, path);\n+\tstrbuf_addstr(&buf, source->path);\n \tfor (int i = 0; i < 256; i++) {\n-\t\tr = for_each_file_in_obj_subdir(i, &buf, the_repository->hash_algo,\n-\t\t\t\t\t\tobj_cb, cruft_cb,\n-\t\t\t\t\t\tsubdir_cb, data);\n+\t\tr = for_each_file_in_obj_subdir(i, &buf, source->odb->repo->hash_algo,\n+\t\t\t\t\t\tobj_cb, cruft_cb, subdir_cb, data);\n \t\tif (r)\n \t\t\tbreak;\n \t}\n@@ -1410,14 +1409,15 @@ int for_each_loose_file_in_objdir(const char *path,\n \treturn r;\n }\n \n-int for_each_loose_object(each_loose_object_fn cb, void *data,\n+int for_each_loose_object(struct object_database *odb,\n+\t\t\t  each_loose_object_fn cb, void *data,\n \t\t\t  enum for_each_object_flags flags)\n {\n \tstruct odb_source *source;\n \n-\todb_prepare_alternates(the_repository->objects);\n-\tfor (source = the_repository->objects->sources; source; source = source->next) {\n-\t\tint r = for_each_loose_file_in_objdir(source->path, cb, NULL,\n+\todb_prepare_alternates(odb);\n+\tfor (source = odb->sources; source; source = source->next) {\n+\t\tint r = for_each_loose_file_in_source(source, cb, NULL,\n \t\t\t\t\t\t      NULL, data);\n \t\tif (r)\n \t\t\treturn r;\ndiff --git a/object-file.h b/object-file.h\nindex d52b335e85b..1b1ab95423d 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -86,7 +86,7 @@ typedef int each_loose_cruft_fn(const char *basename,\n typedef int each_loose_subdir_fn(unsigned int nr,\n \t\t\t\t const char *path,\n \t\t\t\t void *data);\n-int for_each_loose_file_in_objdir(const char *path,\n+int for_each_loose_file_in_source(struct odb_source *source,\n \t\t\t\t  each_loose_object_fn obj_cb,\n \t\t\t\t  each_loose_cruft_fn cruft_cb,\n \t\t\t\t  each_loose_subdir_fn subdir_cb,\n@@ -99,7 +99,8 @@ int for_each_loose_file_in_objdir(const char *path,\n  *\n  * Any flags specific to packs are ignored.\n  */\n-int for_each_loose_object(each_loose_object_fn, void *,\n+int for_each_loose_object(struct object_database *odb,\n+\t\t\t  each_loose_object_fn, void *,\n \t\t\t  enum for_each_object_flags flags);\n \n \ndiff --git a/prune-packed.c b/prune-packed.c\nindex 92fb4fbb0ed..d49dc11957c 100644\n--- a/prune-packed.c\n+++ b/prune-packed.c\n@@ -40,7 +40,7 @@ void prune_packed_objects(int opts)\n \t\tprogress = start_delayed_progress(the_repository,\n \t\t\t\t\t\t  _(\"Removing duplicate objects\"), 256);\n \n-\tfor_each_loose_file_in_objdir(repo_get_object_directory(the_repository),\n+\tfor_each_loose_file_in_source(the_repository->objects->sources,\n \t\t\t\t      prune_object, NULL, prune_subdir, &opts);\n \n \t/* Ensure we show 100% before finishing progress */\ndiff --git a/reachable.c b/reachable.c\nindex e984b68a0c4..5706ccaede3 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -319,7 +319,7 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs,\n \toidset_init(&data.extra_recent_oids, 0);\n \tdata.extra_recent_oids_loaded = 0;\n \n-\tr = for_each_loose_object(add_recent_loose, &data,\n+\tr = for_each_loose_object(the_repository->objects, add_recent_loose, &data,\n \t\t\t\t  FOR_EACH_OBJECT_LOCAL_ONLY);\n \tif (r)\n \t\tgoto done;\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522166","messageId":"20250717-pks-object-file-wo-the-repository-v2-14-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 14/16] object-file: get rid of `the_repository` in `read_loose_object()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:40Z","receivedAt":"2025-07-17T04:57:22Z","isPatch":true,"body":"The function `read_loose_object()` takes a path to an object file and\ntries to parse it. As such, the function does not depend on any specific\nobject database but instead acts as an ODB-independent way to read a\nspecific file. As such, all it needs as input is a repository so that we\ncan derive repo settings and the hash algorithm.\n\nThat repository isn't passed in as a parameter though, as we implicitly\ndepend on the global `the_repository`. Refactor the function so that we\npass in the repository as a parameter.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/fsck.c | 2 +-\n object-file.c  | 9 +++++----\n object-file.h  | 3 ++-\n 3 files changed, 8 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex f0854ce5d84..e9112d884f0 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -633,7 +633,7 @@ static int fsck_loose(const struct object_id *oid, const char *path,\n \toi.sizep = &size;\n \toi.typep = &type;\n \n-\tif (read_loose_object(path, oid, &real_oid, &contents, &oi) < 0) {\n+\tif (read_loose_object(the_repository, path, oid, &real_oid, &contents, &oi) < 0) {\n \t\tif (contents && !oideq(&real_oid, oid))\n \t\t\terr = error(_(\"%s: hash-path mismatch, found at: %s\"),\n \t\t\t\t    oid_to_hex(&real_oid), path);\ndiff --git a/object-file.c b/object-file.c\nindex b894379d22c..f7c07acadc9 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1535,7 +1535,8 @@ static int check_stream_oid(git_zstream *stream,\n \treturn 0;\n }\n \n-int read_loose_object(const char *path,\n+int read_loose_object(struct repository *repo,\n+\t\t      const char *path,\n \t\t      const struct object_id *expected_oid,\n \t\t      struct object_id *real_oid,\n \t\t      void **contents,\n@@ -1574,9 +1575,9 @@ int read_loose_object(const char *path,\n \t}\n \n \tif (*oi->typep == OBJ_BLOB &&\n-\t    *size > repo_settings_get_big_file_threshold(the_repository)) {\n+\t    *size > repo_settings_get_big_file_threshold(repo)) {\n \t\tif (check_stream_oid(&stream, hdr, *size, path, expected_oid,\n-\t\t\t\t     the_repository->hash_algo) < 0)\n+\t\t\t\t     repo->hash_algo) < 0)\n \t\t\tgoto out_inflate;\n \t} else {\n \t\t*contents = unpack_loose_rest(&stream, hdr, *size, expected_oid);\n@@ -1584,7 +1585,7 @@ int read_loose_object(const char *path,\n \t\t\terror(_(\"unable to unpack contents of %s\"), path);\n \t\t\tgoto out_inflate;\n \t\t}\n-\t\thash_object_file(the_repository->hash_algo,\n+\t\thash_object_file(repo->hash_algo,\n \t\t\t\t *contents, *size,\n \t\t\t\t *oi->typep, real_oid);\n \t\tif (!oideq(expected_oid, real_oid))\ndiff --git a/object-file.h b/object-file.h\nindex 1b1ab95423d..52f7979267d 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -210,7 +210,8 @@ int check_and_freshen_file(const char *fn, int freshen);\n  *\n  * Returns 0 on success, negative on error (details may be written to stderr).\n  */\n-int read_loose_object(const char *path,\n+int read_loose_object(struct repository *repo,\n+\t\t      const char *path,\n \t\t      const struct object_id *expected_oid,\n \t\t      struct object_id *real_oid,\n \t\t      void **contents,\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522167","messageId":"20250717-pks-object-file-wo-the-repository-v2-15-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 15/16] object-file: get rid of `the_repository` in `force_object_loose()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:41Z","receivedAt":"2025-07-17T04:57:24Z","isPatch":true,"body":"The function `force_object_loose()` forces an object to become a loose\nobject in case it only exists in its packed form. To do so it implicitly\nrelies on `the_repository`.\n\nRefactor the function by passing a `struct odb_source` as parameter.\nWhile the check whether any such loose object exists already acts on the\nwhole object database, writing the loose object happens in one specific\nsource.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/pack-objects.c |  3 ++-\n object-file.c          | 18 +++++++++---------\n object-file.h          |  3 ++-\n 3 files changed, 13 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 9e85293730b..7ff79d6b376 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4411,7 +4411,8 @@ static void loosen_unused_packed_objects(void)\n \t\t\tif (!packlist_find(&to_pack, &oid) &&\n \t\t\t    !has_sha1_pack_kept_or_nonlocal(&oid) &&\n \t\t\t    !loosened_object_can_be_discarded(&oid, p->mtime)) {\n-\t\t\t\tif (force_object_loose(&oid, p->mtime))\n+\t\t\t\tif (force_object_loose(the_repository->objects->sources,\n+\t\t\t\t\t\t       &oid, p->mtime))\n \t\t\t\t\tdie(_(\"unable to force loose object\"));\n \t\t\t\tloosened_objects_nr++;\n \t\t\t}\ndiff --git a/object-file.c b/object-file.c\nindex f7c07acadc9..e9152d9e04c 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1077,10 +1077,10 @@ int write_object_file(struct odb_source *source,\n \treturn 0;\n }\n \n-int force_object_loose(const struct object_id *oid, time_t mtime)\n+int force_object_loose(struct odb_source *source,\n+\t\t       const struct object_id *oid, time_t mtime)\n {\n-\tstruct repository *repo = the_repository;\n-\tconst struct git_hash_algo *compat = repo->compat_hash_algo;\n+\tconst struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;\n \tvoid *buf;\n \tunsigned long len;\n \tstruct object_info oi = OBJECT_INFO_INIT;\n@@ -1090,24 +1090,24 @@ int force_object_loose(const struct object_id *oid, time_t mtime)\n \tint hdrlen;\n \tint ret;\n \n-\tfor (struct odb_source *source = repo->objects->sources; source; source = source->next)\n-\t\tif (has_loose_object(source, oid))\n+\tfor (struct odb_source *s = source->odb->sources; s; s = s->next)\n+\t\tif (has_loose_object(s, oid))\n \t\t\treturn 0;\n \n \toi.typep = &type;\n \toi.sizep = &len;\n \toi.contentp = &buf;\n-\tif (odb_read_object_info_extended(the_repository->objects, oid, &oi, 0))\n+\tif (odb_read_object_info_extended(source->odb, oid, &oi, 0))\n \t\treturn error(_(\"cannot read object for %s\"), oid_to_hex(oid));\n \tif (compat) {\n-\t\tif (repo_oid_to_algop(repo, oid, compat, &compat_oid))\n+\t\tif (repo_oid_to_algop(source->odb->repo, oid, compat, &compat_oid))\n \t\t\treturn error(_(\"cannot map object %s to %s\"),\n \t\t\t\t     oid_to_hex(oid), compat->name);\n \t}\n \thdrlen = format_object_header(hdr, sizeof(hdr), type, len);\n-\tret = write_loose_object(repo->objects->sources, oid, hdr, hdrlen, buf, len, mtime, 0);\n+\tret = write_loose_object(source, oid, hdr, hdrlen, buf, len, mtime, 0);\n \tif (!ret && compat)\n-\t\tret = repo_add_loose_object_map(the_repository->objects->sources, oid, &compat_oid);\n+\t\tret = repo_add_loose_object_map(source, oid, &compat_oid);\n \tfree(buf);\n \n \treturn ret;\ndiff --git a/object-file.h b/object-file.h\nindex 52f7979267d..15d97630d3b 100644\n--- a/object-file.h\n+++ b/object-file.h\n@@ -161,7 +161,8 @@ int stream_loose_object(struct odb_source *source,\n \t\t\tstruct input_stream *in_stream, size_t len,\n \t\t\tstruct object_id *oid);\n \n-int force_object_loose(const struct object_id *oid, time_t mtime);\n+int force_object_loose(struct odb_source *source,\n+\t\t       const struct object_id *oid, time_t mtime);\n \n /**\n  * With in-core object data in \"buf\", rehash it to make sure the\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522168","messageId":"20250717-pks-object-file-wo-the-repository-v2-16-36d2cd6c700e@pks.im","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-0-36d2cd6c700e@pks.im","subject":"[PATCH v2 16/16] object-file: get rid of `the_repository` in index-related functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-07-17T04:56:42Z","receivedAt":"2025-07-17T04:57:28Z","isPatch":true,"body":"Both `index_fd()` and `index_path()` still use `the_repository` even\nthough they have a repository available via `struct index_state`. Adapt\nthem so that they use the index' repository instead to get rid of this\nglobal dependency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object-file.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/object-file.c b/object-file.c\nindex e9152d9e04c..2bc36ab3ee8 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -1257,7 +1257,7 @@ int index_fd(struct index_state *istate, struct object_id *oid,\n \t\tret = index_stream_convert_blob(istate, oid, fd, path, flags);\n \telse if (!S_ISREG(st->st_mode))\n \t\tret = index_pipe(istate, oid, fd, type, path, flags);\n-\telse if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(the_repository)) ||\n+\telse if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) ||\n \t\t type != OBJ_BLOB ||\n \t\t (path && would_convert_to_git(istate, path)))\n \t\tret = index_core(istate, oid, fd, xsize_t(st->st_size),\n@@ -1291,12 +1291,12 @@ int index_path(struct index_state *istate, struct object_id *oid,\n \t\tif (!(flags & INDEX_WRITE_OBJECT))\n \t\t\thash_object_file(istate->repo->hash_algo, sb.buf, sb.len,\n \t\t\t\t\t OBJ_BLOB, oid);\n-\t\telse if (odb_write_object(the_repository->objects, sb.buf, sb.len, OBJ_BLOB, oid))\n+\t\telse if (odb_write_object(istate->repo->objects, sb.buf, sb.len, OBJ_BLOB, oid))\n \t\t\trc = error(_(\"%s: failed to insert into database\"), path);\n \t\tstrbuf_release(&sb);\n \t\tbreak;\n \tcase S_IFDIR:\n-\t\treturn repo_resolve_gitlink_ref(the_repository, path, \"HEAD\", oid);\n+\t\treturn repo_resolve_gitlink_ref(istate->repo, path, \"HEAD\", oid);\n \tdefault:\n \t\treturn error(_(\"%s: unsupported file type\"), path);\n \t}\n\n-- \n2.50.1.465.gcb3da1c9e6.dirty\n\n"},{"id":"522175","messageId":"CAE7as+Z7b-cpn8=kjP=bQHkiRnLd8XYe9b8_50KYcg4ea7sASQ@mail.gmail.com","threadId":"63771","inReplyTo":"f6479d6a-32a4-4a49-a75c-589978cb9a57@gmail.com","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Ayush Chandekar","fromEmail":"ayu.chandekar@gmail.com","sentAt":"2025-07-17T08:00:38Z","receivedAt":"2025-07-17T08:00:53Z","isPatch":true,"body":"On Tue, Jul 15, 2025 at 9:21 PM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Patrick\n>\n> On 15/07/2025 12:27, Patrick Steinhardt wrote:\n> > On Fri, Jul 11, 2025 at 11:55:27AM -0700, Junio C Hamano wrote:\n> >> Phillip Wood <phillip.wood123@gmail.com> writes:\n> >>\n> >>> I do not think adding prepare_repo_settings() calls all over the place\n> >>> is a good way forward as it makes it very easy to introduce\n> >>> regressions like this. Our builtin commands parse the config at\n> >>> startup for good reasons if we're going to move settings out of\n> >>> git_default_core_config() we should ensure that they are still parsed\n> >>> at startup.\n> >>\n> >> I think that is a good guideline that applies not just to this\n> >> series but to other topics that attempt to move globals to a member\n> >> in struct repository (or repository_settings)\n> >\n> > So... the only real solution that I can think about right now is to\n> > start parsing the repository configuration whenever we instantiate any\n> > repository. E.g., something like the below patch. This has the effect\n> > that the repo settings would always be populated when we have a\n> > repository at hand. Consequently, we wouldn't need to clutter those\n> > `prepare_repo_settings()` calls everywhere anymore.\n> >\n> > But there is a big question: what do we do with invalid configuration\n> > then? Do we want to die immediately when we see such command? The answer\n> > is probably going to be a solid \"sometimes\":\n> >\n> >    - Some commands must function even with an invalid configuration. At\n> >      the very least git-config(1) needs to handle this alright, as\n> >      otherwise it might be impossible to unset/change invalid\n> >      configuration. There may be other such examples.\n>\n> That's a good point.\n>\n> >    - Not all configuration is equal. It may be perfectly fine to ignore\n> >      some configuration, but other configuration may very much be mission\n> >      critical. And whether or not configuration is important isn't really\n> >      something we can decide, as it will depend on the specific use case.\n> >\n> > So I'm afraid that there just isn't a perfect solution here. Does it\n> > make sense to die due to a config key that isn't even used by a specific\n> > command? Maybe. And if not, which config keys _should_ make us die in\n> > case they are invalid?\n> >\n> > The overall situation right now is a proper mess: we have config parsing\n> > cluttered everywhere, and the behaviour is just plain inconsistent. Some\n> > parsing is delayed, some isn't.\n>\n> Indeed. My objection here was that we were delaying the parsing when it\n> wasn't delayed before. Is it feasible to call prepare_repo_settings() in\n> repo_config()? That would at least avoid the problem that moving config\n> settings into `struct repo_settings` changes when the settings are\n> parsed unless the command calls prepare_repo_settings() at start up. As\n> far as I remember `git config` uses config_with_options() so that would\n> not be adversely affected by such a change.()\n>\n\nThis is exactly what came to my mind too while reading Patrick's message.\n\nAs the global variables which were shifted to `struct repo_settings`\nwere once parsed by repo_config(), we would have no problem calling\nprepare_repo_settings() inside it as the behaviour would be the same\nas before, and it checks if the repository is null too.\n\n> > Some is per-repo, some is last-one-wins.\n> > Some config keys will cause us to die in case they are misconfigured,\n> > some will just be ignored.\n> >\n> > So where do we want to end up?\n> >\n> > My dream would be that all configuration were to be defined in one\n> > central place. The configuration should be typed, there should be\n> > verification for each value configured by the user.\n>\n> Being able to verify config settings when they're set would be a great\n> improvement but we're a long way from being able to do that.\n>\n> > All configuration\n> > gets parsed into a structure, and it can be parsed either via a\n> > repository (in which case we take into account its local config), or\n> > only via the global- and system-wide configuration. The whole config\n> > needs to be parsed at startup so that issues like the reported one don't\n> > happen where a subprocess that uses more config keys than the parent\n> > process dies because one of the extra keys is misconfigured.\n> >\n> > But I very much feel like this is a pipe dream right now. We already are\n> > working on multiple fronts to modernize the code base, and I don't quite\n> > feel like opening up _another_ large transformation right now.\n>\n> I agree with this\n>\n> > So I don't quite know what to do while we're not there yet. Without this\n> > large refactoring, all approaches feel like they aren't a perfect fit to\n> > address the bigger issue.\n>\n> I agree addressing all the shortcomings you've outlined would require a\n> lot of refactoring. If we can find a way to avoid introducing anymore\n> shortcomings as we migrate away from global variables that would be a\n> good start.\n>\n> Thanks\n>\n> Phillip\n>\n"},{"id":"522207","messageId":"0026a11f-373f-40e8-aa29-9ada050904a4@gmail.com","threadId":"63771","inReplyTo":"aHehaghOW16vPee7@pks.im","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-17T15:19:31Z","receivedAt":"2025-07-17T15:19:36Z","isPatch":true,"body":"Hi Patrick\n\nOn 16/07/2025 13:56, Patrick Steinhardt wrote:\n> On Tue, Jul 15, 2025 at 06:12:18PM +0200, Patrick Steinhardt wrote:\n>>\n>> Hm, yeah, I think adding it to `repo_config()` might be a viable\n>> approach. I'll give it a try tomorrow and see what breaks :)\n> \n> The answer is \"quite a lot\". I'm now 15 patches deep to try and fix\n> this and am nowhere close to a working state yet. The single biggest\n> issue is `core.shared_repository`, which is used in a ton of places and\n> which causes all kinds of pain.\n\nThat's a shame\n> I think I'll stop working on this for now, and would rather like to drop\n> the last three patches from this series so that we can move forward with\n> it.\n\nThat sounds sensible\n\nThanks\n\nPhillip\n\n"},{"id":"522210","messageId":"xmqqecue7qw9.fsf@gitster.g","threadId":"63771","inReplyTo":"0026a11f-373f-40e8-aa29-9ada050904a4@gmail.com","subject":"Re: [PATCH 17/19] environment: move compression level into repo settings","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-17T15:56:06Z","receivedAt":"2025-07-17T15:56:09Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Patrick\n>\n> On 16/07/2025 13:56, Patrick Steinhardt wrote:\n>> On Tue, Jul 15, 2025 at 06:12:18PM +0200, Patrick Steinhardt wrote:\n>>>\n>>> Hm, yeah, I think adding it to `repo_config()` might be a viable\n>>> approach. I'll give it a try tomorrow and see what breaks :)\n>> The answer is \"quite a lot\". I'm now 15 patches deep to try and fix\n>> this and am nowhere close to a working state yet. The single biggest\n>> issue is `core.shared_repository`, which is used in a ton of places and\n>> which causes all kinds of pain.\n>\n> That's a shame\n>> I think I'll stop working on this for now, and would rather like to drop\n>> the last three patches from this series so that we can move forward with\n>> it.\n>\n> That sounds sensible\n>\n> Thanks\n>\n> Phillip\n\nYeah, thanks for taking a look.  I think shrinking the size of the\nseries is sensible.  It is easier to manage larger number of smaller\npatch series than a single large series.\n\nThanks, both.\n"},{"id":"540944","messageId":"20260405065602.GA1481314@coredump.intra.peff.net","threadId":"63771","inReplyTo":"20250717-pks-object-file-wo-the-repository-v2-1-36d2cd6c700e@pks.im","subject":"Re: [PATCH v2 01/16] object-file: fix -Wsign-compare warnings","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-04-05T06:56:02Z","receivedAt":"2026-04-05T06:56:04Z","isPatch":true,"body":"On Thu, Jul 17, 2025 at 06:56:27AM +0200, Patrick Steinhardt wrote:\n\n> @@ -1268,7 +1265,7 @@ int index_fd(struct index_state *istate, struct object_id *oid,\n>  \t\tret = index_stream_convert_blob(istate, oid, fd, path, flags);\n>  \telse if (!S_ISREG(st->st_mode))\n>  \t\tret = index_pipe(istate, oid, fd, type, path, flags);\n> -\telse if (st->st_size <= repo_settings_get_big_file_threshold(the_repository) ||\n> +\telse if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(the_repository)) ||\n>  \t\t type != OBJ_BLOB ||\n>  \t\t (path && would_convert_to_git(istate, path)))\n>  \t\tret = index_core(istate, oid, fd, xsize_t(st->st_size),\n\nThis is an old thread, but I happened across this code while looking at\nthe function for something more recent. This cast seems quite\nsuspicious. What happens when st->st_size is larger than a size_t (e.g.,\na 4.1GB file on a 32-bit system)? We'll truncate and get a wrong answer.\n\nI think this ought to be comparing in the off_t space instead. Which I\n_think_ was happening before the cast due to C's integer promotion rules\n(even though off_t is signed, because it's larger than size_t on a\n32-bit system and can hold all of size_t's values, then size_t is\npromoted).\n\nIt's sort of academic, since both sides of the conditional end up\ncalling xsize_t(), which will then bail. But it _could_ handle off_t\ncorrectly in the \"else\" clause here, which tries to stream the content.\n\nMore interesting to me (and why I responded to this old patch series) is\nthat inserting a cast to silence -Wsign-compare actually broke the\ncomparison in this case.\n\n-Peff\n"}]}