{"thread":{"id":"64222","subject":"[PATCH 00/49] repack: prepare for incremental MIDX-based repacking","startedAt":"2025-09-28T22:07:13Z","lastAt":"2025-10-17T08:40:19Z","messageCount":163,"participants":["Taylor Blau","Junio C Hamano","Patrick Steinhardt","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":49},"messages":[{"id":"527525","messageId":"cover.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":null,"subject":"[PATCH 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:10Z","receivedAt":"2025-09-28T22:07:13Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Note(s) to the maintainer:\n\n * This series depends on ps/packfile-store at dd52a29b78 (packfile:\n   refactor `get_packed_git_mru()` to work on packfile store,\n   2025-09-23).\n\n * This series is split out of my local copy of\n   tb/incremental-midx-part-3. I suggest queueing it as\n   tb/incremental-midx-part-3.1 ;-).\n\nWhen developing an incremental MIDX/bitmap-based repacking strategy, I\nthought that it might be useful to clean up builtin/repack.c before\nadding any amount of new functionality.\n\nAt more than 1,700+ lines of code, the 'repack' builtin has grown quite\nunwieldy over the years. It has quite a bit of repack-related\nfunctionality, all implemented within the builtin itself. It knows how\nto: manage cruft packs, perform geometric repacking, write new MIDXs,\nrepack promisor objects, keep track of existing packs, and much more.\n\nThis series moves the vast majority of the builtin's functionality into\nseveral new compilation units, all grouped under a new repack.h API:\n\n * repack.c\n * repack-cruft.c\n * repack-filtered.c\n * repack-geometry.c\n * repack-midx.c\n * repack-promisor.c\n\nMoving this code around was less than straightforward, hence the length\nof this series. Likewise, since the repacking code can be rather\nfragile, I chose to go slower here than I otherwise would have (e.g.,\nalmost all code movement changes are strict moves with no rewrites in\nbetween).\n\nThe structure of the series is roughly as follows:\n\n - The first 10 patches reduce the usage of the_repository, and\n   the_hash_algo. They don't quite get us to being able to compile\n   without USE_THE_REPOSITORY_VARIABLE for reasons described in the last\n   commit of this section.\n\n - The next four commits introduces the new repack.o compilation unit,\n   and moves a fairly isolated component of the repack builtin.\n\n - The next five commits move the existing_packs API from out of the\n   builtin into the repack.h API.\n\n - The next five move the generated_pack API.\n\n - The next two move the repack_promisor_objects() API.\n\n - The next four move the pack_geometry API.\n\n - The next nine move various MIDX-related functionality out of the\n   builtin.\n\n - The next nine move remaining functions that write packs out of the\n   repack builtin.\n\n - The final commit cleans up the list of `#include`s within the\n   builtin.\n\nMy hope is that this produces a series that is easier to review, despite\nits rather long length. Each individual commit tries to do as little as\npossible, and most will benefit from being reviewed with --color-moved.\nThe result, in my opinion, is a 'builtin/repack.c' is much cleaner than\nit was when we started.\n\nNothing in this series is a strict requirement for implementing a\nrepacking strategy based on incremental MIDX/bitmaps. But since the new\nfunctionality required for that is lengthy in and of itself, having a\nclean slate to start from seemed worthwhile to me.\n\nThanks in advance for your review!\n\nTaylor Blau (49):\n  builtin/repack.c: avoid \"the_repository\" in `cmd_repack()`\n  builtin/repack.c: avoid \"the_repository\" in existing packs API\n  builtin/repack.c: avoid \"the_repository\" when taking a ref snapshot\n  builtin/repack.c: avoid \"the_repository\" when removing packs\n  builtin/repack.c: avoid \"the_repository\" when repacking promisor\n    objects\n  builtin/repack.c: avoid \"the_hash_algo\" when deleting packs\n  builtin/repack.c: avoid \"the_hash_algo\" in `write_oid()`\n  builtin/repack: avoid \"the_hash_algo\" in `repack_promisor_objects()`\n  builtin/repack.c: avoid \"the_hash_algo\" in `finish_pack_objects_cmd()`\n  builtin/repack.c: avoid using `hash_to_hex()` in pack geometry\n  repack: introduce new compilation unit\n  builtin/repack.c: pass both pack_objects args to repack_config\n  repack: move 'delta_base_offset' to 'struct pack_objects_args'\n  repack: remove 'prepare_pack_objects' from the builtin\n  builtin/repack.c: rename many 'struct existing_packs' functions\n  repack: remove 'remove_redundant_pack' from the builtin\n  builtin/repack.c: pass \"packdir\" when removing packs\n  builtin/repack.c: avoid unnecessary numeric casts in existing_packs\n  repack: remove 'existing_packs' API from the builtin\n  builtin/repack.c: rename \"struct generated_pack_data\"\n  builtin/repack.c: factor our \"generated_pack_install\"\n  builtin/repack.c: pass \"packtmp\" to `generated_pack_populate()`\n  builtin/repack.c: provide pack locations to `generated_pack_install()`\n  repack: remove 'generated_pack' API from the builtin\n  builtin/repack.c: pass \"packtmp\" to `repack_promisor_objects()`\n  builtin/repack.c: remove \"repack_promisor_objects()\" from the builtin\n  builtin/repack.c: rename various pack_geometry functions\n  builtin/repack.c: pass 'pack_kept_objects' to `pack_geometry_init()`\n  builtin/repack.c: pass 'packdir' to `pack_geometry_remove_redundant()`\n  repack: remove pack_geometry API from the builtin\n  builtin/repack.c: remove ref snapshotting from builtin\n  builtin/repack.c: extract opts struct for\n    'write_midx_included_packs()'\n  builtin/repack.c: use a string_list for 'midx_pack_names'\n  repack: keep track of MIDX pack names using existing_packs\n  builtin/repack.c: reorder `remove_redundant_bitmaps()`\n  builtin/repack.c: inline `remove_redundant_bitmaps()`\n  builtin/repack.c: pass `repack_write_midx_opts` to\n    `midx_included_packs`\n  builtin/repack.c: inline packs within `write_midx_included_packs()`\n  repack: 'write_midx_included_packs' API from the builtin\n  builtin/repack.c: introduce `struct write_pack_opts`\n  builtin/repack.c: use `write_pack_opts` within `write_cruft_pack()`\n  repack: move `find_pack_prefix()` out of the builtin\n  repack: extract `write_pack_opts_is_local()`\n  builtin/repack.c: pass `write_pack_opts` to\n    `finish_pack_objects_cmd()`\n  repack: move `finish_pack_objects_cmd()` out of the builtin\n  repack: move `pack_kept_objects` to `struct pack_objects_args`\n  repack: move `write_filtered_pack()` out of the builtin\n  repack: move `write_cruft_pack()` out of the builtin\n  builtin/repack.c: clean up unused `#include`s\n\n Makefile          |    6 +\n builtin/repack.c  | 1348 +++------------------------------------------\n meson.build       |    6 +\n repack-cruft.c    |   99 ++++\n repack-filtered.c |   51 ++\n repack-geometry.c |  232 ++++++++\n repack-midx.c     |  372 +++++++++++++\n repack-promisor.c |  102 ++++\n repack.c          |  361 ++++++++++++\n repack.h          |  146 +++++\n 10 files changed, 1461 insertions(+), 1262 deletions(-)\n create mode 100644 repack-cruft.c\n create mode 100644 repack-filtered.c\n create mode 100644 repack-geometry.c\n create mode 100644 repack-midx.c\n create mode 100644 repack-promisor.c\n create mode 100644 repack.c\n create mode 100644 repack.h\n\n\nbase-commit: dd52a29b78d80e425be660f3b443a42e0374a7d1\n-- \n2.51.0.243.g16eca91f2c0\n"},{"id":"527526","messageId":"aa03aea8ed3d18a48774857dda9630ce553bc66d.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 01/49] builtin/repack.c: avoid \"the_repository\" in `cmd_repack()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:14Z","receivedAt":"2025-09-28T22:07:17Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Reduce builtin/repack.c's reliance on `the_repository` by using the\ncurrently-UNUSED \"repo\" parameter within cmd_repack().\n\nThe following commits will continue to reduce the usage of\nthe_repository in other places within builtin/repack.c.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 31 ++++++++++++++++---------------\n 1 file changed, 16 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex e8730808c5..305782b2c9 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -1247,7 +1247,7 @@ static const char *find_pack_prefix(const char *packdir, const char *packtmp)\n int cmd_repack(int argc,\n \t       const char **argv,\n \t       const char *prefix,\n-\t       struct repository *repo UNUSED)\n+\t       struct repository *repo)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n@@ -1344,7 +1344,7 @@ int cmd_repack(int argc,\n \n \tlist_objects_filter_init(&po_args.filter_options);\n \n-\trepo_config(the_repository, repack_config, &cruft_po_args);\n+\trepo_config(repo, repack_config, &cruft_po_args);\n \n \targc = parse_options(argc, argv, prefix, builtin_repack_options,\n \t\t\t\tgit_repack_usage, 0);\n@@ -1354,7 +1354,7 @@ int cmd_repack(int argc,\n \tpo_args.depth = xstrdup_or_null(opt_depth);\n \tpo_args.threads = xstrdup_or_null(opt_threads);\n \n-\tif (delete_redundant && the_repository->repository_format_precious_objects)\n+\tif (delete_redundant && repo->repository_format_precious_objects)\n \t\tdie(_(\"cannot delete packs in a precious-objects repo\"));\n \n \tdie_for_incompatible_opt3(unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE), \"-A\",\n@@ -1376,7 +1376,7 @@ int cmd_repack(int argc,\n \t\tdie(_(incremental_bitmap_conflict_error));\n \n \tif (write_bitmaps && po_args.local &&\n-\t    odb_has_alternates(the_repository->objects)) {\n+\t    odb_has_alternates(repo->objects)) {\n \t\t/*\n \t\t * When asked to do a local repack, but we have\n \t\t * packfiles that are inherited from an alternate, then\n@@ -1391,7 +1391,8 @@ int cmd_repack(int argc,\n \tif (write_midx && write_bitmaps) {\n \t\tstruct strbuf path = STRBUF_INIT;\n \n-\t\tstrbuf_addf(&path, \"%s/%s_XXXXXX\", repo_get_object_directory(the_repository),\n+\t\tstrbuf_addf(&path, \"%s/%s_XXXXXX\",\n+\t\t\t    repo_get_object_directory(repo),\n \t\t\t    \"bitmap-ref-tips\");\n \n \t\trefs_snapshot = xmks_tempfile(path.buf);\n@@ -1400,7 +1401,7 @@ int cmd_repack(int argc,\n \t\tstrbuf_release(&path);\n \t}\n \n-\tpackdir = mkpathdup(\"%s/pack\", repo_get_object_directory(the_repository));\n+\tpackdir = mkpathdup(\"%s/pack\", repo_get_object_directory(repo));\n \tpacktmp_name = xstrfmt(\".tmp-%d-pack\", (int)getpid());\n \tpacktmp = mkpathdup(\"%s/%s\", packdir, packtmp_name);\n \n@@ -1439,7 +1440,7 @@ int cmd_repack(int argc,\n \t\tstrvec_push(&cmd.args, \"--reflog\");\n \t\tstrvec_push(&cmd.args, \"--indexed-objects\");\n \t}\n-\tif (repo_has_promisor_remote(the_repository))\n+\tif (repo_has_promisor_remote(repo))\n \t\tstrvec_push(&cmd.args, \"--exclude-promisor-objects\");\n \tif (!write_midx) {\n \t\tif (write_bitmaps > 0)\n@@ -1535,7 +1536,7 @@ int cmd_repack(int argc,\n \t\t * midx_has_unknown_packs() will make the decision for\n \t\t * us.\n \t\t */\n-\t\tif (!get_multi_pack_index(the_repository->objects->sources))\n+\t\tif (!get_multi_pack_index(repo->objects->sources))\n \t\t\tmidx_must_contain_cruft = 1;\n \t}\n \n@@ -1618,9 +1619,9 @@ int cmd_repack(int argc,\n \n \tstring_list_sort(&names);\n \n-\tif (get_multi_pack_index(the_repository->objects->sources)) {\n+\tif (get_multi_pack_index(repo->objects->sources)) {\n \t\tstruct multi_pack_index *m =\n-\t\t\tget_multi_pack_index(the_repository->objects->sources);\n+\t\t\tget_multi_pack_index(repo->objects->sources);\n \n \t\tALLOC_ARRAY(midx_pack_names,\n \t\t\t    m->num_packs + m->num_packs_in_base);\n@@ -1631,7 +1632,7 @@ int cmd_repack(int argc,\n \t\t\t\t\txstrdup(m->pack_names[i]);\n \t}\n \n-\tclose_object_store(the_repository->objects);\n+\tclose_object_store(repo->objects);\n \n \t/*\n \t * Ok we have prepared all new packfiles.\n@@ -1688,7 +1689,7 @@ int cmd_repack(int argc,\n \t\t\tgoto cleanup;\n \t}\n \n-\todb_reprepare(the_repository->objects);\n+\todb_reprepare(repo->objects);\n \n \tif (delete_redundant) {\n \t\tint opts = 0;\n@@ -1704,18 +1705,18 @@ int cmd_repack(int argc,\n \t\tif (!keep_unreachable &&\n \t\t    (!(pack_everything & LOOSEN_UNREACHABLE) ||\n \t\t     unpack_unreachable) &&\n-\t\t    is_repository_shallow(the_repository))\n+\t\t    is_repository_shallow(repo))\n \t\t\tprune_shallow(PRUNE_QUICK);\n \t}\n \n \tif (run_update_server_info)\n-\t\tupdate_server_info(the_repository, 0);\n+\t\tupdate_server_info(repo, 0);\n \n \tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) {\n \t\tunsigned flags = 0;\n \t\tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0))\n \t\t\tflags |= MIDX_WRITE_INCREMENTAL;\n-\t\twrite_midx_file(the_repository->objects->sources,\n+\t\twrite_midx_file(repo->objects->sources,\n \t\t\t\tNULL, NULL, flags);\n \t}\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527527","messageId":"664a67c93620edf6727f0617f4dddce99901fcde.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 02/49] builtin/repack.c: avoid \"the_repository\" in existing packs API","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:18Z","receivedAt":"2025-09-28T22:07:21Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"There are a number of spots within builtin/repack.c which refer to\n\"the_repository\", and either make use of the \"existing packs\" API\nor otherwise have a 'struct existing_packs *' in scope.\n\nAdd a \"repo\" member to \"struct existing_packs\" and use that instead of\n\"the_repository\" in such locations.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 305782b2c9..7223553bed 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -126,6 +126,7 @@ static void pack_objects_args_release(struct pack_objects_args *args)\n }\n \n struct existing_packs {\n+\tstruct repository *repo;\n \tstruct string_list kept_packs;\n \tstruct string_list non_kept_packs;\n \tstruct string_list cruft_packs;\n@@ -265,7 +266,7 @@ static void existing_packs_release(struct existing_packs *existing)\n static void collect_pack_filenames(struct existing_packs *existing,\n \t\t\t\t   const struct string_list *extra_keep)\n {\n-\tstruct packfile_store *packs = the_repository->objects->packfiles;\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -498,7 +499,7 @@ static void init_pack_geometry(struct pack_geometry *geometry,\n \t\t\t       struct existing_packs *existing,\n \t\t\t       const struct pack_objects_args *args)\n {\n-\tstruct packfile_store *packs = the_repository->objects->packfiles;\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -1139,7 +1140,7 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n \t\t\t\t      struct existing_packs *existing)\n {\n-\tstruct packfile_store *packs = the_repository->objects->packfiles;\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n \tstruct strbuf buf = STRBUF_INIT;\n \tsize_t i;\n@@ -1405,6 +1406,7 @@ int cmd_repack(int argc,\n \tpacktmp_name = xstrfmt(\".tmp-%d-pack\", (int)getpid());\n \tpacktmp = mkpathdup(\"%s/%s\", packdir, packtmp_name);\n \n+\texisting.repo = repo;\n \tcollect_pack_filenames(&existing, &keep_pack_list);\n \n \tif (geometry.split_factor) {\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527528","messageId":"866e43b0cd74a7bbc13870c66ffd58ca8387aa9e.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 03/49] builtin/repack.c: avoid \"the_repository\" when taking a ref snapshot","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:22Z","receivedAt":"2025-09-28T22:07:25Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Avoid using \"the_repository\" in various MIDX-related ref snapshotting\nfunctions.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 16 +++++++++-------\n 1 file changed, 9 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 7223553bed..113f5fc67f 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -771,6 +771,7 @@ static int midx_has_unknown_packs(char **midx_pack_names,\n }\n \n struct midx_snapshot_ref_data {\n+\tstruct repository *repo;\n \tstruct tempfile *f;\n \tstruct oidset seen;\n \tint preferred;\n@@ -784,13 +785,13 @@ static int midx_snapshot_ref_one(const char *refname UNUSED,\n \tstruct midx_snapshot_ref_data *data = _data;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n \t\toid = &peeled;\n \n \tif (oidset_insert(&data->seen, oid))\n \t\treturn 0; /* already seen */\n \n-\tif (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n@@ -799,11 +800,12 @@ static int midx_snapshot_ref_one(const char *refname UNUSED,\n \treturn 0;\n }\n \n-static void midx_snapshot_refs(struct tempfile *f)\n+static void midx_snapshot_refs(struct repository *repo, struct tempfile *f)\n {\n \tstruct midx_snapshot_ref_data data;\n-\tconst struct string_list *preferred = bitmap_preferred_tips(the_repository);\n+\tconst struct string_list *preferred = bitmap_preferred_tips(repo);\n \n+\tdata.repo = repo;\n \tdata.f = f;\n \tdata.preferred = 0;\n \toidset_init(&data.seen, 0);\n@@ -817,13 +819,13 @@ static void midx_snapshot_refs(struct tempfile *f)\n \n \t\tdata.preferred = 1;\n \t\tfor_each_string_list_item(item, preferred)\n-\t\t\trefs_for_each_ref_in(get_main_ref_store(the_repository),\n+\t\t\trefs_for_each_ref_in(get_main_ref_store(repo),\n \t\t\t\t\t     item->string,\n \t\t\t\t\t     midx_snapshot_ref_one, &data);\n \t\tdata.preferred = 0;\n \t}\n \n-\trefs_for_each_ref(get_main_ref_store(the_repository),\n+\trefs_for_each_ref(get_main_ref_store(repo),\n \t\t\t  midx_snapshot_ref_one, &data);\n \n \tif (close_tempfile_gently(f)) {\n@@ -1397,7 +1399,7 @@ int cmd_repack(int argc,\n \t\t\t    \"bitmap-ref-tips\");\n \n \t\trefs_snapshot = xmks_tempfile(path.buf);\n-\t\tmidx_snapshot_refs(refs_snapshot);\n+\t\tmidx_snapshot_refs(repo, refs_snapshot);\n \n \t\tstrbuf_release(&path);\n \t}\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527529","messageId":"f404dfa34f06a882e5ebf0b6ffa01ba365ac866b.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 04/49] builtin/repack.c: avoid \"the_repository\" when removing packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:26Z","receivedAt":"2025-09-28T22:07:29Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The 'remove_redundant_pack()' function uses \"the_repository\" to obtain,\nand optionally remove, the repository's MIDX. Instead of relying on\n\"the_repository\", pass around a \"struct repository *\" parameter through\nits callers, and use that instead.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 18 ++++++++++--------\n 1 file changed, 10 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 113f5fc67f..93802531e1 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -221,33 +221,35 @@ static void mark_packs_for_deletion(struct existing_packs *existing,\n \tmark_packs_for_deletion_1(names, &existing->cruft_packs);\n }\n \n-static void remove_redundant_pack(const char *dir_name, const char *base_name)\n+static void remove_redundant_pack(struct repository *repo,\n+\t\t\t\t  const char *dir_name, const char *base_name)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct odb_source *source = the_repository->objects->sources;\n+\tstruct odb_source *source = repo->objects->sources;\n \tstruct multi_pack_index *m = get_multi_pack_index(source);\n \tstrbuf_addf(&buf, \"%s.pack\", base_name);\n \tif (m && source->local && midx_contains_pack(m, buf.buf))\n-\t\tclear_midx_file(the_repository);\n+\t\tclear_midx_file(repo);\n \tstrbuf_insertf(&buf, 0, \"%s/\", dir_name);\n \tunlink_pack_path(buf.buf, 1);\n \tstrbuf_release(&buf);\n }\n \n-static void remove_redundant_packs_1(struct string_list *packs)\n+static void remove_redundant_packs_1(struct repository *repo,\n+\t\t\t\t     struct string_list *packs)\n {\n \tstruct string_list_item *item;\n \tfor_each_string_list_item(item, packs) {\n \t\tif (!pack_is_marked_for_deletion(item))\n \t\t\tcontinue;\n-\t\tremove_redundant_pack(packdir, item->string);\n+\t\tremove_redundant_pack(repo, packdir, item->string);\n \t}\n }\n \n static void remove_redundant_existing_packs(struct existing_packs *existing)\n {\n-\tremove_redundant_packs_1(&existing->non_kept_packs);\n-\tremove_redundant_packs_1(&existing->cruft_packs);\n+\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs);\n+\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs);\n }\n \n static void existing_packs_release(struct existing_packs *existing)\n@@ -685,7 +687,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n \t\t    (string_list_has_string(&existing->kept_packs, buf.buf)))\n \t\t\tcontinue;\n \n-\t\tremove_redundant_pack(packdir, buf.buf);\n+\t\tremove_redundant_pack(existing->repo, packdir, buf.buf);\n \t}\n \n \tstrbuf_release(&buf);\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527530","messageId":"6190eeb066332d3ba19bdb141c01924e4c61ddfc.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 05/49] builtin/repack.c: avoid \"the_repository\" when repacking promisor objects","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:30Z","receivedAt":"2025-09-28T22:07:33Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Pass a \"struct repository\" pointer to the 'repack_promisor_objects()'\nfunction to avoid using \"the_repository\".\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 93802531e1..4f08b57ddb 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -407,7 +407,8 @@ static int has_pack_ext(const struct generated_pack_data *data,\n \tBUG(\"unknown pack extension: '%s'\", ext);\n }\n \n-static void repack_promisor_objects(const struct pack_objects_args *args,\n+static void repack_promisor_objects(struct repository *repo,\n+\t\t\t\t    const struct pack_objects_args *args,\n \t\t\t\t    struct string_list *names)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -424,7 +425,7 @@ static void repack_promisor_objects(const struct pack_objects_args *args,\n \t * {type -> existing pack order} ordering when computing deltas instead\n \t * of a {type -> size} ordering, which may produce better deltas.\n \t */\n-\tfor_each_packed_object(the_repository, write_oid, &cmd,\n+\tfor_each_packed_object(repo, write_oid, &cmd,\n \t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n@@ -1458,7 +1459,7 @@ int cmd_repack(int argc,\n \t\tstrvec_push(&cmd.args, \"--delta-islands\");\n \n \tif (pack_everything & ALL_INTO_ONE) {\n-\t\trepack_promisor_objects(&po_args, &names);\n+\t\trepack_promisor_objects(repo, &po_args, &names);\n \n \t\tif (has_existing_non_kept_packs(&existing) &&\n \t\t    delete_redundant &&\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527531","messageId":"2f97882ace437e57e826a681db0a505052eb45a9.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 06/49] builtin/repack.c: avoid \"the_hash_algo\" when deleting packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:33Z","receivedAt":"2025-09-28T22:07:36Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The \"mark_packs_for_deletion_1\" function uses \"the_hash_algo->hexsz\" to\nisolate a pack's checksum before deleting it to avoid deleting a newly\nwritten pack having the same checksum (that is, some generated pack\nwound up identical to an existing pack).\n\nAvoid this by passing down a \"struct git_hash_algo\" pointer, and refer to\nthe hash algorithm through it instead.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 10 ++++++----\n 1 file changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 4f08b57ddb..094f5a0cc2 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -168,11 +168,12 @@ static int pack_is_retained(struct string_list_item *item)\n \treturn (uintptr_t)item->util & RETAIN_PACK;\n }\n \n-static void mark_packs_for_deletion_1(struct string_list *names,\n+static void mark_packs_for_deletion_1(const struct git_hash_algo *algop,\n+\t\t\t\t      struct string_list *names,\n \t\t\t\t      struct string_list *list)\n {\n \tstruct string_list_item *item;\n-\tconst int hexsz = the_hash_algo->hexsz;\n+\tconst int hexsz = algop->hexsz;\n \n \tfor_each_string_list_item(item, list) {\n \t\tchar *sha1;\n@@ -217,8 +218,9 @@ static void mark_packs_for_deletion(struct existing_packs *existing,\n \t\t\t\t    struct string_list *names)\n \n {\n-\tmark_packs_for_deletion_1(names, &existing->non_kept_packs);\n-\tmark_packs_for_deletion_1(names, &existing->cruft_packs);\n+\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n+\tmark_packs_for_deletion_1(algop, names, &existing->non_kept_packs);\n+\tmark_packs_for_deletion_1(algop, names, &existing->cruft_packs);\n }\n \n static void remove_redundant_pack(struct repository *repo,\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527532","messageId":"37a7dea848f9618f7243fd9ff8ed704adfb3cf4e.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 07/49] builtin/repack.c: avoid \"the_hash_algo\" in `write_oid()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:37Z","receivedAt":"2025-09-28T22:07:40Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as the previous commit, avoid referring directly to\n\"the_hash_algo\" within builtin/repack.c::write_oid().\n\nUnlike the previous commit, we are within a callback function, so must\nintroduce a new struct to pass additional data through its \"data\"\npointer.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 15 ++++++++++++---\n 1 file changed, 12 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 094f5a0cc2..7d62959dc2 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -339,6 +339,11 @@ static void prepare_pack_objects(struct child_process *cmd,\n \tcmd->out = -1;\n }\n \n+struct write_oid_context {\n+\tstruct child_process *cmd;\n+\tconst struct git_hash_algo *algop;\n+};\n+\n /*\n  * Write oid to the given struct child_process's stdin, starting it first if\n  * necessary.\n@@ -347,14 +352,15 @@ static int write_oid(const struct object_id *oid,\n \t\t     struct packed_git *pack UNUSED,\n \t\t     uint32_t pos UNUSED, void *data)\n {\n-\tstruct child_process *cmd = data;\n+\tstruct write_oid_context *ctx = data;\n+\tstruct child_process *cmd = ctx->cmd;\n \n \tif (cmd->in == -1) {\n \t\tif (start_command(cmd))\n \t\t\tdie(_(\"could not start pack-objects to repack promisor objects\"));\n \t}\n \n-\tif (write_in_full(cmd->in, oid_to_hex(oid), the_hash_algo->hexsz) < 0 ||\n+\tif (write_in_full(cmd->in, oid_to_hex(oid), ctx->algop->hexsz) < 0 ||\n \t    write_in_full(cmd->in, \"\\n\", 1) < 0)\n \t\tdie(_(\"failed to feed promisor objects to pack-objects\"));\n \treturn 0;\n@@ -413,6 +419,7 @@ static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t    const struct pack_objects_args *args,\n \t\t\t\t    struct string_list *names)\n {\n+\tstruct write_oid_context ctx;\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tFILE *out;\n \tstruct strbuf line = STRBUF_INIT;\n@@ -427,7 +434,9 @@ static void repack_promisor_objects(struct repository *repo,\n \t * {type -> existing pack order} ordering when computing deltas instead\n \t * of a {type -> size} ordering, which may produce better deltas.\n \t */\n-\tfor_each_packed_object(repo, write_oid, &cmd,\n+\tctx.cmd = &cmd;\n+\tctx.algop = repo->hash_algo;\n+\tfor_each_packed_object(repo, write_oid, &ctx,\n \t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527533","messageId":"f5a87f2a65796caae2f981b6fec32bdfd87e4e3d.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 08/49] builtin/repack: avoid \"the_hash_algo\" in `repack_promisor_objects()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:41Z","receivedAt":"2025-09-28T22:07:44Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as the previous commits, avoid referring directly to\n\"the_hash_algo\" within builtin/repack.c::repack_promisor_objects().\n\nSince there is already a repository pointer in scope, use its hash_algo\nvalue instead.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 7d62959dc2..a7e94ed03c 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -452,7 +452,7 @@ static void repack_promisor_objects(struct repository *repo,\n \t\tstruct string_list_item *item;\n \t\tchar *promisor_name;\n \n-\t\tif (line.len != the_hash_algo->hexsz)\n+\t\tif (line.len != repo->hash_algo->hexsz)\n \t\t\tdie(_(\"repack: Expecting full hex object ID lines only from pack-objects.\"));\n \t\titem = string_list_append(names, line.buf);\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527534","messageId":"f16dfbf0c7fdb2ff9bbfa8f3cdbc849916a722bd.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 09/49] builtin/repack.c: avoid \"the_hash_algo\" in `finish_pack_objects_cmd()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:45Z","receivedAt":"2025-09-28T22:07:48Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as previous commits, avoid referring directly to\n\"the_hash_algo\" in builtin/repack.c::finish_pack_objects_cmd() and\ninstead accept one as a parameter to the function.\n\nSince this function has a number of callers throughout the builtin, the\ndiff is a little noisier than previous commits. However, each hunk is\nlimited to passing the hash_algo parameter from a repository pointer\nthat is already in scope.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 13 ++++++++-----\n 1 file changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a7e94ed03c..a043704aa8 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -1073,7 +1073,8 @@ static void remove_redundant_bitmaps(struct string_list *include,\n \tstrbuf_release(&path);\n }\n \n-static int finish_pack_objects_cmd(struct child_process *cmd,\n+static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n+\t\t\t\t   struct child_process *cmd,\n \t\t\t\t   struct string_list *names,\n \t\t\t\t   int local)\n {\n@@ -1084,7 +1085,7 @@ static int finish_pack_objects_cmd(struct child_process *cmd,\n \twhile (strbuf_getline_lf(&line, out) != EOF) {\n \t\tstruct string_list_item *item;\n \n-\t\tif (line.len != the_hash_algo->hexsz)\n+\t\tif (line.len != algop->hexsz)\n \t\t\tdie(_(\"repack: Expecting full hex object ID lines only \"\n \t\t\t      \"from pack-objects.\"));\n \t\t/*\n@@ -1150,7 +1151,8 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n \tfclose(in);\n \n-\treturn finish_pack_objects_cmd(&cmd, names, local);\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n+\t\t\t\t       local);\n }\n \n static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n@@ -1247,7 +1249,8 @@ static int write_cruft_pack(const struct pack_objects_args *args,\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfclose(in);\n \n-\treturn finish_pack_objects_cmd(&cmd, names, local);\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n+\t\t\t\t       local);\n }\n \n static const char *find_pack_prefix(const char *packdir, const char *packtmp)\n@@ -1534,7 +1537,7 @@ int cmd_repack(int argc,\n \t\tfclose(in);\n \t}\n \n-\tret = finish_pack_objects_cmd(&cmd, &names, 1);\n+\tret = finish_pack_objects_cmd(repo->hash_algo, &cmd, &names, 1);\n \tif (ret)\n \t\tgoto cleanup;\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527535","messageId":"de4886466cab65dc72c5f800d680a610dcb0563c.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 10/49] builtin/repack.c: avoid using `hash_to_hex()` in pack geometry","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:49Z","receivedAt":"2025-09-28T22:07:52Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In previous commits, we started passing either repository or\ngit_hash_algo pointers around to various spots within builtin/repack.c\nto reduce our dependency on the_repository in the hope of undef'ing\nUSE_THE_REPOSITORY_VARIABLE.\n\nThis commit takes us as far as we can (easily) go in that direction by\nremoving the only use of a convenience function that only exists when\nUSE_THE_REPOSITORY_VARIABLE is defined.\n\nUnfortunately, the only other such function is \"is_bare_repository()\",\nwhich is less than straightforward to convert into, say,\n\"repo_is_bare()\", the latter of the two accepting a repository pointer.\n\nPunt on that for now, and declare this commit as the stopping point for\nour efforts in the direction of undef'ing USE_THE_REPOSITORY_VARIABLE.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a043704aa8..0d35f15b4b 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -683,12 +683,14 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n \t\t\t\t\t    struct string_list *names,\n \t\t\t\t\t    struct existing_packs *existing)\n {\n+\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n \tstruct strbuf buf = STRBUF_INIT;\n \tuint32_t i;\n \n \tfor (i = 0; i < geometry->split; i++) {\n \t\tstruct packed_git *p = geometry->pack[i];\n-\t\tif (string_list_has_string(names, hash_to_hex(p->hash)))\n+\t\tif (string_list_has_string(names, hash_to_hex_algop(p->hash,\n+\t\t\t\t\t\t\t\t    algop)))\n \t\t\tcontinue;\n \n \t\tstrbuf_reset(&buf);\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527536","messageId":"fe3328eb3d8202f8fa8cbef11cca215ace8b2285.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 11/49] repack: introduce new compilation unit","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:53Z","receivedAt":"2025-09-28T22:07:56Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Over the years, builtin/repack.c has turned into a grab-bag of\nfunctionality powering the 'git repack' builtin. Among its many\ncapabilities, it:\n\n - can build and spawn 'git pack-objects' commands, which in turn\n   generate new packs\n - has infrastructure to manage the set of existing packs in a\n   repository\n - has infrastructure to split a sequence of packs into a geometric\n   progression based on object size\n - can manage both generating and combining cruft packs together\n - can write new MIDXs\n\nto name a few.\n\nAs a result, this builtin has accumulated a lot of code, making adding\nnew functionality difficult. In the future, 'repack' will learn how to\nmanage a chain of incremental MIDXs, adding yet more functionality into\nthe builtin.\n\nAs a prerequisite step, let's first move some of the functionality in\nthe builtin into its own repack.[ch].\n\nThis will be done over the course of many steps, since there are many\nindividual components, some of which will end up in other, yet-to-exist\ncompilation units of their own. Some of the code movement here is also\nnon-trivial, so performing it in individual steps will make it easier to\nverify.\n\nLet's start by migrating 'struct pack_objects_args' (and the related\ncorresponding pack_objects_args_release() function) into repack.h, and\nteach both the Makefile and Meson how to build the new compilation unit.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile         |  1 +\n builtin/repack.c | 25 +------------------------\n meson.build      |  1 +\n repack.c         | 11 +++++++++++\n repack.h         | 23 +++++++++++++++++++++++\n 5 files changed, 37 insertions(+), 24 deletions(-)\n create mode 100644 repack.c\n create mode 100644 repack.h\n\ndiff --git a/Makefile b/Makefile\nindex 555b7f4dc3..b220f0f7d8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1136,6 +1136,7 @@ LIB_OBJS += refs/packed-backend.o\n LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n+LIB_OBJS += repack.o\n LIB_OBJS += replace-object.o\n LIB_OBJS += repo-settings.o\n LIB_OBJS += repository.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 0d35f15b4b..6dfcb3327e 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -19,6 +19,7 @@\n #include \"prune-packed.h\"\n #include \"odb.h\"\n #include \"promisor-remote.h\"\n+#include \"repack.h\"\n #include \"shallow.h\"\n #include \"pack.h\"\n #include \"pack-bitmap.h\"\n@@ -53,21 +54,6 @@ static const char incremental_bitmap_conflict_error[] = N_(\n \"--no-write-bitmap-index or disable the pack.writeBitmaps configuration.\"\n );\n \n-struct pack_objects_args {\n-\tchar *window;\n-\tchar *window_memory;\n-\tchar *depth;\n-\tchar *threads;\n-\tunsigned long max_pack_size;\n-\tint no_reuse_delta;\n-\tint no_reuse_object;\n-\tint quiet;\n-\tint local;\n-\tint name_hash_version;\n-\tint path_walk;\n-\tstruct list_objects_filter_options filter_options;\n-};\n-\n static int repack_config(const char *var, const char *value,\n \t\t\t const struct config_context *ctx, void *cb)\n {\n@@ -116,15 +102,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-static void pack_objects_args_release(struct pack_objects_args *args)\n-{\n-\tfree(args->window);\n-\tfree(args->window_memory);\n-\tfree(args->depth);\n-\tfree(args->threads);\n-\tlist_objects_filter_release(&args->filter_options);\n-}\n-\n struct existing_packs {\n \tstruct repository *repo;\n \tstruct string_list kept_packs;\ndiff --git a/meson.build b/meson.build\nindex e8ec0eca16..8ab287a2ed 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -462,6 +462,7 @@ libgit_sources = [\n   'reftable/tree.c',\n   'reftable/writer.c',\n   'remote.c',\n+  'repack.c',\n   'replace-object.c',\n   'repo-settings.c',\n   'repository.c',\ndiff --git a/repack.c b/repack.c\nnew file mode 100644\nindex 0000000000..a1f5b796fb\n--- /dev/null\n+++ b/repack.c\n@@ -0,0 +1,11 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+\n+void pack_objects_args_release(struct pack_objects_args *args)\n+{\n+\tfree(args->window);\n+\tfree(args->window_memory);\n+\tfree(args->depth);\n+\tfree(args->threads);\n+\tlist_objects_filter_release(&args->filter_options);\n+}\ndiff --git a/repack.h b/repack.h\nnew file mode 100644\nindex 0000000000..421d439d5a\n--- /dev/null\n+++ b/repack.h\n@@ -0,0 +1,23 @@\n+#ifndef REPACK_H\n+#define REPACK_H\n+\n+#include \"list-objects-filter-options.h\"\n+\n+struct pack_objects_args {\n+\tchar *window;\n+\tchar *window_memory;\n+\tchar *depth;\n+\tchar *threads;\n+\tunsigned long max_pack_size;\n+\tint no_reuse_delta;\n+\tint no_reuse_object;\n+\tint quiet;\n+\tint local;\n+\tint name_hash_version;\n+\tint path_walk;\n+\tstruct list_objects_filter_options filter_options;\n+};\n+\n+void pack_objects_args_release(struct pack_objects_args *args);\n+\n+#endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527537","messageId":"75386eed60c80a52c42720e4918853ff05c5584c.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 12/49] builtin/repack.c: pass both pack_objects args to repack_config","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:07:56Z","receivedAt":"2025-09-28T22:07:59Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"A subsequent commit will remove 'delta_base_offset' as a static variable\nwithin builtin/repack.c, and reintroduce it as a member of the 'struct\npack_objects_args'.\n\nAs a result, the repack_config callback will need to have both the\ncruft- and non-cruft 'struct pack_objects_args's in scope. Introduce a\nnew 'struct repack_config_ctx' to allow the callee to provide both\npointers to the callback.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 15 +++++++++++++--\n 1 file changed, 13 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 6dfcb3327e..af6de8d77a 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -54,10 +54,16 @@ static const char incremental_bitmap_conflict_error[] = N_(\n \"--no-write-bitmap-index or disable the pack.writeBitmaps configuration.\"\n );\n \n+struct repack_config_ctx {\n+\tstruct pack_objects_args *po_args;\n+\tstruct pack_objects_args *cruft_po_args;\n+};\n+\n static int repack_config(const char *var, const char *value,\n \t\t\t const struct config_context *ctx, void *cb)\n {\n-\tstruct pack_objects_args *cruft_po_args = cb;\n+\tstruct repack_config_ctx *repack_ctx = cb;\n+\tstruct pack_objects_args *cruft_po_args = repack_ctx->cruft_po_args;\n \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n \t\tdelta_base_offset = git_config_bool(var, value);\n \t\treturn 0;\n@@ -1260,6 +1266,7 @@ int cmd_repack(int argc,\n \tsize_t midx_pack_names_nr = 0;\n \n \t/* variables to be filled by option parsing */\n+\tstruct repack_config_ctx config_ctx;\n \tint delete_redundant = 0;\n \tconst char *unpack_unreachable = NULL;\n \tint keep_unreachable = 0;\n@@ -1343,7 +1350,11 @@ int cmd_repack(int argc,\n \n \tlist_objects_filter_init(&po_args.filter_options);\n \n-\trepo_config(repo, repack_config, &cruft_po_args);\n+\tmemset(&config_ctx, 0, sizeof(config_ctx));\n+\tconfig_ctx.po_args = &po_args;\n+\tconfig_ctx.cruft_po_args = &cruft_po_args;\n+\n+\trepo_config(repo, repack_config, &config_ctx);\n \n \targc = parse_options(argc, argv, prefix, builtin_repack_options,\n \t\t\t\tgit_repack_usage, 0);\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527538","messageId":"06347b6ab23d5ab1d9a7945da67473b5b9e02ad1.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 13/49] repack: move 'delta_base_offset' to 'struct pack_objects_args'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:00Z","receivedAt":"2025-09-28T22:08:03Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The static variable 'delta_base_offset' determines whether or not we\npass the \"--delta-base-offset\" command-line argument when spawning\npack-objects as a child process. Its introduction dates back to when\nrepack was rewritten in C, all the way back in a1bbc6c017 (repack:\nrewrite the shell script in C, 2013-09-15).\n\n'struct pack_objects_args' was introduced much later on in 4571324b99\n(builtin/repack.c: allow configuring cruft pack generation, 2022-05-20),\nbut did not move the 'delta_base_offset' variable.\n\nSince the 'delta_base_offset' is a property of an individual\npack-objects command, re-introduce that variable as a member of 'struct\npack_objects_args', which will enable further code movement in the\nsubsequent commits.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 11 ++++++-----\n repack.h         |  3 +++\n 2 files changed, 9 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex af6de8d77a..f4af830353 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -34,7 +34,6 @@\n #define RETAIN_PACK 2\n \n static int pack_everything;\n-static int delta_base_offset = 1;\n static int pack_kept_objects = -1;\n static int write_bitmaps = -1;\n static int use_delta_islands;\n@@ -63,9 +62,10 @@ static int repack_config(const char *var, const char *value,\n \t\t\t const struct config_context *ctx, void *cb)\n {\n \tstruct repack_config_ctx *repack_ctx = cb;\n+\tstruct pack_objects_args *po_args = repack_ctx->po_args;\n \tstruct pack_objects_args *cruft_po_args = repack_ctx->cruft_po_args;\n \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n-\t\tdelta_base_offset = git_config_bool(var, value);\n+\t\tpo_args->delta_base_offset = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"repack.packkeptobjects\")) {\n@@ -315,7 +315,7 @@ static void prepare_pack_objects(struct child_process *cmd,\n \t\tstrvec_push(&cmd->args,  \"--local\");\n \tif (args->quiet)\n \t\tstrvec_push(&cmd->args,  \"--quiet\");\n-\tif (delta_base_offset)\n+\tif (args->delta_base_offset)\n \t\tstrvec_push(&cmd->args,  \"--delta-base-offset\");\n \tstrvec_push(&cmd->args, out);\n \tcmd->git_cmd = 1;\n@@ -1271,8 +1271,8 @@ int cmd_repack(int argc,\n \tconst char *unpack_unreachable = NULL;\n \tint keep_unreachable = 0;\n \tstruct string_list keep_pack_list = STRING_LIST_INIT_NODUP;\n-\tstruct pack_objects_args po_args = { 0 };\n-\tstruct pack_objects_args cruft_po_args = { 0 };\n+\tstruct pack_objects_args po_args = PACK_OBJECTS_ARGS_INIT;\n+\tstruct pack_objects_args cruft_po_args = PACK_OBJECTS_ARGS_INIT;\n \tint write_midx = 0;\n \tconst char *cruft_expiration = NULL;\n \tconst char *expire_to = NULL;\n@@ -1567,6 +1567,7 @@ int cmd_repack(int argc,\n \n \t\tcruft_po_args.local = po_args.local;\n \t\tcruft_po_args.quiet = po_args.quiet;\n+\t\tcruft_po_args.delta_base_offset = po_args.delta_base_offset;\n \n \t\tret = write_cruft_pack(&cruft_po_args, packtmp, pack_prefix,\n \t\t\t\t       cruft_expiration,\ndiff --git a/repack.h b/repack.h\nindex 421d439d5a..12632d7fec 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -15,9 +15,12 @@ struct pack_objects_args {\n \tint local;\n \tint name_hash_version;\n \tint path_walk;\n+\tint delta_base_offset;\n \tstruct list_objects_filter_options filter_options;\n };\n \n+#define PACK_OBJECTS_ARGS_INIT { .delta_base_offset = 1 }\n+\n void pack_objects_args_release(struct pack_objects_args *args);\n \n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527539","messageId":"2d9022385d69bf288867f417a1222a8b157d0bd8.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 14/49] repack: remove 'prepare_pack_objects' from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:04Z","receivedAt":"2025-09-28T22:08:07Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that the 'prepare_pack_objects' function no longer refers to\nexternal, static variables, move it out to repack.h as generic\nfunctionality.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 34 ----------------------------------\n repack.c         | 35 +++++++++++++++++++++++++++++++++++\n repack.h         |  5 +++++\n 3 files changed, 40 insertions(+), 34 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex f4af830353..ff93654cfe 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -288,40 +288,6 @@ static void collect_pack_filenames(struct existing_packs *existing,\n \tstrbuf_release(&buf);\n }\n \n-static void prepare_pack_objects(struct child_process *cmd,\n-\t\t\t\t const struct pack_objects_args *args,\n-\t\t\t\t const char *out)\n-{\n-\tstrvec_push(&cmd->args, \"pack-objects\");\n-\tif (args->window)\n-\t\tstrvec_pushf(&cmd->args, \"--window=%s\", args->window);\n-\tif (args->window_memory)\n-\t\tstrvec_pushf(&cmd->args, \"--window-memory=%s\", args->window_memory);\n-\tif (args->depth)\n-\t\tstrvec_pushf(&cmd->args, \"--depth=%s\", args->depth);\n-\tif (args->threads)\n-\t\tstrvec_pushf(&cmd->args, \"--threads=%s\", args->threads);\n-\tif (args->max_pack_size)\n-\t\tstrvec_pushf(&cmd->args, \"--max-pack-size=%lu\", args->max_pack_size);\n-\tif (args->no_reuse_delta)\n-\t\tstrvec_pushf(&cmd->args, \"--no-reuse-delta\");\n-\tif (args->no_reuse_object)\n-\t\tstrvec_pushf(&cmd->args, \"--no-reuse-object\");\n-\tif (args->name_hash_version)\n-\t\tstrvec_pushf(&cmd->args, \"--name-hash-version=%d\", args->name_hash_version);\n-\tif (args->path_walk)\n-\t\tstrvec_pushf(&cmd->args, \"--path-walk\");\n-\tif (args->local)\n-\t\tstrvec_push(&cmd->args,  \"--local\");\n-\tif (args->quiet)\n-\t\tstrvec_push(&cmd->args,  \"--quiet\");\n-\tif (args->delta_base_offset)\n-\t\tstrvec_push(&cmd->args,  \"--delta-base-offset\");\n-\tstrvec_push(&cmd->args, out);\n-\tcmd->git_cmd = 1;\n-\tcmd->out = -1;\n-}\n-\n struct write_oid_context {\n \tstruct child_process *cmd;\n \tconst struct git_hash_algo *algop;\ndiff --git a/repack.c b/repack.c\nindex a1f5b796fb..91b6e1cc09 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -1,5 +1,40 @@\n #include \"git-compat-util.h\"\n #include \"repack.h\"\n+#include \"run-command.h\"\n+\n+void prepare_pack_objects(struct child_process *cmd,\n+\t\t\t  const struct pack_objects_args *args,\n+\t\t\t  const char *out)\n+{\n+\tstrvec_push(&cmd->args, \"pack-objects\");\n+\tif (args->window)\n+\t\tstrvec_pushf(&cmd->args, \"--window=%s\", args->window);\n+\tif (args->window_memory)\n+\t\tstrvec_pushf(&cmd->args, \"--window-memory=%s\", args->window_memory);\n+\tif (args->depth)\n+\t\tstrvec_pushf(&cmd->args, \"--depth=%s\", args->depth);\n+\tif (args->threads)\n+\t\tstrvec_pushf(&cmd->args, \"--threads=%s\", args->threads);\n+\tif (args->max_pack_size)\n+\t\tstrvec_pushf(&cmd->args, \"--max-pack-size=%lu\", args->max_pack_size);\n+\tif (args->no_reuse_delta)\n+\t\tstrvec_pushf(&cmd->args, \"--no-reuse-delta\");\n+\tif (args->no_reuse_object)\n+\t\tstrvec_pushf(&cmd->args, \"--no-reuse-object\");\n+\tif (args->name_hash_version)\n+\t\tstrvec_pushf(&cmd->args, \"--name-hash-version=%d\", args->name_hash_version);\n+\tif (args->path_walk)\n+\t\tstrvec_pushf(&cmd->args, \"--path-walk\");\n+\tif (args->local)\n+\t\tstrvec_push(&cmd->args,  \"--local\");\n+\tif (args->quiet)\n+\t\tstrvec_push(&cmd->args,  \"--quiet\");\n+\tif (args->delta_base_offset)\n+\t\tstrvec_push(&cmd->args,  \"--delta-base-offset\");\n+\tstrvec_push(&cmd->args, out);\n+\tcmd->git_cmd = 1;\n+\tcmd->out = -1;\n+}\n \n void pack_objects_args_release(struct pack_objects_args *args)\n {\ndiff --git a/repack.h b/repack.h\nindex 12632d7fec..3f7ec20735 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -21,6 +21,11 @@ struct pack_objects_args {\n \n #define PACK_OBJECTS_ARGS_INIT { .delta_base_offset = 1 }\n \n+struct child_process;\n+\n+void prepare_pack_objects(struct child_process *cmd,\n+\t\t\t  const struct pack_objects_args *args,\n+\t\t\t  const char *out);\n void pack_objects_args_release(struct pack_objects_args *args);\n \n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527540","messageId":"02e26f78ac8e3b7e800385cb60d2581398492cc9.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 15/49] builtin/repack.c: rename many 'struct existing_packs' functions","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:08Z","receivedAt":"2025-09-28T22:08:11Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Rename many of the 'struct existing_packs'-related functions according\nto the convention introduced in and described by 541204aabe\n(Documentation: document naming schema for structs and their functions,\n2024-07-30).\n\nNote that some functions which operate over an individual entry in the\nlist of existing packs are prefixed with \"existing_pack_\" instead of the\nplural form.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 66 +++++++++++++++++++++++++-----------------------\n 1 file changed, 34 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex ff93654cfe..f82e6c3930 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -121,39 +121,39 @@ struct existing_packs {\n \t.cruft_packs = STRING_LIST_INIT_DUP, \\\n }\n \n-static int has_existing_non_kept_packs(const struct existing_packs *existing)\n+static int existing_packs_has_non_kept(const struct existing_packs *existing)\n {\n \treturn existing->non_kept_packs.nr || existing->cruft_packs.nr;\n }\n \n-static void pack_mark_for_deletion(struct string_list_item *item)\n+static void existing_pack_mark_for_deletion(struct string_list_item *item)\n {\n \titem->util = (void*)((uintptr_t)item->util | DELETE_PACK);\n }\n \n-static void pack_unmark_for_deletion(struct string_list_item *item)\n+static void existing_pack_unmark_for_deletion(struct string_list_item *item)\n {\n \titem->util = (void*)((uintptr_t)item->util & ~DELETE_PACK);\n }\n \n-static int pack_is_marked_for_deletion(struct string_list_item *item)\n+static int existing_pack_is_marked_for_deletion(struct string_list_item *item)\n {\n \treturn (uintptr_t)item->util & DELETE_PACK;\n }\n \n-static void pack_mark_retained(struct string_list_item *item)\n+static void existing_packs_mark_retained(struct string_list_item *item)\n {\n \titem->util = (void*)((uintptr_t)item->util | RETAIN_PACK);\n }\n \n-static int pack_is_retained(struct string_list_item *item)\n+static int existing_pack_is_retained(struct string_list_item *item)\n {\n \treturn (uintptr_t)item->util & RETAIN_PACK;\n }\n \n-static void mark_packs_for_deletion_1(const struct git_hash_algo *algop,\n-\t\t\t\t      struct string_list *names,\n-\t\t\t\t      struct string_list *list)\n+static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop,\n+\t\t\t\t\t       struct string_list *names,\n+\t\t\t\t\t       struct string_list *list)\n {\n \tstruct string_list_item *item;\n \tconst int hexsz = algop->hexsz;\n@@ -165,8 +165,8 @@ static void mark_packs_for_deletion_1(const struct git_hash_algo *algop,\n \t\t\tcontinue;\n \t\tsha1 = item->string + len - hexsz;\n \n-\t\tif (pack_is_retained(item)) {\n-\t\t\tpack_unmark_for_deletion(item);\n+\t\tif (existing_pack_is_retained(item)) {\n+\t\t\texisting_pack_unmark_for_deletion(item);\n \t\t} else if (!string_list_has_string(names, sha1)) {\n \t\t\t/*\n \t\t\t * Mark this pack for deletion, which ensures\n@@ -175,13 +175,13 @@ static void mark_packs_for_deletion_1(const struct git_hash_algo *algop,\n \t\t\t * will actually delete this pack (if `-d` was\n \t\t\t * given).\n \t\t\t */\n-\t\t\tpack_mark_for_deletion(item);\n+\t\t\texisting_pack_mark_for_deletion(item);\n \t\t}\n \t}\n }\n \n-static void retain_cruft_pack(struct existing_packs *existing,\n-\t\t\t      struct packed_git *cruft)\n+static void existing_packs_retain_cruft(struct existing_packs *existing,\n+\t\t\t\t\tstruct packed_git *cruft)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct string_list_item *item;\n@@ -193,17 +193,19 @@ static void retain_cruft_pack(struct existing_packs *existing,\n \tif (!item)\n \t\tBUG(\"could not find cruft pack '%s'\", pack_basename(cruft));\n \n-\tpack_mark_retained(item);\n+\texisting_packs_mark_retained(item);\n \tstrbuf_release(&buf);\n }\n \n-static void mark_packs_for_deletion(struct existing_packs *existing,\n-\t\t\t\t    struct string_list *names)\n+static void existing_packs_mark_for_deletion(struct existing_packs *existing,\n+\t\t\t\t\t     struct string_list *names)\n \n {\n \tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n-\tmark_packs_for_deletion_1(algop, names, &existing->non_kept_packs);\n-\tmark_packs_for_deletion_1(algop, names, &existing->cruft_packs);\n+\texisting_packs_mark_for_deletion_1(algop, names,\n+\t\t\t\t\t   &existing->non_kept_packs);\n+\texisting_packs_mark_for_deletion_1(algop, names,\n+\t\t\t\t\t   &existing->cruft_packs);\n }\n \n static void remove_redundant_pack(struct repository *repo,\n@@ -225,13 +227,13 @@ static void remove_redundant_packs_1(struct repository *repo,\n {\n \tstruct string_list_item *item;\n \tfor_each_string_list_item(item, packs) {\n-\t\tif (!pack_is_marked_for_deletion(item))\n+\t\tif (!existing_pack_is_marked_for_deletion(item))\n \t\t\tcontinue;\n \t\tremove_redundant_pack(repo, packdir, item->string);\n \t}\n }\n \n-static void remove_redundant_existing_packs(struct existing_packs *existing)\n+static void existing_packs_remove_redundant(struct existing_packs *existing)\n {\n \tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs);\n \tremove_redundant_packs_1(existing->repo, &existing->cruft_packs);\n@@ -250,7 +252,7 @@ static void existing_packs_release(struct existing_packs *existing)\n  * .keep file or not.  Packs without a .keep file are not to be kept\n  * if we are going to pack everything into one file.\n  */\n-static void collect_pack_filenames(struct existing_packs *existing,\n+static void existing_packs_collect(struct existing_packs *existing,\n \t\t\t\t   const struct string_list *extra_keep)\n {\n \tstruct packfile_store *packs = existing->repo->objects->packfiles;\n@@ -721,7 +723,7 @@ static int midx_has_unknown_packs(char **midx_pack_names,\n \n \t\t\titem = string_list_lookup(&existing->non_kept_packs,\n \t\t\t\t\t\t  pack_name);\n-\t\t\tif (item && !pack_is_marked_for_deletion(item))\n+\t\t\tif (item && !existing_pack_is_marked_for_deletion(item))\n \t\t\t\tcontinue;\n \t\t}\n \n@@ -851,7 +853,7 @@ static void midx_included_packs(struct string_list *include,\n \t\t}\n \t} else {\n \t\tfor_each_string_list_item(item, &existing->non_kept_packs) {\n-\t\t\tif (pack_is_marked_for_deletion(item))\n+\t\t\tif (existing_pack_is_marked_for_deletion(item))\n \t\t\t\tcontinue;\n \n \t\t\tstrbuf_reset(&buf);\n@@ -888,10 +890,10 @@ static void midx_included_packs(struct string_list *include,\n \t\t\t * --geometric case, but doing so is unnecessary\n \t\t\t *  since no packs are marked as pending\n \t\t\t *  deletion (since we only call\n-\t\t\t *  `mark_packs_for_deletion()` when doing an\n-\t\t\t *  all-into-one repack).\n+\t\t\t *  `existing_packs_mark_for_deletion()` when\n+\t\t\t *  doing an all-into-one repack).\n \t\t\t */\n-\t\t\tif (pack_is_marked_for_deletion(item))\n+\t\t\tif (existing_pack_is_marked_for_deletion(item))\n \t\t\t\tcontinue;\n \n \t\t\tstrbuf_reset(&buf);\n@@ -1128,7 +1130,7 @@ static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n \t\tif (p->pack_size < combine_cruft_below_size) {\n \t\t\tfprintf(in, \"-%s\\n\", pack_basename(p));\n \t\t} else {\n-\t\t\tretain_cruft_pack(existing, p);\n+\t\t\texisting_packs_retain_cruft(existing, p);\n \t\t\tfprintf(in, \"%s\\n\", pack_basename(p));\n \t\t}\n \t}\n@@ -1382,7 +1384,7 @@ int cmd_repack(int argc,\n \tpacktmp = mkpathdup(\"%s/%s\", packdir, packtmp_name);\n \n \texisting.repo = repo;\n-\tcollect_pack_filenames(&existing, &keep_pack_list);\n+\texisting_packs_collect(&existing, &keep_pack_list);\n \n \tif (geometry.split_factor) {\n \t\tif (pack_everything)\n@@ -1431,7 +1433,7 @@ int cmd_repack(int argc,\n \tif (pack_everything & ALL_INTO_ONE) {\n \t\trepack_promisor_objects(repo, &po_args, &names);\n \n-\t\tif (has_existing_non_kept_packs(&existing) &&\n+\t\tif (existing_packs_has_non_kept(&existing) &&\n \t\t    delete_redundant &&\n \t\t    !(pack_everything & PACK_CRUFT)) {\n \t\t\tfor_each_string_list_item(item, &names) {\n@@ -1647,7 +1649,7 @@ int cmd_repack(int argc,\n \t/* End of pack replacement. */\n \n \tif (delete_redundant && pack_everything & ALL_INTO_ONE)\n-\t\tmark_packs_for_deletion(&existing, &names);\n+\t\texisting_packs_mark_for_deletion(&existing, &names);\n \n \tif (write_midx) {\n \t\tstruct string_list include = STRING_LIST_INIT_DUP;\n@@ -1671,7 +1673,7 @@ int cmd_repack(int argc,\n \n \tif (delete_redundant) {\n \t\tint opts = 0;\n-\t\tremove_redundant_existing_packs(&existing);\n+\t\texisting_packs_remove_redundant(&existing);\n \n \t\tif (geometry.split_factor)\n \t\t\tgeometry_remove_redundant_packs(&geometry, &names,\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527541","messageId":"d0a40e8e577b07391ea7330b3d7051732de0d043.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 16/49] repack: remove 'remove_redundant_pack' from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:12Z","receivedAt":"2025-09-28T22:08:15Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Extract \"remove_redundant_pack()\" as generic repack-related\nfunctionality by moving its implementation to the repack.[ch]\ncompilation unit.\n\nThis is a prerequisite to moving the \"existing_packs\" API, which is one\nof the callers of this function. (The remaining caller in the pack\ngeometry code will eventually move to its own compilation unit as well,\nand will likewise rely on this function.)\n\nWhile moving it over, prefix the function name with \"repack_\" to\nindicate that it belongs to the repack-subsystem.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 18 ++----------------\n repack.c         | 18 ++++++++++++++++++\n repack.h         |  3 +++\n 3 files changed, 23 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex f82e6c3930..31137cf711 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -208,20 +208,6 @@ static void existing_packs_mark_for_deletion(struct existing_packs *existing,\n \t\t\t\t\t   &existing->cruft_packs);\n }\n \n-static void remove_redundant_pack(struct repository *repo,\n-\t\t\t\t  const char *dir_name, const char *base_name)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tstruct odb_source *source = repo->objects->sources;\n-\tstruct multi_pack_index *m = get_multi_pack_index(source);\n-\tstrbuf_addf(&buf, \"%s.pack\", base_name);\n-\tif (m && source->local && midx_contains_pack(m, buf.buf))\n-\t\tclear_midx_file(repo);\n-\tstrbuf_insertf(&buf, 0, \"%s/\", dir_name);\n-\tunlink_pack_path(buf.buf, 1);\n-\tstrbuf_release(&buf);\n-}\n-\n static void remove_redundant_packs_1(struct repository *repo,\n \t\t\t\t     struct string_list *packs)\n {\n@@ -229,7 +215,7 @@ static void remove_redundant_packs_1(struct repository *repo,\n \tfor_each_string_list_item(item, packs) {\n \t\tif (!existing_pack_is_marked_for_deletion(item))\n \t\t\tcontinue;\n-\t\tremove_redundant_pack(repo, packdir, item->string);\n+\t\trepack_remove_redundant_pack(repo, packdir, item->string);\n \t}\n }\n \n@@ -652,7 +638,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n \t\t    (string_list_has_string(&existing->kept_packs, buf.buf)))\n \t\t\tcontinue;\n \n-\t\tremove_redundant_pack(existing->repo, packdir, buf.buf);\n+\t\trepack_remove_redundant_pack(existing->repo, packdir, buf.buf);\n \t}\n \n \tstrbuf_release(&buf);\ndiff --git a/repack.c b/repack.c\nindex 91b6e1cc09..3aaa351b5b 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -1,5 +1,9 @@\n #include \"git-compat-util.h\"\n+#include \"midx.h\"\n+#include \"odb.h\"\n+#include \"packfile.h\"\n #include \"repack.h\"\n+#include \"repository.h\"\n #include \"run-command.h\"\n \n void prepare_pack_objects(struct child_process *cmd,\n@@ -44,3 +48,17 @@ void pack_objects_args_release(struct pack_objects_args *args)\n \tfree(args->threads);\n \tlist_objects_filter_release(&args->filter_options);\n }\n+\n+void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n+\t\t\t\t  const char *base_name)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct odb_source *source = repo->objects->sources;\n+\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\tstrbuf_addf(&buf, \"%s.pack\", base_name);\n+\tif (m && source->local && midx_contains_pack(m, buf.buf))\n+\t\tclear_midx_file(repo);\n+\tstrbuf_insertf(&buf, 0, \"%s/\", dir_name);\n+\tunlink_pack_path(buf.buf, 1);\n+\tstrbuf_release(&buf);\n+}\ndiff --git a/repack.h b/repack.h\nindex 3f7ec20735..a62bfa2ff9 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -28,4 +28,7 @@ void prepare_pack_objects(struct child_process *cmd,\n \t\t\t  const char *out);\n void pack_objects_args_release(struct pack_objects_args *args);\n \n+void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n+\t\t\t\t  const char *base_name);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527542","messageId":"df75c3130bced8d5489dc9184701a0a544617049.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 17/49] builtin/repack.c: pass \"packdir\" when removing packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:16Z","receivedAt":"2025-09-28T22:08:19Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"builtin/repack.c defines a static \"packdir\" to instruct pack-objects on\nwhere to write any new packfiles. This is also the directory scanned\nwhen removing any packfiles which were made redundant by the latest\nrepack.\n\nPrepare to move the \"existing_packs_remove_redundant\" function to its\nown compilation unit by passing in this information as a parameter to\nthat function.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 14 +++++++++-----\n 1 file changed, 9 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 31137cf711..c5a88eda12 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -209,7 +209,8 @@ static void existing_packs_mark_for_deletion(struct existing_packs *existing,\n }\n \n static void remove_redundant_packs_1(struct repository *repo,\n-\t\t\t\t     struct string_list *packs)\n+\t\t\t\t     struct string_list *packs,\n+\t\t\t\t     const char *packdir)\n {\n \tstruct string_list_item *item;\n \tfor_each_string_list_item(item, packs) {\n@@ -219,10 +220,13 @@ static void remove_redundant_packs_1(struct repository *repo,\n \t}\n }\n \n-static void existing_packs_remove_redundant(struct existing_packs *existing)\n+static void existing_packs_remove_redundant(struct existing_packs *existing,\n+\t\t\t\t\t    const char *packdir)\n {\n-\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs);\n-\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs);\n+\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs,\n+\t\t\t\t packdir);\n+\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs,\n+\t\t\t\t packdir);\n }\n \n static void existing_packs_release(struct existing_packs *existing)\n@@ -1659,7 +1663,7 @@ int cmd_repack(int argc,\n \n \tif (delete_redundant) {\n \t\tint opts = 0;\n-\t\texisting_packs_remove_redundant(&existing);\n+\t\texisting_packs_remove_redundant(&existing, packdir);\n \n \t\tif (geometry.split_factor)\n \t\t\tgeometry_remove_redundant_packs(&geometry, &names,\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527543","messageId":"f413f958ebb44acce37cd2f0555d820ad7600c0c.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 18/49] builtin/repack.c: avoid unnecessary numeric casts in existing_packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:19Z","receivedAt":"2025-09-28T22:08:23Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"There are a couple of spots that cause warnings within the\nexisting_packs API without DISABLE_SIGN_COMPARE_WARNINGS under\nDEVELOPER=1 mode.\n\nIn both cases, we have int values that are being compared against size_t\nones. Neither of these two cases are incorrect, and the cast is\ncompletely OK in practice. But both are unnecessary, since:\n\n - in existing_packs_mark_for_deletion_1(), 'hexsz' should be defined as\n   a size_t anyway, since algop->hexsz is.\n\n - in existing_packs_collect(), 'i' should be defined as a size_t since\n   it is counting up to the value of a string_list's 'nr' field.\n\n(This patch is a little bit of noise, but I would rather see us squelch\nthese warnings ahead of moving the existing_packs API into a separate\ncompilation unit to avoid having to define DISABLE_SIGN_COMPARE_WARNINGS\nin repack.c.)\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex c5a88eda12..e13943b637 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -156,7 +156,7 @@ static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop\n \t\t\t\t\t       struct string_list *list)\n {\n \tstruct string_list_item *item;\n-\tconst int hexsz = algop->hexsz;\n+\tconst size_t hexsz = algop->hexsz;\n \n \tfor_each_string_list_item(item, list) {\n \t\tchar *sha1;\n@@ -250,7 +250,7 @@ static void existing_packs_collect(struct existing_packs *existing,\n \tstruct strbuf buf = STRBUF_INIT;\n \n \tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n-\t\tint i;\n+\t\tsize_t i;\n \t\tconst char *base;\n \n \t\tif (!p->pack_local)\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527544","messageId":"ae267fcc820dc19497f9f897bc5915b2040739e5.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 19/49] repack: remove 'existing_packs' API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:23Z","receivedAt":"2025-09-28T22:08:27Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The repack builtin defines an API for keeping track of which packs\nwere found in the repository at the beginning of the repack operation.\nThis is used to classify what state a pack was in (kept, non-kept, or\ncruft), and is also used to mark which packs to delete (or keep) at the\nend of a repack operation.\n\nNow that the prerequisite refactoring is complete, this API is isolated\nenough that it can be moved out to repack.ch and removed from the\nbuiltin entirely.\n\nAs a result, some of its functions become static within repack.c,\ncleaning up the visible API.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 173 -----------------------------------------------\n repack.c         | 157 ++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  35 ++++++++++\n 3 files changed, 192 insertions(+), 173 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex e13943b637..a168c88791 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -3,7 +3,6 @@\n \n #include \"builtin.h\"\n #include \"config.h\"\n-#include \"dir.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n@@ -108,178 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-struct existing_packs {\n-\tstruct repository *repo;\n-\tstruct string_list kept_packs;\n-\tstruct string_list non_kept_packs;\n-\tstruct string_list cruft_packs;\n-};\n-\n-#define EXISTING_PACKS_INIT { \\\n-\t.kept_packs = STRING_LIST_INIT_DUP, \\\n-\t.non_kept_packs = STRING_LIST_INIT_DUP, \\\n-\t.cruft_packs = STRING_LIST_INIT_DUP, \\\n-}\n-\n-static int existing_packs_has_non_kept(const struct existing_packs *existing)\n-{\n-\treturn existing->non_kept_packs.nr || existing->cruft_packs.nr;\n-}\n-\n-static void existing_pack_mark_for_deletion(struct string_list_item *item)\n-{\n-\titem->util = (void*)((uintptr_t)item->util | DELETE_PACK);\n-}\n-\n-static void existing_pack_unmark_for_deletion(struct string_list_item *item)\n-{\n-\titem->util = (void*)((uintptr_t)item->util & ~DELETE_PACK);\n-}\n-\n-static int existing_pack_is_marked_for_deletion(struct string_list_item *item)\n-{\n-\treturn (uintptr_t)item->util & DELETE_PACK;\n-}\n-\n-static void existing_packs_mark_retained(struct string_list_item *item)\n-{\n-\titem->util = (void*)((uintptr_t)item->util | RETAIN_PACK);\n-}\n-\n-static int existing_pack_is_retained(struct string_list_item *item)\n-{\n-\treturn (uintptr_t)item->util & RETAIN_PACK;\n-}\n-\n-static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop,\n-\t\t\t\t\t       struct string_list *names,\n-\t\t\t\t\t       struct string_list *list)\n-{\n-\tstruct string_list_item *item;\n-\tconst size_t hexsz = algop->hexsz;\n-\n-\tfor_each_string_list_item(item, list) {\n-\t\tchar *sha1;\n-\t\tsize_t len = strlen(item->string);\n-\t\tif (len < hexsz)\n-\t\t\tcontinue;\n-\t\tsha1 = item->string + len - hexsz;\n-\n-\t\tif (existing_pack_is_retained(item)) {\n-\t\t\texisting_pack_unmark_for_deletion(item);\n-\t\t} else if (!string_list_has_string(names, sha1)) {\n-\t\t\t/*\n-\t\t\t * Mark this pack for deletion, which ensures\n-\t\t\t * that this pack won't be included in a MIDX\n-\t\t\t * (if `--write-midx` was given) and that we\n-\t\t\t * will actually delete this pack (if `-d` was\n-\t\t\t * given).\n-\t\t\t */\n-\t\t\texisting_pack_mark_for_deletion(item);\n-\t\t}\n-\t}\n-}\n-\n-static void existing_packs_retain_cruft(struct existing_packs *existing,\n-\t\t\t\t\tstruct packed_git *cruft)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tstruct string_list_item *item;\n-\n-\tstrbuf_addstr(&buf, pack_basename(cruft));\n-\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\titem = string_list_lookup(&existing->cruft_packs, buf.buf);\n-\tif (!item)\n-\t\tBUG(\"could not find cruft pack '%s'\", pack_basename(cruft));\n-\n-\texisting_packs_mark_retained(item);\n-\tstrbuf_release(&buf);\n-}\n-\n-static void existing_packs_mark_for_deletion(struct existing_packs *existing,\n-\t\t\t\t\t     struct string_list *names)\n-\n-{\n-\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n-\texisting_packs_mark_for_deletion_1(algop, names,\n-\t\t\t\t\t   &existing->non_kept_packs);\n-\texisting_packs_mark_for_deletion_1(algop, names,\n-\t\t\t\t\t   &existing->cruft_packs);\n-}\n-\n-static void remove_redundant_packs_1(struct repository *repo,\n-\t\t\t\t     struct string_list *packs,\n-\t\t\t\t     const char *packdir)\n-{\n-\tstruct string_list_item *item;\n-\tfor_each_string_list_item(item, packs) {\n-\t\tif (!existing_pack_is_marked_for_deletion(item))\n-\t\t\tcontinue;\n-\t\trepack_remove_redundant_pack(repo, packdir, item->string);\n-\t}\n-}\n-\n-static void existing_packs_remove_redundant(struct existing_packs *existing,\n-\t\t\t\t\t    const char *packdir)\n-{\n-\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs,\n-\t\t\t\t packdir);\n-\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs,\n-\t\t\t\t packdir);\n-}\n-\n-static void existing_packs_release(struct existing_packs *existing)\n-{\n-\tstring_list_clear(&existing->kept_packs, 0);\n-\tstring_list_clear(&existing->non_kept_packs, 0);\n-\tstring_list_clear(&existing->cruft_packs, 0);\n-}\n-\n-/*\n- * Adds all packs hex strings (pack-$HASH) to either packs->non_kept\n- * or packs->kept based on whether each pack has a corresponding\n- * .keep file or not.  Packs without a .keep file are not to be kept\n- * if we are going to pack everything into one file.\n- */\n-static void existing_packs_collect(struct existing_packs *existing,\n-\t\t\t\t   const struct string_list *extra_keep)\n-{\n-\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n-\tstruct packed_git *p;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n-\t\tsize_t i;\n-\t\tconst char *base;\n-\n-\t\tif (!p->pack_local)\n-\t\t\tcontinue;\n-\n-\t\tbase = pack_basename(p);\n-\n-\t\tfor (i = 0; i < extra_keep->nr; i++)\n-\t\t\tif (!fspathcmp(base, extra_keep->items[i].string))\n-\t\t\t\tbreak;\n-\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addstr(&buf, base);\n-\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\t\tif ((extra_keep->nr > 0 && i < extra_keep->nr) || p->pack_keep)\n-\t\t\tstring_list_append(&existing->kept_packs, buf.buf);\n-\t\telse if (p->is_cruft)\n-\t\t\tstring_list_append(&existing->cruft_packs, buf.buf);\n-\t\telse\n-\t\t\tstring_list_append(&existing->non_kept_packs, buf.buf);\n-\t}\n-\n-\tstring_list_sort(&existing->kept_packs);\n-\tstring_list_sort(&existing->non_kept_packs);\n-\tstring_list_sort(&existing->cruft_packs);\n-\tstrbuf_release(&buf);\n-}\n-\n struct write_oid_context {\n \tstruct child_process *cmd;\n \tconst struct git_hash_algo *algop;\ndiff --git a/repack.c b/repack.c\nindex 3aaa351b5b..9182e1c50b 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -1,4 +1,5 @@\n #include \"git-compat-util.h\"\n+#include \"dir.h\"\n #include \"midx.h\"\n #include \"odb.h\"\n #include \"packfile.h\"\n@@ -62,3 +63,159 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \tunlink_pack_path(buf.buf, 1);\n \tstrbuf_release(&buf);\n }\n+\n+#define DELETE_PACK 1\n+#define RETAIN_PACK 2\n+\n+void existing_packs_collect(struct existing_packs *existing,\n+\t\t\t    const struct string_list *extra_keep)\n+{\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n+\tstruct packed_git *p;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n+\t\tsize_t i;\n+\t\tconst char *base;\n+\n+\t\tif (!p->pack_local)\n+\t\t\tcontinue;\n+\n+\t\tbase = pack_basename(p);\n+\n+\t\tfor (i = 0; i < extra_keep->nr; i++)\n+\t\t\tif (!fspathcmp(base, extra_keep->items[i].string))\n+\t\t\t\tbreak;\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, base);\n+\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\t\tif ((extra_keep->nr > 0 && i < extra_keep->nr) || p->pack_keep)\n+\t\t\tstring_list_append(&existing->kept_packs, buf.buf);\n+\t\telse if (p->is_cruft)\n+\t\t\tstring_list_append(&existing->cruft_packs, buf.buf);\n+\t\telse\n+\t\t\tstring_list_append(&existing->non_kept_packs, buf.buf);\n+\t}\n+\n+\tstring_list_sort(&existing->kept_packs);\n+\tstring_list_sort(&existing->non_kept_packs);\n+\tstring_list_sort(&existing->cruft_packs);\n+\tstrbuf_release(&buf);\n+}\n+\n+int existing_packs_has_non_kept(const struct existing_packs *existing)\n+{\n+\treturn existing->non_kept_packs.nr || existing->cruft_packs.nr;\n+}\n+\n+static void existing_pack_mark_for_deletion(struct string_list_item *item)\n+{\n+\titem->util = (void*)((uintptr_t)item->util | DELETE_PACK);\n+}\n+\n+static void existing_pack_unmark_for_deletion(struct string_list_item *item)\n+{\n+\titem->util = (void*)((uintptr_t)item->util & ~DELETE_PACK);\n+}\n+\n+int existing_pack_is_marked_for_deletion(struct string_list_item *item)\n+{\n+\treturn (uintptr_t)item->util & DELETE_PACK;\n+}\n+\n+static void existing_packs_mark_retained(struct string_list_item *item)\n+{\n+\titem->util = (void*)((uintptr_t)item->util | RETAIN_PACK);\n+}\n+\n+static int existing_pack_is_retained(struct string_list_item *item)\n+{\n+\treturn (uintptr_t)item->util & RETAIN_PACK;\n+}\n+\n+static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop,\n+\t\t\t\t\t       struct string_list *names,\n+\t\t\t\t\t       struct string_list *list)\n+{\n+\tstruct string_list_item *item;\n+\tconst size_t hexsz = algop->hexsz;\n+\n+\tfor_each_string_list_item(item, list) {\n+\t\tchar *sha1;\n+\t\tsize_t len = strlen(item->string);\n+\t\tif (len < hexsz)\n+\t\t\tcontinue;\n+\t\tsha1 = item->string + len - hexsz;\n+\n+\t\tif (existing_pack_is_retained(item)) {\n+\t\t\texisting_pack_unmark_for_deletion(item);\n+\t\t} else if (!string_list_has_string(names, sha1)) {\n+\t\t\t/*\n+\t\t\t * Mark this pack for deletion, which ensures\n+\t\t\t * that this pack won't be included in a MIDX\n+\t\t\t * (if `--write-midx` was given) and that we\n+\t\t\t * will actually delete this pack (if `-d` was\n+\t\t\t * given).\n+\t\t\t */\n+\t\t\texisting_pack_mark_for_deletion(item);\n+\t\t}\n+\t}\n+}\n+\n+void existing_packs_retain_cruft(struct existing_packs *existing,\n+\t\t\t\t struct packed_git *cruft)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\n+\tstrbuf_addstr(&buf, pack_basename(cruft));\n+\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\titem = string_list_lookup(&existing->cruft_packs, buf.buf);\n+\tif (!item)\n+\t\tBUG(\"could not find cruft pack '%s'\", pack_basename(cruft));\n+\n+\texisting_packs_mark_retained(item);\n+\tstrbuf_release(&buf);\n+}\n+\n+void existing_packs_mark_for_deletion(struct existing_packs *existing,\n+\t\t\t\t      struct string_list *names)\n+\n+{\n+\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n+\texisting_packs_mark_for_deletion_1(algop, names,\n+\t\t\t\t\t   &existing->non_kept_packs);\n+\texisting_packs_mark_for_deletion_1(algop, names,\n+\t\t\t\t\t   &existing->cruft_packs);\n+}\n+\n+static void remove_redundant_packs_1(struct repository *repo,\n+\t\t\t\t     struct string_list *packs,\n+\t\t\t\t     const char *packdir)\n+{\n+\tstruct string_list_item *item;\n+\tfor_each_string_list_item(item, packs) {\n+\t\tif (!existing_pack_is_marked_for_deletion(item))\n+\t\t\tcontinue;\n+\t\trepack_remove_redundant_pack(repo, packdir, item->string);\n+\t}\n+}\n+\n+void existing_packs_remove_redundant(struct existing_packs *existing,\n+\t\t\t\t     const char *packdir)\n+{\n+\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs,\n+\t\t\t\t packdir);\n+\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs,\n+\t\t\t\t packdir);\n+}\n+\n+void existing_packs_release(struct existing_packs *existing)\n+{\n+\tstring_list_clear(&existing->kept_packs, 0);\n+\tstring_list_clear(&existing->non_kept_packs, 0);\n+\tstring_list_clear(&existing->cruft_packs, 0);\n+}\ndiff --git a/repack.h b/repack.h\nindex a62bfa2ff9..19796e2243 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -2,6 +2,7 @@\n #define REPACK_H\n \n #include \"list-objects-filter-options.h\"\n+#include \"string-list.h\"\n \n struct pack_objects_args {\n \tchar *window;\n@@ -31,4 +32,38 @@ void pack_objects_args_release(struct pack_objects_args *args);\n void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \t\t\t\t  const char *base_name);\n \n+struct repository;\n+struct packed_git;\n+\n+struct existing_packs {\n+\tstruct repository *repo;\n+\tstruct string_list kept_packs;\n+\tstruct string_list non_kept_packs;\n+\tstruct string_list cruft_packs;\n+};\n+\n+#define EXISTING_PACKS_INIT { \\\n+\t.kept_packs = STRING_LIST_INIT_DUP, \\\n+\t.non_kept_packs = STRING_LIST_INIT_DUP, \\\n+\t.cruft_packs = STRING_LIST_INIT_DUP, \\\n+}\n+\n+/*\n+ * Adds all packs hex strings (pack-$HASH) to either packs->non_kept\n+ * or packs->kept based on whether each pack has a corresponding\n+ * .keep file or not.  Packs without a .keep file are not to be kept\n+ * if we are going to pack everything into one file.\n+ */\n+void existing_packs_collect(struct existing_packs *existing,\n+\t\t\t    const struct string_list *extra_keep);\n+int existing_packs_has_non_kept(const struct existing_packs *existing);\n+int existing_pack_is_marked_for_deletion(struct string_list_item *item);\n+void existing_packs_retain_cruft(struct existing_packs *existing,\n+\t\t\t\t struct packed_git *cruft);\n+void existing_packs_mark_for_deletion(struct existing_packs *existing,\n+\t\t\t\t      struct string_list *names);\n+void existing_packs_remove_redundant(struct existing_packs *existing,\n+\t\t\t\t     const char *packdir);\n+void existing_packs_release(struct existing_packs *existing);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527545","messageId":"ee3e1233f5b64dc059aea42bf4e3859c72bfd534.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 20/49] builtin/repack.c: rename \"struct generated_pack_data\"","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:27Z","receivedAt":"2025-09-28T22:08:31Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The name \"generated_pack_data\" is somewhat redundant, since the contents\nof the struct *is* the data associated with the generated pack.\n\nRename the structure to just \"generated_pack\", resulting in less awkward\nfunction names, like \"generated_pack_has_ext()\" which is preferable to\n\"generated_pack_data_has_ext()\".\n\nRename a few related functions to align with the convention that\nfunctions to do with a struct \"S\" should be prefixed with \"S_\".\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 32 ++++++++++++++++----------------\n 1 file changed, 16 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a168c88791..a4d80b6b04 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -146,15 +146,15 @@ static struct {\n \t{\".idx\"},\n };\n \n-struct generated_pack_data {\n+struct generated_pack {\n \tstruct tempfile *tempfiles[ARRAY_SIZE(exts)];\n };\n \n-static struct generated_pack_data *populate_pack_exts(const char *name)\n+static struct generated_pack *generated_pack_populate(const char *name)\n {\n \tstruct stat statbuf;\n \tstruct strbuf path = STRBUF_INIT;\n-\tstruct generated_pack_data *data = xcalloc(1, sizeof(*data));\n+\tstruct generated_pack *pack = xcalloc(1, sizeof(*pack));\n \tint i;\n \n \tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n@@ -164,21 +164,21 @@ static struct generated_pack_data *populate_pack_exts(const char *name)\n \t\tif (stat(path.buf, &statbuf))\n \t\t\tcontinue;\n \n-\t\tdata->tempfiles[i] = register_tempfile(path.buf);\n+\t\tpack->tempfiles[i] = register_tempfile(path.buf);\n \t}\n \n \tstrbuf_release(&path);\n-\treturn data;\n+\treturn pack;\n }\n \n-static int has_pack_ext(const struct generated_pack_data *data,\n-\t\t\tconst char *ext)\n+static int generated_pack_has_ext(const struct generated_pack *pack,\n+\t\t\t\t  const char *ext)\n {\n \tint i;\n \tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n \t\tif (strcmp(exts[i].name, ext))\n \t\t\tcontinue;\n-\t\treturn !!data->tempfiles[i];\n+\t\treturn !!pack->tempfiles[i];\n \t}\n \tBUG(\"unknown pack extension: '%s'\", ext);\n }\n@@ -239,7 +239,7 @@ static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t\t  line.buf);\n \t\twrite_promisor_file(promisor_name, NULL, 0);\n \n-\t\titem->util = populate_pack_exts(item->string);\n+\t\titem->util = generated_pack_populate(item->string);\n \n \t\tfree(promisor_name);\n \t}\n@@ -780,8 +780,8 @@ static int write_midx_included_packs(struct string_list *include,\n \t\t * will suffice, so pick the first one.)\n \t\t */\n \t\tfor_each_string_list_item(item, names) {\n-\t\t\tstruct generated_pack_data *data = item->util;\n-\t\t\tif (has_pack_ext(data, \".mtimes\"))\n+\t\t\tstruct generated_pack *pack = item->util;\n+\t\t\tif (generated_pack_has_ext(pack, \".mtimes\"))\n \t\t\t\tcontinue;\n \n \t\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=pack-%s.pack\",\n@@ -864,7 +864,7 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t */\n \t\tif (local) {\n \t\t\titem = string_list_append(names, line.buf);\n-\t\t\titem->util = populate_pack_exts(line.buf);\n+\t\t\titem->util = generated_pack_populate(line.buf);\n \t\t}\n \t}\n \tfclose(out);\n@@ -1435,7 +1435,7 @@ int cmd_repack(int argc,\n \t * Ok we have prepared all new packfiles.\n \t */\n \tfor_each_string_list_item(item, &names) {\n-\t\tstruct generated_pack_data *data = item->util;\n+\t\tstruct generated_pack *pack = item->util;\n \n \t\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n \t\t\tchar *fname;\n@@ -1443,8 +1443,8 @@ int cmd_repack(int argc,\n \t\t\tfname = mkpathdup(\"%s/pack-%s%s\",\n \t\t\t\t\tpackdir, item->string, exts[ext].name);\n \n-\t\t\tif (data->tempfiles[ext]) {\n-\t\t\t\tconst char *fname_old = get_tempfile_path(data->tempfiles[ext]);\n+\t\t\tif (pack->tempfiles[ext]) {\n+\t\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n \t\t\t\tstruct stat statbuffer;\n \n \t\t\t\tif (!stat(fname_old, &statbuffer)) {\n@@ -1452,7 +1452,7 @@ int cmd_repack(int argc,\n \t\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n \t\t\t\t}\n \n-\t\t\t\tif (rename_tempfile(&data->tempfiles[ext], fname))\n+\t\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n \t\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"), fname);\n \t\t\t} else if (!exts[ext].optional)\n \t\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527546","messageId":"96d27261e75fd2ae46d9633b3d4eff243dbcc187.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 21/49] builtin/repack.c: factor our \"generated_pack_install\"","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:31Z","receivedAt":"2025-09-28T22:08:35Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Once all new packs are known to exist, 'repack' installs their contents\nfrom their temporary location into their permanent one. This is a\nsemi-involved procedure for each pack, since for each extension (e.g.,\n\".idx\", \".pack\", \".mtimes\", and so on) we have to either:\n\n - adjust the filemode of the temporary file before renaming it into\n   place, or\n\n - die() if we are missing a non-optional extension, or\n\n - unlink() any existing file for extensions that we did not generate\n   (e.g., if a non-cruft pack we generated was identical to, say, a\n   cruft pack which existed at the beginning of the process, we have to\n   remove the \".mtimes\" file).\n\nExtract this procedure into its own function, and call it\n\"generated_pack_install\"(). This will set us up for pulling this\nfunction out of the builtin entirely and making it part of the repack.h\nAPI, which will be done in a future commit.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 66 ++++++++++++++++++++++++++----------------------\n 1 file changed, 36 insertions(+), 30 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a4d80b6b04..8c3a5f4f80 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -183,6 +183,38 @@ static int generated_pack_has_ext(const struct generated_pack *pack,\n \tBUG(\"unknown pack extension: '%s'\", ext);\n }\n \n+static void generated_pack_install(struct generated_pack *pack,\n+\t\t\t\t   const char *name)\n+{\n+\tint ext;\n+\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n+\t\tchar *fname;\n+\n+\t\tfname = mkpathdup(\"%s/pack-%s%s\", packdir, name,\n+\t\t\t\t  exts[ext].name);\n+\n+\t\tif (pack->tempfiles[ext]) {\n+\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n+\t\t\tstruct stat statbuffer;\n+\n+\t\t\tif (!stat(fname_old, &statbuffer)) {\n+\t\t\t\tstatbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);\n+\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n+\t\t\t}\n+\n+\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n+\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"),\n+\t\t\t\t\t  fname);\n+\t\t} else if (!exts[ext].optional)\n+\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n+\t\t\t    exts[ext].name, packtmp, name);\n+\t\telse if (unlink(fname) < 0 && errno != ENOENT)\n+\t\t\tdie_errno(_(\"could not unlink: %s\"), fname);\n+\n+\t\tfree(fname);\n+\t}\n+}\n+\n static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t    const struct pack_objects_args *args,\n \t\t\t\t    struct string_list *names)\n@@ -1045,7 +1077,7 @@ int cmd_repack(int argc,\n \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n \tstruct pack_geometry geometry = { 0 };\n \tstruct tempfile *refs_snapshot = NULL;\n-\tint i, ext, ret;\n+\tint i, ret;\n \tint show_progress;\n \tchar **midx_pack_names = NULL;\n \tsize_t midx_pack_names_nr = 0;\n@@ -1434,35 +1466,9 @@ int cmd_repack(int argc,\n \t/*\n \t * Ok we have prepared all new packfiles.\n \t */\n-\tfor_each_string_list_item(item, &names) {\n-\t\tstruct generated_pack *pack = item->util;\n-\n-\t\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n-\t\t\tchar *fname;\n-\n-\t\t\tfname = mkpathdup(\"%s/pack-%s%s\",\n-\t\t\t\t\tpackdir, item->string, exts[ext].name);\n-\n-\t\t\tif (pack->tempfiles[ext]) {\n-\t\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n-\t\t\t\tstruct stat statbuffer;\n-\n-\t\t\t\tif (!stat(fname_old, &statbuffer)) {\n-\t\t\t\t\tstatbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);\n-\t\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n-\t\t\t\t}\n-\n-\t\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n-\t\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"), fname);\n-\t\t\t} else if (!exts[ext].optional)\n-\t\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n-\t\t\t\t    exts[ext].name, packtmp, item->string);\n-\t\t\telse if (unlink(fname) < 0 && errno != ENOENT)\n-\t\t\t\tdie_errno(_(\"could not unlink: %s\"), fname);\n-\n-\t\t\tfree(fname);\n-\t\t}\n-\t}\n+\tfor_each_string_list_item(item, &names)\n+\t\tgenerated_pack_install((struct generated_pack *)item->util,\n+\t\t\t\t       item->string);\n \t/* End of pack replacement. */\n \n \tif (delete_redundant && pack_everything & ALL_INTO_ONE)\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527547","messageId":"ab0dc6668339f24787dc80d2dc05f0d31cfadae6.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 22/49] builtin/repack.c: pass \"packtmp\" to `generated_pack_populate()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:35Z","receivedAt":"2025-09-28T22:08:38Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as previous commits, this function needs to know the\ntemporary pack prefix, which it currently accesses through the static\n\"packtmp\" variable within builtin/repack.c.\n\nPass it explicitly as a function parameter to facilitate moving this\nfunction out of builtin/repack.c entirely.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 8c3a5f4f80..2141c43bd2 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -150,7 +150,8 @@ struct generated_pack {\n \tstruct tempfile *tempfiles[ARRAY_SIZE(exts)];\n };\n \n-static struct generated_pack *generated_pack_populate(const char *name)\n+static struct generated_pack *generated_pack_populate(const char *name,\n+\t\t\t\t\t\t      const char *packtmp)\n {\n \tstruct stat statbuf;\n \tstruct strbuf path = STRBUF_INIT;\n@@ -271,7 +272,7 @@ static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t\t  line.buf);\n \t\twrite_promisor_file(promisor_name, NULL, 0);\n \n-\t\titem->util = generated_pack_populate(item->string);\n+\t\titem->util = generated_pack_populate(item->string, packtmp);\n \n \t\tfree(promisor_name);\n \t}\n@@ -896,7 +897,7 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t */\n \t\tif (local) {\n \t\t\titem = string_list_append(names, line.buf);\n-\t\t\titem->util = generated_pack_populate(line.buf);\n+\t\t\titem->util = generated_pack_populate(line.buf, packtmp);\n \t\t}\n \t}\n \tfclose(out);\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527548","messageId":"e9d525d83a2f763429bdeb89f6f73f06a70f3319.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 23/49] builtin/repack.c: provide pack locations to `generated_pack_install()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:39Z","receivedAt":"2025-09-28T22:08:42Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Repeat what was done in the preceding commit for the\n`generated_pack_install()` function, which needs both \"packdir\" and\n\"packtmp\".\n\n(As an aside, it is somewhat unfortunate that the final three parameters\nto this function are all \"const char *\", making errors like passing\n\"packdir\" and \"packtmp\" in the wrong order easy. We could define a new\nstructure here, but that may be too heavy-handed.)\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 2141c43bd2..a4f0a19453 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -185,7 +185,8 @@ static int generated_pack_has_ext(const struct generated_pack *pack,\n }\n \n static void generated_pack_install(struct generated_pack *pack,\n-\t\t\t\t   const char *name)\n+\t\t\t\t   const char *name,\n+\t\t\t\t   const char *packdir, const char *packtmp)\n {\n \tint ext;\n \tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n@@ -1469,7 +1470,7 @@ int cmd_repack(int argc,\n \t */\n \tfor_each_string_list_item(item, &names)\n \t\tgenerated_pack_install((struct generated_pack *)item->util,\n-\t\t\t\t       item->string);\n+\t\t\t\t       item->string, packdir, packtmp);\n \t/* End of pack replacement. */\n \n \tif (delete_redundant && pack_everything & ALL_INTO_ONE)\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527549","messageId":"9979b77979b1f2611dbb2ff8369d596a6130c55b.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 24/49] repack: remove 'generated_pack' API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:43Z","receivedAt":"2025-09-28T22:08:46Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that we have factored the \"generated_pack\" API, we can move it to\nrepack.ch, further slimming down builtin/repack.c.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 83 ------------------------------------------------\n repack.c         | 83 ++++++++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  8 +++++\n 3 files changed, 91 insertions(+), 83 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a4f0a19453..b7826e676b 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -134,89 +134,6 @@ static int write_oid(const struct object_id *oid,\n \treturn 0;\n }\n \n-static struct {\n-\tconst char *name;\n-\tunsigned optional:1;\n-} exts[] = {\n-\t{\".pack\"},\n-\t{\".rev\", 1},\n-\t{\".mtimes\", 1},\n-\t{\".bitmap\", 1},\n-\t{\".promisor\", 1},\n-\t{\".idx\"},\n-};\n-\n-struct generated_pack {\n-\tstruct tempfile *tempfiles[ARRAY_SIZE(exts)];\n-};\n-\n-static struct generated_pack *generated_pack_populate(const char *name,\n-\t\t\t\t\t\t      const char *packtmp)\n-{\n-\tstruct stat statbuf;\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstruct generated_pack *pack = xcalloc(1, sizeof(*pack));\n-\tint i;\n-\n-\tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n-\t\tstrbuf_reset(&path);\n-\t\tstrbuf_addf(&path, \"%s-%s%s\", packtmp, name, exts[i].name);\n-\n-\t\tif (stat(path.buf, &statbuf))\n-\t\t\tcontinue;\n-\n-\t\tpack->tempfiles[i] = register_tempfile(path.buf);\n-\t}\n-\n-\tstrbuf_release(&path);\n-\treturn pack;\n-}\n-\n-static int generated_pack_has_ext(const struct generated_pack *pack,\n-\t\t\t\t  const char *ext)\n-{\n-\tint i;\n-\tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n-\t\tif (strcmp(exts[i].name, ext))\n-\t\t\tcontinue;\n-\t\treturn !!pack->tempfiles[i];\n-\t}\n-\tBUG(\"unknown pack extension: '%s'\", ext);\n-}\n-\n-static void generated_pack_install(struct generated_pack *pack,\n-\t\t\t\t   const char *name,\n-\t\t\t\t   const char *packdir, const char *packtmp)\n-{\n-\tint ext;\n-\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n-\t\tchar *fname;\n-\n-\t\tfname = mkpathdup(\"%s/pack-%s%s\", packdir, name,\n-\t\t\t\t  exts[ext].name);\n-\n-\t\tif (pack->tempfiles[ext]) {\n-\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n-\t\t\tstruct stat statbuffer;\n-\n-\t\t\tif (!stat(fname_old, &statbuffer)) {\n-\t\t\t\tstatbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);\n-\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n-\t\t\t}\n-\n-\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n-\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"),\n-\t\t\t\t\t  fname);\n-\t\t} else if (!exts[ext].optional)\n-\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n-\t\t\t    exts[ext].name, packtmp, name);\n-\t\telse if (unlink(fname) < 0 && errno != ENOENT)\n-\t\t\tdie_errno(_(\"could not unlink: %s\"), fname);\n-\n-\t\tfree(fname);\n-\t}\n-}\n-\n static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t    const struct pack_objects_args *args,\n \t\t\t\t    struct string_list *names)\ndiff --git a/repack.c b/repack.c\nindex 9182e1c50b..d8afdd352d 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -3,9 +3,11 @@\n #include \"midx.h\"\n #include \"odb.h\"\n #include \"packfile.h\"\n+#include \"path.h\"\n #include \"repack.h\"\n #include \"repository.h\"\n #include \"run-command.h\"\n+#include \"tempfile.h\"\n \n void prepare_pack_objects(struct child_process *cmd,\n \t\t\t  const struct pack_objects_args *args,\n@@ -219,3 +221,84 @@ void existing_packs_release(struct existing_packs *existing)\n \tstring_list_clear(&existing->non_kept_packs, 0);\n \tstring_list_clear(&existing->cruft_packs, 0);\n }\n+\n+static struct {\n+\tconst char *name;\n+\tunsigned optional:1;\n+} exts[] = {\n+\t{\".pack\"},\n+\t{\".rev\", 1},\n+\t{\".mtimes\", 1},\n+\t{\".bitmap\", 1},\n+\t{\".promisor\", 1},\n+\t{\".idx\"},\n+};\n+\n+struct generated_pack {\n+\tstruct tempfile *tempfiles[ARRAY_SIZE(exts)];\n+};\n+\n+struct generated_pack *generated_pack_populate(const char *name,\n+\t\t\t\t\t       const char *packtmp)\n+{\n+\tstruct stat statbuf;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstruct generated_pack *pack = xcalloc(1, sizeof(*pack));\n+\tsize_t i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addf(&path, \"%s-%s%s\", packtmp, name, exts[i].name);\n+\n+\t\tif (stat(path.buf, &statbuf))\n+\t\t\tcontinue;\n+\n+\t\tpack->tempfiles[i] = register_tempfile(path.buf);\n+\t}\n+\n+\tstrbuf_release(&path);\n+\treturn pack;\n+}\n+\n+int generated_pack_has_ext(const struct generated_pack *pack, const char *ext)\n+{\n+\tsize_t i;\n+\tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n+\t\tif (strcmp(exts[i].name, ext))\n+\t\t\tcontinue;\n+\t\treturn !!pack->tempfiles[i];\n+\t}\n+\tBUG(\"unknown pack extension: '%s'\", ext);\n+}\n+\n+void generated_pack_install(struct generated_pack *pack, const char *name,\n+\t\t\t    const char *packdir, const char *packtmp)\n+{\n+\tsize_t ext;\n+\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n+\t\tchar *fname;\n+\n+\t\tfname = mkpathdup(\"%s/pack-%s%s\", packdir, name,\n+\t\t\t\t  exts[ext].name);\n+\n+\t\tif (pack->tempfiles[ext]) {\n+\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n+\t\t\tstruct stat statbuffer;\n+\n+\t\t\tif (!stat(fname_old, &statbuffer)) {\n+\t\t\t\tstatbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);\n+\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n+\t\t\t}\n+\n+\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n+\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"),\n+\t\t\t\t\t  fname);\n+\t\t} else if (!exts[ext].optional)\n+\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n+\t\t\t    exts[ext].name, packtmp, name);\n+\t\telse if (unlink(fname) < 0 && errno != ENOENT)\n+\t\t\tdie_errno(_(\"could not unlink: %s\"), fname);\n+\n+\t\tfree(fname);\n+\t}\n+}\ndiff --git a/repack.h b/repack.h\nindex 19796e2243..f37eb49524 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -66,4 +66,12 @@ void existing_packs_remove_redundant(struct existing_packs *existing,\n \t\t\t\t     const char *packdir);\n void existing_packs_release(struct existing_packs *existing);\n \n+struct generated_pack;\n+\n+struct generated_pack *generated_pack_populate(const char *name,\n+\t\t\t\t\t       const char *packtmp);\n+int generated_pack_has_ext(const struct generated_pack *pack, const char *ext);\n+void generated_pack_install(struct generated_pack *pack, const char *name,\n+\t\t\t    const char *packdir, const char *packtmp);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527550","messageId":"67effcb4ea8c02198194f452296e2f16dc717599.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 25/49] builtin/repack.c: pass \"packtmp\" to `repack_promisor_objects()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:47Z","receivedAt":"2025-09-28T22:08:50Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as previous commit(s), pass the \"packtmp\" variable\nto \"repack_promisor_objects()\" as an explicit parameter of the function,\npreparing us to move this function in a following commit.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex b7826e676b..ca7658e38f 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -136,7 +136,8 @@ static int write_oid(const struct object_id *oid,\n \n static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t    const struct pack_objects_args *args,\n-\t\t\t\t    struct string_list *names)\n+\t\t\t\t    struct string_list *names,\n+\t\t\t\t    const char *packtmp)\n {\n \tstruct write_oid_context ctx;\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -1199,7 +1200,7 @@ int cmd_repack(int argc,\n \t\tstrvec_push(&cmd.args, \"--delta-islands\");\n \n \tif (pack_everything & ALL_INTO_ONE) {\n-\t\trepack_promisor_objects(repo, &po_args, &names);\n+\t\trepack_promisor_objects(repo, &po_args, &names, packtmp);\n \n \t\tif (existing_packs_has_non_kept(&existing) &&\n \t\t    delete_redundant &&\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527551","messageId":"3ba6e86f6116cc42fc8f7d31707dd514f4bc16a4.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 26/49] builtin/repack.c: remove \"repack_promisor_objects()\" from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:51Z","receivedAt":"2025-09-28T22:08:54Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that we have properly factored the portion of the builtin which is\nresponsible for repacking promisor objects, we can move that function\n(and associated dependencies) out of the builtin entirely.\n\nSimilar to previous extractions, this function is declared in repack.h,\nbut implemented in a separate repack-promisor.c file. This is done to\nseparate promisor-specific repacking functionality from generic repack\nutilities (like \"existing_packs\", and \"generated_pack\" APIs).\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile          |   1 +\n builtin/repack.c  |  95 ------------------------------------------\n meson.build       |   1 +\n repack-promisor.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++\n repack.h          |   4 ++\n 5 files changed, 108 insertions(+), 95 deletions(-)\n create mode 100644 repack-promisor.c\n\ndiff --git a/Makefile b/Makefile\nindex b220f0f7d8..0a73f8adaf 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1137,6 +1137,7 @@ LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n+LIB_OBJS += repack-promisor.o\n LIB_OBJS += replace-object.o\n LIB_OBJS += repo-settings.o\n LIB_OBJS += repository.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex ca7658e38f..18c3df7200 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,101 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-struct write_oid_context {\n-\tstruct child_process *cmd;\n-\tconst struct git_hash_algo *algop;\n-};\n-\n-/*\n- * Write oid to the given struct child_process's stdin, starting it first if\n- * necessary.\n- */\n-static int write_oid(const struct object_id *oid,\n-\t\t     struct packed_git *pack UNUSED,\n-\t\t     uint32_t pos UNUSED, void *data)\n-{\n-\tstruct write_oid_context *ctx = data;\n-\tstruct child_process *cmd = ctx->cmd;\n-\n-\tif (cmd->in == -1) {\n-\t\tif (start_command(cmd))\n-\t\t\tdie(_(\"could not start pack-objects to repack promisor objects\"));\n-\t}\n-\n-\tif (write_in_full(cmd->in, oid_to_hex(oid), ctx->algop->hexsz) < 0 ||\n-\t    write_in_full(cmd->in, \"\\n\", 1) < 0)\n-\t\tdie(_(\"failed to feed promisor objects to pack-objects\"));\n-\treturn 0;\n-}\n-\n-static void repack_promisor_objects(struct repository *repo,\n-\t\t\t\t    const struct pack_objects_args *args,\n-\t\t\t\t    struct string_list *names,\n-\t\t\t\t    const char *packtmp)\n-{\n-\tstruct write_oid_context ctx;\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tFILE *out;\n-\tstruct strbuf line = STRBUF_INIT;\n-\n-\tprepare_pack_objects(&cmd, args, packtmp);\n-\tcmd.in = -1;\n-\n-\t/*\n-\t * NEEDSWORK: Giving pack-objects only the OIDs without any ordering\n-\t * hints may result in suboptimal deltas in the resulting pack. See if\n-\t * the OIDs can be sent with fake paths such that pack-objects can use a\n-\t * {type -> existing pack order} ordering when computing deltas instead\n-\t * of a {type -> size} ordering, which may produce better deltas.\n-\t */\n-\tctx.cmd = &cmd;\n-\tctx.algop = repo->hash_algo;\n-\tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n-\n-\tif (cmd.in == -1) {\n-\t\t/* No packed objects; cmd was never started */\n-\t\tchild_process_clear(&cmd);\n-\t\treturn;\n-\t}\n-\n-\tclose(cmd.in);\n-\n-\tout = xfdopen(cmd.out, \"r\");\n-\twhile (strbuf_getline_lf(&line, out) != EOF) {\n-\t\tstruct string_list_item *item;\n-\t\tchar *promisor_name;\n-\n-\t\tif (line.len != repo->hash_algo->hexsz)\n-\t\t\tdie(_(\"repack: Expecting full hex object ID lines only from pack-objects.\"));\n-\t\titem = string_list_append(names, line.buf);\n-\n-\t\t/*\n-\t\t * pack-objects creates the .pack and .idx files, but not the\n-\t\t * .promisor file. Create the .promisor file, which is empty.\n-\t\t *\n-\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n-\t\t * .promisor files containing the ref names and associated\n-\t\t * hashes at the point of generation of the corresponding\n-\t\t * packfile, but this would not preserve their contents. Maybe\n-\t\t * concatenate the contents of all .promisor files instead of\n-\t\t * just creating a new empty file.\n-\t\t */\n-\t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n-\t\t\t\t\t  line.buf);\n-\t\twrite_promisor_file(promisor_name, NULL, 0);\n-\n-\t\titem->util = generated_pack_populate(item->string, packtmp);\n-\n-\t\tfree(promisor_name);\n-\t}\n-\n-\tfclose(out);\n-\tif (finish_command(&cmd))\n-\t\tdie(_(\"could not finish pack-objects to repack promisor objects\"));\n-\tstrbuf_release(&line);\n-}\n-\n struct pack_geometry {\n \tstruct packed_git **pack;\n \tuint32_t pack_nr, pack_alloc;\ndiff --git a/meson.build b/meson.build\nindex 8ab287a2ed..ca5603b456 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -463,6 +463,7 @@ libgit_sources = [\n   'reftable/writer.c',\n   'remote.c',\n   'repack.c',\n+  'repack-promisor.c',\n   'replace-object.c',\n   'repo-settings.c',\n   'repository.c',\ndiff --git a/repack-promisor.c b/repack-promisor.c\nnew file mode 100644\nindex 0000000000..8bf42fc715\n--- /dev/null\n+++ b/repack-promisor.c\n@@ -0,0 +1,102 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"run-command.h\"\n+#include \"hex.h\"\n+#include \"repository.h\"\n+#include \"packfile.h\"\n+#include \"path.h\"\n+#include \"pack.h\"\n+\n+struct write_oid_context {\n+\tstruct child_process *cmd;\n+\tconst struct git_hash_algo *algop;\n+};\n+\n+/*\n+ * Write oid to the given struct child_process's stdin, starting it first if\n+ * necessary.\n+ */\n+static int write_oid(const struct object_id *oid,\n+\t\t     struct packed_git *pack UNUSED,\n+\t\t     uint32_t pos UNUSED, void *data)\n+{\n+\tstruct write_oid_context *ctx = data;\n+\tstruct child_process *cmd = ctx->cmd;\n+\n+\tif (cmd->in == -1) {\n+\t\tif (start_command(cmd))\n+\t\t\tdie(_(\"could not start pack-objects to repack promisor objects\"));\n+\t}\n+\n+\tif (write_in_full(cmd->in, oid_to_hex(oid), ctx->algop->hexsz) < 0 ||\n+\t    write_in_full(cmd->in, \"\\n\", 1) < 0)\n+\t\tdie(_(\"failed to feed promisor objects to pack-objects\"));\n+\treturn 0;\n+}\n+\n+void repack_promisor_objects(struct repository *repo,\n+\t\t\t     const struct pack_objects_args *args,\n+\t\t\t     struct string_list *names, const char *packtmp)\n+{\n+\tstruct write_oid_context ctx;\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tFILE *out;\n+\tstruct strbuf line = STRBUF_INIT;\n+\n+\tprepare_pack_objects(&cmd, args, packtmp);\n+\tcmd.in = -1;\n+\n+\t/*\n+\t * NEEDSWORK: Giving pack-objects only the OIDs without any ordering\n+\t * hints may result in suboptimal deltas in the resulting pack. See if\n+\t * the OIDs can be sent with fake paths such that pack-objects can use a\n+\t * {type -> existing pack order} ordering when computing deltas instead\n+\t * of a {type -> size} ordering, which may produce better deltas.\n+\t */\n+\tctx.cmd = &cmd;\n+\tctx.algop = repo->hash_algo;\n+\tfor_each_packed_object(repo, write_oid, &ctx,\n+\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\n+\tif (cmd.in == -1) {\n+\t\t/* No packed objects; cmd was never started */\n+\t\tchild_process_clear(&cmd);\n+\t\treturn;\n+\t}\n+\n+\tclose(cmd.in);\n+\n+\tout = xfdopen(cmd.out, \"r\");\n+\twhile (strbuf_getline_lf(&line, out) != EOF) {\n+\t\tstruct string_list_item *item;\n+\t\tchar *promisor_name;\n+\n+\t\tif (line.len != repo->hash_algo->hexsz)\n+\t\t\tdie(_(\"repack: Expecting full hex object ID lines only from pack-objects.\"));\n+\t\titem = string_list_append(names, line.buf);\n+\n+\t\t/*\n+\t\t * pack-objects creates the .pack and .idx files, but not the\n+\t\t * .promisor file. Create the .promisor file, which is empty.\n+\t\t *\n+\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n+\t\t * .promisor files containing the ref names and associated\n+\t\t * hashes at the point of generation of the corresponding\n+\t\t * packfile, but this would not preserve their contents. Maybe\n+\t\t * concatenate the contents of all .promisor files instead of\n+\t\t * just creating a new empty file.\n+\t\t */\n+\t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n+\t\t\t\t\t  line.buf);\n+\t\twrite_promisor_file(promisor_name, NULL, 0);\n+\n+\t\titem->util = generated_pack_populate(item->string, packtmp);\n+\n+\t\tfree(promisor_name);\n+\t}\n+\n+\tfclose(out);\n+\tif (finish_command(&cmd))\n+\t\tdie(_(\"could not finish pack-objects to repack promisor objects\"));\n+\tstrbuf_release(&line);\n+}\ndiff --git a/repack.h b/repack.h\nindex f37eb49524..19dc4fd738 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -74,4 +74,8 @@ int generated_pack_has_ext(const struct generated_pack *pack, const char *ext);\n void generated_pack_install(struct generated_pack *pack, const char *name,\n \t\t\t    const char *packdir, const char *packtmp);\n \n+void repack_promisor_objects(struct repository *repo,\n+\t\t\t     const struct pack_objects_args *args,\n+\t\t\t     struct string_list *names, const char *packtmp);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527552","messageId":"dd67366476172eb1b5b0d2bb2a4dfe92b96b83ba.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 27/49] builtin/repack.c: rename various pack_geometry functions","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:54Z","receivedAt":"2025-09-28T22:08:58Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Rename functions which work with 'struct pack_geometry' to begin with\n\"pack_geometry_\".\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 52 ++++++++++++++++++++++++------------------------\n 1 file changed, 26 insertions(+), 26 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 18c3df7200..2ce1ae3364 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -115,17 +115,17 @@ struct pack_geometry {\n \tint split_factor;\n };\n \n-static uint32_t geometry_pack_weight(struct packed_git *p)\n+static uint32_t pack_geometry_weight(struct packed_git *p)\n {\n \tif (open_pack_index(p))\n \t\tdie(_(\"cannot open index for %s\"), p->pack_name);\n \treturn p->num_objects;\n }\n \n-static int geometry_cmp(const void *va, const void *vb)\n+static int pack_geometry_cmp(const void *va, const void *vb)\n {\n-\tuint32_t aw = geometry_pack_weight(*(struct packed_git **)va),\n-\t\t bw = geometry_pack_weight(*(struct packed_git **)vb);\n+\tuint32_t aw = pack_geometry_weight(*(struct packed_git **)va),\n+\t\t bw = pack_geometry_weight(*(struct packed_git **)vb);\n \n \tif (aw < bw)\n \t\treturn -1;\n@@ -134,7 +134,7 @@ static int geometry_cmp(const void *va, const void *vb)\n \treturn 0;\n }\n \n-static void init_pack_geometry(struct pack_geometry *geometry,\n+static void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\t       struct existing_packs *existing,\n \t\t\t       const struct pack_objects_args *args)\n {\n@@ -184,11 +184,11 @@ static void init_pack_geometry(struct pack_geometry *geometry,\n \t\tgeometry->pack_nr++;\n \t}\n \n-\tQSORT(geometry->pack, geometry->pack_nr, geometry_cmp);\n+\tQSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);\n \tstrbuf_release(&buf);\n }\n \n-static void split_pack_geometry(struct pack_geometry *geometry)\n+static void pack_geometry_split(struct pack_geometry *geometry)\n {\n \tuint32_t i;\n \tuint32_t split;\n@@ -208,13 +208,13 @@ static void split_pack_geometry(struct pack_geometry *geometry)\n \t\tstruct packed_git *prev = geometry->pack[i - 1];\n \n \t\tif (unsigned_mult_overflows(geometry->split_factor,\n-\t\t\t\t\t    geometry_pack_weight(prev)))\n+\t\t\t\t\t    pack_geometry_weight(prev)))\n \t\t\tdie(_(\"pack %s too large to consider in geometric \"\n \t\t\t      \"progression\"),\n \t\t\t    prev->pack_name);\n \n-\t\tif (geometry_pack_weight(ours) <\n-\t\t    geometry->split_factor * geometry_pack_weight(prev))\n+\t\tif (pack_geometry_weight(ours) <\n+\t\t    geometry->split_factor * pack_geometry_weight(prev))\n \t\t\tbreak;\n \t}\n \n@@ -242,9 +242,9 @@ static void split_pack_geometry(struct pack_geometry *geometry)\n \tfor (i = 0; i < split; i++) {\n \t\tstruct packed_git *p = geometry->pack[i];\n \n-\t\tif (unsigned_add_overflows(total_size, geometry_pack_weight(p)))\n+\t\tif (unsigned_add_overflows(total_size, pack_geometry_weight(p)))\n \t\t\tdie(_(\"pack %s too large to roll up\"), p->pack_name);\n-\t\ttotal_size += geometry_pack_weight(p);\n+\t\ttotal_size += pack_geometry_weight(p);\n \t}\n \tfor (i = split; i < geometry->pack_nr; i++) {\n \t\tstruct packed_git *ours = geometry->pack[i];\n@@ -253,15 +253,15 @@ static void split_pack_geometry(struct pack_geometry *geometry)\n \t\t\t\t\t    total_size))\n \t\t\tdie(_(\"pack %s too large to roll up\"), ours->pack_name);\n \n-\t\tif (geometry_pack_weight(ours) <\n+\t\tif (pack_geometry_weight(ours) <\n \t\t    geometry->split_factor * total_size) {\n \t\t\tif (unsigned_add_overflows(total_size,\n-\t\t\t\t\t\t   geometry_pack_weight(ours)))\n+\t\t\t\t\t\t   pack_geometry_weight(ours)))\n \t\t\t\tdie(_(\"pack %s too large to roll up\"),\n \t\t\t\t    ours->pack_name);\n \n \t\t\tsplit++;\n-\t\t\ttotal_size += geometry_pack_weight(ours);\n+\t\t\ttotal_size += pack_geometry_weight(ours);\n \t\t} else\n \t\t\tbreak;\n \t}\n@@ -269,7 +269,7 @@ static void split_pack_geometry(struct pack_geometry *geometry)\n \tgeometry->split = split;\n }\n \n-static struct packed_git *get_preferred_pack(struct pack_geometry *geometry)\n+static struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)\n {\n \tuint32_t i;\n \n@@ -304,9 +304,9 @@ static struct packed_git *get_preferred_pack(struct pack_geometry *geometry)\n \treturn NULL;\n }\n \n-static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n-\t\t\t\t\t    struct string_list *names,\n-\t\t\t\t\t    struct existing_packs *existing)\n+static void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n+\t\t\t\t\t   struct string_list *names,\n+\t\t\t\t\t   struct existing_packs *existing)\n {\n \tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -332,7 +332,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n \tstrbuf_release(&buf);\n }\n \n-static void free_pack_geometry(struct pack_geometry *geometry)\n+static void pack_geometry_release(struct pack_geometry *geometry)\n {\n \tif (!geometry)\n \t\treturn;\n@@ -599,7 +599,7 @@ static int write_midx_included_packs(struct string_list *include,\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n-\tstruct packed_git *preferred = get_preferred_pack(geometry);\n+\tstruct packed_git *preferred = pack_geometry_preferred_pack(geometry);\n \tFILE *in;\n \tint ret;\n \n@@ -1063,8 +1063,8 @@ int cmd_repack(int argc,\n \tif (geometry.split_factor) {\n \t\tif (pack_everything)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--geometric\", \"-A/-a\");\n-\t\tinit_pack_geometry(&geometry, &existing, &po_args);\n-\t\tsplit_pack_geometry(&geometry);\n+\t\tpack_geometry_init(&geometry, &existing, &po_args);\n+\t\tpack_geometry_split(&geometry);\n \t}\n \n \tprepare_pack_objects(&cmd, &po_args, packtmp);\n@@ -1324,8 +1324,8 @@ int cmd_repack(int argc,\n \t\texisting_packs_remove_redundant(&existing, packdir);\n \n \t\tif (geometry.split_factor)\n-\t\t\tgeometry_remove_redundant_packs(&geometry, &names,\n-\t\t\t\t\t\t\t&existing);\n+\t\t\tpack_geometry_remove_redundant(&geometry, &names,\n+\t\t\t\t\t\t       &existing);\n \t\tif (show_progress)\n \t\t\topts |= PRUNE_PACKED_VERBOSE;\n \t\tprune_packed_objects(opts);\n@@ -1352,7 +1352,7 @@ int cmd_repack(int argc,\n \tstring_list_clear(&keep_pack_list, 0);\n \tstring_list_clear(&names, 1);\n \texisting_packs_release(&existing);\n-\tfree_pack_geometry(&geometry);\n+\tpack_geometry_release(&geometry);\n \tfor (size_t i = 0; i < midx_pack_names_nr; i++)\n \t\tfree(midx_pack_names[i]);\n \tfree(midx_pack_names);\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527553","messageId":"8408fd8474eab618e37a96203084bd2b58e4eaea.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 28/49] builtin/repack.c: pass 'pack_kept_objects' to `pack_geometry_init()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:08:58Z","receivedAt":"2025-09-28T22:09:01Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Prepare to move pack_geometry-related APIs to their own compilation unit\nby passing in the static \"pack_kept_objects\" variable directly as a\nparameter to the 'pack_geometry_init()' function.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 2ce1ae3364..60dce45556 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -136,7 +136,8 @@ static int pack_geometry_cmp(const void *va, const void *vb)\n \n static void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\t       struct existing_packs *existing,\n-\t\t\t       const struct pack_objects_args *args)\n+\t\t\t       const struct pack_objects_args *args,\n+\t\t\t       int pack_kept_objects)\n {\n \tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n@@ -1063,7 +1064,8 @@ int cmd_repack(int argc,\n \tif (geometry.split_factor) {\n \t\tif (pack_everything)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--geometric\", \"-A/-a\");\n-\t\tpack_geometry_init(&geometry, &existing, &po_args);\n+\t\tpack_geometry_init(&geometry, &existing, &po_args,\n+\t\t\t\t   pack_kept_objects);\n \t\tpack_geometry_split(&geometry);\n \t}\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527554","messageId":"76be98b386e3dd5937114e3b4488fada21a9ae7a.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 29/49] builtin/repack.c: pass 'packdir' to `pack_geometry_remove_redundant()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:02Z","receivedAt":"2025-09-28T22:09:05Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"For similar reasons as the preceding commit, pass the \"packdir\" variable\ndirectly to `pack_geometry_remove_redundant()` as a parameter to the\nfunction.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 60dce45556..9e2523d948 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -307,7 +307,8 @@ static struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geo\n \n static void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n \t\t\t\t\t   struct string_list *names,\n-\t\t\t\t\t   struct existing_packs *existing)\n+\t\t\t\t\t   struct existing_packs *existing,\n+\t\t\t\t\t   const char *packdir)\n {\n \tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -1327,7 +1328,7 @@ int cmd_repack(int argc,\n \n \t\tif (geometry.split_factor)\n \t\t\tpack_geometry_remove_redundant(&geometry, &names,\n-\t\t\t\t\t\t       &existing);\n+\t\t\t\t\t\t       &existing, packdir);\n \t\tif (show_progress)\n \t\t\topts |= PRUNE_PACKED_VERBOSE;\n \t\tprune_packed_objects(opts);\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527555","messageId":"54a2feaea9a88c66aeffe1fa0146ed5a811ae737.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 30/49] repack: remove pack_geometry API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:06Z","receivedAt":"2025-09-28T22:09:09Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that the pack_geometry API is fully factored and isolated from the\nrest of the builtin, declare it within repack.h and move its\nimplementation to \"repack-geometry.c\" as a separate component.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile          |   1 +\n builtin/repack.c  | 235 ----------------------------------------------\n meson.build       |   1 +\n repack-geometry.c | 233 +++++++++++++++++++++++++++++++++++++++++++++\n repack.h          |  20 ++++\n 5 files changed, 255 insertions(+), 235 deletions(-)\n create mode 100644 repack-geometry.c\n\ndiff --git a/Makefile b/Makefile\nindex 0a73f8adaf..a6da12a326 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1137,6 +1137,7 @@ LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n+LIB_OBJS += repack-geometry.o\n LIB_OBJS += repack-promisor.o\n LIB_OBJS += replace-object.o\n LIB_OBJS += repo-settings.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 9e2523d948..b49e2dab9a 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,241 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-struct pack_geometry {\n-\tstruct packed_git **pack;\n-\tuint32_t pack_nr, pack_alloc;\n-\tuint32_t split;\n-\n-\tint split_factor;\n-};\n-\n-static uint32_t pack_geometry_weight(struct packed_git *p)\n-{\n-\tif (open_pack_index(p))\n-\t\tdie(_(\"cannot open index for %s\"), p->pack_name);\n-\treturn p->num_objects;\n-}\n-\n-static int pack_geometry_cmp(const void *va, const void *vb)\n-{\n-\tuint32_t aw = pack_geometry_weight(*(struct packed_git **)va),\n-\t\t bw = pack_geometry_weight(*(struct packed_git **)vb);\n-\n-\tif (aw < bw)\n-\t\treturn -1;\n-\tif (aw > bw)\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n-static void pack_geometry_init(struct pack_geometry *geometry,\n-\t\t\t       struct existing_packs *existing,\n-\t\t\t       const struct pack_objects_args *args,\n-\t\t\t       int pack_kept_objects)\n-{\n-\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n-\tstruct packed_git *p;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n-\t\tif (args->local && !p->pack_local)\n-\t\t\t/*\n-\t\t\t * When asked to only repack local packfiles we skip\n-\t\t\t * over any packfiles that are borrowed from alternate\n-\t\t\t * object directories.\n-\t\t\t */\n-\t\t\tcontinue;\n-\n-\t\tif (!pack_kept_objects) {\n-\t\t\t/*\n-\t\t\t * Any pack that has its pack_keep bit set will\n-\t\t\t * appear in existing->kept_packs below, but\n-\t\t\t * this saves us from doing a more expensive\n-\t\t\t * check.\n-\t\t\t */\n-\t\t\tif (p->pack_keep)\n-\t\t\t\tcontinue;\n-\n-\t\t\t/*\n-\t\t\t * The pack may be kept via the --keep-pack\n-\t\t\t * option; check 'existing->kept_packs' to\n-\t\t\t * determine whether to ignore it.\n-\t\t\t */\n-\t\t\tstrbuf_reset(&buf);\n-\t\t\tstrbuf_addstr(&buf, pack_basename(p));\n-\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\t\t\tif (string_list_has_string(&existing->kept_packs, buf.buf))\n-\t\t\t\tcontinue;\n-\t\t}\n-\t\tif (p->is_cruft)\n-\t\t\tcontinue;\n-\n-\t\tALLOC_GROW(geometry->pack,\n-\t\t\t   geometry->pack_nr + 1,\n-\t\t\t   geometry->pack_alloc);\n-\n-\t\tgeometry->pack[geometry->pack_nr] = p;\n-\t\tgeometry->pack_nr++;\n-\t}\n-\n-\tQSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);\n-\tstrbuf_release(&buf);\n-}\n-\n-static void pack_geometry_split(struct pack_geometry *geometry)\n-{\n-\tuint32_t i;\n-\tuint32_t split;\n-\toff_t total_size = 0;\n-\n-\tif (!geometry->pack_nr) {\n-\t\tgeometry->split = geometry->pack_nr;\n-\t\treturn;\n-\t}\n-\n-\t/*\n-\t * First, count the number of packs (in descending order of size) which\n-\t * already form a geometric progression.\n-\t */\n-\tfor (i = geometry->pack_nr - 1; i > 0; i--) {\n-\t\tstruct packed_git *ours = geometry->pack[i];\n-\t\tstruct packed_git *prev = geometry->pack[i - 1];\n-\n-\t\tif (unsigned_mult_overflows(geometry->split_factor,\n-\t\t\t\t\t    pack_geometry_weight(prev)))\n-\t\t\tdie(_(\"pack %s too large to consider in geometric \"\n-\t\t\t      \"progression\"),\n-\t\t\t    prev->pack_name);\n-\n-\t\tif (pack_geometry_weight(ours) <\n-\t\t    geometry->split_factor * pack_geometry_weight(prev))\n-\t\t\tbreak;\n-\t}\n-\n-\tsplit = i;\n-\n-\tif (split) {\n-\t\t/*\n-\t\t * Move the split one to the right, since the top element in the\n-\t\t * last-compared pair can't be in the progression. Only do this\n-\t\t * when we split in the middle of the array (otherwise if we got\n-\t\t * to the end, then the split is in the right place).\n-\t\t */\n-\t\tsplit++;\n-\t}\n-\n-\t/*\n-\t * Then, anything to the left of 'split' must be in a new pack. But,\n-\t * creating that new pack may cause packs in the heavy half to no longer\n-\t * form a geometric progression.\n-\t *\n-\t * Compute an expected size of the new pack, and then determine how many\n-\t * packs in the heavy half need to be joined into it (if any) to restore\n-\t * the geometric progression.\n-\t */\n-\tfor (i = 0; i < split; i++) {\n-\t\tstruct packed_git *p = geometry->pack[i];\n-\n-\t\tif (unsigned_add_overflows(total_size, pack_geometry_weight(p)))\n-\t\t\tdie(_(\"pack %s too large to roll up\"), p->pack_name);\n-\t\ttotal_size += pack_geometry_weight(p);\n-\t}\n-\tfor (i = split; i < geometry->pack_nr; i++) {\n-\t\tstruct packed_git *ours = geometry->pack[i];\n-\n-\t\tif (unsigned_mult_overflows(geometry->split_factor,\n-\t\t\t\t\t    total_size))\n-\t\t\tdie(_(\"pack %s too large to roll up\"), ours->pack_name);\n-\n-\t\tif (pack_geometry_weight(ours) <\n-\t\t    geometry->split_factor * total_size) {\n-\t\t\tif (unsigned_add_overflows(total_size,\n-\t\t\t\t\t\t   pack_geometry_weight(ours)))\n-\t\t\t\tdie(_(\"pack %s too large to roll up\"),\n-\t\t\t\t    ours->pack_name);\n-\n-\t\t\tsplit++;\n-\t\t\ttotal_size += pack_geometry_weight(ours);\n-\t\t} else\n-\t\t\tbreak;\n-\t}\n-\n-\tgeometry->split = split;\n-}\n-\n-static struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)\n-{\n-\tuint32_t i;\n-\n-\tif (!geometry) {\n-\t\t/*\n-\t\t * No geometry means either an all-into-one repack (in which\n-\t\t * case there is only one pack left and it is the largest) or an\n-\t\t * incremental one.\n-\t\t *\n-\t\t * If repacking incrementally, then we could check the size of\n-\t\t * all packs to determine which should be preferred, but leave\n-\t\t * this for later.\n-\t\t */\n-\t\treturn NULL;\n-\t}\n-\tif (geometry->split == geometry->pack_nr)\n-\t\treturn NULL;\n-\n-\t/*\n-\t * The preferred pack is the largest pack above the split line. In\n-\t * other words, it is the largest pack that does not get rolled up in\n-\t * the geometric repack.\n-\t */\n-\tfor (i = geometry->pack_nr; i > geometry->split; i--)\n-\t\t/*\n-\t\t * A pack that is not local would never be included in a\n-\t\t * multi-pack index. We thus skip over any non-local packs.\n-\t\t */\n-\t\tif (geometry->pack[i - 1]->pack_local)\n-\t\t\treturn geometry->pack[i - 1];\n-\n-\treturn NULL;\n-}\n-\n-static void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n-\t\t\t\t\t   struct string_list *names,\n-\t\t\t\t\t   struct existing_packs *existing,\n-\t\t\t\t\t   const char *packdir)\n-{\n-\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tuint32_t i;\n-\n-\tfor (i = 0; i < geometry->split; i++) {\n-\t\tstruct packed_git *p = geometry->pack[i];\n-\t\tif (string_list_has_string(names, hash_to_hex_algop(p->hash,\n-\t\t\t\t\t\t\t\t    algop)))\n-\t\t\tcontinue;\n-\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addstr(&buf, pack_basename(p));\n-\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\t\tif ((p->pack_keep) ||\n-\t\t    (string_list_has_string(&existing->kept_packs, buf.buf)))\n-\t\t\tcontinue;\n-\n-\t\trepack_remove_redundant_pack(existing->repo, packdir, buf.buf);\n-\t}\n-\n-\tstrbuf_release(&buf);\n-}\n-\n-static void pack_geometry_release(struct pack_geometry *geometry)\n-{\n-\tif (!geometry)\n-\t\treturn;\n-\n-\tfree(geometry->pack);\n-}\n-\n static int midx_has_unknown_packs(char **midx_pack_names,\n \t\t\t\t  size_t midx_pack_names_nr,\n \t\t\t\t  struct string_list *include,\ndiff --git a/meson.build b/meson.build\nindex ca5603b456..c929ad6f7e 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -463,6 +463,7 @@ libgit_sources = [\n   'reftable/writer.c',\n   'remote.c',\n   'repack.c',\n+  'repack-geometry.c',\n   'repack-promisor.c',\n   'replace-object.c',\n   'repo-settings.c',\ndiff --git a/repack-geometry.c b/repack-geometry.c\nnew file mode 100644\nindex 0000000000..a879f2fe49\n--- /dev/null\n+++ b/repack-geometry.c\n@@ -0,0 +1,233 @@\n+#define DISABLE_SIGN_COMPARE_WARNINGS\n+\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"hex.h\"\n+#include \"packfile.h\"\n+\n+static uint32_t pack_geometry_weight(struct packed_git *p)\n+{\n+\tif (open_pack_index(p))\n+\t\tdie(_(\"cannot open index for %s\"), p->pack_name);\n+\treturn p->num_objects;\n+}\n+\n+static int pack_geometry_cmp(const void *va, const void *vb)\n+{\n+\tuint32_t aw = pack_geometry_weight(*(struct packed_git **)va),\n+\t\t bw = pack_geometry_weight(*(struct packed_git **)vb);\n+\n+\tif (aw < bw)\n+\t\treturn -1;\n+\tif (aw > bw)\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+void pack_geometry_init(struct pack_geometry *geometry,\n+\t\t\tstruct existing_packs *existing,\n+\t\t\tconst struct pack_objects_args *args,\n+\t\t\tint pack_kept_objects)\n+{\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n+\tstruct packed_git *p;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n+\t\tif (args->local && !p->pack_local)\n+\t\t\t/*\n+\t\t\t * When asked to only repack local packfiles we skip\n+\t\t\t * over any packfiles that are borrowed from alternate\n+\t\t\t * object directories.\n+\t\t\t */\n+\t\t\tcontinue;\n+\n+\t\tif (!pack_kept_objects) {\n+\t\t\t/*\n+\t\t\t * Any pack that has its pack_keep bit set will\n+\t\t\t * appear in existing->kept_packs below, but\n+\t\t\t * this saves us from doing a more expensive\n+\t\t\t * check.\n+\t\t\t */\n+\t\t\tif (p->pack_keep)\n+\t\t\t\tcontinue;\n+\n+\t\t\t/*\n+\t\t\t * The pack may be kept via the --keep-pack\n+\t\t\t * option; check 'existing->kept_packs' to\n+\t\t\t * determine whether to ignore it.\n+\t\t\t */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addstr(&buf, pack_basename(p));\n+\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\t\t\tif (string_list_has_string(&existing->kept_packs, buf.buf))\n+\t\t\t\tcontinue;\n+\t\t}\n+\t\tif (p->is_cruft)\n+\t\t\tcontinue;\n+\n+\t\tALLOC_GROW(geometry->pack,\n+\t\t\t   geometry->pack_nr + 1,\n+\t\t\t   geometry->pack_alloc);\n+\n+\t\tgeometry->pack[geometry->pack_nr] = p;\n+\t\tgeometry->pack_nr++;\n+\t}\n+\n+\tQSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);\n+\tstrbuf_release(&buf);\n+}\n+\n+void pack_geometry_split(struct pack_geometry *geometry)\n+{\n+\tuint32_t i;\n+\tuint32_t split;\n+\toff_t total_size = 0;\n+\n+\tif (!geometry->pack_nr) {\n+\t\tgeometry->split = geometry->pack_nr;\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * First, count the number of packs (in descending order of size) which\n+\t * already form a geometric progression.\n+\t */\n+\tfor (i = geometry->pack_nr - 1; i > 0; i--) {\n+\t\tstruct packed_git *ours = geometry->pack[i];\n+\t\tstruct packed_git *prev = geometry->pack[i - 1];\n+\n+\t\tif (unsigned_mult_overflows(geometry->split_factor,\n+\t\t\t\t\t    pack_geometry_weight(prev)))\n+\t\t\tdie(_(\"pack %s too large to consider in geometric \"\n+\t\t\t      \"progression\"),\n+\t\t\t    prev->pack_name);\n+\n+\t\tif (pack_geometry_weight(ours) <\n+\t\t    geometry->split_factor * pack_geometry_weight(prev))\n+\t\t\tbreak;\n+\t}\n+\n+\tsplit = i;\n+\n+\tif (split) {\n+\t\t/*\n+\t\t * Move the split one to the right, since the top element in the\n+\t\t * last-compared pair can't be in the progression. Only do this\n+\t\t * when we split in the middle of the array (otherwise if we got\n+\t\t * to the end, then the split is in the right place).\n+\t\t */\n+\t\tsplit++;\n+\t}\n+\n+\t/*\n+\t * Then, anything to the left of 'split' must be in a new pack. But,\n+\t * creating that new pack may cause packs in the heavy half to no longer\n+\t * form a geometric progression.\n+\t *\n+\t * Compute an expected size of the new pack, and then determine how many\n+\t * packs in the heavy half need to be joined into it (if any) to restore\n+\t * the geometric progression.\n+\t */\n+\tfor (i = 0; i < split; i++) {\n+\t\tstruct packed_git *p = geometry->pack[i];\n+\n+\t\tif (unsigned_add_overflows(total_size, pack_geometry_weight(p)))\n+\t\t\tdie(_(\"pack %s too large to roll up\"), p->pack_name);\n+\t\ttotal_size += pack_geometry_weight(p);\n+\t}\n+\tfor (i = split; i < geometry->pack_nr; i++) {\n+\t\tstruct packed_git *ours = geometry->pack[i];\n+\n+\t\tif (unsigned_mult_overflows(geometry->split_factor,\n+\t\t\t\t\t    total_size))\n+\t\t\tdie(_(\"pack %s too large to roll up\"), ours->pack_name);\n+\n+\t\tif (pack_geometry_weight(ours) <\n+\t\t    geometry->split_factor * total_size) {\n+\t\t\tif (unsigned_add_overflows(total_size,\n+\t\t\t\t\t\t   pack_geometry_weight(ours)))\n+\t\t\t\tdie(_(\"pack %s too large to roll up\"),\n+\t\t\t\t    ours->pack_name);\n+\n+\t\t\tsplit++;\n+\t\t\ttotal_size += pack_geometry_weight(ours);\n+\t\t} else\n+\t\t\tbreak;\n+\t}\n+\n+\tgeometry->split = split;\n+}\n+\n+struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)\n+{\n+\tuint32_t i;\n+\n+\tif (!geometry) {\n+\t\t/*\n+\t\t * No geometry means either an all-into-one repack (in which\n+\t\t * case there is only one pack left and it is the largest) or an\n+\t\t * incremental one.\n+\t\t *\n+\t\t * If repacking incrementally, then we could check the size of\n+\t\t * all packs to determine which should be preferred, but leave\n+\t\t * this for later.\n+\t\t */\n+\t\treturn NULL;\n+\t}\n+\tif (geometry->split == geometry->pack_nr)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * The preferred pack is the largest pack above the split line. In\n+\t * other words, it is the largest pack that does not get rolled up in\n+\t * the geometric repack.\n+\t */\n+\tfor (i = geometry->pack_nr; i > geometry->split; i--)\n+\t\t/*\n+\t\t * A pack that is not local would never be included in a\n+\t\t * multi-pack index. We thus skip over any non-local packs.\n+\t\t */\n+\t\tif (geometry->pack[i - 1]->pack_local)\n+\t\t\treturn geometry->pack[i - 1];\n+\n+\treturn NULL;\n+}\n+\n+void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n+\t\t\t\t    struct string_list *names,\n+\t\t\t\t    struct existing_packs *existing,\n+\t\t\t\t    const char *packdir)\n+{\n+\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tuint32_t i;\n+\n+\tfor (i = 0; i < geometry->split; i++) {\n+\t\tstruct packed_git *p = geometry->pack[i];\n+\t\tif (string_list_has_string(names, hash_to_hex_algop(p->hash,\n+\t\t\t\t\t\t\t\t    algop)))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, pack_basename(p));\n+\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\t\tif ((p->pack_keep) ||\n+\t\t    (string_list_has_string(&existing->kept_packs, buf.buf)))\n+\t\t\tcontinue;\n+\n+\t\trepack_remove_redundant_pack(existing->repo, packdir, buf.buf);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+void pack_geometry_release(struct pack_geometry *geometry)\n+{\n+\tif (!geometry)\n+\t\treturn;\n+\n+\tfree(geometry->pack);\n+}\ndiff --git a/repack.h b/repack.h\nindex 19dc4fd738..cea7969ae4 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -78,4 +78,24 @@ void repack_promisor_objects(struct repository *repo,\n \t\t\t     const struct pack_objects_args *args,\n \t\t\t     struct string_list *names, const char *packtmp);\n \n+struct pack_geometry {\n+\tstruct packed_git **pack;\n+\tuint32_t pack_nr, pack_alloc;\n+\tuint32_t split;\n+\n+\tint split_factor;\n+};\n+\n+void pack_geometry_init(struct pack_geometry *geometry,\n+\t\t\tstruct existing_packs *existing,\n+\t\t\tconst struct pack_objects_args *args,\n+\t\t\tint pack_kept_objects);\n+void pack_geometry_split(struct pack_geometry *geometry);\n+struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry);\n+void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n+\t\t\t\t    struct string_list *names,\n+\t\t\t\t    struct existing_packs *existing,\n+\t\t\t\t    const char *packdir);\n+void pack_geometry_release(struct pack_geometry *geometry);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527556","messageId":"06ca4ab5b7b7d062bf552505de74db29b185a99f.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 31/49] builtin/repack.c: remove ref snapshotting from builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:10Z","receivedAt":"2025-09-28T22:09:13Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"When writing a MIDX, 'git repack' takes a snapshot of the repository's\nreferences and writes the result out to a file, which it then passes to\n'git multi-pack-index write' via the '--refs-snapshot'.\n\nThis is done in order to make bitmap selections with respect to what we\nare packing, thus avoiding a race where an incoming reference update\ncauses us to try and write a bitmap for a commit not present in the\nMIDX.\n\nExtract this functionality out into a new repack-midx.c compilation\nunit, and expose the necessary functions via the repack.h API.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile         |  1 +\n builtin/repack.c | 68 ------------------------------------------\n meson.build      |  1 +\n repack-midx.c    | 77 ++++++++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  4 +++\n 5 files changed, 83 insertions(+), 68 deletions(-)\n create mode 100644 repack-midx.c\n\ndiff --git a/Makefile b/Makefile\nindex a6da12a326..3360743afb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1138,6 +1138,7 @@ LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n LIB_OBJS += repack-geometry.o\n+LIB_OBJS += repack-midx.o\n LIB_OBJS += repack-promisor.o\n LIB_OBJS += replace-object.o\n LIB_OBJS += repo-settings.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex b49e2dab9a..5a1f1cb562 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -178,74 +178,6 @@ static int midx_has_unknown_packs(char **midx_pack_names,\n \treturn 0;\n }\n \n-struct midx_snapshot_ref_data {\n-\tstruct repository *repo;\n-\tstruct tempfile *f;\n-\tstruct oidset seen;\n-\tint preferred;\n-};\n-\n-static int midx_snapshot_ref_one(const char *refname UNUSED,\n-\t\t\t\t const char *referent UNUSED,\n-\t\t\t\t const struct object_id *oid,\n-\t\t\t\t int flag UNUSED, void *_data)\n-{\n-\tstruct midx_snapshot_ref_data *data = _data;\n-\tstruct object_id peeled;\n-\n-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n-\t\toid = &peeled;\n-\n-\tif (oidset_insert(&data->seen, oid))\n-\t\treturn 0; /* already seen */\n-\n-\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n-\t\treturn 0;\n-\n-\tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n-\t\toid_to_hex(oid));\n-\n-\treturn 0;\n-}\n-\n-static void midx_snapshot_refs(struct repository *repo, struct tempfile *f)\n-{\n-\tstruct midx_snapshot_ref_data data;\n-\tconst struct string_list *preferred = bitmap_preferred_tips(repo);\n-\n-\tdata.repo = repo;\n-\tdata.f = f;\n-\tdata.preferred = 0;\n-\toidset_init(&data.seen, 0);\n-\n-\tif (!fdopen_tempfile(f, \"w\"))\n-\t\t die(_(\"could not open tempfile %s for writing\"),\n-\t\t     get_tempfile_path(f));\n-\n-\tif (preferred) {\n-\t\tstruct string_list_item *item;\n-\n-\t\tdata.preferred = 1;\n-\t\tfor_each_string_list_item(item, preferred)\n-\t\t\trefs_for_each_ref_in(get_main_ref_store(repo),\n-\t\t\t\t\t     item->string,\n-\t\t\t\t\t     midx_snapshot_ref_one, &data);\n-\t\tdata.preferred = 0;\n-\t}\n-\n-\trefs_for_each_ref(get_main_ref_store(repo),\n-\t\t\t  midx_snapshot_ref_one, &data);\n-\n-\tif (close_tempfile_gently(f)) {\n-\t\tint save_errno = errno;\n-\t\tdelete_tempfile(&f);\n-\t\terrno = save_errno;\n-\t\tdie_errno(_(\"could not close refs snapshot tempfile\"));\n-\t}\n-\n-\toidset_clear(&data.seen);\n-}\n-\n static void midx_included_packs(struct string_list *include,\n \t\t\t\tstruct existing_packs *existing,\n \t\t\t\tchar **midx_pack_names,\ndiff --git a/meson.build b/meson.build\nindex c929ad6f7e..42171d1851 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -464,6 +464,7 @@ libgit_sources = [\n   'remote.c',\n   'repack.c',\n   'repack-geometry.c',\n+  'repack-midx.c',\n   'repack-promisor.c',\n   'replace-object.c',\n   'repo-settings.c',\ndiff --git a/repack-midx.c b/repack-midx.c\nnew file mode 100644\nindex 0000000000..354df729a5\n--- /dev/null\n+++ b/repack-midx.c\n@@ -0,0 +1,77 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"hash.h\"\n+#include \"hex.h\"\n+#include \"odb.h\"\n+#include \"oidset.h\"\n+#include \"pack-bitmap.h\"\n+#include \"refs.h\"\n+#include \"tempfile.h\"\n+\n+struct midx_snapshot_ref_data {\n+\tstruct repository *repo;\n+\tstruct tempfile *f;\n+\tstruct oidset seen;\n+\tint preferred;\n+};\n+\n+static int midx_snapshot_ref_one(const char *refname UNUSED,\n+\t\t\t\t const char *referent UNUSED,\n+\t\t\t\t const struct object_id *oid,\n+\t\t\t\t int flag UNUSED, void *_data)\n+{\n+\tstruct midx_snapshot_ref_data *data = _data;\n+\tstruct object_id peeled;\n+\n+\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n+\t\toid = &peeled;\n+\n+\tif (oidset_insert(&data->seen, oid))\n+\t\treturn 0; /* already seen */\n+\n+\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n+\t\toid_to_hex(oid));\n+\n+\treturn 0;\n+}\n+\n+void midx_snapshot_refs(struct repository *repo, struct tempfile *f)\n+{\n+\tstruct midx_snapshot_ref_data data;\n+\tconst struct string_list *preferred = bitmap_preferred_tips(repo);\n+\n+\tdata.repo = repo;\n+\tdata.f = f;\n+\tdata.preferred = 0;\n+\toidset_init(&data.seen, 0);\n+\n+\tif (!fdopen_tempfile(f, \"w\"))\n+\t\t die(_(\"could not open tempfile %s for writing\"),\n+\t\t     get_tempfile_path(f));\n+\n+\tif (preferred) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tdata.preferred = 1;\n+\t\tfor_each_string_list_item(item, preferred)\n+\t\t\trefs_for_each_ref_in(get_main_ref_store(repo),\n+\t\t\t\t\t     item->string,\n+\t\t\t\t\t     midx_snapshot_ref_one, &data);\n+\t\tdata.preferred = 0;\n+\t}\n+\n+\trefs_for_each_ref(get_main_ref_store(repo),\n+\t\t\t  midx_snapshot_ref_one, &data);\n+\n+\tif (close_tempfile_gently(f)) {\n+\t\tint save_errno = errno;\n+\t\tdelete_tempfile(&f);\n+\t\terrno = save_errno;\n+\t\tdie_errno(_(\"could not close refs snapshot tempfile\"));\n+\t}\n+\n+\toidset_clear(&data.seen);\n+}\ndiff --git a/repack.h b/repack.h\nindex cea7969ae4..803e129224 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -98,4 +98,8 @@ void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n \t\t\t\t    const char *packdir);\n void pack_geometry_release(struct pack_geometry *geometry);\n \n+struct tempfile;\n+\n+void midx_snapshot_refs(struct repository *repo, struct tempfile *f);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527557","messageId":"3b7e204a40609264898971bf3522428696553ce0.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 32/49] builtin/repack.c: extract opts struct for 'write_midx_included_packs()'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:14Z","receivedAt":"2025-09-28T22:09:17Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The function 'write_midx_included_packs()', which is responsible for\nwriting a new MIDX with a given set of included packs, currently takes a\nlist of six arguments.\n\nIn order to extract this function out of the builtin, we have to pass\nin a few additional parameters, like 'midx_must_contain_cruft' and\n'packdir', which are currently declared as static variables within the\nbuiltin/repack.c compilation unit.\n\nInstead of adding additional parameters to `write_midx_included_packs()`\nextract out an \"opts\" struct that names these parameters, and pass a\npointer to that, making it less cumbersome to add additional parameters.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 52 +++++++++++++++++++++++++++++++-----------------\n 1 file changed, 34 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 5a1f1cb562..e4b8aa2c6f 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,6 +107,17 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n+struct repack_write_midx_opts {\n+\tstruct string_list *include;\n+\tstruct pack_geometry *geometry;\n+\tstruct string_list *names;\n+\tconst char *refs_snapshot;\n+\tconst char *packdir;\n+\tint show_progress;\n+\tint write_bitmaps;\n+\tint midx_must_contain_cruft;\n+};\n+\n static int midx_has_unknown_packs(char **midx_pack_names,\n \t\t\t\t  size_t midx_pack_names_nr,\n \t\t\t\t  struct string_list *include,\n@@ -290,19 +301,15 @@ static void midx_included_packs(struct string_list *include,\n \tstrbuf_release(&buf);\n }\n \n-static int write_midx_included_packs(struct string_list *include,\n-\t\t\t\t     struct pack_geometry *geometry,\n-\t\t\t\t     struct string_list *names,\n-\t\t\t\t     const char *refs_snapshot,\n-\t\t\t\t     int show_progress, int write_bitmaps)\n+static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n-\tstruct packed_git *preferred = pack_geometry_preferred_pack(geometry);\n+\tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n \tFILE *in;\n \tint ret;\n \n-\tif (!include->nr)\n+\tif (!opts->include->nr)\n \t\treturn 0;\n \n \tcmd.in = -1;\n@@ -311,18 +318,18 @@ static int write_midx_included_packs(struct string_list *include,\n \tstrvec_push(&cmd.args, \"multi-pack-index\");\n \tstrvec_pushl(&cmd.args, \"write\", \"--stdin-packs\", NULL);\n \n-\tif (show_progress)\n+\tif (opts->show_progress)\n \t\tstrvec_push(&cmd.args, \"--progress\");\n \telse\n \t\tstrvec_push(&cmd.args, \"--no-progress\");\n \n-\tif (write_bitmaps)\n+\tif (opts->write_bitmaps)\n \t\tstrvec_push(&cmd.args, \"--bitmap\");\n \n \tif (preferred)\n \t\tstrvec_pushf(&cmd.args, \"--preferred-pack=%s\",\n \t\t\t     pack_basename(preferred));\n-\telse if (names->nr) {\n+\telse if (opts->names->nr) {\n \t\t/* The largest pack was repacked, meaning that either\n \t\t * one or two packs exist depending on whether the\n \t\t * repository has a cruft pack or not.\n@@ -335,7 +342,7 @@ static int write_midx_included_packs(struct string_list *include,\n \t\t * `--max-pack-size` was given, but any one of them\n \t\t * will suffice, so pick the first one.)\n \t\t */\n-\t\tfor_each_string_list_item(item, names) {\n+\t\tfor_each_string_list_item(item, opts->names) {\n \t\t\tstruct generated_pack *pack = item->util;\n \t\t\tif (generated_pack_has_ext(pack, \".mtimes\"))\n \t\t\t\tcontinue;\n@@ -355,15 +362,16 @@ static int write_midx_included_packs(struct string_list *include,\n \t\t;\n \t}\n \n-\tif (refs_snapshot)\n-\t\tstrvec_pushf(&cmd.args, \"--refs-snapshot=%s\", refs_snapshot);\n+\tif (opts->refs_snapshot)\n+\t\tstrvec_pushf(&cmd.args, \"--refs-snapshot=%s\",\n+\t\t\t     opts->refs_snapshot);\n \n \tret = start_command(&cmd);\n \tif (ret)\n \t\treturn ret;\n \n \tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, include)\n+\tfor_each_string_list_item(item, opts->include)\n \t\tfprintf(in, \"%s\\n\", item->string);\n \tfclose(in);\n \n@@ -1001,15 +1009,23 @@ int cmd_repack(int argc,\n \n \tif (write_midx) {\n \t\tstruct string_list include = STRING_LIST_INIT_DUP;\n+\t\tstruct repack_write_midx_opts opts = {\n+\t\t\t.include = &include,\n+\t\t\t.geometry = &geometry,\n+\t\t\t.names = &names,\n+\t\t\t.refs_snapshot = refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,\n+\t\t\t.packdir = packdir,\n+\t\t\t.show_progress = show_progress,\n+\t\t\t.write_bitmaps = write_bitmaps > 0,\n+\t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n+\t\t};\n \t\tmidx_included_packs(&include, &existing, midx_pack_names,\n \t\t\t\t    midx_pack_names_nr, &names, &geometry);\n \n-\t\tret = write_midx_included_packs(&include, &geometry, &names,\n-\t\t\t\t\t\trefs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,\n-\t\t\t\t\t\tshow_progress, write_bitmaps > 0);\n+\t\tret = write_midx_included_packs(&opts);\n \n \t\tif (!ret && write_bitmaps)\n-\t\t\tremove_redundant_bitmaps(&include, packdir);\n+\t\t\tremove_redundant_bitmaps(&include, opts.packdir);\n \n \t\tstring_list_clear(&include, 0);\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527558","messageId":"88445297596acff18e6427400a4a0e5a049dd923.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 33/49] builtin/repack.c: use a string_list for 'midx_pack_names'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:18Z","receivedAt":"2025-09-28T22:09:21Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"When writing a new MIDX, repack must determine whether or not there are\nany packs in the MIDX it is replacing (if one exists) that are not\nsomehow represented in the new MIDX (e.g., either by preserving the pack\nverbatim, or rolling it up as part of a geometric repack, etc.).\n\nIn order to do this, it keeps track of a list of pack names from the\nMIDX present in the repository at the start of the repack operation.\nSince we manipulate and close the object store, we cannot rely on the\nrepository's in-core representation of the MIDX, since this is subject\nto change and/or go away.\n\nWhen this behavior was introduced in 5ee86c273b (repack: exclude cruft\npack(s) from the MIDX where possible, 2025-06-23), we maintained an\narray of character pointers instead of using a convenience API, such as\nstring-list.h.\n\nStore the list of MIDX pack names in a string_list, thereby reducing the\nnumber of parameters we have to pass to `midx_has_unknown_packs()`.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 40 +++++++++++++++++-----------------------\n 1 file changed, 17 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex e4b8aa2c6f..8ae56b05e9 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -118,18 +118,17 @@ struct repack_write_midx_opts {\n \tint midx_must_contain_cruft;\n };\n \n-static int midx_has_unknown_packs(char **midx_pack_names,\n-\t\t\t\t  size_t midx_pack_names_nr,\n+static int midx_has_unknown_packs(struct string_list *midx_pack_names,\n \t\t\t\t  struct string_list *include,\n \t\t\t\t  struct pack_geometry *geometry,\n \t\t\t\t  struct existing_packs *existing)\n {\n-\tsize_t i;\n+\tstruct string_list_item *item;\n \n \tstring_list_sort(include);\n \n-\tfor (i = 0; i < midx_pack_names_nr; i++) {\n-\t\tconst char *pack_name = midx_pack_names[i];\n+\tfor_each_string_list_item(item, midx_pack_names) {\n+\t\tconst char *pack_name = item->string;\n \n \t\t/*\n \t\t * Determine whether or not each MIDX'd pack from the existing\n@@ -191,8 +190,7 @@ static int midx_has_unknown_packs(char **midx_pack_names,\n \n static void midx_included_packs(struct string_list *include,\n \t\t\t\tstruct existing_packs *existing,\n-\t\t\t\tchar **midx_pack_names,\n-\t\t\t\tsize_t midx_pack_names_nr,\n+\t\t\t\tstruct string_list *midx_pack_names,\n \t\t\t\tstruct string_list *names,\n \t\t\t\tstruct pack_geometry *geometry)\n {\n@@ -247,8 +245,8 @@ static void midx_included_packs(struct string_list *include,\n \t}\n \n \tif (midx_must_contain_cruft ||\n-\t    midx_has_unknown_packs(midx_pack_names, midx_pack_names_nr,\n-\t\t\t\t   include, geometry, existing)) {\n+\t    midx_has_unknown_packs(midx_pack_names, include, geometry,\n+\t\t\t\t   existing)) {\n \t\t/*\n \t\t * If there are one or more unknown pack(s) present (see\n \t\t * midx_has_unknown_packs() for what makes a pack\n@@ -606,13 +604,12 @@ int cmd_repack(int argc,\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n \tstruct string_list names = STRING_LIST_INIT_DUP;\n+\tstruct string_list midx_pack_names = STRING_LIST_INIT_DUP;\n \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n \tstruct pack_geometry geometry = { 0 };\n \tstruct tempfile *refs_snapshot = NULL;\n \tint i, ret;\n \tint show_progress;\n-\tchar **midx_pack_names = NULL;\n-\tsize_t midx_pack_names_nr = 0;\n \n \t/* variables to be filled by option parsing */\n \tstruct repack_config_ctx config_ctx;\n@@ -985,13 +982,12 @@ int cmd_repack(int argc,\n \t\tstruct multi_pack_index *m =\n \t\t\tget_multi_pack_index(repo->objects->sources);\n \n-\t\tALLOC_ARRAY(midx_pack_names,\n-\t\t\t    m->num_packs + m->num_packs_in_base);\n-\n-\t\tfor (; m; m = m->base_midx)\n-\t\t\tfor (uint32_t i = 0; i < m->num_packs; i++)\n-\t\t\t\tmidx_pack_names[midx_pack_names_nr++] =\n-\t\t\t\t\txstrdup(m->pack_names[i]);\n+\t\tfor (; m; m = m->base_midx) {\n+\t\t\tfor (uint32_t i = 0; i < m->num_packs; i++) {\n+\t\t\t\tstring_list_append(&midx_pack_names,\n+\t\t\t\t\t\t   m->pack_names[i]);\n+\t\t\t}\n+\t\t}\n \t}\n \n \tclose_object_store(repo->objects);\n@@ -1019,8 +1015,8 @@ int cmd_repack(int argc,\n \t\t\t.write_bitmaps = write_bitmaps > 0,\n \t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n \t\t};\n-\t\tmidx_included_packs(&include, &existing, midx_pack_names,\n-\t\t\t\t    midx_pack_names_nr, &names, &geometry);\n+\t\tmidx_included_packs(&include, &existing, &midx_pack_names,\n+\t\t\t\t    &names, &geometry);\n \n \t\tret = write_midx_included_packs(&opts);\n \n@@ -1067,11 +1063,9 @@ int cmd_repack(int argc,\n cleanup:\n \tstring_list_clear(&keep_pack_list, 0);\n \tstring_list_clear(&names, 1);\n+\tstring_list_clear(&midx_pack_names, 0);\n \texisting_packs_release(&existing);\n \tpack_geometry_release(&geometry);\n-\tfor (size_t i = 0; i < midx_pack_names_nr; i++)\n-\t\tfree(midx_pack_names[i]);\n-\tfree(midx_pack_names);\n \tpack_objects_args_release(&po_args);\n \tpack_objects_args_release(&cruft_po_args);\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527559","messageId":"ccf2c6a517c4e03ef55a4277e1c9b1459c8d5c41.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 34/49] repack: keep track of MIDX pack names using existing_packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:22Z","receivedAt":"2025-09-28T22:09:25Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Instead of storing the list of MIDX pack names separately, let's inline\nit into the existing_packs struct, further reducing the number of\nparameters we have to pass around.\n\nThis amounts to adding a new string_list to the existing_packs struct,\nand populating it via `existing_packs_collect()`. This is fairly\nstraightforward to do, since we are already looping over all packs, all\nwe need to do is:\n\n    if (p->multi_pack_index)\n        string_list_append(&existing->midx_packs, pack_basename(p));\n\nNote, however, that this check *must* come before other conditions where\nwe discard and do not keep track of a pack, including the condition \"if\n(!p->pack_local)\" immediately below. This is because the existing\nroutine which collects MIDX pack names does so blindly, and does not\ndiscard, for example, non-local packs.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 26 ++++----------------------\n repack.c         |  5 +++++\n repack.h         |  1 +\n 3 files changed, 10 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 8ae56b05e9..251dd08b0a 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -118,8 +118,7 @@ struct repack_write_midx_opts {\n \tint midx_must_contain_cruft;\n };\n \n-static int midx_has_unknown_packs(struct string_list *midx_pack_names,\n-\t\t\t\t  struct string_list *include,\n+static int midx_has_unknown_packs(struct string_list *include,\n \t\t\t\t  struct pack_geometry *geometry,\n \t\t\t\t  struct existing_packs *existing)\n {\n@@ -127,7 +126,7 @@ static int midx_has_unknown_packs(struct string_list *midx_pack_names,\n \n \tstring_list_sort(include);\n \n-\tfor_each_string_list_item(item, midx_pack_names) {\n+\tfor_each_string_list_item(item, &existing->midx_packs) {\n \t\tconst char *pack_name = item->string;\n \n \t\t/*\n@@ -190,7 +189,6 @@ static int midx_has_unknown_packs(struct string_list *midx_pack_names,\n \n static void midx_included_packs(struct string_list *include,\n \t\t\t\tstruct existing_packs *existing,\n-\t\t\t\tstruct string_list *midx_pack_names,\n \t\t\t\tstruct string_list *names,\n \t\t\t\tstruct pack_geometry *geometry)\n {\n@@ -245,8 +243,7 @@ static void midx_included_packs(struct string_list *include,\n \t}\n \n \tif (midx_must_contain_cruft ||\n-\t    midx_has_unknown_packs(midx_pack_names, include, geometry,\n-\t\t\t\t   existing)) {\n+\t    midx_has_unknown_packs(include, geometry, existing)) {\n \t\t/*\n \t\t * If there are one or more unknown pack(s) present (see\n \t\t * midx_has_unknown_packs() for what makes a pack\n@@ -604,7 +601,6 @@ int cmd_repack(int argc,\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n \tstruct string_list names = STRING_LIST_INIT_DUP;\n-\tstruct string_list midx_pack_names = STRING_LIST_INIT_DUP;\n \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n \tstruct pack_geometry geometry = { 0 };\n \tstruct tempfile *refs_snapshot = NULL;\n@@ -978,18 +974,6 @@ int cmd_repack(int argc,\n \n \tstring_list_sort(&names);\n \n-\tif (get_multi_pack_index(repo->objects->sources)) {\n-\t\tstruct multi_pack_index *m =\n-\t\t\tget_multi_pack_index(repo->objects->sources);\n-\n-\t\tfor (; m; m = m->base_midx) {\n-\t\t\tfor (uint32_t i = 0; i < m->num_packs; i++) {\n-\t\t\t\tstring_list_append(&midx_pack_names,\n-\t\t\t\t\t\t   m->pack_names[i]);\n-\t\t\t}\n-\t\t}\n-\t}\n-\n \tclose_object_store(repo->objects);\n \n \t/*\n@@ -1015,8 +999,7 @@ int cmd_repack(int argc,\n \t\t\t.write_bitmaps = write_bitmaps > 0,\n \t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n \t\t};\n-\t\tmidx_included_packs(&include, &existing, &midx_pack_names,\n-\t\t\t\t    &names, &geometry);\n+\t\tmidx_included_packs(&include, &existing, &names, &geometry);\n \n \t\tret = write_midx_included_packs(&opts);\n \n@@ -1063,7 +1046,6 @@ int cmd_repack(int argc,\n cleanup:\n \tstring_list_clear(&keep_pack_list, 0);\n \tstring_list_clear(&names, 1);\n-\tstring_list_clear(&midx_pack_names, 0);\n \texisting_packs_release(&existing);\n \tpack_geometry_release(&geometry);\n \tpack_objects_args_release(&po_args);\ndiff --git a/repack.c b/repack.c\nindex d8afdd352d..1d485e0112 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -80,6 +80,9 @@ void existing_packs_collect(struct existing_packs *existing,\n \t\tsize_t i;\n \t\tconst char *base;\n \n+\t\tif (p->multi_pack_index)\n+\t\t\tstring_list_append(&existing->midx_packs,\n+\t\t\t\t\t    pack_basename(p));\n \t\tif (!p->pack_local)\n \t\t\tcontinue;\n \n@@ -104,6 +107,7 @@ void existing_packs_collect(struct existing_packs *existing,\n \tstring_list_sort(&existing->kept_packs);\n \tstring_list_sort(&existing->non_kept_packs);\n \tstring_list_sort(&existing->cruft_packs);\n+\tstring_list_sort(&existing->midx_packs);\n \tstrbuf_release(&buf);\n }\n \n@@ -220,6 +224,7 @@ void existing_packs_release(struct existing_packs *existing)\n \tstring_list_clear(&existing->kept_packs, 0);\n \tstring_list_clear(&existing->non_kept_packs, 0);\n \tstring_list_clear(&existing->cruft_packs, 0);\n+\tstring_list_clear(&existing->midx_packs, 0);\n }\n \n static struct {\ndiff --git a/repack.h b/repack.h\nindex 803e129224..6aa5b4e0f0 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -40,6 +40,7 @@ struct existing_packs {\n \tstruct string_list kept_packs;\n \tstruct string_list non_kept_packs;\n \tstruct string_list cruft_packs;\n+\tstruct string_list midx_packs;\n };\n \n #define EXISTING_PACKS_INIT { \\\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527560","messageId":"85d229fff5aad2240612b09416e6d012e12d0a63.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 35/49] builtin/repack.c: reorder `remove_redundant_bitmaps()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:26Z","receivedAt":"2025-09-28T22:09:29Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The next commit will inline the call to `remove_redundant_bitmaps()`\ninto `write_midx_included_packs()`. Reorder these two functions to avoid\na forward declaration to `remove_redundant_bitmaps()`.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 58 ++++++++++++++++++++++++------------------------\n 1 file changed, 29 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 251dd08b0a..957a9b5f9e 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -296,6 +296,35 @@ static void midx_included_packs(struct string_list *include,\n \tstrbuf_release(&buf);\n }\n \n+static void remove_redundant_bitmaps(struct string_list *include,\n+\t\t\t\t     const char *packdir)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\tsize_t packdir_len;\n+\n+\tstrbuf_addstr(&path, packdir);\n+\tstrbuf_addch(&path, '/');\n+\tpackdir_len = path.len;\n+\n+\t/*\n+\t * Remove any pack bitmaps corresponding to packs which are now\n+\t * included in the MIDX.\n+\t */\n+\tfor_each_string_list_item(item, include) {\n+\t\tstrbuf_addstr(&path, item->string);\n+\t\tstrbuf_strip_suffix(&path, \".idx\");\n+\t\tstrbuf_addstr(&path, \".bitmap\");\n+\n+\t\tif (unlink(path.buf) && errno != ENOENT)\n+\t\t\twarning_errno(_(\"could not remove stale bitmap: %s\"),\n+\t\t\t\t      path.buf);\n+\n+\t\tstrbuf_setlen(&path, packdir_len);\n+\t}\n+\tstrbuf_release(&path);\n+}\n+\n static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -373,35 +402,6 @@ static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n \treturn finish_command(&cmd);\n }\n \n-static void remove_redundant_bitmaps(struct string_list *include,\n-\t\t\t\t     const char *packdir)\n-{\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstruct string_list_item *item;\n-\tsize_t packdir_len;\n-\n-\tstrbuf_addstr(&path, packdir);\n-\tstrbuf_addch(&path, '/');\n-\tpackdir_len = path.len;\n-\n-\t/*\n-\t * Remove any pack bitmaps corresponding to packs which are now\n-\t * included in the MIDX.\n-\t */\n-\tfor_each_string_list_item(item, include) {\n-\t\tstrbuf_addstr(&path, item->string);\n-\t\tstrbuf_strip_suffix(&path, \".idx\");\n-\t\tstrbuf_addstr(&path, \".bitmap\");\n-\n-\t\tif (unlink(path.buf) && errno != ENOENT)\n-\t\t\twarning_errno(_(\"could not remove stale bitmap: %s\"),\n-\t\t\t\t      path.buf);\n-\n-\t\tstrbuf_setlen(&path, packdir_len);\n-\t}\n-\tstrbuf_release(&path);\n-}\n-\n static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t\t\t   struct child_process *cmd,\n \t\t\t\t   struct string_list *names,\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527561","messageId":"2bf732009ab8330d207ab3a4653fe784b1a5df83.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 36/49] builtin/repack.c: inline `remove_redundant_bitmaps()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:30Z","receivedAt":"2025-09-28T22:09:34Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"After writing a new MIDX, the repack command removes any bitmaps\nbelonging to packs which were written into the MIDX.\n\nThis is currently done in a separate function outside of\n`write_midx_included_packs()`, which forces the caller to keep track of\nthe set of packs written into the MIDX.\n\nPrepare to no longer require the caller to keep track of such\ninformation by inlining the clean-up into `write_midx_included_packs()`.\nFuture commits will make the caller oblivious to the set of packs\nincluded in the MIDX altogether.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 15 ++++++++-------\n 1 file changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 957a9b5f9e..b55c8934e8 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -331,10 +331,10 @@ static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n \tstruct string_list_item *item;\n \tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n \tFILE *in;\n-\tint ret;\n+\tint ret = 0;\n \n \tif (!opts->include->nr)\n-\t\treturn 0;\n+\t\tgoto done;\n \n \tcmd.in = -1;\n \tcmd.git_cmd = 1;\n@@ -392,14 +392,18 @@ static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n \n \tret = start_command(&cmd);\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto done;\n \n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, opts->include)\n \t\tfprintf(in, \"%s\\n\", item->string);\n \tfclose(in);\n \n-\treturn finish_command(&cmd);\n+\tret = finish_command(&cmd);\n+done:\n+\tif (!ret && opts->write_bitmaps)\n+\t\tremove_redundant_bitmaps(opts->include, opts->packdir);\n+\treturn ret;\n }\n \n static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n@@ -1003,9 +1007,6 @@ int cmd_repack(int argc,\n \n \t\tret = write_midx_included_packs(&opts);\n \n-\t\tif (!ret && write_bitmaps)\n-\t\t\tremove_redundant_bitmaps(&include, opts.packdir);\n-\n \t\tstring_list_clear(&include, 0);\n \n \t\tif (ret)\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527562","messageId":"60659a7549fd0dd416f9e04f191666b24a422374.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 37/49] builtin/repack.c: pass `repack_write_midx_opts` to `midx_included_packs`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:35Z","receivedAt":"2025-09-28T22:09:39Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Instead of passing individual parameters (in this case, \"existing\",\n\"names\", and \"geometry\") to `midx_included_packs()`, pass a pointer to a\n`repack_write_midx_opts` structure instead.\n\nBesides reducing the number of parameters necessary to call the\n`midx_included_packs` function, this refactoring sets us up nicely to\ninline the call to `midx_included_packs()` into\n`write_midx_included_packs()`, thus making the caller (in this case,\n`cmd_repack()`) oblivious to the set of packs being written into the\nMIDX.\n\nIn order to do this, `repack_write_midx_opts` has to keep track of the\nset of existing packs, so add an additional field to point to that set.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 13 ++++++++-----\n 1 file changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex b55c8934e8..bd3034a4f0 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -108,6 +108,7 @@ static int repack_config(const char *var, const char *value,\n }\n \n struct repack_write_midx_opts {\n+\tstruct existing_packs *existing;\n \tstruct string_list *include;\n \tstruct pack_geometry *geometry;\n \tstruct string_list *names;\n@@ -188,10 +189,11 @@ static int midx_has_unknown_packs(struct string_list *include,\n }\n \n static void midx_included_packs(struct string_list *include,\n-\t\t\t\tstruct existing_packs *existing,\n-\t\t\t\tstruct string_list *names,\n-\t\t\t\tstruct pack_geometry *geometry)\n+\t\t\t\tstruct repack_write_midx_opts *opts)\n {\n+\tstruct existing_packs *existing = opts->existing;\n+\tstruct pack_geometry *geometry = opts->geometry;\n+\tstruct string_list *names = opts->names;\n \tstruct string_list_item *item;\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -242,7 +244,7 @@ static void midx_included_packs(struct string_list *include,\n \t\t}\n \t}\n \n-\tif (midx_must_contain_cruft ||\n+\tif (opts->midx_must_contain_cruft ||\n \t    midx_has_unknown_packs(include, geometry, existing)) {\n \t\t/*\n \t\t * If there are one or more unknown pack(s) present (see\n@@ -994,6 +996,7 @@ int cmd_repack(int argc,\n \tif (write_midx) {\n \t\tstruct string_list include = STRING_LIST_INIT_DUP;\n \t\tstruct repack_write_midx_opts opts = {\n+\t\t\t.existing = &existing,\n \t\t\t.include = &include,\n \t\t\t.geometry = &geometry,\n \t\t\t.names = &names,\n@@ -1003,7 +1006,7 @@ int cmd_repack(int argc,\n \t\t\t.write_bitmaps = write_bitmaps > 0,\n \t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n \t\t};\n-\t\tmidx_included_packs(&include, &existing, &names, &geometry);\n+\t\tmidx_included_packs(&include, &opts);\n \n \t\tret = write_midx_included_packs(&opts);\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527563","messageId":"c2c7ca9f9e917392de3819ffffffcf78622972b4.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 38/49] builtin/repack.c: inline packs within `write_midx_included_packs()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:40Z","receivedAt":"2025-09-28T22:09:44Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"To write a MIDX at the end of a repack operation, 'git repack' presently\ncomputes the set of packs to write into the MIDX, before invoking\n`write_midx_included_packs()` with a `string_list` containing those\npacks.\n\nThe logic for computing which packs are supposed to appear in the\nresulting MIDX is within `midx_included_packs()`, where it is aware of\ndetails like which cruft pack(s) were written/combined, if/how we did a\ngeometric repack, etc.\n\nComputing this list ourselves before providing it to the sole function\nto make use of that list `write_midx_included_packs()` is somewhat\nawkward. In the future, repack will learn how to write incremental\nMIDXs, which will use a very different pack selection routine.\n\nInstead of doing something like:\n\n    struct string_list included_packs = STRING_LIST_INIT_DUP;\n    if (incremental) {\n        midx_incremental_included_packs(&included_packs, ...):\n        write_midx_incremental_included_packs(&included_packs, ...);\n    } else {\n        midx_included_packs(&included_packs, ...):\n        write_midx_included_packs(&included_packs, ...);\n    }\n\nin the future, let's have each function which writes a MIDX be\nresponsible for itself computing the list of included packs. Inline the\ndeclaration and initialization of `included_packs` into the\n`write_midx_included_packs()` function itself, and repeat that pattern\nin the future when we introduce new ways to write MIDXs.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 17 ++++++++---------\n 1 file changed, 8 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex bd3034a4f0..e2acba6312 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -109,7 +109,6 @@ static int repack_config(const char *var, const char *value,\n \n struct repack_write_midx_opts {\n \tstruct existing_packs *existing;\n-\tstruct string_list *include;\n \tstruct pack_geometry *geometry;\n \tstruct string_list *names;\n \tconst char *refs_snapshot;\n@@ -330,12 +329,14 @@ static void remove_redundant_bitmaps(struct string_list *include,\n static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct string_list include = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *item;\n \tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n \tFILE *in;\n \tint ret = 0;\n \n-\tif (!opts->include->nr)\n+\tmidx_included_packs(&include, opts);\n+\tif (!include.nr)\n \t\tgoto done;\n \n \tcmd.in = -1;\n@@ -397,14 +398,17 @@ static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n \t\tgoto done;\n \n \tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, opts->include)\n+\tfor_each_string_list_item(item, &include)\n \t\tfprintf(in, \"%s\\n\", item->string);\n \tfclose(in);\n \n \tret = finish_command(&cmd);\n done:\n \tif (!ret && opts->write_bitmaps)\n-\t\tremove_redundant_bitmaps(opts->include, opts->packdir);\n+\t\tremove_redundant_bitmaps(&include, opts->packdir);\n+\n+\tstring_list_clear(&include, 0);\n+\n \treturn ret;\n }\n \n@@ -994,10 +998,8 @@ int cmd_repack(int argc,\n \t\texisting_packs_mark_for_deletion(&existing, &names);\n \n \tif (write_midx) {\n-\t\tstruct string_list include = STRING_LIST_INIT_DUP;\n \t\tstruct repack_write_midx_opts opts = {\n \t\t\t.existing = &existing,\n-\t\t\t.include = &include,\n \t\t\t.geometry = &geometry,\n \t\t\t.names = &names,\n \t\t\t.refs_snapshot = refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,\n@@ -1006,12 +1008,9 @@ int cmd_repack(int argc,\n \t\t\t.write_bitmaps = write_bitmaps > 0,\n \t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n \t\t};\n-\t\tmidx_included_packs(&include, &opts);\n \n \t\tret = write_midx_included_packs(&opts);\n \n-\t\tstring_list_clear(&include, 0);\n-\n \t\tif (ret)\n \t\t\tgoto cleanup;\n \t}\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527564","messageId":"c28891443bba0b2b84c44d8968cd1ca1710c99ab.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 39/49] repack: 'write_midx_included_packs' API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:46Z","receivedAt":"2025-09-28T22:09:50Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that we have sufficiently cleaned up the write_midx_included_packs()\nfunction, we can move it (along with the struct repack_write_midx_opts)\nout of the builtin, and into the repack.h header.\n\nSince this function (and the static ones that it depends on) are\nMIDX-specific details of the repacking process, move them to the\nrepack-midx.c compilation unit instead of the general repack.c one.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 305 -----------------------------------------------\n repack-midx.c    | 295 +++++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  12 ++\n 3 files changed, 307 insertions(+), 305 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex e2acba6312..5fed79e826 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,311 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-struct repack_write_midx_opts {\n-\tstruct existing_packs *existing;\n-\tstruct pack_geometry *geometry;\n-\tstruct string_list *names;\n-\tconst char *refs_snapshot;\n-\tconst char *packdir;\n-\tint show_progress;\n-\tint write_bitmaps;\n-\tint midx_must_contain_cruft;\n-};\n-\n-static int midx_has_unknown_packs(struct string_list *include,\n-\t\t\t\t  struct pack_geometry *geometry,\n-\t\t\t\t  struct existing_packs *existing)\n-{\n-\tstruct string_list_item *item;\n-\n-\tstring_list_sort(include);\n-\n-\tfor_each_string_list_item(item, &existing->midx_packs) {\n-\t\tconst char *pack_name = item->string;\n-\n-\t\t/*\n-\t\t * Determine whether or not each MIDX'd pack from the existing\n-\t\t * MIDX (if any) is represented in the new MIDX. For each pack\n-\t\t * in the MIDX, it must either be:\n-\t\t *\n-\t\t *  - In the \"include\" list of packs to be included in the new\n-\t\t *    MIDX. Note this function is called before the include\n-\t\t *    list is populated with any cruft pack(s).\n-\t\t *\n-\t\t *  - Below the geometric split line (if using pack geometry),\n-\t\t *    indicating that the pack won't be included in the new\n-\t\t *    MIDX, but its contents were rolled up as part of the\n-\t\t *    geometric repack.\n-\t\t *\n-\t\t *  - In the existing non-kept packs list (if not using pack\n-\t\t *    geometry), and marked as non-deleted.\n-\t\t */\n-\t\tif (string_list_has_string(include, pack_name)) {\n-\t\t\tcontinue;\n-\t\t} else if (geometry) {\n-\t\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\t\tuint32_t j;\n-\n-\t\t\tfor (j = 0; j < geometry->split; j++) {\n-\t\t\t\tstrbuf_reset(&buf);\n-\t\t\t\tstrbuf_addstr(&buf, pack_basename(geometry->pack[j]));\n-\t\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\t\t\t\tstrbuf_addstr(&buf, \".idx\");\n-\n-\t\t\t\tif (!strcmp(pack_name, buf.buf)) {\n-\t\t\t\t\tstrbuf_release(&buf);\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t\t}\n-\n-\t\t\tstrbuf_release(&buf);\n-\n-\t\t\tif (j < geometry->split)\n-\t\t\t\tcontinue;\n-\t\t} else {\n-\t\t\tstruct string_list_item *item;\n-\n-\t\t\titem = string_list_lookup(&existing->non_kept_packs,\n-\t\t\t\t\t\t  pack_name);\n-\t\t\tif (item && !existing_pack_is_marked_for_deletion(item))\n-\t\t\t\tcontinue;\n-\t\t}\n-\n-\t\t/*\n-\t\t * If we got to this point, the MIDX includes some pack that we\n-\t\t * don't know about.\n-\t\t */\n-\t\treturn 1;\n-\t}\n-\n-\treturn 0;\n-}\n-\n-static void midx_included_packs(struct string_list *include,\n-\t\t\t\tstruct repack_write_midx_opts *opts)\n-{\n-\tstruct existing_packs *existing = opts->existing;\n-\tstruct pack_geometry *geometry = opts->geometry;\n-\tstruct string_list *names = opts->names;\n-\tstruct string_list_item *item;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tfor_each_string_list_item(item, &existing->kept_packs) {\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n-\t\tstring_list_insert(include, buf.buf);\n-\t}\n-\n-\tfor_each_string_list_item(item, names) {\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"pack-%s.idx\", item->string);\n-\t\tstring_list_insert(include, buf.buf);\n-\t}\n-\n-\tif (geometry->split_factor) {\n-\t\tuint32_t i;\n-\n-\t\tfor (i = geometry->split; i < geometry->pack_nr; i++) {\n-\t\t\tstruct packed_git *p = geometry->pack[i];\n-\n-\t\t\t/*\n-\t\t\t * The multi-pack index never refers to packfiles part\n-\t\t\t * of an alternate object database, so we skip these.\n-\t\t\t * While git-multi-pack-index(1) would silently ignore\n-\t\t\t * them anyway, this allows us to skip executing the\n-\t\t\t * command completely when we have only non-local\n-\t\t\t * packfiles.\n-\t\t\t */\n-\t\t\tif (!p->pack_local)\n-\t\t\t\tcontinue;\n-\n-\t\t\tstrbuf_reset(&buf);\n-\t\t\tstrbuf_addstr(&buf, pack_basename(p));\n-\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\t\t\tstrbuf_addstr(&buf, \".idx\");\n-\n-\t\t\tstring_list_insert(include, buf.buf);\n-\t\t}\n-\t} else {\n-\t\tfor_each_string_list_item(item, &existing->non_kept_packs) {\n-\t\t\tif (existing_pack_is_marked_for_deletion(item))\n-\t\t\t\tcontinue;\n-\n-\t\t\tstrbuf_reset(&buf);\n-\t\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n-\t\t\tstring_list_insert(include, buf.buf);\n-\t\t}\n-\t}\n-\n-\tif (opts->midx_must_contain_cruft ||\n-\t    midx_has_unknown_packs(include, geometry, existing)) {\n-\t\t/*\n-\t\t * If there are one or more unknown pack(s) present (see\n-\t\t * midx_has_unknown_packs() for what makes a pack\n-\t\t * \"unknown\") in the MIDX before the repack, keep them\n-\t\t * as they may be required to form a reachability\n-\t\t * closure if the MIDX is bitmapped.\n-\t\t *\n-\t\t * For example, a cruft pack can be required to form a\n-\t\t * reachability closure if the MIDX is bitmapped and one\n-\t\t * or more of the bitmap's selected commits reaches a\n-\t\t * once-cruft object that was later made reachable.\n-\t\t */\n-\t\tfor_each_string_list_item(item, &existing->cruft_packs) {\n-\t\t\t/*\n-\t\t\t * When doing a --geometric repack, there is no\n-\t\t\t * need to check for deleted packs, since we're\n-\t\t\t * by definition not doing an ALL_INTO_ONE\n-\t\t\t * repack (hence no packs will be deleted).\n-\t\t\t * Otherwise we must check for and exclude any\n-\t\t\t * packs which are enqueued for deletion.\n-\t\t\t *\n-\t\t\t * So we could omit the conditional below in the\n-\t\t\t * --geometric case, but doing so is unnecessary\n-\t\t\t *  since no packs are marked as pending\n-\t\t\t *  deletion (since we only call\n-\t\t\t *  `existing_packs_mark_for_deletion()` when\n-\t\t\t *  doing an all-into-one repack).\n-\t\t\t */\n-\t\t\tif (existing_pack_is_marked_for_deletion(item))\n-\t\t\t\tcontinue;\n-\n-\t\t\tstrbuf_reset(&buf);\n-\t\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n-\t\t\tstring_list_insert(include, buf.buf);\n-\t\t}\n-\t} else {\n-\t\t/*\n-\t\t * Modern versions of Git (with the appropriate\n-\t\t * configuration setting) will write new copies of\n-\t\t * once-cruft objects when doing a --geometric repack.\n-\t\t *\n-\t\t * If the MIDX has no cruft pack, new packs written\n-\t\t * during a --geometric repack will not rely on the\n-\t\t * cruft pack to form a reachability closure, so we can\n-\t\t * avoid including them in the MIDX in that case.\n-\t\t */\n-\t\t;\n-\t}\n-\n-\tstrbuf_release(&buf);\n-}\n-\n-static void remove_redundant_bitmaps(struct string_list *include,\n-\t\t\t\t     const char *packdir)\n-{\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstruct string_list_item *item;\n-\tsize_t packdir_len;\n-\n-\tstrbuf_addstr(&path, packdir);\n-\tstrbuf_addch(&path, '/');\n-\tpackdir_len = path.len;\n-\n-\t/*\n-\t * Remove any pack bitmaps corresponding to packs which are now\n-\t * included in the MIDX.\n-\t */\n-\tfor_each_string_list_item(item, include) {\n-\t\tstrbuf_addstr(&path, item->string);\n-\t\tstrbuf_strip_suffix(&path, \".idx\");\n-\t\tstrbuf_addstr(&path, \".bitmap\");\n-\n-\t\tif (unlink(path.buf) && errno != ENOENT)\n-\t\t\twarning_errno(_(\"could not remove stale bitmap: %s\"),\n-\t\t\t\t      path.buf);\n-\n-\t\tstrbuf_setlen(&path, packdir_len);\n-\t}\n-\tstrbuf_release(&path);\n-}\n-\n-static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n-{\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tstruct string_list include = STRING_LIST_INIT_DUP;\n-\tstruct string_list_item *item;\n-\tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n-\tFILE *in;\n-\tint ret = 0;\n-\n-\tmidx_included_packs(&include, opts);\n-\tif (!include.nr)\n-\t\tgoto done;\n-\n-\tcmd.in = -1;\n-\tcmd.git_cmd = 1;\n-\n-\tstrvec_push(&cmd.args, \"multi-pack-index\");\n-\tstrvec_pushl(&cmd.args, \"write\", \"--stdin-packs\", NULL);\n-\n-\tif (opts->show_progress)\n-\t\tstrvec_push(&cmd.args, \"--progress\");\n-\telse\n-\t\tstrvec_push(&cmd.args, \"--no-progress\");\n-\n-\tif (opts->write_bitmaps)\n-\t\tstrvec_push(&cmd.args, \"--bitmap\");\n-\n-\tif (preferred)\n-\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=%s\",\n-\t\t\t     pack_basename(preferred));\n-\telse if (opts->names->nr) {\n-\t\t/* The largest pack was repacked, meaning that either\n-\t\t * one or two packs exist depending on whether the\n-\t\t * repository has a cruft pack or not.\n-\t\t *\n-\t\t * Select the non-cruft one as preferred to encourage\n-\t\t * pack-reuse among packs containing reachable objects\n-\t\t * over unreachable ones.\n-\t\t *\n-\t\t * (Note we could write multiple packs here if\n-\t\t * `--max-pack-size` was given, but any one of them\n-\t\t * will suffice, so pick the first one.)\n-\t\t */\n-\t\tfor_each_string_list_item(item, opts->names) {\n-\t\t\tstruct generated_pack *pack = item->util;\n-\t\t\tif (generated_pack_has_ext(pack, \".mtimes\"))\n-\t\t\t\tcontinue;\n-\n-\t\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=pack-%s.pack\",\n-\t\t\t\t     item->string);\n-\t\t\tbreak;\n-\t\t}\n-\t} else {\n-\t\t/*\n-\t\t * No packs were kept, and no packs were written. The\n-\t\t * only thing remaining are .keep packs (unless\n-\t\t * --pack-kept-objects was given).\n-\t\t *\n-\t\t * Set the `--preferred-pack` arbitrarily here.\n-\t\t */\n-\t\t;\n-\t}\n-\n-\tif (opts->refs_snapshot)\n-\t\tstrvec_pushf(&cmd.args, \"--refs-snapshot=%s\",\n-\t\t\t     opts->refs_snapshot);\n-\n-\tret = start_command(&cmd);\n-\tif (ret)\n-\t\tgoto done;\n-\n-\tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, &include)\n-\t\tfprintf(in, \"%s\\n\", item->string);\n-\tfclose(in);\n-\n-\tret = finish_command(&cmd);\n-done:\n-\tif (!ret && opts->write_bitmaps)\n-\t\tremove_redundant_bitmaps(&include, opts->packdir);\n-\n-\tstring_list_clear(&include, 0);\n-\n-\treturn ret;\n-}\n-\n static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t\t\t   struct child_process *cmd,\n \t\t\t\t   struct string_list *names,\ndiff --git a/repack-midx.c b/repack-midx.c\nindex 354df729a5..6f6202c5bc 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -6,6 +6,7 @@\n #include \"oidset.h\"\n #include \"pack-bitmap.h\"\n #include \"refs.h\"\n+#include \"run-command.h\"\n #include \"tempfile.h\"\n \n struct midx_snapshot_ref_data {\n@@ -75,3 +76,297 @@ void midx_snapshot_refs(struct repository *repo, struct tempfile *f)\n \n \toidset_clear(&data.seen);\n }\n+\n+static int midx_has_unknown_packs(struct string_list *include,\n+\t\t\t\t  struct pack_geometry *geometry,\n+\t\t\t\t  struct existing_packs *existing)\n+{\n+\tstruct string_list_item *item;\n+\n+\tstring_list_sort(include);\n+\n+\tfor_each_string_list_item(item, &existing->midx_packs) {\n+\t\tconst char *pack_name = item->string;\n+\n+\t\t/*\n+\t\t * Determine whether or not each MIDX'd pack from the existing\n+\t\t * MIDX (if any) is represented in the new MIDX. For each pack\n+\t\t * in the MIDX, it must either be:\n+\t\t *\n+\t\t *  - In the \"include\" list of packs to be included in the new\n+\t\t *    MIDX. Note this function is called before the include\n+\t\t *    list is populated with any cruft pack(s).\n+\t\t *\n+\t\t *  - Below the geometric split line (if using pack geometry),\n+\t\t *    indicating that the pack won't be included in the new\n+\t\t *    MIDX, but its contents were rolled up as part of the\n+\t\t *    geometric repack.\n+\t\t *\n+\t\t *  - In the existing non-kept packs list (if not using pack\n+\t\t *    geometry), and marked as non-deleted.\n+\t\t */\n+\t\tif (string_list_has_string(include, pack_name)) {\n+\t\t\tcontinue;\n+\t\t} else if (geometry) {\n+\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\t\tuint32_t j;\n+\n+\t\t\tfor (j = 0; j < geometry->split; j++) {\n+\t\t\t\tstrbuf_reset(&buf);\n+\t\t\t\tstrbuf_addstr(&buf, pack_basename(geometry->pack[j]));\n+\t\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\t\t\t\tstrbuf_addstr(&buf, \".idx\");\n+\n+\t\t\t\tif (!strcmp(pack_name, buf.buf)) {\n+\t\t\t\t\tstrbuf_release(&buf);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tstrbuf_release(&buf);\n+\n+\t\t\tif (j < geometry->split)\n+\t\t\t\tcontinue;\n+\t\t} else {\n+\t\t\tstruct string_list_item *item;\n+\n+\t\t\titem = string_list_lookup(&existing->non_kept_packs,\n+\t\t\t\t\t\t  pack_name);\n+\t\t\tif (item && !existing_pack_is_marked_for_deletion(item))\n+\t\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/*\n+\t\t * If we got to this point, the MIDX includes some pack that we\n+\t\t * don't know about.\n+\t\t */\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void midx_included_packs(struct string_list *include,\n+\t\t\t\tstruct repack_write_midx_opts *opts)\n+{\n+\tstruct existing_packs *existing = opts->existing;\n+\tstruct pack_geometry *geometry = opts->geometry;\n+\tstruct string_list *names = opts->names;\n+\tstruct string_list_item *item;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tfor_each_string_list_item(item, &existing->kept_packs) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n+\t\tstring_list_insert(include, buf.buf);\n+\t}\n+\n+\tfor_each_string_list_item(item, names) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"pack-%s.idx\", item->string);\n+\t\tstring_list_insert(include, buf.buf);\n+\t}\n+\n+\tif (geometry->split_factor) {\n+\t\tuint32_t i;\n+\n+\t\tfor (i = geometry->split; i < geometry->pack_nr; i++) {\n+\t\t\tstruct packed_git *p = geometry->pack[i];\n+\n+\t\t\t/*\n+\t\t\t * The multi-pack index never refers to packfiles part\n+\t\t\t * of an alternate object database, so we skip these.\n+\t\t\t * While git-multi-pack-index(1) would silently ignore\n+\t\t\t * them anyway, this allows us to skip executing the\n+\t\t\t * command completely when we have only non-local\n+\t\t\t * packfiles.\n+\t\t\t */\n+\t\t\tif (!p->pack_local)\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addstr(&buf, pack_basename(p));\n+\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\t\t\tstrbuf_addstr(&buf, \".idx\");\n+\n+\t\t\tstring_list_insert(include, buf.buf);\n+\t\t}\n+\t} else {\n+\t\tfor_each_string_list_item(item, &existing->non_kept_packs) {\n+\t\t\tif (existing_pack_is_marked_for_deletion(item))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n+\t\t\tstring_list_insert(include, buf.buf);\n+\t\t}\n+\t}\n+\n+\tif (opts->midx_must_contain_cruft ||\n+\t    midx_has_unknown_packs(include, geometry, existing)) {\n+\t\t/*\n+\t\t * If there are one or more unknown pack(s) present (see\n+\t\t * midx_has_unknown_packs() for what makes a pack\n+\t\t * \"unknown\") in the MIDX before the repack, keep them\n+\t\t * as they may be required to form a reachability\n+\t\t * closure if the MIDX is bitmapped.\n+\t\t *\n+\t\t * For example, a cruft pack can be required to form a\n+\t\t * reachability closure if the MIDX is bitmapped and one\n+\t\t * or more of the bitmap's selected commits reaches a\n+\t\t * once-cruft object that was later made reachable.\n+\t\t */\n+\t\tfor_each_string_list_item(item, &existing->cruft_packs) {\n+\t\t\t/*\n+\t\t\t * When doing a --geometric repack, there is no\n+\t\t\t * need to check for deleted packs, since we're\n+\t\t\t * by definition not doing an ALL_INTO_ONE\n+\t\t\t * repack (hence no packs will be deleted).\n+\t\t\t * Otherwise we must check for and exclude any\n+\t\t\t * packs which are enqueued for deletion.\n+\t\t\t *\n+\t\t\t * So we could omit the conditional below in the\n+\t\t\t * --geometric case, but doing so is unnecessary\n+\t\t\t *  since no packs are marked as pending\n+\t\t\t *  deletion (since we only call\n+\t\t\t *  `existing_packs_mark_for_deletion()` when\n+\t\t\t *  doing an all-into-one repack).\n+\t\t\t */\n+\t\t\tif (existing_pack_is_marked_for_deletion(item))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n+\t\t\tstring_list_insert(include, buf.buf);\n+\t\t}\n+\t} else {\n+\t\t/*\n+\t\t * Modern versions of Git (with the appropriate\n+\t\t * configuration setting) will write new copies of\n+\t\t * once-cruft objects when doing a --geometric repack.\n+\t\t *\n+\t\t * If the MIDX has no cruft pack, new packs written\n+\t\t * during a --geometric repack will not rely on the\n+\t\t * cruft pack to form a reachability closure, so we can\n+\t\t * avoid including them in the MIDX in that case.\n+\t\t */\n+\t\t;\n+\t}\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+static void remove_redundant_bitmaps(struct string_list *include,\n+\t\t\t\t     const char *packdir)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\tsize_t packdir_len;\n+\n+\tstrbuf_addstr(&path, packdir);\n+\tstrbuf_addch(&path, '/');\n+\tpackdir_len = path.len;\n+\n+\t/*\n+\t * Remove any pack bitmaps corresponding to packs which are now\n+\t * included in the MIDX.\n+\t */\n+\tfor_each_string_list_item(item, include) {\n+\t\tstrbuf_addstr(&path, item->string);\n+\t\tstrbuf_strip_suffix(&path, \".idx\");\n+\t\tstrbuf_addstr(&path, \".bitmap\");\n+\n+\t\tif (unlink(path.buf) && errno != ENOENT)\n+\t\t\twarning_errno(_(\"could not remove stale bitmap: %s\"),\n+\t\t\t\t      path.buf);\n+\n+\t\tstrbuf_setlen(&path, packdir_len);\n+\t}\n+\tstrbuf_release(&path);\n+}\n+\n+int write_midx_included_packs(struct repack_write_midx_opts *opts)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct string_list include = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n+\tFILE *in;\n+\tint ret = 0;\n+\n+\tmidx_included_packs(&include, opts);\n+\tif (!include.nr)\n+\t\tgoto done;\n+\n+\tcmd.in = -1;\n+\tcmd.git_cmd = 1;\n+\n+\tstrvec_push(&cmd.args, \"multi-pack-index\");\n+\tstrvec_pushl(&cmd.args, \"write\", \"--stdin-packs\", NULL);\n+\n+\tif (opts->show_progress)\n+\t\tstrvec_push(&cmd.args, \"--progress\");\n+\telse\n+\t\tstrvec_push(&cmd.args, \"--no-progress\");\n+\n+\tif (opts->write_bitmaps)\n+\t\tstrvec_push(&cmd.args, \"--bitmap\");\n+\n+\tif (preferred)\n+\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=%s\",\n+\t\t\t     pack_basename(preferred));\n+\telse if (opts->names->nr) {\n+\t\t/* The largest pack was repacked, meaning that either\n+\t\t * one or two packs exist depending on whether the\n+\t\t * repository has a cruft pack or not.\n+\t\t *\n+\t\t * Select the non-cruft one as preferred to encourage\n+\t\t * pack-reuse among packs containing reachable objects\n+\t\t * over unreachable ones.\n+\t\t *\n+\t\t * (Note we could write multiple packs here if\n+\t\t * `--max-pack-size` was given, but any one of them\n+\t\t * will suffice, so pick the first one.)\n+\t\t */\n+\t\tfor_each_string_list_item(item, opts->names) {\n+\t\t\tstruct generated_pack *pack = item->util;\n+\t\t\tif (generated_pack_has_ext(pack, \".mtimes\"))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=pack-%s.pack\",\n+\t\t\t\t     item->string);\n+\t\t\tbreak;\n+\t\t}\n+\t} else {\n+\t\t/*\n+\t\t * No packs were kept, and no packs were written. The\n+\t\t * only thing remaining are .keep packs (unless\n+\t\t * --pack-kept-objects was given).\n+\t\t *\n+\t\t * Set the `--preferred-pack` arbitrarily here.\n+\t\t */\n+\t\t;\n+\t}\n+\n+\tif (opts->refs_snapshot)\n+\t\tstrvec_pushf(&cmd.args, \"--refs-snapshot=%s\",\n+\t\t\t     opts->refs_snapshot);\n+\n+\tret = start_command(&cmd);\n+\tif (ret)\n+\t\tgoto done;\n+\n+\tin = xfdopen(cmd.in, \"w\");\n+\tfor_each_string_list_item(item, &include)\n+\t\tfprintf(in, \"%s\\n\", item->string);\n+\tfclose(in);\n+\n+\tret = finish_command(&cmd);\n+done:\n+\tif (!ret && opts->write_bitmaps)\n+\t\tremove_redundant_bitmaps(&include, opts->packdir);\n+\n+\tstring_list_clear(&include, 0);\n+\n+\treturn ret;\n+}\ndiff --git a/repack.h b/repack.h\nindex 6aa5b4e0f0..25a31ac0a0 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -101,6 +101,18 @@ void pack_geometry_release(struct pack_geometry *geometry);\n \n struct tempfile;\n \n+struct repack_write_midx_opts {\n+\tstruct existing_packs *existing;\n+\tstruct pack_geometry *geometry;\n+\tstruct string_list *names;\n+\tconst char *refs_snapshot;\n+\tconst char *packdir;\n+\tint show_progress;\n+\tint write_bitmaps;\n+\tint midx_must_contain_cruft;\n+};\n+\n void midx_snapshot_refs(struct repository *repo, struct tempfile *f);\n+int write_midx_included_packs(struct repack_write_midx_opts *opts);\n \n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527565","messageId":"109109618687efeb932bee7dd5882557ab0576f4.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 40/49] builtin/repack.c: introduce `struct write_pack_opts`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:51Z","receivedAt":"2025-09-28T22:09:56Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"There are various functions within the 'repack' builtin which are\nresponsible for writing different kinds of packs. They include:\n\n - `static int write_filtered_pack(...)`\n - `static int write_cruft_pack(...)`\n\nas well as the function `finish_pack_objects_cmd()`, which is\nresponsible for finalizing a new pack write, and recording the checksum\nof its contents in the 'names' list.\n\nBoth of these `write_` functions have a few things in common. They both\ntake a pointer to the 'pack_objects_args' struct, as well as a pair of\ncharacter pointers for `destination` and `pack_prefix`.\n\nInstead of repeating those arguments for each function, let's extract an\noptions struct called \"write_pack_opts\" which has these three parameters\nas member fields. While we're at it, add fields for \"packdir,\" and\n\"packtmp\", both of which are static variables within the builtin, and\nneed to be read from within these two functions.\n\nThis will shorten the list of parameters that callers have to provide to\n`write_filtered_pack()`, avoid ambiguity when passing multiple variables\nof the same type, and provide a unified interface for the two functions\nmentioned earlier.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 28 +++++++++++++++-------------\n repack.h         |  8 ++++++++\n 2 files changed, 23 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 5fed79e826..6df7c88085 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -138,9 +138,7 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \treturn finish_command(cmd);\n }\n \n-static int write_filtered_pack(const struct pack_objects_args *args,\n-\t\t\t       const char *destination,\n-\t\t\t       const char *pack_prefix,\n+static int write_filtered_pack(struct write_pack_opts *opts,\n \t\t\t       struct existing_packs *existing,\n \t\t\t       struct string_list *names)\n {\n@@ -150,9 +148,9 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n \tint ret;\n \tconst char *caret;\n \tconst char *scratch;\n-\tint local = skip_prefix(destination, packdir, &scratch);\n+\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n \n-\tprepare_pack_objects(&cmd, args, destination);\n+\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n \n \tstrvec_push(&cmd.args, \"--stdin-packs\");\n \n@@ -175,7 +173,7 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n \t */\n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"^%s-%s.pack\\n\", pack_prefix, item->string);\n+\t\tfprintf(in, \"^%s-%s.pack\\n\", opts->pack_prefix, item->string);\n \tfor_each_string_list_item(item, &existing->non_kept_packs)\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfor_each_string_list_item(item, &existing->cruft_packs)\n@@ -665,14 +663,18 @@ int cmd_repack(int argc,\n \t}\n \n \tif (po_args.filter_options.choice) {\n-\t\tif (!filter_to)\n-\t\t\tfilter_to = packtmp;\n+\t\tstruct write_pack_opts opts = {\n+\t\t\t.po_args = &po_args,\n+\t\t\t.destination = filter_to,\n+\t\t\t.pack_prefix = find_pack_prefix(packdir, packtmp),\n+\t\t\t.packdir = packdir,\n+\t\t\t.packtmp = packtmp,\n+\t\t};\n \n-\t\tret = write_filtered_pack(&po_args,\n-\t\t\t\t\t  filter_to,\n-\t\t\t\t\t  find_pack_prefix(packdir, packtmp),\n-\t\t\t\t\t  &existing,\n-\t\t\t\t\t  &names);\n+\t\tif (!opts.destination)\n+\t\t\topts.destination = packtmp;\n+\n+\t\tret = write_filtered_pack(&opts, &existing, &names);\n \t\tif (ret)\n \t\t\tgoto cleanup;\n \t}\ndiff --git a/repack.h b/repack.h\nindex 25a31ac0a0..6ef503f623 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -32,6 +32,14 @@ void pack_objects_args_release(struct pack_objects_args *args);\n void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \t\t\t\t  const char *base_name);\n \n+struct write_pack_opts {\n+\tstruct pack_objects_args *po_args;\n+\tconst char *destination;\n+\tconst char *pack_prefix;\n+\tconst char *packdir;\n+\tconst char *packtmp;\n+};\n+\n struct repository;\n struct packed_git;\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527566","messageId":"7826afc17376f0d4a7f3169d9aafce21b3273ff5.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 41/49] builtin/repack.c: use `write_pack_opts` within `write_cruft_pack()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:09:57Z","receivedAt":"2025-09-28T22:10:01Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Similar to the changes made in the previous commit to\n`write_filtered_pack()`, teach `write_cruft_pack()` to take a\n`write_pack_opts` struct and use that where possible.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 27 ++++++++++++++-------------\n 1 file changed, 14 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 6df7c88085..501359c580 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -221,9 +221,7 @@ static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n \tstrbuf_release(&buf);\n }\n \n-static int write_cruft_pack(const struct pack_objects_args *args,\n-\t\t\t    const char *destination,\n-\t\t\t    const char *pack_prefix,\n+static int write_cruft_pack(struct write_pack_opts *opts,\n \t\t\t    const char *cruft_expiration,\n \t\t\t    unsigned long combine_cruft_below_size,\n \t\t\t    struct string_list *names,\n@@ -234,9 +232,9 @@ static int write_cruft_pack(const struct pack_objects_args *args,\n \tFILE *in;\n \tint ret;\n \tconst char *scratch;\n-\tint local = skip_prefix(destination, packdir, &scratch);\n+\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n \n-\tprepare_pack_objects(&cmd, args, destination);\n+\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n \n \tstrvec_push(&cmd.args, \"--cruft\");\n \tif (cruft_expiration)\n@@ -267,7 +265,7 @@ static int write_cruft_pack(const struct pack_objects_args *args,\n \t */\n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"%s-%s.pack\\n\", pack_prefix, item->string);\n+\t\tfprintf(in, \"%s-%s.pack\\n\", opts->pack_prefix, item->string);\n \tif (combine_cruft_below_size && !cruft_expiration) {\n \t\tcombine_small_cruft_packs(in, combine_cruft_below_size,\n \t\t\t\t\t  existing);\n@@ -599,6 +597,13 @@ int cmd_repack(int argc,\n \n \tif (pack_everything & PACK_CRUFT) {\n \t\tconst char *pack_prefix = find_pack_prefix(packdir, packtmp);\n+\t\tstruct write_pack_opts opts = {\n+\t\t\t.po_args = &cruft_po_args,\n+\t\t\t.destination = packtmp,\n+\t\t\t.pack_prefix = pack_prefix,\n+\t\t\t.packtmp = packtmp,\n+\t\t\t.packdir = packdir,\n+\t\t};\n \n \t\tif (!cruft_po_args.window)\n \t\t\tcruft_po_args.window = xstrdup_or_null(po_args.window);\n@@ -615,8 +620,7 @@ int cmd_repack(int argc,\n \t\tcruft_po_args.quiet = po_args.quiet;\n \t\tcruft_po_args.delta_base_offset = po_args.delta_base_offset;\n \n-\t\tret = write_cruft_pack(&cruft_po_args, packtmp, pack_prefix,\n-\t\t\t\t       cruft_expiration,\n+\t\tret = write_cruft_pack(&opts, cruft_expiration,\n \t\t\t\t       combine_cruft_below_size, &names,\n \t\t\t\t       &existing);\n \t\tif (ret)\n@@ -651,11 +655,8 @@ int cmd_repack(int argc,\n \t\t\t * pack, but rather removing all cruft packs from the\n \t\t\t * main repository regardless of size.\n \t\t\t */\n-\t\t\tret = write_cruft_pack(&cruft_po_args, expire_to,\n-\t\t\t\t\t       pack_prefix,\n-\t\t\t\t\t       NULL,\n-\t\t\t\t\t       0ul,\n-\t\t\t\t\t       &names,\n+\t\t\topts.destination = expire_to;\n+\t\t\tret = write_cruft_pack(&opts, NULL, 0ul, &names,\n \t\t\t\t\t       &existing);\n \t\t\tif (ret)\n \t\t\t\tgoto cleanup;\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527567","messageId":"c50ffce0781d0fbb24eb72f437dab257694a6b99.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 42/49] repack: move `find_pack_prefix()` out of the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:10:02Z","receivedAt":"2025-09-28T22:10:07Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Both callers within the repack builtin which call functions that take a\n'write_pack_opts' structure have the following pattern:\n\n    struct write_pack_opts opts = {\n        .packdir = packdir,\n        .packtmp = packtmp,\n        .pack_prefix = find_pack_prefix(packdir, packtmp),\n        /* ... */\n    };\n    int ret = write_some_kind_of_pack(&opts, /* ... */);\n\n, but both \"packdir\" and \"packtmp\" are fields within the write_pack_opts\nstruct itself!\n\nInstead of also computing the pack_prefix ahead of time, let's have the\ncallees compute it themselves by moving `find_pack_prefix()` out of the\nrepack builtin, and have it take a write_pack_opts pointer instead of\nthe \"packdir\" and \"packtmp\" fields directly.\n\nThis avoids the callers having to do some prep work that is common\nbetween the two of them, but also avoids the potential pitfall of\naccidentally writing:\n\n    .pack_prefix = find_pack_prefix(packtmp, packdir),\n\n(which is well-typed) when the caller meant to instead write:\n\n    .pack_prefix = find_pack_prefix(packdir, packtmp),\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 20 ++++----------------\n repack.c         | 11 +++++++++++\n repack.h         |  3 ++-\n 3 files changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 501359c580..4d86920618 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -149,6 +149,7 @@ static int write_filtered_pack(struct write_pack_opts *opts,\n \tconst char *caret;\n \tconst char *scratch;\n \tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n+\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n \n@@ -173,7 +174,7 @@ static int write_filtered_pack(struct write_pack_opts *opts,\n \t */\n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"^%s-%s.pack\\n\", opts->pack_prefix, item->string);\n+\t\tfprintf(in, \"^%s-%s.pack\\n\", pack_prefix, item->string);\n \tfor_each_string_list_item(item, &existing->non_kept_packs)\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfor_each_string_list_item(item, &existing->cruft_packs)\n@@ -233,6 +234,7 @@ static int write_cruft_pack(struct write_pack_opts *opts,\n \tint ret;\n \tconst char *scratch;\n \tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n+\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n \n@@ -265,7 +267,7 @@ static int write_cruft_pack(struct write_pack_opts *opts,\n \t */\n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"%s-%s.pack\\n\", opts->pack_prefix, item->string);\n+\t\tfprintf(in, \"%s-%s.pack\\n\", pack_prefix, item->string);\n \tif (combine_cruft_below_size && !cruft_expiration) {\n \t\tcombine_small_cruft_packs(in, combine_cruft_below_size,\n \t\t\t\t\t  existing);\n@@ -283,17 +285,6 @@ static int write_cruft_pack(struct write_pack_opts *opts,\n \t\t\t\t       local);\n }\n \n-static const char *find_pack_prefix(const char *packdir, const char *packtmp)\n-{\n-\tconst char *pack_prefix;\n-\tif (!skip_prefix(packtmp, packdir, &pack_prefix))\n-\t\tdie(_(\"pack prefix %s does not begin with objdir %s\"),\n-\t\t    packtmp, packdir);\n-\tif (*pack_prefix == '/')\n-\t\tpack_prefix++;\n-\treturn pack_prefix;\n-}\n-\n int cmd_repack(int argc,\n \t       const char **argv,\n \t       const char *prefix,\n@@ -596,11 +587,9 @@ int cmd_repack(int argc,\n \t}\n \n \tif (pack_everything & PACK_CRUFT) {\n-\t\tconst char *pack_prefix = find_pack_prefix(packdir, packtmp);\n \t\tstruct write_pack_opts opts = {\n \t\t\t.po_args = &cruft_po_args,\n \t\t\t.destination = packtmp,\n-\t\t\t.pack_prefix = pack_prefix,\n \t\t\t.packtmp = packtmp,\n \t\t\t.packdir = packdir,\n \t\t};\n@@ -667,7 +656,6 @@ int cmd_repack(int argc,\n \t\tstruct write_pack_opts opts = {\n \t\t\t.po_args = &po_args,\n \t\t\t.destination = filter_to,\n-\t\t\t.pack_prefix = find_pack_prefix(packdir, packtmp),\n \t\t\t.packdir = packdir,\n \t\t\t.packtmp = packtmp,\n \t\t};\ndiff --git a/repack.c b/repack.c\nindex 1d485e0112..c4326a532d 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -66,6 +66,17 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \tstrbuf_release(&buf);\n }\n \n+const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts)\n+{\n+\tconst char *pack_prefix;\n+\tif (!skip_prefix(opts->packtmp, opts->packdir, &pack_prefix))\n+\t\tdie(_(\"pack prefix %s does not begin with objdir %s\"),\n+\t\t    opts->packtmp, opts->packdir);\n+\tif (*pack_prefix == '/')\n+\t\tpack_prefix++;\n+\treturn pack_prefix;\n+}\n+\n #define DELETE_PACK 1\n #define RETAIN_PACK 2\n \ndiff --git a/repack.h b/repack.h\nindex 6ef503f623..46d2312fa9 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -35,11 +35,12 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n struct write_pack_opts {\n \tstruct pack_objects_args *po_args;\n \tconst char *destination;\n-\tconst char *pack_prefix;\n \tconst char *packdir;\n \tconst char *packtmp;\n };\n \n+const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts);\n+\n struct repository;\n struct packed_git;\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527568","messageId":"1fbe3d0d7f37840b2d9faf6f62e94abd6a664ded.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 43/49] repack: extract `write_pack_opts_is_local()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:10:08Z","receivedAt":"2025-09-28T22:10:12Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Similar to the previous commit, the functions `write_cruft_pack()` and\n`write_filtered_pack()` both compute a \"local\" variable via the exact\nsame mechanism:\n\n    const char *scratch;\n    int local = skip_prefix(opts->destination, opts->packdir, &scratch);\n\nNot only does this cause us to repeat the same pair of lines, it also\nintroduces an unnecessary \"scratch\" variable that is common between both\nfunctions.\n\nInstead of repeating ourselves, let's extract that functionality into a\nnew function in the repack.h API called \"write_pack_opts_is_local()\".\nThat function takes a pointer to a \"struct write_pack_opts\" (which has\nas fields both \"destination\" and \"packdir\"), and can encapsulate the\ndangling \"scratch\" field.\n\nExtract that function and make it visible within the repack.h API, and\nuse it within both `write_cruft_pack()` and `write_filtered_pack()`.\n\nThe remaining duplication (that is, that both `write_cruft_pack()` and\n`write_filtered_pack()` still both call `write_pack_opts_is_local()`)\nwill be addressed in the following commit.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 6 ++----\n repack.c         | 6 ++++++\n repack.h         | 1 +\n 3 files changed, 9 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 4d86920618..be8e6689fc 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -147,8 +147,7 @@ static int write_filtered_pack(struct write_pack_opts *opts,\n \tFILE *in;\n \tint ret;\n \tconst char *caret;\n-\tconst char *scratch;\n-\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n+\tint local = write_pack_opts_is_local(opts);\n \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n@@ -232,8 +231,7 @@ static int write_cruft_pack(struct write_pack_opts *opts,\n \tstruct string_list_item *item;\n \tFILE *in;\n \tint ret;\n-\tconst char *scratch;\n-\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n+\tint local = write_pack_opts_is_local(opts);\n \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\ndiff --git a/repack.c b/repack.c\nindex c4326a532d..7af297ae48 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -77,6 +77,12 @@ const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts)\n \treturn pack_prefix;\n }\n \n+int write_pack_opts_is_local(struct write_pack_opts *opts)\n+{\n+\tconst char *scratch;\n+\treturn skip_prefix(opts->destination, opts->packdir, &scratch);\n+}\n+\n #define DELETE_PACK 1\n #define RETAIN_PACK 2\n \ndiff --git a/repack.h b/repack.h\nindex 46d2312fa9..16f2de2ea9 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -40,6 +40,7 @@ struct write_pack_opts {\n };\n \n const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts);\n+int write_pack_opts_is_local(struct write_pack_opts *opts);\n \n struct repository;\n struct packed_git;\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527569","messageId":"11f78993f1d2d56b204398788780b30318d83afa.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 44/49] builtin/repack.c: pass `write_pack_opts` to `finish_pack_objects_cmd()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:10:14Z","receivedAt":"2025-09-28T22:10:18Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"To prepare to move the `finish_pack_objects_cmd()` function out of the\nbuiltin and into the repack.h API, there are a couple of things we need\nto do first:\n\n - First, let's take advantage of `write_pack_opts_is_local()` function\n   introduced in the previous commit instead of passing \"local\"\n   explicitly.\n\n - Let's also avoid referring to the static 'packtmp' field within\n   builtin/repack.c by instead accessing it through the write_pack_opts\n   argument.\n\nThere are three callers which need to adjust themselves in order to\naccount for this change. The callers which reside in write_cruft_pack()\nand write_filtered_pack() both already have an \"opts\" in scope, so they\ncan pass it through transparently.\n\nThe other call (at the bottom of `cmd_repack()`) needs to initialize its\nown write_pack_opts to pass the necessary fields over to the direct call\nto `finish_pack_objects_cmd()`.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 24 ++++++++++++++----------\n 1 file changed, 14 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex be8e6689fc..8db95305c8 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -108,11 +108,12 @@ static int repack_config(const char *var, const char *value,\n }\n \n static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n+\t\t\t\t   struct write_pack_opts *opts,\n \t\t\t\t   struct child_process *cmd,\n-\t\t\t\t   struct string_list *names,\n-\t\t\t\t   int local)\n+\t\t\t\t   struct string_list *names)\n {\n \tFILE *out;\n+\tint local = write_pack_opts_is_local(opts);\n \tstruct strbuf line = STRBUF_INIT;\n \n \tout = xfdopen(cmd->out, \"r\");\n@@ -128,7 +129,8 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t */\n \t\tif (local) {\n \t\t\titem = string_list_append(names, line.buf);\n-\t\t\titem->util = generated_pack_populate(line.buf, packtmp);\n+\t\t\titem->util = generated_pack_populate(line.buf,\n+\t\t\t\t\t\t\t     opts->packtmp);\n \t\t}\n \t}\n \tfclose(out);\n@@ -147,7 +149,6 @@ static int write_filtered_pack(struct write_pack_opts *opts,\n \tFILE *in;\n \tint ret;\n \tconst char *caret;\n-\tint local = write_pack_opts_is_local(opts);\n \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n@@ -183,8 +184,8 @@ static int write_filtered_pack(struct write_pack_opts *opts,\n \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n \tfclose(in);\n \n-\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n-\t\t\t\t       local);\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n+\t\t\t\t       names);\n }\n \n static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n@@ -231,7 +232,6 @@ static int write_cruft_pack(struct write_pack_opts *opts,\n \tstruct string_list_item *item;\n \tFILE *in;\n \tint ret;\n-\tint local = write_pack_opts_is_local(opts);\n \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n@@ -279,8 +279,8 @@ static int write_cruft_pack(struct write_pack_opts *opts,\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfclose(in);\n \n-\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n-\t\t\t\t       local);\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n+\t\t\t\t       names);\n }\n \n int cmd_repack(int argc,\n@@ -294,6 +294,7 @@ int cmd_repack(int argc,\n \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n \tstruct pack_geometry geometry = { 0 };\n \tstruct tempfile *refs_snapshot = NULL;\n+\tstruct write_pack_opts opts = { 0 };\n \tint i, ret;\n \tint show_progress;\n \n@@ -560,7 +561,10 @@ int cmd_repack(int argc,\n \t\tfclose(in);\n \t}\n \n-\tret = finish_pack_objects_cmd(repo->hash_algo, &cmd, &names, 1);\n+\topts.packdir = packdir;\n+\topts.destination = packdir;\n+\topts.packtmp = packtmp;\n+\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n \tif (ret)\n \t\tgoto cleanup;\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527570","messageId":"a38d14e411246d4f7a02a826262e9e7500b69237.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 45/49] repack: move `finish_pack_objects_cmd()` out of the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:10:19Z","receivedAt":"2025-09-28T22:10:24Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as the previous commit(s), now that the function\n`finish_pack_objects_cmd()` has no explicit dependencies within the\nrepack builtin, let's extract it.\n\nThis prepares us to extract the remaining two functions within the\nrepack builtin that explicitly write packfiles, which are\n`write_cruft_pack()` and `write_filtered_pack()`, which will be done in\nthe future commits.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 33 ---------------------------------\n repack.c         | 33 +++++++++++++++++++++++++++++++++\n repack.h         |  5 +++++\n 3 files changed, 38 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 8db95305c8..836a006607 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,39 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n-\t\t\t\t   struct write_pack_opts *opts,\n-\t\t\t\t   struct child_process *cmd,\n-\t\t\t\t   struct string_list *names)\n-{\n-\tFILE *out;\n-\tint local = write_pack_opts_is_local(opts);\n-\tstruct strbuf line = STRBUF_INIT;\n-\n-\tout = xfdopen(cmd->out, \"r\");\n-\twhile (strbuf_getline_lf(&line, out) != EOF) {\n-\t\tstruct string_list_item *item;\n-\n-\t\tif (line.len != algop->hexsz)\n-\t\t\tdie(_(\"repack: Expecting full hex object ID lines only \"\n-\t\t\t      \"from pack-objects.\"));\n-\t\t/*\n-\t\t * Avoid putting packs written outside of the repository in the\n-\t\t * list of names.\n-\t\t */\n-\t\tif (local) {\n-\t\t\titem = string_list_append(names, line.buf);\n-\t\t\titem->util = generated_pack_populate(line.buf,\n-\t\t\t\t\t\t\t     opts->packtmp);\n-\t\t}\n-\t}\n-\tfclose(out);\n-\n-\tstrbuf_release(&line);\n-\n-\treturn finish_command(cmd);\n-}\n-\n static int write_filtered_pack(struct write_pack_opts *opts,\n \t\t\t       struct existing_packs *existing,\n \t\t\t       struct string_list *names)\ndiff --git a/repack.c b/repack.c\nindex 7af297ae48..8a0e4789fa 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -83,6 +83,39 @@ int write_pack_opts_is_local(struct write_pack_opts *opts)\n \treturn skip_prefix(opts->destination, opts->packdir, &scratch);\n }\n \n+int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n+\t\t\t    struct write_pack_opts *opts,\n+\t\t\t    struct child_process *cmd,\n+\t\t\t    struct string_list *names)\n+{\n+\tFILE *out;\n+\tint local = write_pack_opts_is_local(opts);\n+\tstruct strbuf line = STRBUF_INIT;\n+\n+\tout = xfdopen(cmd->out, \"r\");\n+\twhile (strbuf_getline_lf(&line, out) != EOF) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tif (line.len != algop->hexsz)\n+\t\t\tdie(_(\"repack: Expecting full hex object ID lines only \"\n+\t\t\t      \"from pack-objects.\"));\n+\t\t/*\n+\t\t * Avoid putting packs written outside of the repository in the\n+\t\t * list of names.\n+\t\t */\n+\t\tif (local) {\n+\t\t\titem = string_list_append(names, line.buf);\n+\t\t\titem->util = generated_pack_populate(line.buf,\n+\t\t\t\t\t\t\t     opts->packtmp);\n+\t\t}\n+\t}\n+\tfclose(out);\n+\n+\tstrbuf_release(&line);\n+\n+\treturn finish_command(cmd);\n+}\n+\n #define DELETE_PACK 1\n #define RETAIN_PACK 2\n \ndiff --git a/repack.h b/repack.h\nindex 16f2de2ea9..9351293233 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -42,6 +42,11 @@ struct write_pack_opts {\n const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts);\n int write_pack_opts_is_local(struct write_pack_opts *opts);\n \n+int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n+\t\t\t    struct write_pack_opts *opts,\n+\t\t\t    struct child_process *cmd,\n+\t\t\t    struct string_list *names);\n+\n struct repository;\n struct packed_git;\n \n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527571","messageId":"2d9a879bb027ef8dbd7b4ff7eccd2d038bbfb84c.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 46/49] repack: move `pack_kept_objects` to `struct pack_objects_args`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:10:25Z","receivedAt":"2025-09-28T22:10:28Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The \"pack_kept_objects\" variable is defined as static to the repack\nbuiltin, but is inherently related to the pack-objects arguments that\nthe builtin uses when generating new packs.\n\nMove that field into the \"struct pack_objects_args\", and shuffle around\nwhere we append the corresponding command-line option when preparing a\npack-objects process. Specifically:\n\n - `write_cruft_pack()` always wants to pass \"--honor-pack-keep\", so\n   explicitly set the `pack_kept_objects` field to \"0\" when initializing\n   the `write_pack_opts` struct before calling `write_cruft_pack()`.\n\n - `write_filtered_pack()` no longer needs to handle writing the\n   command-line option \"--honor-pack-keep\" when preparing a pack-objects\n   process, since its call to `prepare_pack_objects()` will have already\n   taken care of that.\n\n   `write_filtered_pack()` also reads the `pack_kept_objects` field to\n   determine whether to write the existing kept packs with a leading \"^\"\n   character, so update that to read through the `po_args` pointer\n   instead.\n\n - `cmd_repack()` also no longer has to write the \"--honor-pack-keep\"\n   flag explicitly, since this is also handled via its call to\n   `prepare_pack_objects()`.\n\nSince there is a default value for \"pack_kept_objects\" that relies on\nwhether or not we are writing a bitmap (and not writing a MIDX), extract\na default initializer for `struct pack_objects_args` that keeps this\nconditional default behavior.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c  | 20 +++++++-------------\n repack-geometry.c |  5 ++---\n repack.c          |  2 ++\n repack.h          |  9 ++++++---\n 4 files changed, 17 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 836a006607..9d89217b77 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -33,7 +33,6 @@\n #define RETAIN_PACK 2\n \n static int pack_everything;\n-static int pack_kept_objects = -1;\n static int write_bitmaps = -1;\n static int use_delta_islands;\n static int run_update_server_info = 1;\n@@ -68,7 +67,7 @@ static int repack_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"repack.packkeptobjects\")) {\n-\t\tpack_kept_objects = git_config_bool(var, value);\n+\t\tpo_args->pack_kept_objects = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"repack.writebitmaps\") ||\n@@ -122,8 +121,6 @@ static int write_filtered_pack(struct write_pack_opts *opts,\n \n \tstrvec_push(&cmd.args, \"--stdin-packs\");\n \n-\tif (!pack_kept_objects)\n-\t\tstrvec_push(&cmd.args, \"--honor-pack-keep\");\n \tfor_each_string_list_item(item, &existing->kept_packs)\n \t\tstrvec_pushf(&cmd.args, \"--keep-pack=%s\", item->string);\n \n@@ -146,7 +143,7 @@ static int write_filtered_pack(struct write_pack_opts *opts,\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfor_each_string_list_item(item, &existing->cruft_packs)\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n-\tcaret = pack_kept_objects ? \"\" : \"^\";\n+\tcaret = opts->po_args->pack_kept_objects ? \"\" : \"^\";\n \tfor_each_string_list_item(item, &existing->kept_packs)\n \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n \tfclose(in);\n@@ -208,7 +205,6 @@ static int write_cruft_pack(struct write_pack_opts *opts,\n \t\tstrvec_pushf(&cmd.args, \"--cruft-expiration=%s\",\n \t\t\t     cruft_expiration);\n \n-\tstrvec_push(&cmd.args, \"--honor-pack-keep\");\n \tstrvec_push(&cmd.args, \"--non-empty\");\n \n \tcmd.in = -1;\n@@ -333,7 +329,7 @@ int cmd_repack(int argc,\n \t\tOPT_UNSIGNED(0, \"max-pack-size\", &po_args.max_pack_size,\n \t\t\t     N_(\"maximum size of each packfile\")),\n \t\tOPT_PARSE_LIST_OBJECTS_FILTER(&po_args.filter_options),\n-\t\tOPT_BOOL(0, \"pack-kept-objects\", &pack_kept_objects,\n+\t\tOPT_BOOL(0, \"pack-kept-objects\", &po_args.pack_kept_objects,\n \t\t\t\tN_(\"repack objects in packs marked with .keep\")),\n \t\tOPT_STRING_LIST(0, \"keep-pack\", &keep_pack_list, N_(\"name\"),\n \t\t\t\tN_(\"do not repack this pack\")),\n@@ -379,8 +375,8 @@ int cmd_repack(int argc,\n \t\t    (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository()))\n \t\t\twrite_bitmaps = 0;\n \t}\n-\tif (pack_kept_objects < 0)\n-\t\tpack_kept_objects = write_bitmaps > 0 && !write_midx;\n+\tif (po_args.pack_kept_objects < 0)\n+\t\tpo_args.pack_kept_objects = write_bitmaps > 0 && !write_midx;\n \n \tif (write_bitmaps && !(pack_everything & ALL_INTO_ONE) && !write_midx)\n \t\tdie(_(incremental_bitmap_conflict_error));\n@@ -421,8 +417,7 @@ int cmd_repack(int argc,\n \tif (geometry.split_factor) {\n \t\tif (pack_everything)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--geometric\", \"-A/-a\");\n-\t\tpack_geometry_init(&geometry, &existing, &po_args,\n-\t\t\t\t   pack_kept_objects);\n+\t\tpack_geometry_init(&geometry, &existing, &po_args);\n \t\tpack_geometry_split(&geometry);\n \t}\n \n@@ -431,8 +426,6 @@ int cmd_repack(int argc,\n \tshow_progress = !po_args.quiet && isatty(2);\n \n \tstrvec_push(&cmd.args, \"--keep-true-parents\");\n-\tif (!pack_kept_objects)\n-\t\tstrvec_push(&cmd.args, \"--honor-pack-keep\");\n \tfor (i = 0; i < keep_pack_list.nr; i++)\n \t\tstrvec_pushf(&cmd.args, \"--keep-pack=%s\",\n \t\t\t     keep_pack_list.items[i].string);\n@@ -577,6 +570,7 @@ int cmd_repack(int argc,\n \t\tcruft_po_args.local = po_args.local;\n \t\tcruft_po_args.quiet = po_args.quiet;\n \t\tcruft_po_args.delta_base_offset = po_args.delta_base_offset;\n+\t\tcruft_po_args.pack_kept_objects = 0;\n \n \t\tret = write_cruft_pack(&opts, cruft_expiration,\n \t\t\t\t       combine_cruft_below_size, &names,\ndiff --git a/repack-geometry.c b/repack-geometry.c\nindex a879f2fe49..fd4a89a115 100644\n--- a/repack-geometry.c\n+++ b/repack-geometry.c\n@@ -26,8 +26,7 @@ static int pack_geometry_cmp(const void *va, const void *vb)\n \n void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\tstruct existing_packs *existing,\n-\t\t\tconst struct pack_objects_args *args,\n-\t\t\tint pack_kept_objects)\n+\t\t\tconst struct pack_objects_args *args)\n {\n \tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n@@ -42,7 +41,7 @@ void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\t */\n \t\t\tcontinue;\n \n-\t\tif (!pack_kept_objects) {\n+\t\tif (!args->pack_kept_objects) {\n \t\t\t/*\n \t\t\t * Any pack that has its pack_keep bit set will\n \t\t\t * appear in existing->kept_packs below, but\ndiff --git a/repack.c b/repack.c\nindex 8a0e4789fa..1982a48165 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -38,6 +38,8 @@ void prepare_pack_objects(struct child_process *cmd,\n \t\tstrvec_push(&cmd->args,  \"--quiet\");\n \tif (args->delta_base_offset)\n \t\tstrvec_push(&cmd->args,  \"--delta-base-offset\");\n+\tif (!args->pack_kept_objects)\n+\t\tstrvec_push(&cmd->args,  \"--honor-pack-keep\");\n \tstrvec_push(&cmd->args, out);\n \tcmd->git_cmd = 1;\n \tcmd->out = -1;\ndiff --git a/repack.h b/repack.h\nindex 9351293233..4a1c4eb606 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -17,10 +17,14 @@ struct pack_objects_args {\n \tint name_hash_version;\n \tint path_walk;\n \tint delta_base_offset;\n+\tint pack_kept_objects;\n \tstruct list_objects_filter_options filter_options;\n };\n \n-#define PACK_OBJECTS_ARGS_INIT { .delta_base_offset = 1 }\n+#define PACK_OBJECTS_ARGS_INIT { \\\n+\t.delta_base_offset = 1, \\\n+\t.pack_kept_objects = -1, \\\n+}\n \n struct child_process;\n \n@@ -104,8 +108,7 @@ struct pack_geometry {\n \n void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\tstruct existing_packs *existing,\n-\t\t\tconst struct pack_objects_args *args,\n-\t\t\tint pack_kept_objects);\n+\t\t\tconst struct pack_objects_args *args);\n void pack_geometry_split(struct pack_geometry *geometry);\n struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry);\n void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527572","messageId":"d0df1397176501fa6bf13a4a846d21e2b1ee3865.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 47/49] repack: move `write_filtered_pack()` out of the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:10:30Z","receivedAt":"2025-09-28T22:10:33Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar fashion as in previous commits, move the function\n`write_filtered_pack()` out of the builtin and into its own compilation\nunit.\n\nThis function is now part of the repack.h API, but implemented in its\nown \"repack-filtered.c\" unit as it is a separate component from other\nkinds of repacking operations.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile          |  1 +\n builtin/repack.c  | 46 ------------------------------------------\n meson.build       |  1 +\n repack-filtered.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++\n repack.h          |  4 ++++\n 5 files changed, 57 insertions(+), 46 deletions(-)\n create mode 100644 repack-filtered.c\n\ndiff --git a/Makefile b/Makefile\nindex 3360743afb..1e2c09da07 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1137,6 +1137,7 @@ LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n+LIB_OBJS += repack-filtered.o\n LIB_OBJS += repack-geometry.o\n LIB_OBJS += repack-midx.o\n LIB_OBJS += repack-promisor.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 9d89217b77..a9fc09a24d 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -106,52 +106,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-static int write_filtered_pack(struct write_pack_opts *opts,\n-\t\t\t       struct existing_packs *existing,\n-\t\t\t       struct string_list *names)\n-{\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tstruct string_list_item *item;\n-\tFILE *in;\n-\tint ret;\n-\tconst char *caret;\n-\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n-\n-\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n-\n-\tstrvec_push(&cmd.args, \"--stdin-packs\");\n-\n-\tfor_each_string_list_item(item, &existing->kept_packs)\n-\t\tstrvec_pushf(&cmd.args, \"--keep-pack=%s\", item->string);\n-\n-\tcmd.in = -1;\n-\n-\tret = start_command(&cmd);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/*\n-\t * Here 'names' contains only the pack(s) that were just\n-\t * written, which is exactly the packs we want to keep. Also\n-\t * 'existing_kept_packs' already contains the packs in\n-\t * 'keep_pack_list'.\n-\t */\n-\tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"^%s-%s.pack\\n\", pack_prefix, item->string);\n-\tfor_each_string_list_item(item, &existing->non_kept_packs)\n-\t\tfprintf(in, \"%s.pack\\n\", item->string);\n-\tfor_each_string_list_item(item, &existing->cruft_packs)\n-\t\tfprintf(in, \"%s.pack\\n\", item->string);\n-\tcaret = opts->po_args->pack_kept_objects ? \"\" : \"^\";\n-\tfor_each_string_list_item(item, &existing->kept_packs)\n-\t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n-\tfclose(in);\n-\n-\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n-\t\t\t\t       names);\n-}\n-\n static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n \t\t\t\t      struct existing_packs *existing)\n {\ndiff --git a/meson.build b/meson.build\nindex 42171d1851..4c60e016c3 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -463,6 +463,7 @@ libgit_sources = [\n   'reftable/writer.c',\n   'remote.c',\n   'repack.c',\n+  'repack-filtered.c',\n   'repack-geometry.c',\n   'repack-midx.c',\n   'repack-promisor.c',\ndiff --git a/repack-filtered.c b/repack-filtered.c\nnew file mode 100644\nindex 0000000000..96c7b02bb6\n--- /dev/null\n+++ b/repack-filtered.c\n@@ -0,0 +1,51 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"repository.h\"\n+#include \"run-command.h\"\n+#include \"string-list.h\"\n+\n+int write_filtered_pack(struct write_pack_opts *opts,\n+\t\t\tstruct existing_packs *existing,\n+\t\t\tstruct string_list *names)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct string_list_item *item;\n+\tFILE *in;\n+\tint ret;\n+\tconst char *caret;\n+\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n+\n+\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n+\n+\tstrvec_push(&cmd.args, \"--stdin-packs\");\n+\n+\tfor_each_string_list_item(item, &existing->kept_packs)\n+\t\tstrvec_pushf(&cmd.args, \"--keep-pack=%s\", item->string);\n+\n+\tcmd.in = -1;\n+\n+\tret = start_command(&cmd);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Here 'names' contains only the pack(s) that were just\n+\t * written, which is exactly the packs we want to keep. Also\n+\t * 'existing_kept_packs' already contains the packs in\n+\t * 'keep_pack_list'.\n+\t */\n+\tin = xfdopen(cmd.in, \"w\");\n+\tfor_each_string_list_item(item, names)\n+\t\tfprintf(in, \"^%s-%s.pack\\n\", pack_prefix, item->string);\n+\tfor_each_string_list_item(item, &existing->non_kept_packs)\n+\t\tfprintf(in, \"%s.pack\\n\", item->string);\n+\tfor_each_string_list_item(item, &existing->cruft_packs)\n+\t\tfprintf(in, \"%s.pack\\n\", item->string);\n+\tcaret = opts->po_args->pack_kept_objects ? \"\" : \"^\";\n+\tfor_each_string_list_item(item, &existing->kept_packs)\n+\t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n+\tfclose(in);\n+\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n+\t\t\t\t       names);\n+}\ndiff --git a/repack.h b/repack.h\nindex 4a1c4eb606..a7ddbe784b 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -133,4 +133,8 @@ struct repack_write_midx_opts {\n void midx_snapshot_refs(struct repository *repo, struct tempfile *f);\n int write_midx_included_packs(struct repack_write_midx_opts *opts);\n \n+int write_filtered_pack(struct write_pack_opts *opts,\n+\t\t\tstruct existing_packs *existing,\n+\t\t\tstruct string_list *names);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527573","messageId":"4c6ec504e39e17f6114f4deb2e86c6a4f4f4109a.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 48/49] repack: move `write_cruft_pack()` out of the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:10:34Z","receivedAt":"2025-09-28T22:10:37Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In an identical fashion as the previous commit, move the function\n`write_cruft_pack()` into its own compilation unit, and make the\nfunction visible through the repack.h API.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile         |  1 +\n builtin/repack.c | 94 ---------------------------------------------\n meson.build      |  1 +\n repack-cruft.c   | 99 ++++++++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  6 +++\n 5 files changed, 107 insertions(+), 94 deletions(-)\n create mode 100644 repack-cruft.c\n\ndiff --git a/Makefile b/Makefile\nindex 1e2c09da07..72d7a4686f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1137,6 +1137,7 @@ LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n+LIB_OBJS += repack-cruft.o\n LIB_OBJS += repack-filtered.o\n LIB_OBJS += repack-geometry.o\n LIB_OBJS += repack-midx.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a9fc09a24d..9171ca66a7 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -106,100 +106,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n-\t\t\t\t      struct existing_packs *existing)\n-{\n-\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n-\tstruct packed_git *p;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tsize_t i;\n-\n-\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n-\t\tif (!(p->is_cruft && p->pack_local))\n-\t\t\tcontinue;\n-\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addstr(&buf, pack_basename(p));\n-\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\t\tif (!string_list_has_string(&existing->cruft_packs, buf.buf))\n-\t\t\tcontinue;\n-\n-\t\tif (p->pack_size < combine_cruft_below_size) {\n-\t\t\tfprintf(in, \"-%s\\n\", pack_basename(p));\n-\t\t} else {\n-\t\t\texisting_packs_retain_cruft(existing, p);\n-\t\t\tfprintf(in, \"%s\\n\", pack_basename(p));\n-\t\t}\n-\t}\n-\n-\tfor (i = 0; i < existing->non_kept_packs.nr; i++)\n-\t\tfprintf(in, \"-%s.pack\\n\",\n-\t\t\texisting->non_kept_packs.items[i].string);\n-\n-\tstrbuf_release(&buf);\n-}\n-\n-static int write_cruft_pack(struct write_pack_opts *opts,\n-\t\t\t    const char *cruft_expiration,\n-\t\t\t    unsigned long combine_cruft_below_size,\n-\t\t\t    struct string_list *names,\n-\t\t\t    struct existing_packs *existing)\n-{\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tstruct string_list_item *item;\n-\tFILE *in;\n-\tint ret;\n-\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n-\n-\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n-\n-\tstrvec_push(&cmd.args, \"--cruft\");\n-\tif (cruft_expiration)\n-\t\tstrvec_pushf(&cmd.args, \"--cruft-expiration=%s\",\n-\t\t\t     cruft_expiration);\n-\n-\tstrvec_push(&cmd.args, \"--non-empty\");\n-\n-\tcmd.in = -1;\n-\n-\tret = start_command(&cmd);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/*\n-\t * names has a confusing double use: it both provides the list\n-\t * of just-written new packs, and accepts the name of the cruft\n-\t * pack we are writing.\n-\t *\n-\t * By the time it is read here, it contains only the pack(s)\n-\t * that were just written, which is exactly the set of packs we\n-\t * want to consider kept.\n-\t *\n-\t * If `--expire-to` is given, the double-use served by `names`\n-\t * ensures that the pack written to `--expire-to` excludes any\n-\t * objects contained in the cruft pack.\n-\t */\n-\tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"%s-%s.pack\\n\", pack_prefix, item->string);\n-\tif (combine_cruft_below_size && !cruft_expiration) {\n-\t\tcombine_small_cruft_packs(in, combine_cruft_below_size,\n-\t\t\t\t\t  existing);\n-\t} else {\n-\t\tfor_each_string_list_item(item, &existing->non_kept_packs)\n-\t\t\tfprintf(in, \"-%s.pack\\n\", item->string);\n-\t\tfor_each_string_list_item(item, &existing->cruft_packs)\n-\t\t\tfprintf(in, \"-%s.pack\\n\", item->string);\n-\t}\n-\tfor_each_string_list_item(item, &existing->kept_packs)\n-\t\tfprintf(in, \"%s.pack\\n\", item->string);\n-\tfclose(in);\n-\n-\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n-\t\t\t\t       names);\n-}\n-\n int cmd_repack(int argc,\n \t       const char **argv,\n \t       const char *prefix,\ndiff --git a/meson.build b/meson.build\nindex 4c60e016c3..2993b767dc 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -463,6 +463,7 @@ libgit_sources = [\n   'reftable/writer.c',\n   'remote.c',\n   'repack.c',\n+  'repack-cruft.c',\n   'repack-filtered.c',\n   'repack-geometry.c',\n   'repack-midx.c',\ndiff --git a/repack-cruft.c b/repack-cruft.c\nnew file mode 100644\nindex 0000000000..accb98bcdb\n--- /dev/null\n+++ b/repack-cruft.c\n@@ -0,0 +1,99 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"packfile.h\"\n+#include \"repository.h\"\n+#include \"run-command.h\"\n+\n+static void combine_small_cruft_packs(FILE *in, off_t combine_cruft_below_size,\n+\t\t\t\t      struct existing_packs *existing)\n+{\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n+\tstruct packed_git *p;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tsize_t i;\n+\n+\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n+\t\tif (!(p->is_cruft && p->pack_local))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, pack_basename(p));\n+\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\t\tif (!string_list_has_string(&existing->cruft_packs, buf.buf))\n+\t\t\tcontinue;\n+\n+\t\tif (p->pack_size < combine_cruft_below_size) {\n+\t\t\tfprintf(in, \"-%s\\n\", pack_basename(p));\n+\t\t} else {\n+\t\t\texisting_packs_retain_cruft(existing, p);\n+\t\t\tfprintf(in, \"%s\\n\", pack_basename(p));\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < existing->non_kept_packs.nr; i++)\n+\t\tfprintf(in, \"-%s.pack\\n\",\n+\t\t\texisting->non_kept_packs.items[i].string);\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+int write_cruft_pack(struct write_pack_opts *opts,\n+\t\t     const char *cruft_expiration,\n+\t\t     unsigned long combine_cruft_below_size,\n+\t\t     struct string_list *names,\n+\t\t     struct existing_packs *existing)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct string_list_item *item;\n+\tFILE *in;\n+\tint ret;\n+\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n+\n+\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n+\n+\tstrvec_push(&cmd.args, \"--cruft\");\n+\tif (cruft_expiration)\n+\t\tstrvec_pushf(&cmd.args, \"--cruft-expiration=%s\",\n+\t\t\t     cruft_expiration);\n+\n+\tstrvec_push(&cmd.args, \"--non-empty\");\n+\n+\tcmd.in = -1;\n+\n+\tret = start_command(&cmd);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * names has a confusing double use: it both provides the list\n+\t * of just-written new packs, and accepts the name of the cruft\n+\t * pack we are writing.\n+\t *\n+\t * By the time it is read here, it contains only the pack(s)\n+\t * that were just written, which is exactly the set of packs we\n+\t * want to consider kept.\n+\t *\n+\t * If `--expire-to` is given, the double-use served by `names`\n+\t * ensures that the pack written to `--expire-to` excludes any\n+\t * objects contained in the cruft pack.\n+\t */\n+\tin = xfdopen(cmd.in, \"w\");\n+\tfor_each_string_list_item(item, names)\n+\t\tfprintf(in, \"%s-%s.pack\\n\", pack_prefix, item->string);\n+\tif (combine_cruft_below_size && !cruft_expiration) {\n+\t\tcombine_small_cruft_packs(in, combine_cruft_below_size,\n+\t\t\t\t\t  existing);\n+\t} else {\n+\t\tfor_each_string_list_item(item, &existing->non_kept_packs)\n+\t\t\tfprintf(in, \"-%s.pack\\n\", item->string);\n+\t\tfor_each_string_list_item(item, &existing->cruft_packs)\n+\t\t\tfprintf(in, \"-%s.pack\\n\", item->string);\n+\t}\n+\tfor_each_string_list_item(item, &existing->kept_packs)\n+\t\tfprintf(in, \"%s.pack\\n\", item->string);\n+\tfclose(in);\n+\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n+\t\t\t\t       names);\n+}\ndiff --git a/repack.h b/repack.h\nindex a7ddbe784b..15886c3488 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -137,4 +137,10 @@ int write_filtered_pack(struct write_pack_opts *opts,\n \t\t\tstruct existing_packs *existing,\n \t\t\tstruct string_list *names);\n \n+int write_cruft_pack(struct write_pack_opts *opts,\n+\t\t     const char *cruft_expiration,\n+\t\t     unsigned long combine_cruft_below_size,\n+\t\t     struct string_list *names,\n+\t\t     struct existing_packs *existing);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.243.g16eca91f2c0\n\n"},{"id":"527574","messageId":"16eca91f2c04b430ed40d1aa296a086d75195b14.1759097191.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH 49/49] builtin/repack.c: clean up unused `#include`s","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-09-28T22:10:38Z","receivedAt":"2025-09-28T22:10:41Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Over the past several dozen commits, we have moved a large amount of\nfunctionality out of the repack builtin and into other files like\nrepack.c, repack-cruft.c, repack-filtered.c, repack-midx.c, and\nrepack-promisor.c.\n\nThese files specify the minimal set of `#include`s that they need to\ncompile successfully, but we did not change the set of `#include`s in\nthe repack builtin itself.\n\nNow that the code movement is complete, let's clean up that set of\n`#include`s and trim down the builtin to include the minimal amount of\nexternal headers necessary to compile.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 9 ---------\n 1 file changed, 9 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 9171ca66a7..ad60c4290d 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -4,26 +4,17 @@\n #include \"builtin.h\"\n #include \"config.h\"\n #include \"environment.h\"\n-#include \"gettext.h\"\n-#include \"hex.h\"\n #include \"parse-options.h\"\n #include \"path.h\"\n #include \"run-command.h\"\n #include \"server-info.h\"\n-#include \"strbuf.h\"\n #include \"string-list.h\"\n-#include \"strvec.h\"\n #include \"midx.h\"\n #include \"packfile.h\"\n #include \"prune-packed.h\"\n-#include \"odb.h\"\n #include \"promisor-remote.h\"\n #include \"repack.h\"\n #include \"shallow.h\"\n-#include \"pack.h\"\n-#include \"pack-bitmap.h\"\n-#include \"refs.h\"\n-#include \"list-objects-filter-options.h\"\n \n #define ALL_INTO_ONE 1\n #define LOOSEN_UNREACHABLE 2\n-- \n2.51.0.243.g16eca91f2c0\n"},{"id":"527576","messageId":"xmqqcy7a5gnb.fsf@gitster.g","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-28T22:58:00Z","receivedAt":"2025-09-28T22:58:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n>  * This series depends on ps/packfile-store at dd52a29b78 (packfile:\n>    refactor `get_packed_git_mru()` to work on packfile store,\n>    2025-09-23).\n>\n>  * This series is split out of my local copy of\n>    tb/incremental-midx-part-3. I suggest queueing it as\n>    tb/incremental-midx-part-3.1 ;-).\n\nBoth of these comments are very much appreciated.  Do we say\nsomething to encourage these in our developer-facing documentation?\n\nThe points to stress are:\n\n - If you are not building directly on top of the recent tip of\n   'master', mention what topics that are not yet in 'master' your\n   patches depend on.\n\n - If your series is a part of a larger effort, briefly describe\n   what your overall direction and grand vision is, and state where\n   in that grand picture the current series fits.\n\n - If you have a short one-line summary for the topic to be used as\n   a topic branch name in mind, do suggest it.\n\nThanks.\n"},{"id":"527610","messageId":"aNsUZtGst4MguEXq@pks.im","threadId":"64222","inReplyTo":"37a7dea848f9618f7243fd9ff8ed704adfb3cf4e.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 07/49] builtin/repack.c: avoid \"the_hash_algo\" in `write_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-29T23:21:10Z","receivedAt":"2025-09-29T23:21:23Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:07:37PM -0400, Taylor Blau wrote:\n> diff --git a/builtin/repack.c b/builtin/repack.c\n> index 094f5a0cc2..7d62959dc2 100644\n> --- a/builtin/repack.c\n> +++ b/builtin/repack.c\n> @@ -413,6 +419,7 @@ static void repack_promisor_objects(struct repository *repo,\n>  \t\t\t\t    const struct pack_objects_args *args,\n>  \t\t\t\t    struct string_list *names)\n>  {\n> +\tstruct write_oid_context ctx;\n>  \tstruct child_process cmd = CHILD_PROCESS_INIT;\n>  \tFILE *out;\n>  \tstruct strbuf line = STRBUF_INIT;\n> @@ -427,7 +434,9 @@ static void repack_promisor_objects(struct repository *repo,\n>  \t * {type -> existing pack order} ordering when computing deltas instead\n>  \t * of a {type -> size} ordering, which may produce better deltas.\n>  \t */\n> -\tfor_each_packed_object(repo, write_oid, &cmd,\n> +\tctx.cmd = &cmd;\n> +\tctx.algop = repo->hash_algo;\n> +\tfor_each_packed_object(repo, write_oid, &ctx,\n>  \t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n\nAs this one is essentially new code, and we have all of the info\navailable right at the start of the problem. Do we maybe want to use\ndesignated initializers?\n\nPatrick\n"},{"id":"527611","messageId":"aNsUcqHqvylWbZCA@pks.im","threadId":"64222","inReplyTo":"de4886466cab65dc72c5f800d680a610dcb0563c.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 10/49] builtin/repack.c: avoid using `hash_to_hex()` in pack geometry","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-29T23:21:22Z","receivedAt":"2025-09-29T23:21:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:07:49PM -0400, Taylor Blau wrote:\n> In previous commits, we started passing either repository or\n> git_hash_algo pointers around to various spots within builtin/repack.c\n> to reduce our dependency on the_repository in the hope of undef'ing\n> USE_THE_REPOSITORY_VARIABLE.\n> \n> This commit takes us as far as we can (easily) go in that direction by\n> removing the only use of a convenience function that only exists when\n> USE_THE_REPOSITORY_VARIABLE is defined.\n> \n> Unfortunately, the only other such function is \"is_bare_repository()\",\n> which is less than straightforward to convert into, say,\n> \"repo_is_bare()\", the latter of the two accepting a repository pointer.\n\nYeah, this function has been annoying me for quite a while already. It's\ndefinitely non-trivial to convert, so punting it into the future makes\nsense to me.\n\nPatrick\n"},{"id":"527612","messageId":"aNsUeH2HudfxIQsx@pks.im","threadId":"64222","inReplyTo":"75386eed60c80a52c42720e4918853ff05c5584c.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 12/49] builtin/repack.c: pass both pack_objects args to repack_config","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-29T23:21:28Z","receivedAt":"2025-09-29T23:21:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:07:56PM -0400, Taylor Blau wrote:\n> diff --git a/builtin/repack.c b/builtin/repack.c\n> index 6dfcb3327e..af6de8d77a 100644\n> --- a/builtin/repack.c\n> +++ b/builtin/repack.c\n> @@ -1260,6 +1266,7 @@ int cmd_repack(int argc,\n>  \tsize_t midx_pack_names_nr = 0;\n>  \n>  \t/* variables to be filled by option parsing */\n> +\tstruct repack_config_ctx config_ctx;\n>  \tint delete_redundant = 0;\n>  \tconst char *unpack_unreachable = NULL;\n>  \tint keep_unreachable = 0;\n> @@ -1343,7 +1350,11 @@ int cmd_repack(int argc,\n>  \n>  \tlist_objects_filter_init(&po_args.filter_options);\n>  \n> -\trepo_config(repo, repack_config, &cruft_po_args);\n> +\tmemset(&config_ctx, 0, sizeof(config_ctx));\n> +\tconfig_ctx.po_args = &po_args;\n> +\tconfig_ctx.cruft_po_args = &cruft_po_args;\n> +\n> +\trepo_config(repo, repack_config, &config_ctx);\n\nNit: same comment here as on a previous commit, we could use a\ndesignated initializer here.\n\nPatrick\n"},{"id":"527613","messageId":"aNsUfrE-2Wc8_1Fm@pks.im","threadId":"64222","inReplyTo":"df75c3130bced8d5489dc9184701a0a544617049.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 17/49] builtin/repack.c: pass \"packdir\" when removing packs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-29T23:21:34Z","receivedAt":"2025-09-29T23:21:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:08:16PM -0400, Taylor Blau wrote:\n> builtin/repack.c defines a static \"packdir\" to instruct pack-objects on\n> where to write any new packfiles. This is also the directory scanned\n> when removing any packfiles which were made redundant by the latest\n> repack.\n> \n> Prepare to move the \"existing_packs_remove_redundant\" function to its\n> own compilation unit by passing in this information as a parameter to\n> that function.\n\nI would have expected that we also get rid of that static variable\neither in this commit or a follow-up. Is there any reason why you\nskipped over this? If so, it would be great to highlight in the commit\nmessage.\n\nPatrick\n"},{"id":"527614","messageId":"aNsUhEVUmzmZ5gp3@pks.im","threadId":"64222","inReplyTo":"ae267fcc820dc19497f9f897bc5915b2040739e5.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 19/49] repack: remove 'existing_packs' API from the builtin","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-29T23:21:40Z","receivedAt":"2025-09-29T23:21:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:08:23PM -0400, Taylor Blau wrote:\n> The repack builtin defines an API for keeping track of which packs\n> were found in the repository at the beginning of the repack operation.\n> This is used to classify what state a pack was in (kept, non-kept, or\n> cruft), and is also used to mark which packs to delete (or keep) at the\n> end of a repack operation.\n> \n> Now that the prerequisite refactoring is complete, this API is isolated\n> enough that it can be moved out to repack.ch and removed from the\n\nTiniest nit, sorry: I found the spelling of \"repack.ch\" a bit confusing\nat first, as it looked like a typo. Maybe say \"repack.{c,h}\" or\n\"repack.[ch]\"?\n\nPatrick\n"},{"id":"527615","messageId":"aNsUiho56-S4MQZg@pks.im","threadId":"64222","inReplyTo":"96d27261e75fd2ae46d9633b3d4eff243dbcc187.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 21/49] builtin/repack.c: factor our \"generated_pack_install\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-29T23:21:46Z","receivedAt":"2025-09-29T23:21:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:08:31PM -0400, Taylor Blau wrote:\n\nIs the \"factor our\" in the commit subject supposed to read \"factor out\"?\n\n> diff --git a/builtin/repack.c b/builtin/repack.c\n> index a4d80b6b04..8c3a5f4f80 100644\n> --- a/builtin/repack.c\n> +++ b/builtin/repack.c\n> @@ -1434,35 +1466,9 @@ int cmd_repack(int argc,\n>  \t/*\n>  \t * Ok we have prepared all new packfiles.\n>  \t */\n> -\tfor_each_string_list_item(item, &names) {\n> -\t\tstruct generated_pack *pack = item->util;\n> -\n> -\t\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n> -\t\t\tchar *fname;\n> -\n> -\t\t\tfname = mkpathdup(\"%s/pack-%s%s\",\n> -\t\t\t\t\tpackdir, item->string, exts[ext].name);\n> -\n> -\t\t\tif (pack->tempfiles[ext]) {\n> -\t\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n> -\t\t\t\tstruct stat statbuffer;\n> -\n> -\t\t\t\tif (!stat(fname_old, &statbuffer)) {\n> -\t\t\t\t\tstatbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);\n> -\t\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n> -\t\t\t\t}\n> -\n> -\t\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n> -\t\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"), fname);\n> -\t\t\t} else if (!exts[ext].optional)\n> -\t\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n> -\t\t\t\t    exts[ext].name, packtmp, item->string);\n> -\t\t\telse if (unlink(fname) < 0 && errno != ENOENT)\n> -\t\t\t\tdie_errno(_(\"could not unlink: %s\"), fname);\n> -\n> -\t\t\tfree(fname);\n> -\t\t}\n> -\t}\n> +\tfor_each_string_list_item(item, &names)\n> +\t\tgenerated_pack_install((struct generated_pack *)item->util,\n\nThis cast should be unnecessary, right? `item->util` is a void pointer,\nso C should do the cast implicitly.\n\nPatrick\n"},{"id":"527616","messageId":"aNsUkenrDY8YHgBn@pks.im","threadId":"64222","inReplyTo":"ab0dc6668339f24787dc80d2dc05f0d31cfadae6.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 22/49] builtin/repack.c: pass \"packtmp\" to `generated_pack_populate()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-29T23:21:53Z","receivedAt":"2025-09-29T23:21:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:08:35PM -0400, Taylor Blau wrote:\n> In a similar spirit as previous commits, this function needs to know the\n> temporary pack prefix, which it currently accesses through the static\n\ns/accesses/accessed/\n\nPatrick\n"},{"id":"527617","messageId":"aNsUl2afoigZZiLm@pks.im","threadId":"64222","inReplyTo":"3ba6e86f6116cc42fc8f7d31707dd514f4bc16a4.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 26/49] builtin/repack.c: remove \"repack_promisor_objects()\" from the builtin","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-29T23:21:59Z","receivedAt":"2025-09-29T23:22:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:08:51PM -0400, Taylor Blau wrote:\n> Now that we have properly factored the portion of the builtin which is\n> responsible for repacking promisor objects, we can move that function\n> (and associated dependencies) out of the builtin entirely.\n> \n> Similar to previous extractions, this function is declared in repack.h,\n> but implemented in a separate repack-promisor.c file. This is done to\n> separate promisor-specific repacking functionality from generic repack\n> utilities (like \"existing_packs\", and \"generated_pack\" APIs).\n\nFeels sensible.\n\n> diff --git a/repack-promisor.c b/repack-promisor.c\n> new file mode 100644\n> index 0000000000..8bf42fc715\n> --- /dev/null\n> +++ b/repack-promisor.c\n> @@ -0,0 +1,102 @@\n> +#include \"git-compat-util.h\"\n> +#include \"repack.h\"\n> +#include \"run-command.h\"\n> +#include \"hex.h\"\n> +#include \"repository.h\"\n> +#include \"packfile.h\"\n> +#include \"path.h\"\n> +#include \"pack.h\"\n\nNit: these headers should probably be sorted lexicographically?\n\n> diff --git a/repack.h b/repack.h\n> index f37eb49524..19dc4fd738 100644\n> --- a/repack.h\n> +++ b/repack.h\n> @@ -74,4 +74,8 @@ int generated_pack_has_ext(const struct generated_pack *pack, const char *ext);\n>  void generated_pack_install(struct generated_pack *pack, const char *name,\n>  \t\t\t    const char *packdir, const char *packtmp);\n>  \n> +void repack_promisor_objects(struct repository *repo,\n> +\t\t\t     const struct pack_objects_args *args,\n> +\t\t\t     struct string_list *names, const char *packtmp);\n> +\n>  #endif /* REPACK_H */\n\nI'm not a huge fan of the code bein gin \"repack-promisor.c\" while the\ndeclarations are in \"repack.h\", as it makes things harder to find from\nmy point of view. But I don't feel too strongly about this.\n\nPatrick\n"},{"id":"527618","messageId":"aNsUnfRPi8cYwGkm@pks.im","threadId":"64222","inReplyTo":"dd67366476172eb1b5b0d2bb2a4dfe92b96b83ba.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 27/49] builtin/repack.c: rename various pack_geometry functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-29T23:22:05Z","receivedAt":"2025-09-29T23:22:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:08:54PM -0400, Taylor Blau wrote:\n> diff --git a/builtin/repack.c b/builtin/repack.c\n> index 18c3df7200..2ce1ae3364 100644\n> --- a/builtin/repack.c\n> +++ b/builtin/repack.c\n> @@ -115,17 +115,17 @@ struct pack_geometry {\n>  \tint split_factor;\n>  };\n>  \n> -static uint32_t geometry_pack_weight(struct packed_git *p)\n> +static uint32_t pack_geometry_weight(struct packed_git *p)\n\nThis rename is a bit of an oddball as we don't work on a `struct\npack_geometry` here.\n\n> @@ -332,7 +332,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n>  \tstrbuf_release(&buf);\n>  }\n>  \n> -static void free_pack_geometry(struct pack_geometry *geometry)\n> +static void pack_geometry_release(struct pack_geometry *geometry)\n>  {\n>  \tif (!geometry)\n>  \t\treturn;\n\nOkay, this function also gets adjusted for the new naming schema we have\naround free/release. Good, but it might be worth it to point it out in\nthe commit message.\n\nPatrick\n"},{"id":"528155","messageId":"aOV1TzXOizrK3d4Q@nand.local","threadId":"64222","inReplyTo":"aNsUZtGst4MguEXq@pks.im","subject":"Re: [PATCH 07/49] builtin/repack.c: avoid \"the_hash_algo\" in `write_oid()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T20:17:19Z","receivedAt":"2025-10-07T20:17:22Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Sep 30, 2025 at 01:21:10AM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:07:37PM -0400, Taylor Blau wrote:\n> > diff --git a/builtin/repack.c b/builtin/repack.c\n> > index 094f5a0cc2..7d62959dc2 100644\n> > --- a/builtin/repack.c\n> > +++ b/builtin/repack.c\n> > @@ -413,6 +419,7 @@ static void repack_promisor_objects(struct repository *repo,\n> >  \t\t\t\t    const struct pack_objects_args *args,\n> >  \t\t\t\t    struct string_list *names)\n> >  {\n> > +\tstruct write_oid_context ctx;\n> >  \tstruct child_process cmd = CHILD_PROCESS_INIT;\n> >  \tFILE *out;\n> >  \tstruct strbuf line = STRBUF_INIT;\n> > @@ -427,7 +434,9 @@ static void repack_promisor_objects(struct repository *repo,\n> >  \t * {type -> existing pack order} ordering when computing deltas instead\n> >  \t * of a {type -> size} ordering, which may produce better deltas.\n> >  \t */\n> > -\tfor_each_packed_object(repo, write_oid, &cmd,\n> > +\tctx.cmd = &cmd;\n> > +\tctx.algop = repo->hash_algo;\n> > +\tfor_each_packed_object(repo, write_oid, &ctx,\n> >  \t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n>\n> As this one is essentially new code, and we have all of the info\n> available right at the start of the problem. Do we maybe want to use\n> designated initializers?\n\nI considered it, but ultimately decided against it here because I find\nit clearer to initialize \"ctx\" right before it is used to avoid\nambiguity.\n\nAlternatively, you could accomplish this with a compound literal, but\nthat's (a) still a test balloon and (b) more verbose than necessary.\n\nThanks,\nTaylor\n"},{"id":"528157","messageId":"aOV2xNDIWGHG2sy/@nand.local","threadId":"64222","inReplyTo":"aNsUfrE-2Wc8_1Fm@pks.im","subject":"Re: [PATCH 17/49] builtin/repack.c: pass \"packdir\" when removing packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T20:23:32Z","receivedAt":"2025-10-07T20:23:35Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Sep 30, 2025 at 01:21:34AM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:08:16PM -0400, Taylor Blau wrote:\n> > builtin/repack.c defines a static \"packdir\" to instruct pack-objects on\n> > where to write any new packfiles. This is also the directory scanned\n> > when removing any packfiles which were made redundant by the latest\n> > repack.\n> >\n> > Prepare to move the \"existing_packs_remove_redundant\" function to its\n> > own compilation unit by passing in this information as a parameter to\n> > that function.\n>\n> I would have expected that we also get rid of that static variable\n> either in this commit or a follow-up. Is there any reason why you\n> skipped over this? If so, it would be great to highlight in the commit\n> message.\n\nWe can't get rid of \"packdir\" quite yet, because it's used in other\nfunctions (which get a similar treatment as this one does in later\npatches).\n\nWe could (and probably should) drop this, packtmp_name, and packtmp as\nstatic variables within builtin/repack.c. We could do it as a fiftieth\npatch in this series like so:\n\n--- 8< ---\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex ad60c4290d..565538bb24 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -27,7 +27,6 @@ static int pack_everything;\n static int write_bitmaps = -1;\n static int use_delta_islands;\n static int run_update_server_info = 1;\n-static char *packdir, *packtmp_name, *packtmp;\n static int midx_must_contain_cruft = 1;\n\n static const char *const git_repack_usage[] = {\n@@ -111,6 +110,7 @@ int cmd_repack(int argc,\n \tstruct write_pack_opts opts = { 0 };\n \tint i, ret;\n \tint show_progress;\n+\tchar *packdir, *packtmp_name, *packtmp;\n\n \t/* variables to be filled by option parsing */\n \tstruct repack_config_ctx config_ctx;\n--- >8 ---\n\nBut I'd like to err on the side of post-merge followups here to avoid\ndumping 49+ more patches on the list. Of course, if there are\nshowstoppers or other objections, I can easily reroll the series, but\nI'd rather avoid doing so on a series this long.\n\nThanks,\nTaylor\n"},{"id":"528158","messageId":"aOV2/6rZFPbOgqct@nand.local","threadId":"64222","inReplyTo":"aNsUhEVUmzmZ5gp3@pks.im","subject":"Re: [PATCH 19/49] repack: remove 'existing_packs' API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T20:24:31Z","receivedAt":"2025-10-07T20:24:33Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Sep 30, 2025 at 01:21:40AM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:08:23PM -0400, Taylor Blau wrote:\n> > The repack builtin defines an API for keeping track of which packs\n> > were found in the repository at the beginning of the repack operation.\n> > This is used to classify what state a pack was in (kept, non-kept, or\n> > cruft), and is also used to mark which packs to delete (or keep) at the\n> > end of a repack operation.\n> >\n> > Now that the prerequisite refactoring is complete, this API is isolated\n> > enough that it can be moved out to repack.ch and removed from the\n>\n> Tiniest nit, sorry: I found the spelling of \"repack.ch\" a bit confusing\n> at first, as it looked like a typo. Maybe say \"repack.{c,h}\" or\n> \"repack.[ch]\"?\n\nAh, sorry, I usually try and write \".[ch]\", but I must have missed it\nhere. I'll adjust it locally and include it in the next round (if any).\n\nThanks,\nTaylor\n"},{"id":"528159","messageId":"aOV3cuZhab7xOdez@nand.local","threadId":"64222","inReplyTo":"aNsUiho56-S4MQZg@pks.im","subject":"Re: [PATCH 21/49] builtin/repack.c: factor our \"generated_pack_install\"","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T20:26:26Z","receivedAt":"2025-10-07T20:26:29Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Sep 30, 2025 at 01:21:46AM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:08:31PM -0400, Taylor Blau wrote:\n>\n> Is the \"factor our\" in the commit subject supposed to read \"factor out\"?\n\nOops, good catch ;-).\n\n> > +\tfor_each_string_list_item(item, &names)\n> > +\t\tgenerated_pack_install((struct generated_pack *)item->util,\n>\n> This cast should be unnecessary, right? `item->util` is a void pointer,\n> so C should do the cast implicitly.\n\nIt's unnecessary, but I dislike implicit casts from 'void*' to any other\ntype. This makes it clearer how we're supposed to interpret the value in\nitem->util, but I'm happy to change it to use the implicit cast if you\nfeel strongly about it.\n\nThanks,\nTaylor\n"},{"id":"528160","messageId":"aOV4QBV8J+71Ikk4@nand.local","threadId":"64222","inReplyTo":"aNsUkenrDY8YHgBn@pks.im","subject":"Re: [PATCH 22/49] builtin/repack.c: pass \"packtmp\" to `generated_pack_populate()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T20:29:52Z","receivedAt":"2025-10-07T20:29:56Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Sep 30, 2025 at 01:21:53AM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:08:35PM -0400, Taylor Blau wrote:\n> > In a similar spirit as previous commits, this function needs to know the\n> > temporary pack prefix, which it currently accesses through the static\n>\n> s/accesses/accessed/\n\nI think that the original is correct here. \"it currently accesses\" as\nopposed to \"it currently accessed\". I have read it back a couple of\ntimes, but it is entirely possible that I'm missing something ;-). LMK.\n\nThanks,\nTaylor\n"},{"id":"528162","messageId":"aOV56Uzley6ebswk@nand.local","threadId":"64222","inReplyTo":"aNsUl2afoigZZiLm@pks.im","subject":"Re: [PATCH 26/49] builtin/repack.c: remove \"repack_promisor_objects()\" from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T20:36:57Z","receivedAt":"2025-10-07T20:37:00Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Sep 30, 2025 at 01:21:59AM +0200, Patrick Steinhardt wrote:\n> > diff --git a/repack-promisor.c b/repack-promisor.c\n> > new file mode 100644\n> > index 0000000000..8bf42fc715\n> > --- /dev/null\n> > +++ b/repack-promisor.c\n> > @@ -0,0 +1,102 @@\n> > +#include \"git-compat-util.h\"\n> > +#include \"repack.h\"\n> > +#include \"run-command.h\"\n> > +#include \"hex.h\"\n> > +#include \"repository.h\"\n> > +#include \"packfile.h\"\n> > +#include \"path.h\"\n> > +#include \"pack.h\"\n>\n> Nit: these headers should probably be sorted lexicographically?\n\nYup, definitely. Fixed.\n\n> > diff --git a/repack.h b/repack.h\n> > index f37eb49524..19dc4fd738 100644\n> > --- a/repack.h\n> > +++ b/repack.h\n> > @@ -74,4 +74,8 @@ int generated_pack_has_ext(const struct generated_pack *pack, const char *ext);\n> >  void generated_pack_install(struct generated_pack *pack, const char *name,\n> >  \t\t\t    const char *packdir, const char *packtmp);\n> >\n> > +void repack_promisor_objects(struct repository *repo,\n> > +\t\t\t     const struct pack_objects_args *args,\n> > +\t\t\t     struct string_list *names, const char *packtmp);\n> > +\n> >  #endif /* REPACK_H */\n>\n> I'm not a huge fan of the code bein gin \"repack-promisor.c\" while the\n> declarations are in \"repack.h\", as it makes things harder to find from\n> my point of view. But I don't feel too strongly about this.\n\nI think we have precedence for this in other places throughout the code\n(for e.g., \"midx.h\" => \"midx.c\" and \"midx-write.c\", \"pack-bitmap.h\" =>\n\"pack-bitmap.c\" and \"pack-bitmap-write.c\", etc), so I think it's OK\nhere.\n\nIf you change your mind and do feel strongly about it later on, I'm\nhappy to adjust it ;-).\n\nThanks,\nTaylor\n"},{"id":"528163","messageId":"aOV6SaGhyHYsU3Bu@nand.local","threadId":"64222","inReplyTo":"aNsUnfRPi8cYwGkm@pks.im","subject":"Re: [PATCH 27/49] builtin/repack.c: rename various pack_geometry functions","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T20:38:33Z","receivedAt":"2025-10-07T20:38:36Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Sep 30, 2025 at 01:22:05AM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:08:54PM -0400, Taylor Blau wrote:\n> > diff --git a/builtin/repack.c b/builtin/repack.c\n> > index 18c3df7200..2ce1ae3364 100644\n> > --- a/builtin/repack.c\n> > +++ b/builtin/repack.c\n> > @@ -115,17 +115,17 @@ struct pack_geometry {\n> >  \tint split_factor;\n> >  };\n> >\n> > -static uint32_t geometry_pack_weight(struct packed_git *p)\n> > +static uint32_t pack_geometry_weight(struct packed_git *p)\n>\n> This rename is a bit of an oddball as we don't work on a `struct\n> pack_geometry` here.\n\nAgreed. I made the change because the concept of a pack's \"weight\" in\nthis instance is tied to the pack_geometry API here, not an intrinsic\nproperty of the pack.\n\n> > @@ -332,7 +332,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n> >  \tstrbuf_release(&buf);\n> >  }\n> >\n> > -static void free_pack_geometry(struct pack_geometry *geometry)\n> > +static void pack_geometry_release(struct pack_geometry *geometry)\n> >  {\n> >  \tif (!geometry)\n> >  \t\treturn;\n>\n> Okay, this function also gets adjusted for the new naming schema we have\n> around free/release. Good, but it might be worth it to point it out in\n> the commit message.\n\nI wrote the commit message to be generic to cover \"various pack_geometry\nfunctions\". Did you want me to be more specific about which functions\nwere renamed?\n\nThanks,\nTaylor\n"},{"id":"528164","messageId":"aOV7k4cPcuhC4Mzz@nand.local","threadId":"64222","inReplyTo":"xmqqcy7a5gnb.fsf@gitster.g","subject":"Re: [PATCH 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T20:44:03Z","receivedAt":"2025-10-07T20:44:06Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Sun, Sep 28, 2025 at 03:58:00PM -0700, Junio C Hamano wrote:\n> Taylor Blau <me@ttaylorr.com> writes:\n>\n> >  * This series depends on ps/packfile-store at dd52a29b78 (packfile:\n> >    refactor `get_packed_git_mru()` to work on packfile store,\n> >    2025-09-23).\n> >\n> >  * This series is split out of my local copy of\n> >    tb/incremental-midx-part-3. I suggest queueing it as\n> >    tb/incremental-midx-part-3.1 ;-).\n>\n> Both of these comments are very much appreciated.  Do we say\n> something to encourage these in our developer-facing documentation?\n\nThanks.\n\n> The points to stress are:\n>\n>  - If you are not building directly on top of the recent tip of\n>    'master', mention what topics that are not yet in 'master' your\n>    patches depend on.\n\nI think that this one is covered in general by 0a02ca2383\n(SubmittingPatches: simplify guidance for choosing a starting point,\n2023-07-14). The language added there is a little more generic, since it\nsuggests mentioning how you constructed a base branch when a new topic\ndepends on >1 other topics.\n\nI would imagine it's clear to readers there that \"if I am describing how\nto build a base branch which merges in multiple topics, I should\nlikewise list the topic branch by name if I am building on top of a\nsingle one already in the maintainer's tree.\"\n\n>  - If your series is a part of a larger effort, briefly describe\n>    what your overall direction and grand vision is, and state where\n>    in that grand picture the current series fits.\n\nNot covered.\n\n>  - If you have a short one-line summary for the topic to be used as\n>    a topic branch name in mind, do suggest it.\n\nSort of covered by your d255105c99 (SubmittingPatches: release-notes\nentry experiment, 2024-03-25), but that talks more about the summary for\nRelNotes purposes, not the topic branch name.\n\nI'll send out a short series to add some more guidelines to cover the\nabove in our Documentation/SubmittingPatches.\n\n> Thanks.\n\nThanks,\nTaylor\n"},{"id":"528197","messageId":"aOXn9Vd6t_xOjWpQ@pks.im","threadId":"64222","inReplyTo":"aOV6SaGhyHYsU3Bu@nand.local","subject":"Re: [PATCH 27/49] builtin/repack.c: rename various pack_geometry functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T04:26:29Z","receivedAt":"2025-10-08T04:26:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 04:38:33PM -0400, Taylor Blau wrote:\n> On Tue, Sep 30, 2025 at 01:22:05AM +0200, Patrick Steinhardt wrote:\n> > On Sun, Sep 28, 2025 at 06:08:54PM -0400, Taylor Blau wrote:\n> > > diff --git a/builtin/repack.c b/builtin/repack.c\n> > > index 18c3df7200..2ce1ae3364 100644\n> > > --- a/builtin/repack.c\n> > > +++ b/builtin/repack.c\n> > > @@ -332,7 +332,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n> > >  \tstrbuf_release(&buf);\n> > >  }\n> > >\n> > > -static void free_pack_geometry(struct pack_geometry *geometry)\n> > > +static void pack_geometry_release(struct pack_geometry *geometry)\n> > >  {\n> > >  \tif (!geometry)\n> > >  \t\treturn;\n> >\n> > Okay, this function also gets adjusted for the new naming schema we have\n> > around free/release. Good, but it might be worth it to point it out in\n> > the commit message.\n> \n> I wrote the commit message to be generic to cover \"various pack_geometry\n> functions\". Did you want me to be more specific about which functions\n> were renamed?\n\nNot in general, no. But I felt that this method here was somewhat\nspecial because we not only rename to have `pack_geometry_` as prefix,\nbut also to use the correct verb.\n\nPatrick\n"},{"id":"528198","messageId":"aOXoIfG4FyHL03GG@pks.im","threadId":"64222","inReplyTo":"aOV4QBV8J+71Ikk4@nand.local","subject":"Re: [PATCH 22/49] builtin/repack.c: pass \"packtmp\" to `generated_pack_populate()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T04:27:13Z","receivedAt":"2025-10-08T04:27:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 04:29:52PM -0400, Taylor Blau wrote:\n> On Tue, Sep 30, 2025 at 01:21:53AM +0200, Patrick Steinhardt wrote:\n> > On Sun, Sep 28, 2025 at 06:08:35PM -0400, Taylor Blau wrote:\n> > > In a similar spirit as previous commits, this function needs to know the\n> > > temporary pack prefix, which it currently accesses through the static\n> >\n> > s/accesses/accessed/\n> \n> I think that the original is correct here. \"it currently accesses\" as\n> opposed to \"it currently accessed\". I have read it back a couple of\n> times, but it is entirely possible that I'm missing something ;-). LMK.\n\nHuh, yeah, no idea what I was thinking here.\n\nPatrick\n"},{"id":"528199","messageId":"aOXoaNYoauCg0CpV@pks.im","threadId":"64222","inReplyTo":"aOV3cuZhab7xOdez@nand.local","subject":"Re: [PATCH 21/49] builtin/repack.c: factor our \"generated_pack_install\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T04:28:24Z","receivedAt":"2025-10-08T04:28:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 04:26:26PM -0400, Taylor Blau wrote:\n> On Tue, Sep 30, 2025 at 01:21:46AM +0200, Patrick Steinhardt wrote:\n> > On Sun, Sep 28, 2025 at 06:08:31PM -0400, Taylor Blau wrote:\n> > > +\tfor_each_string_list_item(item, &names)\n> > > +\t\tgenerated_pack_install((struct generated_pack *)item->util,\n> >\n> > This cast should be unnecessary, right? `item->util` is a void pointer,\n> > so C should do the cast implicitly.\n> \n> It's unnecessary, but I dislike implicit casts from 'void*' to any other\n> type. This makes it clearer how we're supposed to interpret the value in\n> item->util, but I'm happy to change it to use the implicit cast if you\n> feel strongly about it.\n\nI think it's common practice to not have such an explicit cast in our\ncode base. But in any case, I don't feel strongly about this.\n\nPatrick\n"},{"id":"528454","messageId":"20251010051955.GA1965904@coredump.intra.peff.net","threadId":"64222","inReplyTo":"664a67c93620edf6727f0617f4dddce99901fcde.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 02/49] builtin/repack.c: avoid \"the_repository\" in existing packs API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T05:19:55Z","receivedAt":"2025-10-10T05:19:57Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:07:18PM -0400, Taylor Blau wrote:\n\n>  struct existing_packs {\n> +\tstruct repository *repo;\n>  \tstruct string_list kept_packs;\n>  \tstruct string_list non_kept_packs;\n>  \tstruct string_list cruft_packs;\n> @@ -265,7 +266,7 @@ static void existing_packs_release(struct existing_packs *existing)\n>  static void collect_pack_filenames(struct existing_packs *existing,\n>  \t\t\t\t   const struct string_list *extra_keep)\n>  {\n> -\tstruct packfile_store *packs = the_repository->objects->packfiles;\n> +\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n\nI found it a little funny to pass around a repository struct as part of\nexisting_packs, since they're not directly related. But I think it is\nmostly just a convenience to do so, and \"existing_packs\" is really\nstoring an overall context.\n\nIt could probably be scoped down to pass around an object_database\ninstead, or maybe even a packfile store, which somehow feels a little\nmore \"correct\" to me as context. But I have trouble imagining that being\nhelpful to any new code (why would it have an object_store but a not a\nrepo object?). And I can easily imagine having the repo available being\nuseful for future refactorings.\n\nSo I think what you've written here is probably a good path forward. I\nwanted to outline my thinking because I suspect the same will apply to\nother patches in this series (i.e., they don't always need a repo\nobject, but it's simplest to pass them one).\n\n-Peff\n"},{"id":"528456","messageId":"20251010052256.GB1965904@coredump.intra.peff.net","threadId":"64222","inReplyTo":"f404dfa34f06a882e5ebf0b6ffa01ba365ac866b.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 04/49] builtin/repack.c: avoid \"the_repository\" when removing packs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T05:22:56Z","receivedAt":"2025-10-10T05:22:58Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:07:26PM -0400, Taylor Blau wrote:\n\n> -static void remove_redundant_pack(const char *dir_name, const char *base_name)\n> +static void remove_redundant_pack(struct repository *repo,\n> +\t\t\t\t  const char *dir_name, const char *base_name)\n>  {\n>  \tstruct strbuf buf = STRBUF_INIT;\n> -\tstruct odb_source *source = the_repository->objects->sources;\n> +\tstruct odb_source *source = repo->objects->sources;\n>  \tstruct multi_pack_index *m = get_multi_pack_index(source);\n>  \tstrbuf_addf(&buf, \"%s.pack\", base_name);\n>  \tif (m && source->local && midx_contains_pack(m, buf.buf))\n> -\t\tclear_midx_file(the_repository);\n> +\t\tclear_midx_file(repo);\n>  \tstrbuf_insertf(&buf, 0, \"%s/\", dir_name);\n>  \tunlink_pack_path(buf.buf, 1);\n>  \tstrbuf_release(&buf);\n>  }\n\nAh, yeah, this is a good example of what I was talking about in the\nother part of the thread. Probably this function could just take the\nobject_source pointer to find the midx. But then it wouldn't be able to\nuse clear_midx_file(). That function arguably should itself take an\nobject_source pointer and not a repo pointer, but it feels like minimal\ngain to go around trying to tighten these (and certainly something we\ncould easily do later, even programatically, if we cared).\n\n-Peff\n"},{"id":"528458","messageId":"20251010053115.GC1965904@coredump.intra.peff.net","threadId":"64222","inReplyTo":"f16dfbf0c7fdb2ff9bbfa8f3cdbc849916a722bd.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 09/49] builtin/repack.c: avoid \"the_hash_algo\" in `finish_pack_objects_cmd()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T05:31:15Z","receivedAt":"2025-10-10T05:31:22Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:07:45PM -0400, Taylor Blau wrote:\n\n> @@ -1150,7 +1151,8 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n>  \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n>  \tfclose(in);\n>  \n> -\treturn finish_pack_objects_cmd(&cmd, names, local);\n> +\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n> +\t\t\t\t       local);\n>  }\n\nOK, my last musing on which types to pass around, I promise. ;) This is\na great counter-example to my initial comment on patch 2: if we had put\nan object_source into the \"existing\" struct back then, we wouldn't be\nable to pull the hash_algo out now.\n\n(Well, sort of; object_database does have a pointer back to its\ncontaining repo, which itself feels kind of weird. I'd just as soon not\nrely on that, here, though).\n\n> -static int finish_pack_objects_cmd(struct child_process *cmd,\n> +static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n> +\t\t\t\t   struct child_process *cmd,\n\nI am mildly surprised at some of these spots that _only_ need a\nhash_algo and nothing else, but the proof of the pudding is in the\ncompiling. As they say. Well, as I say.\n\n-Peff\n"},{"id":"528461","messageId":"20251010054804.GD1965904@coredump.intra.peff.net","threadId":"64222","inReplyTo":"fe3328eb3d8202f8fa8cbef11cca215ace8b2285.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 11/49] repack: introduce new compilation unit","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T05:48:04Z","receivedAt":"2025-10-10T05:48:05Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:07:53PM -0400, Taylor Blau wrote:\n\n> Let's start by migrating 'struct pack_objects_args' (and the related\n> corresponding pack_objects_args_release() function) into repack.h, and\n> teach both the Makefile and Meson how to build the new compilation unit.\n\nThis name is a little generic for something that now has external scope,\nsince it is specific to repack's view of what it might want to pass to\npack_objects (and not something that builtin/pack-objects.c cares about\nitself).\n\nI don't have a great alternative name, though. I guess it could be\nrepack_pack_objects_args or something, though that is getting to be a\nmouthful.\n\nI dunno. Maybe it is OK as-is. If I stretch I can imagine a world where\npack_objects_args is for the lib-ified version of cmd_pack_objects() to\nuse (like we have send_pack_args, etc). But TBH I find it unlikely that\nwe'd go that way anytime soon (and if we did, we'd maybe even be able to\nrefactor repack to more directly use the struct offered by\npack-objects).\n\n-Peff\n"},{"id":"528462","messageId":"20251010055431.GE1965904@coredump.intra.peff.net","threadId":"64222","inReplyTo":"06347b6ab23d5ab1d9a7945da67473b5b9e02ad1.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 13/49] repack: move 'delta_base_offset' to 'struct pack_objects_args'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T05:54:31Z","receivedAt":"2025-10-10T05:54:33Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:08:00PM -0400, Taylor Blau wrote:\n\n> The static variable 'delta_base_offset' determines whether or not we\n> pass the \"--delta-base-offset\" command-line argument when spawning\n> pack-objects as a child process. Its introduction dates back to when\n> repack was rewritten in C, all the way back in a1bbc6c017 (repack:\n> rewrite the shell script in C, 2013-09-15).\n> \n> 'struct pack_objects_args' was introduced much later on in 4571324b99\n> (builtin/repack.c: allow configuring cruft pack generation, 2022-05-20),\n> but did not move the 'delta_base_offset' variable.\n> \n> Since the 'delta_base_offset' is a property of an individual\n> pack-objects command, re-introduce that variable as a member of 'struct\n> pack_objects_args', which will enable further code movement in the\n> subsequent commits.\n\nHmm, OK. It is true that it is a property of a pack-objects invocation,\nbut would we ever want it to be different between the two? I'd think\nnot, and from what I can see:\n\n> +\t\tcruft_po_args.delta_base_offset = po_args.delta_base_offset;\n\nwe'd always use the same value in both cases. It kind of makes me wonder\nif it should be in the repack_ctx variable instead, then, with both\npack-objects invocations pulling from a common source. But I could also\nbelieve that makes life harder later on, when you want to pass just the\npack_objects_args to prepare_pack_objects().\n\nSo I dunno. Mainly I am worried about somehow forgetting to set the flag\nin the right spot, but in practice there really is only place where it\nneeds to get pulled over from po_args to cruft_po_args.\n\n-Peff\n"},{"id":"528463","messageId":"20251010060401.GF1965904@coredump.intra.peff.net","threadId":"64222","inReplyTo":"df75c3130bced8d5489dc9184701a0a544617049.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 17/49] builtin/repack.c: pass \"packdir\" when removing packs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T06:04:01Z","receivedAt":"2025-10-10T06:04:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:08:16PM -0400, Taylor Blau wrote:\n\n> builtin/repack.c defines a static \"packdir\" to instruct pack-objects on\n> where to write any new packfiles. This is also the directory scanned\n> when removing any packfiles which were made redundant by the latest\n> repack.\n> \n> Prepare to move the \"existing_packs_remove_redundant\" function to its\n> own compilation unit by passing in this information as a parameter to\n> that function.\n\nOK. I don't think there's anything wrong here, but it feels like we\nought to be able to pull this from the existing \"struct repository\"\ncontext that's in the existing_packs struct.\n\nI had to dig a little to find it, but I think that always comes from:\n\n  packdir = mkpathdup(\"%s/pack\", repo_get_object_directory(repo));\n\nin cmd_repack(). So we'd have to recreate that string ourselves. It\nfeels like object_database (or I guess odb_source) should have an easy\nway to get the pack directory, but it doesn't seem to.\n\nSo I dunno. I think it is mostly academic, as you are just moving around\ncode here. But as an API, the separate \"packdir\" argument makes me\nwonder if I can pass anything I like to these functions. But really, it\nshould match the packdir used to create the lists in the existing_packs\nstruct. So it's kind of a brittle API. I'm not sure how much we should\ncare for this series, as it is mostly about moving code around, and not\nproviding some kind of flexible repack API.\n\n-Peff\n"},{"id":"528464","messageId":"20251010060836.GG1965904@coredump.intra.peff.net","threadId":"64222","inReplyTo":"ae267fcc820dc19497f9f897bc5915b2040739e5.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 19/49] repack: remove 'existing_packs' API from the builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T06:08:36Z","receivedAt":"2025-10-10T06:08:38Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:08:23PM -0400, Taylor Blau wrote:\n\n> --- a/repack.h\n> +++ b/repack.h\n> @@ -2,6 +2,7 @@\n>  #define REPACK_H\n>  \n>  #include \"list-objects-filter-options.h\"\n> +#include \"string-list.h\"\n>  \n>  struct pack_objects_args {\n>  \tchar *window;\n> @@ -31,4 +32,38 @@ void pack_objects_args_release(struct pack_objects_args *args);\n>  void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n>  \t\t\t\t  const char *base_name);\n>  \n> +struct repository;\n> +struct packed_git;\n> +\n> +struct existing_packs {\n> +\tstruct repository *repo;\n> +\tstruct string_list kept_packs;\n> +\tstruct string_list non_kept_packs;\n> +\tstruct string_list cruft_packs;\n> +};\n\nEarlier, I saw remove_redundant_pack() got namespaced with a \"repack_\"\nprefix. Which I think makes sense, since it's a pretty generic name and\nthe symbol now has global visibility. But none of this existing_packs\nstuff got the same treatment.\n\nI don't know how much it matters. I certainly have no plans to call\nsomething existing_packs elsewhere in the code, but I do wonder if the\nnaming should make it more clear this is all repack-related.\n\n-Peff\n"},{"id":"528465","messageId":"20251010061449.GH1965904@coredump.intra.peff.net","threadId":"64222","inReplyTo":"aOV3cuZhab7xOdez@nand.local","subject":"Re: [PATCH 21/49] builtin/repack.c: factor our \"generated_pack_install\"","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T06:14:49Z","receivedAt":"2025-10-10T06:14:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 07, 2025 at 04:26:26PM -0400, Taylor Blau wrote:\n\n> > > +\tfor_each_string_list_item(item, &names)\n> > > +\t\tgenerated_pack_install((struct generated_pack *)item->util,\n> >\n> > This cast should be unnecessary, right? `item->util` is a void pointer,\n> > so C should do the cast implicitly.\n> \n> It's unnecessary, but I dislike implicit casts from 'void*' to any other\n> type. This makes it clearer how we're supposed to interpret the value in\n> item->util, but I'm happy to change it to use the implicit cast if you\n> feel strongly about it.\n\nI tend to avoid casts when possible, because they can mask unexpected\nconversions. E.g., if item->util's type changed, your cast means we\nwould never notice it.\n\nBut that cuts both ways. If the prototype for generated_pack_install()\nchanges, it is only your cast that would tell the compiler that this\ncaller needs to be updated. Ultimately it is the spot that _sets_\nitem->util that needs to change, but hopefully flagging this spot would\npoint us in the right direction.\n\nAnd that does seem more likely than item->util changing away from void.\n(To be clear, I don't think either is that likely, I'm just musing on\nwhether casts like this are helpful in general).\n\n-Peff\n"},{"id":"528468","messageId":"20251010062913.GJ1965904@coredump.intra.peff.net","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T06:29:13Z","receivedAt":"2025-10-10T06:29:15Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:07:10PM -0400, Taylor Blau wrote:\n\n> This series moves the vast majority of the builtin's functionality into\n> several new compilation units, all grouped under a new repack.h API:\n> \n>  * repack.c\n>  * repack-cruft.c\n>  * repack-filtered.c\n>  * repack-geometry.c\n>  * repack-midx.c\n>  * repack-promisor.c\n\nI've read up through patch 37. My eyes were starting to glaze over at\nthat point, and it looked like the patches were starting to get more\ninteresting, so I've saved the rest for another session.\n\nFrom what I read, the changes all look reasonable. I brought up a few\nsmall naming and interface questions, but I don't think there's anything\nincorrect here. And there may be diminishing returns in polishing an API\nwhich is realistically only going to be called from one place.\n\nThe split you have seems reasonable. It's not quite as clean as one\nmight hope (e.g., repack's existing_packs gets passed around to some of\nthe other subsystems). But again, diminishing returns on trying to\nuntangle it further.\n\n-Peff\n"},{"id":"528500","messageId":"aOj72bpVTCXuFSHN@pks.im","threadId":"64222","inReplyTo":"c2c7ca9f9e917392de3819ffffffcf78622972b4.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 38/49] builtin/repack.c: inline packs within `write_midx_included_packs()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-10T12:28:09Z","receivedAt":"2025-10-10T12:28:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:09:40PM -0400, Taylor Blau wrote:\n> To write a MIDX at the end of a repack operation, 'git repack' presently\n> computes the set of packs to write into the MIDX, before invoking\n> `write_midx_included_packs()` with a `string_list` containing those\n> packs.\n> \n> The logic for computing which packs are supposed to appear in the\n> resulting MIDX is within `midx_included_packs()`, where it is aware of\n> details like which cruft pack(s) were written/combined, if/how we did a\n> geometric repack, etc.\n> \n> Computing this list ourselves before providing it to the sole function\n> to make use of that list `write_midx_included_packs()` is somewhat\n> awkward. In the future, repack will learn how to write incremental\n> MIDXs, which will use a very different pack selection routine.\n> \n> Instead of doing something like:\n> \n>     struct string_list included_packs = STRING_LIST_INIT_DUP;\n>     if (incremental) {\n>         midx_incremental_included_packs(&included_packs, ...):\n>         write_midx_incremental_included_packs(&included_packs, ...);\n>     } else {\n>         midx_included_packs(&included_packs, ...):\n>         write_midx_included_packs(&included_packs, ...);\n>     }\n> \n> in the future, let's have each function which writes a MIDX be\n\ns/writes/that &/\n\nOther than that this patch LGTM.\n\nPatrick\n"},{"id":"528501","messageId":"aOj75uPA6cFW9WhN@pks.im","threadId":"64222","inReplyTo":"109109618687efeb932bee7dd5882557ab0576f4.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 40/49] builtin/repack.c: introduce `struct write_pack_opts`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-10T12:28:22Z","receivedAt":"2025-10-10T12:28:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:09:51PM -0400, Taylor Blau wrote:\n> diff --git a/builtin/repack.c b/builtin/repack.c\n> index 5fed79e826..6df7c88085 100644\n> --- a/builtin/repack.c\n> +++ b/builtin/repack.c\n> @@ -138,9 +138,7 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n>  \treturn finish_command(cmd);\n>  }\n>  \n> -static int write_filtered_pack(const struct pack_objects_args *args,\n> -\t\t\t       const char *destination,\n> -\t\t\t       const char *pack_prefix,\n> +static int write_filtered_pack(struct write_pack_opts *opts,\n>  \t\t\t       struct existing_packs *existing,\n>  \t\t\t       struct string_list *names)\n\nIs there any reason why the new argument isn't marked as `const`?\n\nPatrick\n"},{"id":"528502","messageId":"aOj79rYGyYWYUGGY@pks.im","threadId":"64222","inReplyTo":"7826afc17376f0d4a7f3169d9aafce21b3273ff5.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 41/49] builtin/repack.c: use `write_pack_opts` within `write_cruft_pack()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-10T12:28:38Z","receivedAt":"2025-10-10T12:28:45Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:09:57PM -0400, Taylor Blau wrote:\n> diff --git a/builtin/repack.c b/builtin/repack.c\n> index 6df7c88085..501359c580 100644\n> --- a/builtin/repack.c\n> +++ b/builtin/repack.c\n> @@ -221,9 +221,7 @@ static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n>  \tstrbuf_release(&buf);\n>  }\n>  \n> -static int write_cruft_pack(const struct pack_objects_args *args,\n> -\t\t\t    const char *destination,\n> -\t\t\t    const char *pack_prefix,\n> +static int write_cruft_pack(struct write_pack_opts *opts,\n>  \t\t\t    const char *cruft_expiration,\n>  \t\t\t    unsigned long combine_cruft_below_size,\n>  \t\t\t    struct string_list *names,\n\nSame question here regarding the constness. Subsequent patches also\nsurface this question.\n\nPatrick\n"},{"id":"528503","messageId":"aOj8CUgapqZEhnlK@pks.im","threadId":"64222","inReplyTo":"1fbe3d0d7f37840b2d9faf6f62e94abd6a664ded.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 43/49] repack: extract `write_pack_opts_is_local()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-10T12:28:57Z","receivedAt":"2025-10-10T12:29:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Sep 28, 2025 at 06:10:08PM -0400, Taylor Blau wrote:\n> diff --git a/repack.c b/repack.c\n> index c4326a532d..7af297ae48 100644\n> --- a/repack.c\n> +++ b/repack.c\n> @@ -77,6 +77,12 @@ const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts)\n>  \treturn pack_prefix;\n>  }\n>  \n> +int write_pack_opts_is_local(struct write_pack_opts *opts)\n> +{\n> +\tconst char *scratch;\n> +\treturn skip_prefix(opts->destination, opts->packdir, &scratch);\n> +}\n> +\n>  #define DELETE_PACK 1\n>  #define RETAIN_PACK 2\n>  \n\nNow that we accept booleans in our codebase, should this function return\na `bool` instead?\n\n> diff --git a/repack.h b/repack.h\n> index 46d2312fa9..16f2de2ea9 100644\n> --- a/repack.h\n> +++ b/repack.h\n> @@ -40,6 +40,7 @@ struct write_pack_opts {\n>  };\n>  \n>  const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts);\n> +int write_pack_opts_is_local(struct write_pack_opts *opts);\n\nIt would be good if all of these these new library functions could come\nwith some documentation what they actually do.\n\nPatrick\n"},{"id":"528542","messageId":"aOmNWlwG3WPNChKi@nand.local","threadId":"64222","inReplyTo":"20251010051955.GA1965904@coredump.intra.peff.net","subject":"Re: [PATCH 02/49] builtin/repack.c: avoid \"the_repository\" in existing packs API","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T22:48:58Z","receivedAt":"2025-10-10T22:49:00Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 01:19:55AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:07:18PM -0400, Taylor Blau wrote:\n>\n> >  struct existing_packs {\n> > +\tstruct repository *repo;\n> >  \tstruct string_list kept_packs;\n> >  \tstruct string_list non_kept_packs;\n> >  \tstruct string_list cruft_packs;\n> > @@ -265,7 +266,7 @@ static void existing_packs_release(struct existing_packs *existing)\n> >  static void collect_pack_filenames(struct existing_packs *existing,\n> >  \t\t\t\t   const struct string_list *extra_keep)\n> >  {\n> > -\tstruct packfile_store *packs = the_repository->objects->packfiles;\n> > +\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n>\n> I found it a little funny to pass around a repository struct as part of\n> existing_packs, since they're not directly related. But I think it is\n> mostly just a convenience to do so, and \"existing_packs\" is really\n> storing an overall context.\n\nYeah, the patch as-is suggests that we could have gotten away with just\nadding a pointer to the packfile_store within existing_packs. I tried to\ndo that locally, and it works up until \"builtin/repack.c: rename many\n'struct existing_packs' functions\", where we start to want to use\n\"repo\", or \"repo->hash_algo\" :-<.\n\n> It could probably be scoped down to pass around an object_database\n> instead, or maybe even a packfile store, which somehow feels a little\n> more \"correct\" to me as context. But I have trouble imagining that being\n> helpful to any new code (why would it have an object_store but a not a\n> repo object?). And I can easily imagine having the repo available being\n> useful for future refactorings.\n>\n> So I think what you've written here is probably a good path forward. I\n> wanted to outline my thinking because I suspect the same will apply to\n> other patches in this series (i.e., they don't always need a repo\n> object, but it's simplest to pass them one).\n\nYeah, exactly. In general for this series I am trying to do as few\nchanges to the actual functions as possible. There are some (like the\nrenaming in the later patch I mentioned above), but only in service of\ntrying to have a clean repack.h (at least from a naming standpoint).\n\nThere is lots of opportunity to clean up the repack code further. I\nagree it would be nice to have existing_packs only take a\npackfile_store, and pass around the repository in other places as\nneeded. I'm happy to investigate that, but as a follow-up to this\nseries, since I want to change as few things as possible here.\n\nThanks,\nTaylor\n"},{"id":"528543","messageId":"aOmNks8E1wQC7MnS@nand.local","threadId":"64222","inReplyTo":"20251010052256.GB1965904@coredump.intra.peff.net","subject":"Re: [PATCH 04/49] builtin/repack.c: avoid \"the_repository\" when removing packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T22:49:54Z","receivedAt":"2025-10-10T22:49:57Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 01:22:56AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:07:26PM -0400, Taylor Blau wrote:\n>\n> > -static void remove_redundant_pack(const char *dir_name, const char *base_name)\n> > +static void remove_redundant_pack(struct repository *repo,\n> > +\t\t\t\t  const char *dir_name, const char *base_name)\n> >  {\n> >  \tstruct strbuf buf = STRBUF_INIT;\n> > -\tstruct odb_source *source = the_repository->objects->sources;\n> > +\tstruct odb_source *source = repo->objects->sources;\n> >  \tstruct multi_pack_index *m = get_multi_pack_index(source);\n> >  \tstrbuf_addf(&buf, \"%s.pack\", base_name);\n> >  \tif (m && source->local && midx_contains_pack(m, buf.buf))\n> > -\t\tclear_midx_file(the_repository);\n> > +\t\tclear_midx_file(repo);\n> >  \tstrbuf_insertf(&buf, 0, \"%s/\", dir_name);\n> >  \tunlink_pack_path(buf.buf, 1);\n> >  \tstrbuf_release(&buf);\n> >  }\n>\n> Ah, yeah, this is a good example of what I was talking about in the\n> other part of the thread. Probably this function could just take the\n> object_source pointer to find the midx. But then it wouldn't be able to\n> use clear_midx_file(). That function arguably should itself take an\n> object_source pointer and not a repo pointer, but it feels like minimal\n> gain to go around trying to tighten these (and certainly something we\n> could easily do later, even programatically, if we cared).\n\nYup, I agree. The changes to `clear_midx_file()` seem like a good\ncandidate for #leftoverbits to me.\n\nThanks,\nTaylor\n"},{"id":"528544","messageId":"aOmN5Hj1xT4Qc6Ed@nand.local","threadId":"64222","inReplyTo":"20251010053115.GC1965904@coredump.intra.peff.net","subject":"Re: [PATCH 09/49] builtin/repack.c: avoid \"the_hash_algo\" in `finish_pack_objects_cmd()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T22:51:16Z","receivedAt":"2025-10-10T22:51:19Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 01:31:15AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:07:45PM -0400, Taylor Blau wrote:\n>\n> > @@ -1150,7 +1151,8 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n> >  \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n> >  \tfclose(in);\n> >\n> > -\treturn finish_pack_objects_cmd(&cmd, names, local);\n> > +\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n> > +\t\t\t\t       local);\n> >  }\n>\n> OK, my last musing on which types to pass around, I promise. ;) This is\n> a great counter-example to my initial comment on patch 2: if we had put\n> an object_source into the \"existing\" struct back then, we wouldn't be\n> able to pull the hash_algo out now.\n>\n> (Well, sort of; object_database does have a pointer back to its\n> containing repo, which itself feels kind of weird. I'd just as soon not\n> rely on that, here, though).\n\nYeah, that feels like a tinier example of what we're talking about here,\njust within the object_database struct itself ;-). I agree that it does\nnot make sense to rely on that, since I think the existence of that\npointer is equally something that we could consider changing.\n\n> > -static int finish_pack_objects_cmd(struct child_process *cmd,\n> > +static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n> > +\t\t\t\t   struct child_process *cmd,\n>\n> I am mildly surprised at some of these spots that _only_ need a\n> hash_algo and nothing else, but the proof of the pudding is in the\n> compiling. As they say. Well, as I say.\n\nI would like that on a t-shirt, please.\n\nThanks,\nTaylor\n"},{"id":"528545","messageId":"aOmOKso3os5fP6cf@nand.local","threadId":"64222","inReplyTo":"20251010054804.GD1965904@coredump.intra.peff.net","subject":"Re: [PATCH 11/49] repack: introduce new compilation unit","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T22:52:26Z","receivedAt":"2025-10-10T22:52:28Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 01:48:04AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:07:53PM -0400, Taylor Blau wrote:\n>\n> > Let's start by migrating 'struct pack_objects_args' (and the related\n> > corresponding pack_objects_args_release() function) into repack.h, and\n> > teach both the Makefile and Meson how to build the new compilation unit.\n>\n> This name is a little generic for something that now has external scope,\n> since it is specific to repack's view of what it might want to pass to\n> pack_objects (and not something that builtin/pack-objects.c cares about\n> itself).\n>\n> I don't have a great alternative name, though. I guess it could be\n> repack_pack_objects_args or something, though that is getting to be a\n> mouthful.\n\nYeah, I had considered `repack_pack_objects_args` initially for exactly\nthat reason, but discarded it because I thought it was too wordy.\n\n> I dunno. Maybe it is OK as-is. If I stretch I can imagine a world where\n> pack_objects_args is for the lib-ified version of cmd_pack_objects() to\n> use (like we have send_pack_args, etc). But TBH I find it unlikely that\n> we'd go that way anytime soon (and if we did, we'd maybe even be able to\n> refactor repack to more directly use the struct offered by\n> pack-objects).\n\nI had a similar concern when writing this patch. I think that that world\nis far enough away that I am comfortable crossing that bridge if/when we\nget there ;-).\n\nThanks,\nTaylor\n"},{"id":"528546","messageId":"aOmOusrJ5J29wpml@nand.local","threadId":"64222","inReplyTo":"20251010055431.GE1965904@coredump.intra.peff.net","subject":"Re: [PATCH 13/49] repack: move 'delta_base_offset' to 'struct pack_objects_args'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T22:54:50Z","receivedAt":"2025-10-10T22:54:52Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 01:54:31AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:08:00PM -0400, Taylor Blau wrote:\n>\n> > The static variable 'delta_base_offset' determines whether or not we\n> > pass the \"--delta-base-offset\" command-line argument when spawning\n> > pack-objects as a child process. Its introduction dates back to when\n> > repack was rewritten in C, all the way back in a1bbc6c017 (repack:\n> > rewrite the shell script in C, 2013-09-15).\n> >\n> > 'struct pack_objects_args' was introduced much later on in 4571324b99\n> > (builtin/repack.c: allow configuring cruft pack generation, 2022-05-20),\n> > but did not move the 'delta_base_offset' variable.\n> >\n> > Since the 'delta_base_offset' is a property of an individual\n> > pack-objects command, re-introduce that variable as a member of 'struct\n> > pack_objects_args', which will enable further code movement in the\n> > subsequent commits.\n>\n> Hmm, OK. It is true that it is a property of a pack-objects invocation,\n> but would we ever want it to be different between the two? I'd think\n> not, and from what I can see:\n>\n> > +\t\tcruft_po_args.delta_base_offset = po_args.delta_base_offset;\n>\n> we'd always use the same value in both cases. It kind of makes me wonder\n> if it should be in the repack_ctx variable instead, then, with both\n> pack-objects invocations pulling from a common source. But I could also\n> believe that makes life harder later on, when you want to pass just the\n> pack_objects_args to prepare_pack_objects().\n\nYeah, we do want to use the same value here in both cases. My thinking\nwhen writing this patch was that whether or not we pass the\n--delta-base-offset command-line argument to pack-objects *is* a\nproperty of the pack-objects invocation.\n\nBut the fact that we want to use the same value when generating\nreachable/geometric packs and when generating cruft packs is more of an\nimplementation detail of repack itself. I like that that detail is made\nexplicit in the line above.\n\nThanks,\nTaylor\n"},{"id":"528547","messageId":"aOmPC6J3equX1rPC@nand.local","threadId":"64222","inReplyTo":"20251010060401.GF1965904@coredump.intra.peff.net","subject":"Re: [PATCH 17/49] builtin/repack.c: pass \"packdir\" when removing packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T22:56:11Z","receivedAt":"2025-10-10T22:56:14Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 02:04:01AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:08:16PM -0400, Taylor Blau wrote:\n>\n> > builtin/repack.c defines a static \"packdir\" to instruct pack-objects on\n> > where to write any new packfiles. This is also the directory scanned\n> > when removing any packfiles which were made redundant by the latest\n> > repack.\n> >\n> > Prepare to move the \"existing_packs_remove_redundant\" function to its\n> > own compilation unit by passing in this information as a parameter to\n> > that function.\n>\n> OK. I don't think there's anything wrong here, but it feels like we\n> ought to be able to pull this from the existing \"struct repository\"\n> context that's in the existing_packs struct.\n>\n> I had to dig a little to find it, but I think that always comes from:\n>\n>   packdir = mkpathdup(\"%s/pack\", repo_get_object_directory(repo));\n>\n> in cmd_repack(). So we'd have to recreate that string ourselves. It\n> feels like object_database (or I guess odb_source) should have an easy\n> way to get the pack directory, but it doesn't seem to.\n>\n> So I dunno. I think it is mostly academic, as you are just moving around\n> code here. But as an API, the separate \"packdir\" argument makes me\n> wonder if I can pass anything I like to these functions. But really, it\n> should match the packdir used to create the lists in the existing_packs\n> struct. So it's kind of a brittle API. I'm not sure how much we should\n> care for this series, as it is mostly about moving code around, and not\n> providing some kind of flexible repack API.\n\nOh, it's extremely brittle, I agree ;-).\n\nI agree with your assessment here that packdir can easily be derived\nfrom the repository object, and that doing so would result in a cleaner,\nless brittle API. I'd like to make those changes, but again would prefer\nto do so on top of this series, since I want to change as few things as\npossible here.\n\nThanks,\nTaylor\n"},{"id":"528548","messageId":"aOmPROWM95FX57wC@nand.local","threadId":"64222","inReplyTo":"20251010060836.GG1965904@coredump.intra.peff.net","subject":"Re: [PATCH 19/49] repack: remove 'existing_packs' API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T22:57:08Z","receivedAt":"2025-10-10T22:57:10Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 02:08:36AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:08:23PM -0400, Taylor Blau wrote:\n>\n> > --- a/repack.h\n> > +++ b/repack.h\n> > @@ -2,6 +2,7 @@\n> >  #define REPACK_H\n> >\n> >  #include \"list-objects-filter-options.h\"\n> > +#include \"string-list.h\"\n> >\n> >  struct pack_objects_args {\n> >  \tchar *window;\n> > @@ -31,4 +32,38 @@ void pack_objects_args_release(struct pack_objects_args *args);\n> >  void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n> >  \t\t\t\t  const char *base_name);\n> >\n> > +struct repository;\n> > +struct packed_git;\n> > +\n> > +struct existing_packs {\n> > +\tstruct repository *repo;\n> > +\tstruct string_list kept_packs;\n> > +\tstruct string_list non_kept_packs;\n> > +\tstruct string_list cruft_packs;\n> > +};\n>\n> Earlier, I saw remove_redundant_pack() got namespaced with a \"repack_\"\n> prefix. Which I think makes sense, since it's a pretty generic name and\n> the symbol now has global visibility. But none of this existing_packs\n> stuff got the same treatment.\n>\n> I don't know how much it matters. I certainly have no plans to call\n> something existing_packs elsewhere in the code, but I do wonder if the\n> naming should make it more clear this is all repack-related.\n\nI think this is a similar case as `struct pack_objects_args`. We could\nprefix this as `repack_existing_packs`, but it seems a bit verbose, and\nexisting_packs is a semi-useful API by itself.\n\nThanks,\nTaylor\n"},{"id":"528549","messageId":"aOmPg7Pb9RqOEuZ5@nand.local","threadId":"64222","inReplyTo":"20251010061449.GH1965904@coredump.intra.peff.net","subject":"Re: [PATCH 21/49] builtin/repack.c: factor our \"generated_pack_install\"","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T22:58:11Z","receivedAt":"2025-10-10T22:58:14Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 02:14:49AM -0400, Jeff King wrote:\n> On Tue, Oct 07, 2025 at 04:26:26PM -0400, Taylor Blau wrote:\n>\n> > > > +\tfor_each_string_list_item(item, &names)\n> > > > +\t\tgenerated_pack_install((struct generated_pack *)item->util,\n> > >\n> > > This cast should be unnecessary, right? `item->util` is a void pointer,\n> > > so C should do the cast implicitly.\n> >\n> > It's unnecessary, but I dislike implicit casts from 'void*' to any other\n> > type. This makes it clearer how we're supposed to interpret the value in\n> > item->util, but I'm happy to change it to use the implicit cast if you\n> > feel strongly about it.\n>\n> I tend to avoid casts when possible, because they can mask unexpected\n> conversions. E.g., if item->util's type changed, your cast means we\n> would never notice it.\n>\n> But that cuts both ways. If the prototype for generated_pack_install()\n> changes, it is only your cast that would tell the compiler that this\n> caller needs to be updated. Ultimately it is the spot that _sets_\n> item->util that needs to change, but hopefully flagging this spot would\n> point us in the right direction.\n>\n> And that does seem more likely than item->util changing away from void.\n> (To be clear, I don't think either is that likely, I'm just musing on\n> whether casts like this are helpful in general).\n\nOK, that's a very compelling argument. I'll drop the cast on my local\nversion.\n\n(As a side note, it doesn't look like we mention this convention in our\nCodingGuidelines, and we probably should, including the rationale that\nyou gave above here. That seems like another good candidate for\n#leftoverbits.)\n\nThanks,\nTaylor\n"},{"id":"528550","messageId":"aOmQI+hpd9OlN/It@nand.local","threadId":"64222","inReplyTo":"aOXn9Vd6t_xOjWpQ@pks.im","subject":"Re: [PATCH 27/49] builtin/repack.c: rename various pack_geometry functions","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T23:00:51Z","receivedAt":"2025-10-10T23:00:54Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Oct 08, 2025 at 06:26:29AM +0200, Patrick Steinhardt wrote:\n> On Tue, Oct 07, 2025 at 04:38:33PM -0400, Taylor Blau wrote:\n> > On Tue, Sep 30, 2025 at 01:22:05AM +0200, Patrick Steinhardt wrote:\n> > > On Sun, Sep 28, 2025 at 06:08:54PM -0400, Taylor Blau wrote:\n> > > > diff --git a/builtin/repack.c b/builtin/repack.c\n> > > > index 18c3df7200..2ce1ae3364 100644\n> > > > --- a/builtin/repack.c\n> > > > +++ b/builtin/repack.c\n> > > > @@ -332,7 +332,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n> > > >  \tstrbuf_release(&buf);\n> > > >  }\n> > > >\n> > > > -static void free_pack_geometry(struct pack_geometry *geometry)\n> > > > +static void pack_geometry_release(struct pack_geometry *geometry)\n> > > >  {\n> > > >  \tif (!geometry)\n> > > >  \t\treturn;\n> > >\n> > > Okay, this function also gets adjusted for the new naming schema we have\n> > > around free/release. Good, but it might be worth it to point it out in\n> > > the commit message.\n> >\n> > I wrote the commit message to be generic to cover \"various pack_geometry\n> > functions\". Did you want me to be more specific about which functions\n> > were renamed?\n>\n> Not in general, no. But I felt that this method here was somewhat\n> special because we not only rename to have `pack_geometry_` as prefix,\n> but also to use the correct verb.\n\nAha, sorry, I see what you're saying. I'll make that clearer in my local\nversion, thanks for suggesting it!\n\nThanks,\nTaylor\n"},{"id":"528551","messageId":"aOmQlkDQOYFDnZdH@nand.local","threadId":"64222","inReplyTo":"aOj72bpVTCXuFSHN@pks.im","subject":"Re: [PATCH 38/49] builtin/repack.c: inline packs within `write_midx_included_packs()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T23:02:46Z","receivedAt":"2025-10-10T23:02:48Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 02:28:09PM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:09:40PM -0400, Taylor Blau wrote:\n> > To write a MIDX at the end of a repack operation, 'git repack' presently\n> > computes the set of packs to write into the MIDX, before invoking\n> > `write_midx_included_packs()` with a `string_list` containing those\n> > packs.\n> >\n> > The logic for computing which packs are supposed to appear in the\n> > resulting MIDX is within `midx_included_packs()`, where it is aware of\n> > details like which cruft pack(s) were written/combined, if/how we did a\n> > geometric repack, etc.\n> >\n> > Computing this list ourselves before providing it to the sole function\n> > to make use of that list `write_midx_included_packs()` is somewhat\n> > awkward. In the future, repack will learn how to write incremental\n> > MIDXs, which will use a very different pack selection routine.\n> >\n> > Instead of doing something like:\n> >\n> >     struct string_list included_packs = STRING_LIST_INIT_DUP;\n> >     if (incremental) {\n> >         midx_incremental_included_packs(&included_packs, ...):\n> >         write_midx_incremental_included_packs(&included_packs, ...);\n> >     } else {\n> >         midx_included_packs(&included_packs, ...):\n> >         write_midx_included_packs(&included_packs, ...);\n> >     }\n> >\n> > in the future, let's have each function which writes a MIDX be\n>\n> s/writes/that &/\n\nGood catch, \"that writes\" is definitely grammatically correct, but I\nthink the substitution is s/which/that/, not s/writes/that &/.\n\nThanks,\nTaylor\n"},{"id":"528552","messageId":"aOmTE0H+22KmHm1O@nand.local","threadId":"64222","inReplyTo":"aOj75uPA6cFW9WhN@pks.im","subject":"Re: [PATCH 40/49] builtin/repack.c: introduce `struct write_pack_opts`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T23:13:23Z","receivedAt":"2025-10-10T23:13:25Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 02:28:22PM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:09:51PM -0400, Taylor Blau wrote:\n> > diff --git a/builtin/repack.c b/builtin/repack.c\n> > index 5fed79e826..6df7c88085 100644\n> > --- a/builtin/repack.c\n> > +++ b/builtin/repack.c\n> > @@ -138,9 +138,7 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n> >  \treturn finish_command(cmd);\n> >  }\n> >\n> > -static int write_filtered_pack(const struct pack_objects_args *args,\n> > -\t\t\t       const char *destination,\n> > -\t\t\t       const char *pack_prefix,\n> > +static int write_filtered_pack(struct write_pack_opts *opts,\n> >  \t\t\t       struct existing_packs *existing,\n> >  \t\t\t       struct string_list *names)\n>\n> Is there any reason why the new argument isn't marked as `const`?\n\nNone other than forgetfulness ;-).\n\nI adjusted it locally, which makes me think that I've accumulated enough\nto merit sending out another round of this series. I'll plan on doing so\nsometime early next week once the review has settled down here.\n\nThanks,\nTaylor\n"},{"id":"528553","messageId":"aOmTNbqC6Pz0B8SL@nand.local","threadId":"64222","inReplyTo":"aOj79rYGyYWYUGGY@pks.im","subject":"Re: [PATCH 41/49] builtin/repack.c: use `write_pack_opts` within `write_cruft_pack()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T23:13:57Z","receivedAt":"2025-10-10T23:14:00Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 02:28:38PM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:09:57PM -0400, Taylor Blau wrote:\n> > diff --git a/builtin/repack.c b/builtin/repack.c\n> > index 6df7c88085..501359c580 100644\n> > --- a/builtin/repack.c\n> > +++ b/builtin/repack.c\n> > @@ -221,9 +221,7 @@ static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n> >  \tstrbuf_release(&buf);\n> >  }\n> >\n> > -static int write_cruft_pack(const struct pack_objects_args *args,\n> > -\t\t\t    const char *destination,\n> > -\t\t\t    const char *pack_prefix,\n> > +static int write_cruft_pack(struct write_pack_opts *opts,\n> >  \t\t\t    const char *cruft_expiration,\n> >  \t\t\t    unsigned long combine_cruft_below_size,\n> >  \t\t\t    struct string_list *names,\n>\n> Same question here regarding the constness. Subsequent patches also\n> surface this question.\n\nYeah, there ended up being a little bit of fallout/churn after marking\nit as const earlier, but those are all fixed up in my local version of\nthese later patches.\n\nThanks,\nTaylor\n"},{"id":"528554","messageId":"aOmTqt4m/R/YrOGg@nand.local","threadId":"64222","inReplyTo":"20251010062913.GJ1965904@coredump.intra.peff.net","subject":"Re: [PATCH 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-10T23:15:54Z","receivedAt":"2025-10-10T23:15:56Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 02:29:13AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:07:10PM -0400, Taylor Blau wrote:\n>\n> > This series moves the vast majority of the builtin's functionality into\n> > several new compilation units, all grouped under a new repack.h API:\n> >\n> >  * repack.c\n> >  * repack-cruft.c\n> >  * repack-filtered.c\n> >  * repack-geometry.c\n> >  * repack-midx.c\n> >  * repack-promisor.c\n>\n> I've read up through patch 37. My eyes were starting to glaze over at\n> that point, and it looked like the patches were starting to get more\n> interesting, so I've saved the rest for another session.\n\nThanks for the review thus far!\n\n> From what I read, the changes all look reasonable. I brought up a few\n> small naming and interface questions, but I don't think there's anything\n> incorrect here. And there may be diminishing returns in polishing an API\n> which is realistically only going to be called from one place.\n>\n> The split you have seems reasonable. It's not quite as clean as one\n> might hope (e.g., repack's existing_packs gets passed around to some of\n> the other subsystems). But again, diminishing returns on trying to\n> untangle it further.\n\nYeah, I think we could probably go on for quite a bit longer polishing\nthe naming, interfaces, etc. I tried to strike a balance in this series\nto change as little as possible while still ending up with a\nhalfway-decent looking repack.h ;-).\n\nAs you noted, the repack code is extremely fragile/brittle. That is a\nbig part of why I both (a) took such an incremental approach as in this\nseries and (b) tried to avoid making too many changes as I moved code\naround.\n\nI think that (b) especially leaves us with an opportunity to clean these\ninterfaces up after this series has landed, and I intend on doing so. My\nhope is that this series gives us a solid foundation to make those\nchanges on, as opposed to further hacking up builtin/repack.c ;-).\n\nThanks,\nTaylor\n"},{"id":"528796","messageId":"20251015102833.GB2250228@coredump.intra.peff.net","threadId":"64222","inReplyTo":"109109618687efeb932bee7dd5882557ab0576f4.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 40/49] builtin/repack.c: introduce `struct write_pack_opts`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-15T10:28:33Z","receivedAt":"2025-10-15T10:28:35Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:09:51PM -0400, Taylor Blau wrote:\n\n> Instead of repeating those arguments for each function, let's extract an\n> options struct called \"write_pack_opts\" which has these three parameters\n> as member fields. While we're at it, add fields for \"packdir,\" and\n> \"packtmp\", both of which are static variables within the builtin, and\n> need to be read from within these two functions.\n\nMakes sense, although...\n\n> +\t\tstruct write_pack_opts opts = {\n> +\t\t\t.po_args = &po_args,\n> +\t\t\t.destination = filter_to,\n> +\t\t\t.pack_prefix = find_pack_prefix(packdir, packtmp),\n> +\t\t\t.packdir = packdir,\n> +\t\t\t.packtmp = packtmp,\n> +\t\t};\n\n...since we are now passing packdir and packtmp anyway, and pack_prefix\nis derived from those, should the called function just do that\nderivation itself? Or do we expect that some callers may eventually use\na different prefix?\n\nProbably not a huge deal either way, but maybe an easy way to tighten up\nthe interface a bit.\n\n-Peff\n"},{"id":"528797","messageId":"20251015102953.GC2250228@coredump.intra.peff.net","threadId":"64222","inReplyTo":"7826afc17376f0d4a7f3169d9aafce21b3273ff5.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 41/49] builtin/repack.c: use `write_pack_opts` within `write_cruft_pack()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-15T10:29:53Z","receivedAt":"2025-10-15T10:29:54Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:09:57PM -0400, Taylor Blau wrote:\n\n> -static int write_cruft_pack(const struct pack_objects_args *args,\n> -\t\t\t    const char *destination,\n> -\t\t\t    const char *pack_prefix,\n> +static int write_cruft_pack(struct write_pack_opts *opts,\n>  \t\t\t    const char *cruft_expiration,\n>  \t\t\t    unsigned long combine_cruft_below_size,\n>  \t\t\t    struct string_list *names,\n\nFollowing on the comment from the previous commit, I guess with my\nproposal we'd have to call find_pack_prefix() ourselves here in\nwrite_cruft_pack().\n\nOTOH, this call...\n\n> @@ -599,6 +597,13 @@ int cmd_repack(int argc,\n>  \n>  \tif (pack_everything & PACK_CRUFT) {\n>  \t\tconst char *pack_prefix = find_pack_prefix(packdir, packtmp);\n> +\t\tstruct write_pack_opts opts = {\n> +\t\t\t.po_args = &cruft_po_args,\n> +\t\t\t.destination = packtmp,\n> +\t\t\t.pack_prefix = pack_prefix,\n> +\t\t\t.packtmp = packtmp,\n> +\t\t\t.packdir = packdir,\n> +\t\t};\n\n...would go away, so I think it is net the same number of lines.\n\n-Peff\n"},{"id":"528798","messageId":"20251015103220.GD2250228@coredump.intra.peff.net","threadId":"64222","inReplyTo":"c50ffce0781d0fbb24eb72f437dab257694a6b99.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 42/49] repack: move `find_pack_prefix()` out of the builtin","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-15T10:32:20Z","receivedAt":"2025-10-15T10:32:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:10:02PM -0400, Taylor Blau wrote:\n\n> Both callers within the repack builtin which call functions that take a\n> 'write_pack_opts' structure have the following pattern:\n> \n>     struct write_pack_opts opts = {\n>         .packdir = packdir,\n>         .packtmp = packtmp,\n>         .pack_prefix = find_pack_prefix(packdir, packtmp),\n>         /* ... */\n>     };\n>     int ret = write_some_kind_of_pack(&opts, /* ... */);\n> \n> , but both \"packdir\" and \"packtmp\" are fields within the write_pack_opts\n> struct itself!\n> \n> Instead of also computing the pack_prefix ahead of time, let's have the\n> callees compute it themselves by moving `find_pack_prefix()` out of the\n> repack builtin, and have it take a write_pack_opts pointer instead of\n> the \"packdir\" and \"packtmp\" fields directly.\n\nOh. You'd really think by now that I'd have learned to read ahead in the\nseries before commenting.\n\nSo you can ignore my responses to the previous two patches. This does\nwhat I propose, plus takes it further by using the opts struct itself.\n\n-Peff\n"},{"id":"528799","messageId":"20251015102635.GA2250228@coredump.intra.peff.net","threadId":"64222","inReplyTo":"c2c7ca9f9e917392de3819ffffffcf78622972b4.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 38/49] builtin/repack.c: inline packs within `write_midx_included_packs()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-15T10:26:35Z","receivedAt":"2025-10-15T10:33:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:09:40PM -0400, Taylor Blau wrote:\n\n> The logic for computing which packs are supposed to appear in the\n> resulting MIDX is within `midx_included_packs()`, where it is aware of\n> details like which cruft pack(s) were written/combined, if/how we did a\n> geometric repack, etc.\n> \n> Computing this list ourselves before providing it to the sole function\n> to make use of that list `write_midx_included_packs()` is somewhat\n> awkward. In the future, repack will learn how to write incremental\n> MIDXs, which will use a very different pack selection routine.\n> \n> Instead of doing something like:\n> \n>     struct string_list included_packs = STRING_LIST_INIT_DUP;\n>     if (incremental) {\n>         midx_incremental_included_packs(&included_packs, ...):\n>         write_midx_incremental_included_packs(&included_packs, ...);\n>     } else {\n>         midx_included_packs(&included_packs, ...):\n>         write_midx_included_packs(&included_packs, ...);\n>     }\n> \n> in the future, let's have each function which writes a MIDX be\n> responsible for itself computing the list of included packs. Inline the\n> declaration and initialization of `included_packs` into the\n> `write_midx_included_packs()` function itself, and repeat that pattern\n> in the future when we introduce new ways to write MIDXs.\n\nOK. I thought at first this was doing something complicated, but it\nreally is just: move the call to midx_included_packs() down into the\nfunction which uses it, rather than doing it in the caller.\n\nWithout another caller we can't judge this, but I'll take your word that\nit helps eventually.\n\nThe patch itself looks like it should be a noop with respect to\nbehavior.\n\n-Peff\n"},{"id":"528801","messageId":"20251015103516.GE2250228@coredump.intra.peff.net","threadId":"64222","inReplyTo":"1fbe3d0d7f37840b2d9faf6f62e94abd6a664ded.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 43/49] repack: extract `write_pack_opts_is_local()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-15T10:35:16Z","receivedAt":"2025-10-15T10:35:18Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:10:08PM -0400, Taylor Blau wrote:\n\n> Similar to the previous commit, the functions `write_cruft_pack()` and\n> `write_filtered_pack()` both compute a \"local\" variable via the exact\n> same mechanism:\n> \n>     const char *scratch;\n>     int local = skip_prefix(opts->destination, opts->packdir, &scratch);\n> \n> Not only does this cause us to repeat the same pair of lines, it also\n> introduces an unnecessary \"scratch\" variable that is common between both\n> functions.\n\nHmm. If we are not looking at \"scratch\", then does that mean\nskip_prefix() is the wrong function to use? I.e., should this just be:\n\n  int local = starts_with(opts->destinations, opts->packdir);\n\n?\n\nIt may still be worth pulling into a separate function to encapsulate\nthe policy logic, but that function could be using starts_with() itself.\n\n-Peff\n"},{"id":"528802","messageId":"20251015110155.GF2250228@coredump.intra.peff.net","threadId":"64222","inReplyTo":"11f78993f1d2d56b204398788780b30318d83afa.1759097191.git.me@ttaylorr.com","subject":"Re: [PATCH 44/49] builtin/repack.c: pass `write_pack_opts` to `finish_pack_objects_cmd()`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-15T11:01:55Z","receivedAt":"2025-10-15T11:01:57Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Sep 28, 2025 at 06:10:14PM -0400, Taylor Blau wrote:\n\n>  int cmd_repack(int argc,\n> @@ -294,6 +294,7 @@ int cmd_repack(int argc,\n>  \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n>  \tstruct pack_geometry geometry = { 0 };\n>  \tstruct tempfile *refs_snapshot = NULL;\n> +\tstruct write_pack_opts opts = { 0 };\n>  \tint i, ret;\n>  \tint show_progress;\n>  \n> @@ -560,7 +561,10 @@ int cmd_repack(int argc,\n>  \t\tfclose(in);\n>  \t}\n>  \n> -\tret = finish_pack_objects_cmd(repo->hash_algo, &cmd, &names, 1);\n> +\topts.packdir = packdir;\n> +\topts.destination = packdir;\n> +\topts.packtmp = packtmp;\n> +\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n>  \tif (ret)\n>  \t\tgoto cleanup;\n\nThis looks a bit funny: we are creating a write_pack_opts struct but\nonly filling in some of the fields. Notably po_args is NULL. It's\nzero'd, which is good, but as a matter of API design, how can a caller\nknow which fields are important for a given function and which are not?\n\nI guess I could have expected cmd_repack() to make a fully fleshed-out\nwrite_pack_opts up front, and then pass it to all functions which need\nit. Rather than making a custom one for this one function.\n\nBut I guess that gets a little weird, because it really has at least\n_two_ such opts: one for the regular pack-objects invocation and one for\nthe cruft pack. And really maybe one more, for the filtered pack? And\nthe conditional blocks for cruft and filter_options packs shadow the\n\"opts\" variable. Yuck.\n\nI'm not sure if there's a good solution. My inclination is to say that\npo_args should not be in \"opts\" at all, and that each function should\ntake a separate po_args as appropriate. And then we could just have one\nwrite_pack_opts in cmd_repack(). But there is another snag: we sometimes\noverride the \"destination\" field with \"filter_to\" or \"expire_to\".\n\nSo the struct really isn't \"here are the opts for repacking\", but rather\n\"let's marshal some arguments into a struct for one function call\".\nWhich of course is a bit verbose in C, hence these extra shadowed \"opts\"\nvariables.\n\nSo I dunno that I am really asking for any change. This might be the\nleast bad way of writing it. I guess if we could avoid the shadowing\nlike this:\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex ad60c4290d..4e4519b180 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -108,7 +108,6 @@ int cmd_repack(int argc,\n \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n \tstruct pack_geometry geometry = { 0 };\n \tstruct tempfile *refs_snapshot = NULL;\n-\tstruct write_pack_opts opts = { 0 };\n \tint i, ret;\n \tint show_progress;\n \n@@ -372,12 +371,15 @@ int cmd_repack(int argc,\n \t\tfclose(in);\n \t}\n \n-\topts.packdir = packdir;\n-\topts.destination = packdir;\n-\topts.packtmp = packtmp;\n-\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n-\tif (ret)\n-\t\tgoto cleanup;\n+\t{\n+\t\tstruct write_pack_opts opts = { 0 };\n+\t\topts.packdir = packdir;\n+\t\topts.destination = packdir;\n+\t\topts.packtmp = packtmp;\n+\t\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n+\t\tif (ret)\n+\t\t\tgoto cleanup;\n+\t}\n \n \tif (!names.nr) {\n \t\tif (!po_args.quiet)\n\nThat might be nice, but introducing the extra block is kind of ugly. Of\ncourse you can get really fancy with a compound literal, like:\n\n@@ -372,10 +371,13 @@ int cmd_repack(int argc,\n \t\tfclose(in);\n \t}\n \n-\topts.packdir = packdir;\n-\topts.destination = packdir;\n-\topts.packtmp = packtmp;\n-\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n+\tret = finish_pack_objects_cmd(repo->hash_algo,\n+\t\t\t\t      &(struct write_pack_opts) {\n+\t\t\t\t\t.packdir = packdir,\n+\t\t\t\t\t.destination = packdir,\n+\t\t\t\t\t.packtmp = packtmp,\n+\t\t\t\t      },\n+\t\t\t\t      &cmd, &names);\n \tif (ret)\n \t\tgoto cleanup;\n \n\nBut that may be a bridge too far.\n\n-Peff\n"},{"id":"528803","messageId":"20251015110653.GG2250228@coredump.intra.peff.net","threadId":"64222","inReplyTo":"aOmTqt4m/R/YrOGg@nand.local","subject":"Re: [PATCH 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-15T11:06:53Z","receivedAt":"2025-10-15T11:06:54Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 10, 2025 at 07:15:54PM -0400, Taylor Blau wrote:\n\n> > I've read up through patch 37. My eyes were starting to glaze over at\n> > that point, and it looked like the patches were starting to get more\n> > interesting, so I've saved the rest for another session.\n> \n> Thanks for the review thus far!\n\nOK, I've finished reading through. The remainder was actually less\ncomplicated than I thought it might be.\n\nI left a few comments, but there's nothing really important. It sounds\nlike you're doing a re-roll for minor fixes anyway, so you can take or\nleave my ponderings as you see fit.\n\n> Yeah, I think we could probably go on for quite a bit longer polishing\n> the naming, interfaces, etc. I tried to strike a balance in this series\n> to change as little as possible while still ending up with a\n> halfway-decent looking repack.h ;-).\n> \n> As you noted, the repack code is extremely fragile/brittle. That is a\n> big part of why I both (a) took such an incremental approach as in this\n> series and (b) tried to avoid making too many changes as I moved code\n> around.\n> \n> I think that (b) especially leaves us with an opportunity to clean these\n> interfaces up after this series has landed, and I intend on doing so. My\n> hope is that this series gives us a solid foundation to make those\n> changes on, as opposed to further hacking up builtin/repack.c ;-).\n\nOK, that all sounds good to me. I think even if you never circle back to\nclean up further, the end result here is a net improvement.\n\n-Peff\n"},{"id":"528841","messageId":"aPAPlb1VvhKfcEJb@nand.local","threadId":"64222","inReplyTo":"20251015102833.GB2250228@coredump.intra.peff.net","subject":"Re: [PATCH 40/49] builtin/repack.c: introduce `struct write_pack_opts`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T21:18:13Z","receivedAt":"2025-10-15T21:18:16Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Oct 15, 2025 at 06:28:33AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:09:51PM -0400, Taylor Blau wrote:\n>\n> > Instead of repeating those arguments for each function, let's extract an\n> > options struct called \"write_pack_opts\" which has these three parameters\n> > as member fields. While we're at it, add fields for \"packdir,\" and\n> > \"packtmp\", both of which are static variables within the builtin, and\n> > need to be read from within these two functions.\n>\n> Makes sense, although...\n>\n> > +\t\tstruct write_pack_opts opts = {\n> > +\t\t\t.po_args = &po_args,\n> > +\t\t\t.destination = filter_to,\n> > +\t\t\t.pack_prefix = find_pack_prefix(packdir, packtmp),\n> > +\t\t\t.packdir = packdir,\n> > +\t\t\t.packtmp = packtmp,\n> > +\t\t};\n>\n> ...since we are now passing packdir and packtmp anyway, and pack_prefix\n> is derived from those, should the called function just do that\n> derivation itself? Or do we expect that some callers may eventually use\n> a different prefix?\n>\n> Probably not a huge deal either way, but maybe an easy way to tighten up\n> the interface a bit.\n\nWe do this a little further down in \"repack: move `find_pack_prefix()`\nout of the builtin\", which I tried to separate since I wanted to go\nincrementally here. But I should mention that that patch is upcoming to\navoid readers wondering why we're not making the change sooner.\n\nThanks,\nTaylor\n"},{"id":"528852","messageId":"aPAP82IZAzmqLHTR@nand.local","threadId":"64222","inReplyTo":"20251015103220.GD2250228@coredump.intra.peff.net","subject":"Re: [PATCH 42/49] repack: move `find_pack_prefix()` out of the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T21:19:47Z","receivedAt":"2025-10-15T21:19:50Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Oct 15, 2025 at 06:32:20AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:10:02PM -0400, Taylor Blau wrote:\n>\n> > Both callers within the repack builtin which call functions that take a\n> > 'write_pack_opts' structure have the following pattern:\n> >\n> >     struct write_pack_opts opts = {\n> >         .packdir = packdir,\n> >         .packtmp = packtmp,\n> >         .pack_prefix = find_pack_prefix(packdir, packtmp),\n> >         /* ... */\n> >     };\n> >     int ret = write_some_kind_of_pack(&opts, /* ... */);\n> >\n> > , but both \"packdir\" and \"packtmp\" are fields within the write_pack_opts\n> > struct itself!\n> >\n> > Instead of also computing the pack_prefix ahead of time, let's have the\n> > callees compute it themselves by moving `find_pack_prefix()` out of the\n> > repack builtin, and have it take a write_pack_opts pointer instead of\n> > the \"packdir\" and \"packtmp\" fields directly.\n>\n> Oh. You'd really think by now that I'd have learned to read ahead in the\n> series before commenting.\n>\n> So you can ignore my responses to the previous two patches. This does\n> what I propose, plus takes it further by using the opts struct itself.\n\n;-).\n\nThanks,\nTaylor\n"},{"id":"528853","messageId":"aPAQWwfw+GjdZ4Yq@nand.local","threadId":"64222","inReplyTo":"aOj8CUgapqZEhnlK@pks.im","subject":"Re: [PATCH 43/49] repack: extract `write_pack_opts_is_local()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T21:21:31Z","receivedAt":"2025-10-15T21:21:34Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Oct 10, 2025 at 02:28:57PM +0200, Patrick Steinhardt wrote:\n> On Sun, Sep 28, 2025 at 06:10:08PM -0400, Taylor Blau wrote:\n> > diff --git a/repack.c b/repack.c\n> > index c4326a532d..7af297ae48 100644\n> > --- a/repack.c\n> > +++ b/repack.c\n> > @@ -77,6 +77,12 @@ const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts)\n> >  \treturn pack_prefix;\n> >  }\n> >\n> > +int write_pack_opts_is_local(struct write_pack_opts *opts)\n> > +{\n> > +\tconst char *scratch;\n> > +\treturn skip_prefix(opts->destination, opts->packdir, &scratch);\n> > +}\n> > +\n> >  #define DELETE_PACK 1\n> >  #define RETAIN_PACK 2\n> >\n>\n> Now that we accept booleans in our codebase, should this function return\n> a `bool` instead?\n\nYeah, I agree that it should eventually return \"bool\", but I don't want\nto make the change in this patch, at least within the context of this\nseries where I'm trying to keep the changes-per-patch as minimal as\npossible.\n\n> > diff --git a/repack.h b/repack.h\n> > index 46d2312fa9..16f2de2ea9 100644\n> > --- a/repack.h\n> > +++ b/repack.h\n> > @@ -40,6 +40,7 @@ struct write_pack_opts {\n> >  };\n> >\n> >  const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts);\n> > +int write_pack_opts_is_local(struct write_pack_opts *opts);\n>\n> It would be good if all of these these new library functions could come\n> with some documentation what they actually do.\n\nFully agreed. I think there is enough here that I'd rather pursue this\nas a separate step, but at least this series moves us in a good\ndirection to document things by putting the functions in a clean header\nthat is more amenable to documentation than the builtin is.\n\nThanks,\nTaylor\n"},{"id":"528859","messageId":"aPARUDOXqsX/niCs@nand.local","threadId":"64222","inReplyTo":"20251015103516.GE2250228@coredump.intra.peff.net","subject":"Re: [PATCH 43/49] repack: extract `write_pack_opts_is_local()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T21:25:36Z","receivedAt":"2025-10-15T21:25:38Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Oct 15, 2025 at 06:35:16AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:10:08PM -0400, Taylor Blau wrote:\n>\n> > Similar to the previous commit, the functions `write_cruft_pack()` and\n> > `write_filtered_pack()` both compute a \"local\" variable via the exact\n> > same mechanism:\n> >\n> >     const char *scratch;\n> >     int local = skip_prefix(opts->destination, opts->packdir, &scratch);\n> >\n> > Not only does this cause us to repeat the same pair of lines, it also\n> > introduces an unnecessary \"scratch\" variable that is common between both\n> > functions.\n>\n> Hmm. If we are not looking at \"scratch\", then does that mean\n> skip_prefix() is the wrong function to use? I.e., should this just be:\n>\n>   int local = starts_with(opts->destinations, opts->packdir);\n>\n> ?\n>\n> It may still be worth pulling into a separate function to encapsulate\n> the policy logic, but that function could be using starts_with() itself.\n\nWhen responding to Patrick's message on this patch I was inclined to not\nmake any more changes in this patch than strictly necessary. But having\ntwo suggestions which are both easy to make, and (more importantly) easy\nto verify the correctness of pushed me to go ahead and make both\nchanges.\n\nThanks,\nTaylor\n"},{"id":"528861","messageId":"aPAThyMlw4oNEBnJ@nand.local","threadId":"64222","inReplyTo":"20251015110155.GF2250228@coredump.intra.peff.net","subject":"Re: [PATCH 44/49] builtin/repack.c: pass `write_pack_opts` to `finish_pack_objects_cmd()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T21:35:03Z","receivedAt":"2025-10-15T21:35:06Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Oct 15, 2025 at 07:01:55AM -0400, Jeff King wrote:\n> On Sun, Sep 28, 2025 at 06:10:14PM -0400, Taylor Blau wrote:\n>\n> >  int cmd_repack(int argc,\n> > @@ -294,6 +294,7 @@ int cmd_repack(int argc,\n> >  \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n> >  \tstruct pack_geometry geometry = { 0 };\n> >  \tstruct tempfile *refs_snapshot = NULL;\n> > +\tstruct write_pack_opts opts = { 0 };\n> >  \tint i, ret;\n> >  \tint show_progress;\n> >\n> > @@ -560,7 +561,10 @@ int cmd_repack(int argc,\n> >  \t\tfclose(in);\n> >  \t}\n> >\n> > -\tret = finish_pack_objects_cmd(repo->hash_algo, &cmd, &names, 1);\n> > +\topts.packdir = packdir;\n> > +\topts.destination = packdir;\n> > +\topts.packtmp = packtmp;\n> > +\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n> >  \tif (ret)\n> >  \t\tgoto cleanup;\n>\n> This looks a bit funny: we are creating a write_pack_opts struct but\n> only filling in some of the fields. Notably po_args is NULL. It's\n> zero'd, which is good, but as a matter of API design, how can a caller\n> know which fields are important for a given function and which are not?\n>\n> I guess I could have expected cmd_repack() to make a fully fleshed-out\n> write_pack_opts up front, and then pass it to all functions which need\n> it. Rather than making a custom one for this one function.\n>\n> But I guess that gets a little weird, because it really has at least\n> _two_ such opts: one for the regular pack-objects invocation and one for\n> the cruft pack. And really maybe one more, for the filtered pack? And\n> the conditional blocks for cruft and filter_options packs shadow the\n> \"opts\" variable. Yuck.\n\nYeah, it is unfortunate that not all instances where we generate a pack\nuse the same pack destination, or even the same set of options to\npack-objects.\n\n> I'm not sure if there's a good solution. My inclination is to say that\n> po_args should not be in \"opts\" at all, and that each function should\n> take a separate po_args as appropriate. And then we could just have one\n> write_pack_opts in cmd_repack(). But there is another snag: we sometimes\n> override the \"destination\" field with \"filter_to\" or \"expire_to\".\n>\n> So the struct really isn't \"here are the opts for repacking\", but rather\n> \"let's marshal some arguments into a struct for one function call\".\n> Which of course is a bit verbose in C, hence these extra shadowed \"opts\"\n> variables.\n\nI think that only solves part of the problem, though. Among the other\nspots that call functions which take a `struct write_pack_opts`, not all\nof them use the same destination, so we'd still have to create multiple\n`struct write_pack_opts` instances in `cmd_repack()`, or pass the\ndestination separately, which feels equally odd to me.\n\n> So I dunno that I am really asking for any change. This might be the\n> least bad way of writing it. I guess if we could avoid the shadowing\n> like this:\n>\n> diff --git a/builtin/repack.c b/builtin/repack.c\n> index ad60c4290d..4e4519b180 100644\n> --- a/builtin/repack.c\n> +++ b/builtin/repack.c\n> @@ -108,7 +108,6 @@ int cmd_repack(int argc,\n>  \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n>  \tstruct pack_geometry geometry = { 0 };\n>  \tstruct tempfile *refs_snapshot = NULL;\n> -\tstruct write_pack_opts opts = { 0 };\n>  \tint i, ret;\n>  \tint show_progress;\n>\n> @@ -372,12 +371,15 @@ int cmd_repack(int argc,\n>  \t\tfclose(in);\n>  \t}\n>\n> -\topts.packdir = packdir;\n> -\topts.destination = packdir;\n> -\topts.packtmp = packtmp;\n> -\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n> -\tif (ret)\n> -\t\tgoto cleanup;\n> +\t{\n> +\t\tstruct write_pack_opts opts = { 0 };\n> +\t\topts.packdir = packdir;\n> +\t\topts.destination = packdir;\n> +\t\topts.packtmp = packtmp;\n> +\t\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n> +\t\tif (ret)\n> +\t\t\tgoto cleanup;\n> +\t}\n>\n>  \tif (!names.nr) {\n>  \t\tif (!po_args.quiet)\n\nYeah, I think that is the most reasonable outcome, at least within the\nscope of this patch/series.\n\n> That might be nice, but introducing the extra block is kind of ugly. Of\n> course you can get really fancy with a compound literal, like:\n>\n> @@ -372,10 +371,13 @@ int cmd_repack(int argc,\n>  \t\tfclose(in);\n>  \t}\n>\n> -\topts.packdir = packdir;\n> -\topts.destination = packdir;\n> -\topts.packtmp = packtmp;\n> -\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n> +\tret = finish_pack_objects_cmd(repo->hash_algo,\n> +\t\t\t\t      &(struct write_pack_opts) {\n> +\t\t\t\t\t.packdir = packdir,\n> +\t\t\t\t\t.destination = packdir,\n> +\t\t\t\t\t.packtmp = packtmp,\n> +\t\t\t\t      },\n> +\t\t\t\t      &cmd, &names);\n>  \tif (ret)\n>  \t\tgoto cleanup;\n>\n>\n> But that may be a bridge too far.\n\nAs much as I'd like to use a compound literal here, 9b2527caa4\n(CodingGuidelines: document test balloons in flight, 2025-07-23)\nprevents us from adding new instances of them in our codebase for the\ntime being.\n\nThanks,\nTaylor\n"},{"id":"528870","messageId":"cover.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1759097191.git.me@ttaylorr.com","subject":"[PATCH v2 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:26:57Z","receivedAt":"2025-10-15T22:27:00Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Note to the maintainer:\n\n * This series has been rebased on to 'master' (which is 4b71b29477\n   (The seventeenth batch, 2025-10-10) at the time of writing) to\n   avoid semantic conflicts now that ps/packfile-store has been\n   merged.\n\nThis is a relatively small reroll of my series to clean up the repack\nbuiltin by introducing a repack.h API, which is the first of ~three\nseries that will implement incremental MIDX/bitmap-based repacking.\n\nA range-diff is included below for convenience, but the changes since\nv1 are generally limited to the following:\n\n * Wording tweaks and a couple of minor typo fixes.\n\n * Dropping explicit casts out of 'void *'.\n\n * Clarification in commit \"builtin/repack.c: introduce `struct\n   write_pack_opts`\" that additional cleanup follows in the upcoming\n   patches.\n\n * Marking parts of the new API as const where possible.\n\n * Using 'bool' as the return type and simplifying the implementation\n   of `write_pack_opts_is_local()`.\n\n * Avoid shadowing \"struct write_pack_opts opts\" in `cmd_repack()`.\n\nOutside of that, the series is unchanged, and I am hopeful that this\nround looks good to reviewers so that we can move on to the more\ninteresting parts of incremental MIDX/bitmap repacking ;-).\n\nThanks in advance for your (re-)review!\n\nTaylor Blau (49):\n  builtin/repack.c: avoid \"the_repository\" in `cmd_repack()`\n  builtin/repack.c: avoid \"the_repository\" in existing packs API\n  builtin/repack.c: avoid \"the_repository\" when taking a ref snapshot\n  builtin/repack.c: avoid \"the_repository\" when removing packs\n  builtin/repack.c: avoid \"the_repository\" when repacking promisor\n    objects\n  builtin/repack.c: avoid \"the_hash_algo\" when deleting packs\n  builtin/repack.c: avoid \"the_hash_algo\" in `write_oid()`\n  builtin/repack: avoid \"the_hash_algo\" in `repack_promisor_objects()`\n  builtin/repack.c: avoid \"the_hash_algo\" in `finish_pack_objects_cmd()`\n  builtin/repack.c: avoid using `hash_to_hex()` in pack geometry\n  repack: introduce new compilation unit\n  builtin/repack.c: pass both pack_objects args to repack_config\n  repack: move 'delta_base_offset' to 'struct pack_objects_args'\n  repack: remove 'prepare_pack_objects' from the builtin\n  builtin/repack.c: rename many 'struct existing_packs' functions\n  repack: remove 'remove_redundant_pack' from the builtin\n  builtin/repack.c: pass \"packdir\" when removing packs\n  builtin/repack.c: avoid unnecessary numeric casts in existing_packs\n  repack: remove 'existing_packs' API from the builtin\n  builtin/repack.c: rename \"struct generated_pack_data\"\n  builtin/repack.c: factor out \"generated_pack_install\"\n  builtin/repack.c: pass \"packtmp\" to `generated_pack_populate()`\n  builtin/repack.c: provide pack locations to `generated_pack_install()`\n  repack: remove 'generated_pack' API from the builtin\n  builtin/repack.c: pass \"packtmp\" to `repack_promisor_objects()`\n  builtin/repack.c: remove \"repack_promisor_objects()\" from the builtin\n  builtin/repack.c: rename various pack_geometry functions\n  builtin/repack.c: pass 'pack_kept_objects' to `pack_geometry_init()`\n  builtin/repack.c: pass 'packdir' to `pack_geometry_remove_redundant()`\n  repack: remove pack_geometry API from the builtin\n  builtin/repack.c: remove ref snapshotting from builtin\n  builtin/repack.c: extract opts struct for\n    'write_midx_included_packs()'\n  builtin/repack.c: use a string_list for 'midx_pack_names'\n  repack: keep track of MIDX pack names using existing_packs\n  builtin/repack.c: reorder `remove_redundant_bitmaps()`\n  builtin/repack.c: inline `remove_redundant_bitmaps()`\n  builtin/repack.c: pass `repack_write_midx_opts` to\n    `midx_included_packs`\n  builtin/repack.c: inline packs within `write_midx_included_packs()`\n  repack: 'write_midx_included_packs' API from the builtin\n  builtin/repack.c: introduce `struct write_pack_opts`\n  builtin/repack.c: use `write_pack_opts` within `write_cruft_pack()`\n  repack: move `find_pack_prefix()` out of the builtin\n  repack: extract `write_pack_opts_is_local()`\n  builtin/repack.c: pass `write_pack_opts` to\n    `finish_pack_objects_cmd()`\n  repack: move `finish_pack_objects_cmd()` out of the builtin\n  repack: move `pack_kept_objects` to `struct pack_objects_args`\n  repack: move `write_filtered_pack()` out of the builtin\n  repack: move `write_cruft_pack()` out of the builtin\n  builtin/repack.c: clean up unused `#include`s\n\n Makefile          |    6 +\n builtin/repack.c  | 1356 +++------------------------------------------\n meson.build       |    6 +\n repack-cruft.c    |   99 ++++\n repack-filtered.c |   51 ++\n repack-geometry.c |  233 ++++++++\n repack-midx.c     |  372 +++++++++++++\n repack-promisor.c |  102 ++++\n repack.c          |  360 ++++++++++++\n repack.h          |  146 +++++\n 10 files changed, 1467 insertions(+), 1264 deletions(-)\n create mode 100644 repack-cruft.c\n create mode 100644 repack-filtered.c\n create mode 100644 repack-geometry.c\n create mode 100644 repack-midx.c\n create mode 100644 repack-promisor.c\n create mode 100644 repack.c\n create mode 100644 repack.h\n\nRange-diff against v1:\n 1:  aa03aea8ed =  1:  fc2b686c49 builtin/repack.c: avoid \"the_repository\" in `cmd_repack()`\n 2:  664a67c936 =  2:  14a65d7c6d builtin/repack.c: avoid \"the_repository\" in existing packs API\n 3:  866e43b0cd =  3:  ff72cd499b builtin/repack.c: avoid \"the_repository\" when taking a ref snapshot\n 4:  f404dfa34f =  4:  75f23ba211 builtin/repack.c: avoid \"the_repository\" when removing packs\n 5:  6190eeb066 =  5:  0914ca113f builtin/repack.c: avoid \"the_repository\" when repacking promisor objects\n 6:  2f97882ace =  6:  238f3d87a0 builtin/repack.c: avoid \"the_hash_algo\" when deleting packs\n 7:  37a7dea848 =  7:  ca3c8a782a builtin/repack.c: avoid \"the_hash_algo\" in `write_oid()`\n 8:  f5a87f2a65 =  8:  34b503618a builtin/repack: avoid \"the_hash_algo\" in `repack_promisor_objects()`\n 9:  f16dfbf0c7 =  9:  cd43a60620 builtin/repack.c: avoid \"the_hash_algo\" in `finish_pack_objects_cmd()`\n10:  de4886466c = 10:  c6787c865d builtin/repack.c: avoid using `hash_to_hex()` in pack geometry\n11:  fe3328eb3d = 11:  87f2903e75 repack: introduce new compilation unit\n12:  75386eed60 = 12:  a812bd3dcb builtin/repack.c: pass both pack_objects args to repack_config\n13:  06347b6ab2 = 13:  f2d1fa51e3 repack: move 'delta_base_offset' to 'struct pack_objects_args'\n14:  2d9022385d = 14:  0c32c3c9b6 repack: remove 'prepare_pack_objects' from the builtin\n15:  02e26f78ac = 15:  a9767dc42d builtin/repack.c: rename many 'struct existing_packs' functions\n16:  d0a40e8e57 = 16:  4edbcb5893 repack: remove 'remove_redundant_pack' from the builtin\n17:  df75c3130b = 17:  4e2a152ebb builtin/repack.c: pass \"packdir\" when removing packs\n18:  f413f958eb = 18:  46aa154b76 builtin/repack.c: avoid unnecessary numeric casts in existing_packs\n19:  ae267fcc82 ! 19:  0c6dea2800 repack: remove 'existing_packs' API from the builtin\n    @@ Commit message\n         end of a repack operation.\n     \n         Now that the prerequisite refactoring is complete, this API is isolated\n    -    enough that it can be moved out to repack.ch and removed from the\n    +    enough that it can be moved out to repack.[ch] and removed from the\n         builtin entirely.\n     \n         As a result, some of its functions become static within repack.c,\n20:  ee3e1233f5 = 20:  53d5b1053f builtin/repack.c: rename \"struct generated_pack_data\"\n21:  96d27261e7 ! 21:  196b92d052 builtin/repack.c: factor our \"generated_pack_install\"\n    @@ Metadata\n     Author: Taylor Blau <me@ttaylorr.com>\n     \n      ## Commit message ##\n    -    builtin/repack.c: factor our \"generated_pack_install\"\n    +    builtin/repack.c: factor out \"generated_pack_install\"\n     \n         Once all new packs are known to exist, 'repack' installs their contents\n         from their temporary location into their permanent one. This is a\n    @@ builtin/repack.c: int cmd_repack(int argc,\n     -\t\t}\n     -\t}\n     +\tfor_each_string_list_item(item, &names)\n    -+\t\tgenerated_pack_install((struct generated_pack *)item->util,\n    -+\t\t\t\t       item->string);\n    ++\t\tgenerated_pack_install(item->util, item->string);\n      \t/* End of pack replacement. */\n      \n      \tif (delete_redundant && pack_everything & ALL_INTO_ONE)\n22:  ab0dc66683 = 22:  acd9a83ed0 builtin/repack.c: pass \"packtmp\" to `generated_pack_populate()`\n23:  e9d525d83a ! 23:  2b81a26a84 builtin/repack.c: provide pack locations to `generated_pack_install()`\n    @@ builtin/repack.c: static int generated_pack_has_ext(const struct generated_pack\n      \tint ext;\n      \tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n     @@ builtin/repack.c: int cmd_repack(int argc,\n    + \t * Ok we have prepared all new packfiles.\n      \t */\n      \tfor_each_string_list_item(item, &names)\n    - \t\tgenerated_pack_install((struct generated_pack *)item->util,\n    --\t\t\t\t       item->string);\n    -+\t\t\t\t       item->string, packdir, packtmp);\n    +-\t\tgenerated_pack_install(item->util, item->string);\n    ++\t\tgenerated_pack_install(item->util, item->string, packdir,\n    ++\t\t\t\t       packtmp);\n      \t/* End of pack replacement. */\n      \n      \tif (delete_redundant && pack_everything & ALL_INTO_ONE)\n24:  9979b77979 = 24:  69003f4561 repack: remove 'generated_pack' API from the builtin\n25:  67effcb4ea = 25:  ca030322cf builtin/repack.c: pass \"packtmp\" to `repack_promisor_objects()`\n26:  3ba6e86f61 ! 26:  1050b856a5 builtin/repack.c: remove \"repack_promisor_objects()\" from the builtin\n    @@ repack-promisor.c (new)\n     @@\n     +#include \"git-compat-util.h\"\n     +#include \"repack.h\"\n    -+#include \"run-command.h\"\n     +#include \"hex.h\"\n    -+#include \"repository.h\"\n    ++#include \"pack.h\"\n     +#include \"packfile.h\"\n     +#include \"path.h\"\n    -+#include \"pack.h\"\n    ++#include \"repository.h\"\n    ++#include \"run-command.h\"\n     +\n     +struct write_oid_context {\n     +\tstruct child_process *cmd;\n27:  dd67366476 ! 27:  0b2fa0cf62 builtin/repack.c: rename various pack_geometry functions\n    @@ Commit message\n         builtin/repack.c: rename various pack_geometry functions\n     \n         Rename functions which work with 'struct pack_geometry' to begin with\n    -    \"pack_geometry_\".\n    +    \"pack_geometry_\". While we're at it, change `free_pack_geometry()` to\n    +    instead be named `pack_geometry_release()` to match our conventions, and\n    +    make clear that that function frees the contents of the struct, not the\n    +    memory allocated to hold the struct itself.\n     \n         Signed-off-by: Taylor Blau <me@ttaylorr.com>\n     \n28:  8408fd8474 = 28:  a3d99c4852 builtin/repack.c: pass 'pack_kept_objects' to `pack_geometry_init()`\n29:  76be98b386 = 29:  0a2a72ea22 builtin/repack.c: pass 'packdir' to `pack_geometry_remove_redundant()`\n30:  54a2feaea9 ! 30:  9172e9a515 repack: remove pack_geometry API from the builtin\n    @@ repack-geometry.c (new)\n     +\n     +#include \"git-compat-util.h\"\n     +#include \"repack.h\"\n    ++#include \"repository.h\"\n     +#include \"hex.h\"\n     +#include \"packfile.h\"\n     +\n31:  06ca4ab5b7 = 31:  5b15d83e65 builtin/repack.c: remove ref snapshotting from builtin\n32:  3b7e204a40 = 32:  077f06c935 builtin/repack.c: extract opts struct for 'write_midx_included_packs()'\n33:  8844529759 = 33:  40ce496de2 builtin/repack.c: use a string_list for 'midx_pack_names'\n34:  ccf2c6a517 = 34:  ec3a8a7dd2 repack: keep track of MIDX pack names using existing_packs\n35:  85d229fff5 = 35:  8042860b26 builtin/repack.c: reorder `remove_redundant_bitmaps()`\n36:  2bf732009a = 36:  af06f60e63 builtin/repack.c: inline `remove_redundant_bitmaps()`\n37:  60659a7549 = 37:  668f4edc9e builtin/repack.c: pass `repack_write_midx_opts` to `midx_included_packs`\n38:  c2c7ca9f9e ! 38:  5bd43e96f2 builtin/repack.c: inline packs within `write_midx_included_packs()`\n    @@ Commit message\n                 write_midx_included_packs(&included_packs, ...);\n             }\n     \n    -    in the future, let's have each function which writes a MIDX be\n    +    in the future, let's have each function that writes a MIDX be\n         responsible for itself computing the list of included packs. Inline the\n         declaration and initialization of `included_packs` into the\n         `write_midx_included_packs()` function itself, and repeat that pattern\n39:  c28891443b = 39:  794122266d repack: 'write_midx_included_packs' API from the builtin\n40:  1091096186 ! 40:  0cb6e78856 builtin/repack.c: introduce `struct write_pack_opts`\n    @@ Commit message\n         of the same type, and provide a unified interface for the two functions\n         mentioned earlier.\n     \n    +    (Note that \"pack_prefix\" can be derived on the fly as a function of\n    +    \"packdir\" and \"packtmp\", making it unnecessary to store \"pack_prefix\"\n    +    explicitly. This commit ignores that potential cleanup in the name of\n    +    doing as few things as possible, but a later commit will make that\n    +    change.)\n    +\n         Signed-off-by: Taylor Blau <me@ttaylorr.com>\n     \n      ## builtin/repack.c ##\n    @@ builtin/repack.c: static int finish_pack_objects_cmd(const struct git_hash_algo\n     -static int write_filtered_pack(const struct pack_objects_args *args,\n     -\t\t\t       const char *destination,\n     -\t\t\t       const char *pack_prefix,\n    -+static int write_filtered_pack(struct write_pack_opts *opts,\n    ++static int write_filtered_pack(const struct write_pack_opts *opts,\n      \t\t\t       struct existing_packs *existing,\n      \t\t\t       struct string_list *names)\n      {\n41:  7826afc173 ! 41:  1a9b578fdd builtin/repack.c: use `write_pack_opts` within `write_cruft_pack()`\n    @@ builtin/repack.c: static void combine_small_cruft_packs(FILE *in, size_t combine\n     -static int write_cruft_pack(const struct pack_objects_args *args,\n     -\t\t\t    const char *destination,\n     -\t\t\t    const char *pack_prefix,\n    -+static int write_cruft_pack(struct write_pack_opts *opts,\n    ++static int write_cruft_pack(const struct write_pack_opts *opts,\n      \t\t\t    const char *cruft_expiration,\n      \t\t\t    unsigned long combine_cruft_below_size,\n      \t\t\t    struct string_list *names,\n42:  c50ffce078 ! 42:  43116b8942 repack: move `find_pack_prefix()` out of the builtin\n    @@ Commit message\n         Signed-off-by: Taylor Blau <me@ttaylorr.com>\n     \n      ## builtin/repack.c ##\n    -@@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_filtered_pack(const struct write_pack_opts *opts,\n      \tconst char *caret;\n      \tconst char *scratch;\n      \tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n    @@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n      \n      \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n      \n    -@@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_filtered_pack(const struct write_pack_opts *opts,\n      \t */\n      \tin = xfdopen(cmd.in, \"w\");\n      \tfor_each_string_list_item(item, names)\n    @@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n      \tfor_each_string_list_item(item, &existing->non_kept_packs)\n      \t\tfprintf(in, \"%s.pack\\n\", item->string);\n      \tfor_each_string_list_item(item, &existing->cruft_packs)\n    -@@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_cruft_pack(const struct write_pack_opts *opts,\n      \tint ret;\n      \tconst char *scratch;\n      \tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n    @@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n      \n      \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n      \n    -@@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_cruft_pack(const struct write_pack_opts *opts,\n      \t */\n      \tin = xfdopen(cmd.in, \"w\");\n      \tfor_each_string_list_item(item, names)\n    @@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n      \tif (combine_cruft_below_size && !cruft_expiration) {\n      \t\tcombine_small_cruft_packs(in, combine_cruft_below_size,\n      \t\t\t\t\t  existing);\n    -@@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_cruft_pack(const struct write_pack_opts *opts,\n      \t\t\t\t       local);\n      }\n      \n    @@ repack.c: void repack_remove_redundant_pack(struct repository *repo, const char\n      \tstrbuf_release(&buf);\n      }\n      \n    -+const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts)\n    ++const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts)\n     +{\n     +\tconst char *pack_prefix;\n     +\tif (!skip_prefix(opts->packtmp, opts->packdir, &pack_prefix))\n    @@ repack.h: void repack_remove_redundant_pack(struct repository *repo, const char\n      \tconst char *packtmp;\n      };\n      \n    -+const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts);\n    ++const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts);\n     +\n      struct repository;\n      struct packed_git;\n43:  1fbe3d0d7f ! 43:  5e3b615993 repack: extract `write_pack_opts_is_local()`\n    @@ Commit message\n     \n         Extract that function and make it visible within the repack.h API, and\n         use it within both `write_cruft_pack()` and `write_filtered_pack()`.\n    +    While we're at it, match our modern conventions by returning a \"bool\"\n    +    instead of \"int\", and use `starts_with()` instead of `skip_prefix()` to\n    +    avoid storing the dummy \"scratch\" variable.\n     \n         The remaining duplication (that is, that both `write_cruft_pack()` and\n         `write_filtered_pack()` still both call `write_pack_opts_is_local()`)\n    @@ Commit message\n         Signed-off-by: Taylor Blau <me@ttaylorr.com>\n     \n      ## builtin/repack.c ##\n    -@@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_filtered_pack(const struct write_pack_opts *opts,\n      \tFILE *in;\n      \tint ret;\n      \tconst char *caret;\n     -\tconst char *scratch;\n     -\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n    -+\tint local = write_pack_opts_is_local(opts);\n    ++\tbool local = write_pack_opts_is_local(opts);\n      \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n      \n      \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n    -@@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_cruft_pack(const struct write_pack_opts *opts,\n      \tstruct string_list_item *item;\n      \tFILE *in;\n      \tint ret;\n     -\tconst char *scratch;\n     -\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n    -+\tint local = write_pack_opts_is_local(opts);\n    ++\tbool local = write_pack_opts_is_local(opts);\n      \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n      \n      \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n     \n      ## repack.c ##\n    -@@ repack.c: const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts)\n    +@@ repack.c: const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts)\n      \treturn pack_prefix;\n      }\n      \n    -+int write_pack_opts_is_local(struct write_pack_opts *opts)\n    ++bool write_pack_opts_is_local(const struct write_pack_opts *opts)\n     +{\n    -+\tconst char *scratch;\n    -+\treturn skip_prefix(opts->destination, opts->packdir, &scratch);\n    ++\treturn starts_with(opts->destination, opts->packdir);\n     +}\n     +\n      #define DELETE_PACK 1\n    @@ repack.h\n     @@ repack.h: struct write_pack_opts {\n      };\n      \n    - const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts);\n    -+int write_pack_opts_is_local(struct write_pack_opts *opts);\n    + const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts);\n    ++bool write_pack_opts_is_local(const struct write_pack_opts *opts);\n      \n      struct repository;\n      struct packed_git;\n44:  11f78993f1 ! 44:  fcbe44d8bb builtin/repack.c: pass `write_pack_opts` to `finish_pack_objects_cmd()`\n    @@ builtin/repack.c: static int repack_config(const char *var, const char *value,\n      }\n      \n      static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n    -+\t\t\t\t   struct write_pack_opts *opts,\n    ++\t\t\t\t   const struct write_pack_opts *opts,\n      \t\t\t\t   struct child_process *cmd,\n     -\t\t\t\t   struct string_list *names,\n     -\t\t\t\t   int local)\n     +\t\t\t\t   struct string_list *names)\n      {\n      \tFILE *out;\n    -+\tint local = write_pack_opts_is_local(opts);\n    ++\tbool local = write_pack_opts_is_local(opts);\n      \tstruct strbuf line = STRBUF_INIT;\n      \n      \tout = xfdopen(cmd->out, \"r\");\n    @@ builtin/repack.c: static int finish_pack_objects_cmd(const struct git_hash_algo\n      \t\t}\n      \t}\n      \tfclose(out);\n    -@@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_filtered_pack(const struct write_pack_opts *opts,\n      \tFILE *in;\n      \tint ret;\n      \tconst char *caret;\n    --\tint local = write_pack_opts_is_local(opts);\n    +-\tbool local = write_pack_opts_is_local(opts);\n      \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n      \n      \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n    -@@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_filtered_pack(const struct write_pack_opts *opts,\n      \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n      \tfclose(in);\n      \n    @@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n      }\n      \n      static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n    -@@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_cruft_pack(const struct write_pack_opts *opts,\n      \tstruct string_list_item *item;\n      \tFILE *in;\n      \tint ret;\n    --\tint local = write_pack_opts_is_local(opts);\n    +-\tbool local = write_pack_opts_is_local(opts);\n      \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n      \n      \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n    -@@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_cruft_pack(const struct write_pack_opts *opts,\n      \t\tfprintf(in, \"%s.pack\\n\", item->string);\n      \tfclose(in);\n      \n    @@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n      }\n      \n      int cmd_repack(int argc,\n    -@@ builtin/repack.c: int cmd_repack(int argc,\n    - \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n    - \tstruct pack_geometry geometry = { 0 };\n    - \tstruct tempfile *refs_snapshot = NULL;\n    -+\tstruct write_pack_opts opts = { 0 };\n    - \tint i, ret;\n    - \tint show_progress;\n    - \n     @@ builtin/repack.c: int cmd_repack(int argc,\n      \t\tfclose(in);\n      \t}\n      \n     -\tret = finish_pack_objects_cmd(repo->hash_algo, &cmd, &names, 1);\n    -+\topts.packdir = packdir;\n    -+\topts.destination = packdir;\n    -+\topts.packtmp = packtmp;\n    -+\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd, &names);\n    - \tif (ret)\n    - \t\tgoto cleanup;\n    +-\tif (ret)\n    +-\t\tgoto cleanup;\n    ++\t{\n    ++\t\tstruct write_pack_opts opts = {\n    ++\t\t\t.packdir = packdir,\n    ++\t\t\t.destination = packdir,\n    ++\t\t\t.packtmp = packtmp,\n    ++\t\t};\n    ++\t\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd,\n    ++\t\t\t\t\t      &names);\n    ++\t\tif (ret)\n    ++\t\t\tgoto cleanup;\n    ++\t}\n      \n    + \tif (!names.nr) {\n    + \t\tif (!po_args.quiet)\n45:  a38d14e411 ! 45:  244260c6ab repack: move `finish_pack_objects_cmd()` out of the builtin\n    @@ builtin/repack.c: static int repack_config(const char *var, const char *value,\n      }\n      \n     -static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n    --\t\t\t\t   struct write_pack_opts *opts,\n    +-\t\t\t\t   const struct write_pack_opts *opts,\n     -\t\t\t\t   struct child_process *cmd,\n     -\t\t\t\t   struct string_list *names)\n     -{\n     -\tFILE *out;\n    --\tint local = write_pack_opts_is_local(opts);\n    +-\tbool local = write_pack_opts_is_local(opts);\n     -\tstruct strbuf line = STRBUF_INIT;\n     -\n     -\tout = xfdopen(cmd->out, \"r\");\n    @@ builtin/repack.c: static int repack_config(const char *var, const char *value,\n     -\treturn finish_command(cmd);\n     -}\n     -\n    - static int write_filtered_pack(struct write_pack_opts *opts,\n    + static int write_filtered_pack(const struct write_pack_opts *opts,\n      \t\t\t       struct existing_packs *existing,\n      \t\t\t       struct string_list *names)\n     \n      ## repack.c ##\n    -@@ repack.c: int write_pack_opts_is_local(struct write_pack_opts *opts)\n    - \treturn skip_prefix(opts->destination, opts->packdir, &scratch);\n    +@@ repack.c: bool write_pack_opts_is_local(const struct write_pack_opts *opts)\n    + \treturn starts_with(opts->destination, opts->packdir);\n      }\n      \n     +int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n    -+\t\t\t    struct write_pack_opts *opts,\n    ++\t\t\t    const struct write_pack_opts *opts,\n     +\t\t\t    struct child_process *cmd,\n     +\t\t\t    struct string_list *names)\n     +{\n     +\tFILE *out;\n    -+\tint local = write_pack_opts_is_local(opts);\n    ++\tbool local = write_pack_opts_is_local(opts);\n     +\tstruct strbuf line = STRBUF_INIT;\n     +\n     +\tout = xfdopen(cmd->out, \"r\");\n    @@ repack.c: int write_pack_opts_is_local(struct write_pack_opts *opts)\n     \n      ## repack.h ##\n     @@ repack.h: struct write_pack_opts {\n    - const char *write_pack_opts_pack_prefix(struct write_pack_opts *opts);\n    - int write_pack_opts_is_local(struct write_pack_opts *opts);\n    + const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts);\n    + bool write_pack_opts_is_local(const struct write_pack_opts *opts);\n      \n     +int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n    -+\t\t\t    struct write_pack_opts *opts,\n    ++\t\t\t    const struct write_pack_opts *opts,\n     +\t\t\t    struct child_process *cmd,\n     +\t\t\t    struct string_list *names);\n     +\n46:  2d9a879bb0 ! 46:  dbc317af27 repack: move `pack_kept_objects` to `struct pack_objects_args`\n    @@ builtin/repack.c: static int repack_config(const char *var, const char *value,\n      \t\treturn 0;\n      \t}\n      \tif (!strcmp(var, \"repack.writebitmaps\") ||\n    -@@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_filtered_pack(const struct write_pack_opts *opts,\n      \n      \tstrvec_push(&cmd.args, \"--stdin-packs\");\n      \n    @@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n      \tfor_each_string_list_item(item, &existing->kept_packs)\n      \t\tstrvec_pushf(&cmd.args, \"--keep-pack=%s\", item->string);\n      \n    -@@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_filtered_pack(const struct write_pack_opts *opts,\n      \t\tfprintf(in, \"%s.pack\\n\", item->string);\n      \tfor_each_string_list_item(item, &existing->cruft_packs)\n      \t\tfprintf(in, \"%s.pack\\n\", item->string);\n    @@ builtin/repack.c: static int write_filtered_pack(struct write_pack_opts *opts,\n      \tfor_each_string_list_item(item, &existing->kept_packs)\n      \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n      \tfclose(in);\n    -@@ builtin/repack.c: static int write_cruft_pack(struct write_pack_opts *opts,\n    +@@ builtin/repack.c: static int write_cruft_pack(const struct write_pack_opts *opts,\n      \t\tstrvec_pushf(&cmd.args, \"--cruft-expiration=%s\",\n      \t\t\t     cruft_expiration);\n      \n47:  d0df139717 ! 47:  7523345056 repack: move `write_filtered_pack()` out of the builtin\n    @@ builtin/repack.c: static int repack_config(const char *var, const char *value,\n      \treturn git_default_config(var, value, ctx, cb);\n      }\n      \n    --static int write_filtered_pack(struct write_pack_opts *opts,\n    +-static int write_filtered_pack(const struct write_pack_opts *opts,\n     -\t\t\t       struct existing_packs *existing,\n     -\t\t\t       struct string_list *names)\n     -{\n    @@ repack-filtered.c (new)\n     +#include \"run-command.h\"\n     +#include \"string-list.h\"\n     +\n    -+int write_filtered_pack(struct write_pack_opts *opts,\n    ++int write_filtered_pack(const struct write_pack_opts *opts,\n     +\t\t\tstruct existing_packs *existing,\n     +\t\t\tstruct string_list *names)\n     +{\n    @@ repack.h: struct repack_write_midx_opts {\n      void midx_snapshot_refs(struct repository *repo, struct tempfile *f);\n      int write_midx_included_packs(struct repack_write_midx_opts *opts);\n      \n    -+int write_filtered_pack(struct write_pack_opts *opts,\n    ++int write_filtered_pack(const struct write_pack_opts *opts,\n     +\t\t\tstruct existing_packs *existing,\n     +\t\t\tstruct string_list *names);\n     +\n48:  4c6ec504e3 ! 48:  567ccd3d3e repack: move `write_cruft_pack()` out of the builtin\n    @@ builtin/repack.c: static int repack_config(const char *var, const char *value,\n     -\tstrbuf_release(&buf);\n     -}\n     -\n    --static int write_cruft_pack(struct write_pack_opts *opts,\n    +-static int write_cruft_pack(const struct write_pack_opts *opts,\n     -\t\t\t    const char *cruft_expiration,\n     -\t\t\t    unsigned long combine_cruft_below_size,\n     -\t\t\t    struct string_list *names,\n    @@ repack-cruft.c (new)\n     +\tstrbuf_release(&buf);\n     +}\n     +\n    -+int write_cruft_pack(struct write_pack_opts *opts,\n    ++int write_cruft_pack(const struct write_pack_opts *opts,\n     +\t\t     const char *cruft_expiration,\n     +\t\t     unsigned long combine_cruft_below_size,\n     +\t\t     struct string_list *names,\n    @@ repack-cruft.c (new)\n     +}\n     \n      ## repack.h ##\n    -@@ repack.h: int write_filtered_pack(struct write_pack_opts *opts,\n    +@@ repack.h: int write_filtered_pack(const struct write_pack_opts *opts,\n      \t\t\tstruct existing_packs *existing,\n      \t\t\tstruct string_list *names);\n      \n    -+int write_cruft_pack(struct write_pack_opts *opts,\n    ++int write_cruft_pack(const struct write_pack_opts *opts,\n     +\t\t     const char *cruft_expiration,\n     +\t\t     unsigned long combine_cruft_below_size,\n     +\t\t     struct string_list *names,\n49:  16eca91f2c = 49:  b7178565ac builtin/repack.c: clean up unused `#include`s\n\nbase-commit: 4b71b294773cc4f7fe48ec3a70079aa8783f373d\n-- \n2.51.0.540.ga7423965ad8\n"},{"id":"528871","messageId":"fc2b686c49f581500e17209a40448671031a7f02.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 01/49] builtin/repack.c: avoid \"the_repository\" in `cmd_repack()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:18Z","receivedAt":"2025-10-15T22:27:20Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Reduce builtin/repack.c's reliance on `the_repository` by using the\ncurrently-UNUSED \"repo\" parameter within cmd_repack().\n\nThe following commits will continue to reduce the usage of\nthe_repository in other places within builtin/repack.c.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 31 ++++++++++++++++---------------\n 1 file changed, 16 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex e8730808c5..305782b2c9 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -1247,7 +1247,7 @@ static const char *find_pack_prefix(const char *packdir, const char *packtmp)\n int cmd_repack(int argc,\n \t       const char **argv,\n \t       const char *prefix,\n-\t       struct repository *repo UNUSED)\n+\t       struct repository *repo)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n@@ -1344,7 +1344,7 @@ int cmd_repack(int argc,\n \n \tlist_objects_filter_init(&po_args.filter_options);\n \n-\trepo_config(the_repository, repack_config, &cruft_po_args);\n+\trepo_config(repo, repack_config, &cruft_po_args);\n \n \targc = parse_options(argc, argv, prefix, builtin_repack_options,\n \t\t\t\tgit_repack_usage, 0);\n@@ -1354,7 +1354,7 @@ int cmd_repack(int argc,\n \tpo_args.depth = xstrdup_or_null(opt_depth);\n \tpo_args.threads = xstrdup_or_null(opt_threads);\n \n-\tif (delete_redundant && the_repository->repository_format_precious_objects)\n+\tif (delete_redundant && repo->repository_format_precious_objects)\n \t\tdie(_(\"cannot delete packs in a precious-objects repo\"));\n \n \tdie_for_incompatible_opt3(unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE), \"-A\",\n@@ -1376,7 +1376,7 @@ int cmd_repack(int argc,\n \t\tdie(_(incremental_bitmap_conflict_error));\n \n \tif (write_bitmaps && po_args.local &&\n-\t    odb_has_alternates(the_repository->objects)) {\n+\t    odb_has_alternates(repo->objects)) {\n \t\t/*\n \t\t * When asked to do a local repack, but we have\n \t\t * packfiles that are inherited from an alternate, then\n@@ -1391,7 +1391,8 @@ int cmd_repack(int argc,\n \tif (write_midx && write_bitmaps) {\n \t\tstruct strbuf path = STRBUF_INIT;\n \n-\t\tstrbuf_addf(&path, \"%s/%s_XXXXXX\", repo_get_object_directory(the_repository),\n+\t\tstrbuf_addf(&path, \"%s/%s_XXXXXX\",\n+\t\t\t    repo_get_object_directory(repo),\n \t\t\t    \"bitmap-ref-tips\");\n \n \t\trefs_snapshot = xmks_tempfile(path.buf);\n@@ -1400,7 +1401,7 @@ int cmd_repack(int argc,\n \t\tstrbuf_release(&path);\n \t}\n \n-\tpackdir = mkpathdup(\"%s/pack\", repo_get_object_directory(the_repository));\n+\tpackdir = mkpathdup(\"%s/pack\", repo_get_object_directory(repo));\n \tpacktmp_name = xstrfmt(\".tmp-%d-pack\", (int)getpid());\n \tpacktmp = mkpathdup(\"%s/%s\", packdir, packtmp_name);\n \n@@ -1439,7 +1440,7 @@ int cmd_repack(int argc,\n \t\tstrvec_push(&cmd.args, \"--reflog\");\n \t\tstrvec_push(&cmd.args, \"--indexed-objects\");\n \t}\n-\tif (repo_has_promisor_remote(the_repository))\n+\tif (repo_has_promisor_remote(repo))\n \t\tstrvec_push(&cmd.args, \"--exclude-promisor-objects\");\n \tif (!write_midx) {\n \t\tif (write_bitmaps > 0)\n@@ -1535,7 +1536,7 @@ int cmd_repack(int argc,\n \t\t * midx_has_unknown_packs() will make the decision for\n \t\t * us.\n \t\t */\n-\t\tif (!get_multi_pack_index(the_repository->objects->sources))\n+\t\tif (!get_multi_pack_index(repo->objects->sources))\n \t\t\tmidx_must_contain_cruft = 1;\n \t}\n \n@@ -1618,9 +1619,9 @@ int cmd_repack(int argc,\n \n \tstring_list_sort(&names);\n \n-\tif (get_multi_pack_index(the_repository->objects->sources)) {\n+\tif (get_multi_pack_index(repo->objects->sources)) {\n \t\tstruct multi_pack_index *m =\n-\t\t\tget_multi_pack_index(the_repository->objects->sources);\n+\t\t\tget_multi_pack_index(repo->objects->sources);\n \n \t\tALLOC_ARRAY(midx_pack_names,\n \t\t\t    m->num_packs + m->num_packs_in_base);\n@@ -1631,7 +1632,7 @@ int cmd_repack(int argc,\n \t\t\t\t\txstrdup(m->pack_names[i]);\n \t}\n \n-\tclose_object_store(the_repository->objects);\n+\tclose_object_store(repo->objects);\n \n \t/*\n \t * Ok we have prepared all new packfiles.\n@@ -1688,7 +1689,7 @@ int cmd_repack(int argc,\n \t\t\tgoto cleanup;\n \t}\n \n-\todb_reprepare(the_repository->objects);\n+\todb_reprepare(repo->objects);\n \n \tif (delete_redundant) {\n \t\tint opts = 0;\n@@ -1704,18 +1705,18 @@ int cmd_repack(int argc,\n \t\tif (!keep_unreachable &&\n \t\t    (!(pack_everything & LOOSEN_UNREACHABLE) ||\n \t\t     unpack_unreachable) &&\n-\t\t    is_repository_shallow(the_repository))\n+\t\t    is_repository_shallow(repo))\n \t\t\tprune_shallow(PRUNE_QUICK);\n \t}\n \n \tif (run_update_server_info)\n-\t\tupdate_server_info(the_repository, 0);\n+\t\tupdate_server_info(repo, 0);\n \n \tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) {\n \t\tunsigned flags = 0;\n \t\tif (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0))\n \t\t\tflags |= MIDX_WRITE_INCREMENTAL;\n-\t\twrite_midx_file(the_repository->objects->sources,\n+\t\twrite_midx_file(repo->objects->sources,\n \t\t\t\tNULL, NULL, flags);\n \t}\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528872","messageId":"14a65d7c6dfde222c66eadc7af626456e428c249.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 02/49] builtin/repack.c: avoid \"the_repository\" in existing packs API","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:21Z","receivedAt":"2025-10-15T22:27:23Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"There are a number of spots within builtin/repack.c which refer to\n\"the_repository\", and either make use of the \"existing packs\" API\nor otherwise have a 'struct existing_packs *' in scope.\n\nAdd a \"repo\" member to \"struct existing_packs\" and use that instead of\n\"the_repository\" in such locations.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 305782b2c9..7223553bed 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -126,6 +126,7 @@ static void pack_objects_args_release(struct pack_objects_args *args)\n }\n \n struct existing_packs {\n+\tstruct repository *repo;\n \tstruct string_list kept_packs;\n \tstruct string_list non_kept_packs;\n \tstruct string_list cruft_packs;\n@@ -265,7 +266,7 @@ static void existing_packs_release(struct existing_packs *existing)\n static void collect_pack_filenames(struct existing_packs *existing,\n \t\t\t\t   const struct string_list *extra_keep)\n {\n-\tstruct packfile_store *packs = the_repository->objects->packfiles;\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -498,7 +499,7 @@ static void init_pack_geometry(struct pack_geometry *geometry,\n \t\t\t       struct existing_packs *existing,\n \t\t\t       const struct pack_objects_args *args)\n {\n-\tstruct packfile_store *packs = the_repository->objects->packfiles;\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -1139,7 +1140,7 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n \t\t\t\t      struct existing_packs *existing)\n {\n-\tstruct packfile_store *packs = the_repository->objects->packfiles;\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n \tstruct strbuf buf = STRBUF_INIT;\n \tsize_t i;\n@@ -1405,6 +1406,7 @@ int cmd_repack(int argc,\n \tpacktmp_name = xstrfmt(\".tmp-%d-pack\", (int)getpid());\n \tpacktmp = mkpathdup(\"%s/%s\", packdir, packtmp_name);\n \n+\texisting.repo = repo;\n \tcollect_pack_filenames(&existing, &keep_pack_list);\n \n \tif (geometry.split_factor) {\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528873","messageId":"ff72cd499bb6430b51db01989f67d9384aa2057b.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 03/49] builtin/repack.c: avoid \"the_repository\" when taking a ref snapshot","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:24Z","receivedAt":"2025-10-15T22:27:26Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Avoid using \"the_repository\" in various MIDX-related ref snapshotting\nfunctions.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 16 +++++++++-------\n 1 file changed, 9 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 7223553bed..113f5fc67f 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -771,6 +771,7 @@ static int midx_has_unknown_packs(char **midx_pack_names,\n }\n \n struct midx_snapshot_ref_data {\n+\tstruct repository *repo;\n \tstruct tempfile *f;\n \tstruct oidset seen;\n \tint preferred;\n@@ -784,13 +785,13 @@ static int midx_snapshot_ref_one(const char *refname UNUSED,\n \tstruct midx_snapshot_ref_data *data = _data;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n \t\toid = &peeled;\n \n \tif (oidset_insert(&data->seen, oid))\n \t\treturn 0; /* already seen */\n \n-\tif (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n@@ -799,11 +800,12 @@ static int midx_snapshot_ref_one(const char *refname UNUSED,\n \treturn 0;\n }\n \n-static void midx_snapshot_refs(struct tempfile *f)\n+static void midx_snapshot_refs(struct repository *repo, struct tempfile *f)\n {\n \tstruct midx_snapshot_ref_data data;\n-\tconst struct string_list *preferred = bitmap_preferred_tips(the_repository);\n+\tconst struct string_list *preferred = bitmap_preferred_tips(repo);\n \n+\tdata.repo = repo;\n \tdata.f = f;\n \tdata.preferred = 0;\n \toidset_init(&data.seen, 0);\n@@ -817,13 +819,13 @@ static void midx_snapshot_refs(struct tempfile *f)\n \n \t\tdata.preferred = 1;\n \t\tfor_each_string_list_item(item, preferred)\n-\t\t\trefs_for_each_ref_in(get_main_ref_store(the_repository),\n+\t\t\trefs_for_each_ref_in(get_main_ref_store(repo),\n \t\t\t\t\t     item->string,\n \t\t\t\t\t     midx_snapshot_ref_one, &data);\n \t\tdata.preferred = 0;\n \t}\n \n-\trefs_for_each_ref(get_main_ref_store(the_repository),\n+\trefs_for_each_ref(get_main_ref_store(repo),\n \t\t\t  midx_snapshot_ref_one, &data);\n \n \tif (close_tempfile_gently(f)) {\n@@ -1397,7 +1399,7 @@ int cmd_repack(int argc,\n \t\t\t    \"bitmap-ref-tips\");\n \n \t\trefs_snapshot = xmks_tempfile(path.buf);\n-\t\tmidx_snapshot_refs(refs_snapshot);\n+\t\tmidx_snapshot_refs(repo, refs_snapshot);\n \n \t\tstrbuf_release(&path);\n \t}\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528874","messageId":"75f23ba211bcdd236fc5040c0732eb880867ec4b.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 04/49] builtin/repack.c: avoid \"the_repository\" when removing packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:27Z","receivedAt":"2025-10-15T22:27:29Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The 'remove_redundant_pack()' function uses \"the_repository\" to obtain,\nand optionally remove, the repository's MIDX. Instead of relying on\n\"the_repository\", pass around a \"struct repository *\" parameter through\nits callers, and use that instead.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 18 ++++++++++--------\n 1 file changed, 10 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 113f5fc67f..93802531e1 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -221,33 +221,35 @@ static void mark_packs_for_deletion(struct existing_packs *existing,\n \tmark_packs_for_deletion_1(names, &existing->cruft_packs);\n }\n \n-static void remove_redundant_pack(const char *dir_name, const char *base_name)\n+static void remove_redundant_pack(struct repository *repo,\n+\t\t\t\t  const char *dir_name, const char *base_name)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct odb_source *source = the_repository->objects->sources;\n+\tstruct odb_source *source = repo->objects->sources;\n \tstruct multi_pack_index *m = get_multi_pack_index(source);\n \tstrbuf_addf(&buf, \"%s.pack\", base_name);\n \tif (m && source->local && midx_contains_pack(m, buf.buf))\n-\t\tclear_midx_file(the_repository);\n+\t\tclear_midx_file(repo);\n \tstrbuf_insertf(&buf, 0, \"%s/\", dir_name);\n \tunlink_pack_path(buf.buf, 1);\n \tstrbuf_release(&buf);\n }\n \n-static void remove_redundant_packs_1(struct string_list *packs)\n+static void remove_redundant_packs_1(struct repository *repo,\n+\t\t\t\t     struct string_list *packs)\n {\n \tstruct string_list_item *item;\n \tfor_each_string_list_item(item, packs) {\n \t\tif (!pack_is_marked_for_deletion(item))\n \t\t\tcontinue;\n-\t\tremove_redundant_pack(packdir, item->string);\n+\t\tremove_redundant_pack(repo, packdir, item->string);\n \t}\n }\n \n static void remove_redundant_existing_packs(struct existing_packs *existing)\n {\n-\tremove_redundant_packs_1(&existing->non_kept_packs);\n-\tremove_redundant_packs_1(&existing->cruft_packs);\n+\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs);\n+\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs);\n }\n \n static void existing_packs_release(struct existing_packs *existing)\n@@ -685,7 +687,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n \t\t    (string_list_has_string(&existing->kept_packs, buf.buf)))\n \t\t\tcontinue;\n \n-\t\tremove_redundant_pack(packdir, buf.buf);\n+\t\tremove_redundant_pack(existing->repo, packdir, buf.buf);\n \t}\n \n \tstrbuf_release(&buf);\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528875","messageId":"0914ca113ff179bbf824c026fe1c0463a5acb7f1.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 05/49] builtin/repack.c: avoid \"the_repository\" when repacking promisor objects","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:30Z","receivedAt":"2025-10-15T22:27:32Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Pass a \"struct repository\" pointer to the 'repack_promisor_objects()'\nfunction to avoid using \"the_repository\".\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 93802531e1..4f08b57ddb 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -407,7 +407,8 @@ static int has_pack_ext(const struct generated_pack_data *data,\n \tBUG(\"unknown pack extension: '%s'\", ext);\n }\n \n-static void repack_promisor_objects(const struct pack_objects_args *args,\n+static void repack_promisor_objects(struct repository *repo,\n+\t\t\t\t    const struct pack_objects_args *args,\n \t\t\t\t    struct string_list *names)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -424,7 +425,7 @@ static void repack_promisor_objects(const struct pack_objects_args *args,\n \t * {type -> existing pack order} ordering when computing deltas instead\n \t * of a {type -> size} ordering, which may produce better deltas.\n \t */\n-\tfor_each_packed_object(the_repository, write_oid, &cmd,\n+\tfor_each_packed_object(repo, write_oid, &cmd,\n \t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n@@ -1458,7 +1459,7 @@ int cmd_repack(int argc,\n \t\tstrvec_push(&cmd.args, \"--delta-islands\");\n \n \tif (pack_everything & ALL_INTO_ONE) {\n-\t\trepack_promisor_objects(&po_args, &names);\n+\t\trepack_promisor_objects(repo, &po_args, &names);\n \n \t\tif (has_existing_non_kept_packs(&existing) &&\n \t\t    delete_redundant &&\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528876","messageId":"238f3d87a05c577a8864073b06cd62cc84611a39.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 06/49] builtin/repack.c: avoid \"the_hash_algo\" when deleting packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:33Z","receivedAt":"2025-10-15T22:27:35Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The \"mark_packs_for_deletion_1\" function uses \"the_hash_algo->hexsz\" to\nisolate a pack's checksum before deleting it to avoid deleting a newly\nwritten pack having the same checksum (that is, some generated pack\nwound up identical to an existing pack).\n\nAvoid this by passing down a \"struct git_hash_algo\" pointer, and refer to\nthe hash algorithm through it instead.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 10 ++++++----\n 1 file changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 4f08b57ddb..094f5a0cc2 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -168,11 +168,12 @@ static int pack_is_retained(struct string_list_item *item)\n \treturn (uintptr_t)item->util & RETAIN_PACK;\n }\n \n-static void mark_packs_for_deletion_1(struct string_list *names,\n+static void mark_packs_for_deletion_1(const struct git_hash_algo *algop,\n+\t\t\t\t      struct string_list *names,\n \t\t\t\t      struct string_list *list)\n {\n \tstruct string_list_item *item;\n-\tconst int hexsz = the_hash_algo->hexsz;\n+\tconst int hexsz = algop->hexsz;\n \n \tfor_each_string_list_item(item, list) {\n \t\tchar *sha1;\n@@ -217,8 +218,9 @@ static void mark_packs_for_deletion(struct existing_packs *existing,\n \t\t\t\t    struct string_list *names)\n \n {\n-\tmark_packs_for_deletion_1(names, &existing->non_kept_packs);\n-\tmark_packs_for_deletion_1(names, &existing->cruft_packs);\n+\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n+\tmark_packs_for_deletion_1(algop, names, &existing->non_kept_packs);\n+\tmark_packs_for_deletion_1(algop, names, &existing->cruft_packs);\n }\n \n static void remove_redundant_pack(struct repository *repo,\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528877","messageId":"ca3c8a782a76c1660852784d8160a7680d7e607a.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 07/49] builtin/repack.c: avoid \"the_hash_algo\" in `write_oid()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:36Z","receivedAt":"2025-10-15T22:27:38Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as the previous commit, avoid referring directly to\n\"the_hash_algo\" within builtin/repack.c::write_oid().\n\nUnlike the previous commit, we are within a callback function, so must\nintroduce a new struct to pass additional data through its \"data\"\npointer.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 15 ++++++++++++---\n 1 file changed, 12 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 094f5a0cc2..7d62959dc2 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -339,6 +339,11 @@ static void prepare_pack_objects(struct child_process *cmd,\n \tcmd->out = -1;\n }\n \n+struct write_oid_context {\n+\tstruct child_process *cmd;\n+\tconst struct git_hash_algo *algop;\n+};\n+\n /*\n  * Write oid to the given struct child_process's stdin, starting it first if\n  * necessary.\n@@ -347,14 +352,15 @@ static int write_oid(const struct object_id *oid,\n \t\t     struct packed_git *pack UNUSED,\n \t\t     uint32_t pos UNUSED, void *data)\n {\n-\tstruct child_process *cmd = data;\n+\tstruct write_oid_context *ctx = data;\n+\tstruct child_process *cmd = ctx->cmd;\n \n \tif (cmd->in == -1) {\n \t\tif (start_command(cmd))\n \t\t\tdie(_(\"could not start pack-objects to repack promisor objects\"));\n \t}\n \n-\tif (write_in_full(cmd->in, oid_to_hex(oid), the_hash_algo->hexsz) < 0 ||\n+\tif (write_in_full(cmd->in, oid_to_hex(oid), ctx->algop->hexsz) < 0 ||\n \t    write_in_full(cmd->in, \"\\n\", 1) < 0)\n \t\tdie(_(\"failed to feed promisor objects to pack-objects\"));\n \treturn 0;\n@@ -413,6 +419,7 @@ static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t    const struct pack_objects_args *args,\n \t\t\t\t    struct string_list *names)\n {\n+\tstruct write_oid_context ctx;\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tFILE *out;\n \tstruct strbuf line = STRBUF_INIT;\n@@ -427,7 +434,9 @@ static void repack_promisor_objects(struct repository *repo,\n \t * {type -> existing pack order} ordering when computing deltas instead\n \t * of a {type -> size} ordering, which may produce better deltas.\n \t */\n-\tfor_each_packed_object(repo, write_oid, &cmd,\n+\tctx.cmd = &cmd;\n+\tctx.algop = repo->hash_algo;\n+\tfor_each_packed_object(repo, write_oid, &ctx,\n \t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n \n \tif (cmd.in == -1) {\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528878","messageId":"34b503618a455b30030ab152e925bb47af969522.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 08/49] builtin/repack: avoid \"the_hash_algo\" in `repack_promisor_objects()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:38Z","receivedAt":"2025-10-15T22:27:41Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as the previous commits, avoid referring directly to\n\"the_hash_algo\" within builtin/repack.c::repack_promisor_objects().\n\nSince there is already a repository pointer in scope, use its hash_algo\nvalue instead.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 7d62959dc2..a7e94ed03c 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -452,7 +452,7 @@ static void repack_promisor_objects(struct repository *repo,\n \t\tstruct string_list_item *item;\n \t\tchar *promisor_name;\n \n-\t\tif (line.len != the_hash_algo->hexsz)\n+\t\tif (line.len != repo->hash_algo->hexsz)\n \t\t\tdie(_(\"repack: Expecting full hex object ID lines only from pack-objects.\"));\n \t\titem = string_list_append(names, line.buf);\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528879","messageId":"cd43a60620979a8ce6a41ad78283be683b83e843.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 09/49] builtin/repack.c: avoid \"the_hash_algo\" in `finish_pack_objects_cmd()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:41Z","receivedAt":"2025-10-15T22:27:43Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as previous commits, avoid referring directly to\n\"the_hash_algo\" in builtin/repack.c::finish_pack_objects_cmd() and\ninstead accept one as a parameter to the function.\n\nSince this function has a number of callers throughout the builtin, the\ndiff is a little noisier than previous commits. However, each hunk is\nlimited to passing the hash_algo parameter from a repository pointer\nthat is already in scope.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 13 ++++++++-----\n 1 file changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a7e94ed03c..a043704aa8 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -1073,7 +1073,8 @@ static void remove_redundant_bitmaps(struct string_list *include,\n \tstrbuf_release(&path);\n }\n \n-static int finish_pack_objects_cmd(struct child_process *cmd,\n+static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n+\t\t\t\t   struct child_process *cmd,\n \t\t\t\t   struct string_list *names,\n \t\t\t\t   int local)\n {\n@@ -1084,7 +1085,7 @@ static int finish_pack_objects_cmd(struct child_process *cmd,\n \twhile (strbuf_getline_lf(&line, out) != EOF) {\n \t\tstruct string_list_item *item;\n \n-\t\tif (line.len != the_hash_algo->hexsz)\n+\t\tif (line.len != algop->hexsz)\n \t\t\tdie(_(\"repack: Expecting full hex object ID lines only \"\n \t\t\t      \"from pack-objects.\"));\n \t\t/*\n@@ -1150,7 +1151,8 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n \tfclose(in);\n \n-\treturn finish_pack_objects_cmd(&cmd, names, local);\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n+\t\t\t\t       local);\n }\n \n static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n@@ -1247,7 +1249,8 @@ static int write_cruft_pack(const struct pack_objects_args *args,\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfclose(in);\n \n-\treturn finish_pack_objects_cmd(&cmd, names, local);\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n+\t\t\t\t       local);\n }\n \n static const char *find_pack_prefix(const char *packdir, const char *packtmp)\n@@ -1534,7 +1537,7 @@ int cmd_repack(int argc,\n \t\tfclose(in);\n \t}\n \n-\tret = finish_pack_objects_cmd(&cmd, &names, 1);\n+\tret = finish_pack_objects_cmd(repo->hash_algo, &cmd, &names, 1);\n \tif (ret)\n \t\tgoto cleanup;\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528880","messageId":"c6787c865de6ce746096da0d5a526dc3d0be6aaf.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 10/49] builtin/repack.c: avoid using `hash_to_hex()` in pack geometry","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:50Z","receivedAt":"2025-10-15T22:27:52Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In previous commits, we started passing either repository or\ngit_hash_algo pointers around to various spots within builtin/repack.c\nto reduce our dependency on the_repository in the hope of undef'ing\nUSE_THE_REPOSITORY_VARIABLE.\n\nThis commit takes us as far as we can (easily) go in that direction by\nremoving the only use of a convenience function that only exists when\nUSE_THE_REPOSITORY_VARIABLE is defined.\n\nUnfortunately, the only other such function is \"is_bare_repository()\",\nwhich is less than straightforward to convert into, say,\n\"repo_is_bare()\", the latter of the two accepting a repository pointer.\n\nPunt on that for now, and declare this commit as the stopping point for\nour efforts in the direction of undef'ing USE_THE_REPOSITORY_VARIABLE.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a043704aa8..0d35f15b4b 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -683,12 +683,14 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n \t\t\t\t\t    struct string_list *names,\n \t\t\t\t\t    struct existing_packs *existing)\n {\n+\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n \tstruct strbuf buf = STRBUF_INIT;\n \tuint32_t i;\n \n \tfor (i = 0; i < geometry->split; i++) {\n \t\tstruct packed_git *p = geometry->pack[i];\n-\t\tif (string_list_has_string(names, hash_to_hex(p->hash)))\n+\t\tif (string_list_has_string(names, hash_to_hex_algop(p->hash,\n+\t\t\t\t\t\t\t\t    algop)))\n \t\t\tcontinue;\n \n \t\tstrbuf_reset(&buf);\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528881","messageId":"87f2903e757f37cabad073077c9ba177f7418433.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 11/49] repack: introduce new compilation unit","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:53Z","receivedAt":"2025-10-15T22:27:55Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Over the years, builtin/repack.c has turned into a grab-bag of\nfunctionality powering the 'git repack' builtin. Among its many\ncapabilities, it:\n\n - can build and spawn 'git pack-objects' commands, which in turn\n   generate new packs\n - has infrastructure to manage the set of existing packs in a\n   repository\n - has infrastructure to split a sequence of packs into a geometric\n   progression based on object size\n - can manage both generating and combining cruft packs together\n - can write new MIDXs\n\nto name a few.\n\nAs a result, this builtin has accumulated a lot of code, making adding\nnew functionality difficult. In the future, 'repack' will learn how to\nmanage a chain of incremental MIDXs, adding yet more functionality into\nthe builtin.\n\nAs a prerequisite step, let's first move some of the functionality in\nthe builtin into its own repack.[ch].\n\nThis will be done over the course of many steps, since there are many\nindividual components, some of which will end up in other, yet-to-exist\ncompilation units of their own. Some of the code movement here is also\nnon-trivial, so performing it in individual steps will make it easier to\nverify.\n\nLet's start by migrating 'struct pack_objects_args' (and the related\ncorresponding pack_objects_args_release() function) into repack.h, and\nteach both the Makefile and Meson how to build the new compilation unit.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile         |  1 +\n builtin/repack.c | 25 +------------------------\n meson.build      |  1 +\n repack.c         | 11 +++++++++++\n repack.h         | 23 +++++++++++++++++++++++\n 5 files changed, 37 insertions(+), 24 deletions(-)\n create mode 100644 repack.c\n create mode 100644 repack.h\n\ndiff --git a/Makefile b/Makefile\nindex 7ea149598d..58fc05030b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1249,6 +1249,7 @@ LIB_OBJS += refs/packed-backend.o\n LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n+LIB_OBJS += repack.o\n LIB_OBJS += replace-object.o\n LIB_OBJS += repo-settings.o\n LIB_OBJS += repository.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 0d35f15b4b..6dfcb3327e 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -19,6 +19,7 @@\n #include \"prune-packed.h\"\n #include \"odb.h\"\n #include \"promisor-remote.h\"\n+#include \"repack.h\"\n #include \"shallow.h\"\n #include \"pack.h\"\n #include \"pack-bitmap.h\"\n@@ -53,21 +54,6 @@ static const char incremental_bitmap_conflict_error[] = N_(\n \"--no-write-bitmap-index or disable the pack.writeBitmaps configuration.\"\n );\n \n-struct pack_objects_args {\n-\tchar *window;\n-\tchar *window_memory;\n-\tchar *depth;\n-\tchar *threads;\n-\tunsigned long max_pack_size;\n-\tint no_reuse_delta;\n-\tint no_reuse_object;\n-\tint quiet;\n-\tint local;\n-\tint name_hash_version;\n-\tint path_walk;\n-\tstruct list_objects_filter_options filter_options;\n-};\n-\n static int repack_config(const char *var, const char *value,\n \t\t\t const struct config_context *ctx, void *cb)\n {\n@@ -116,15 +102,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-static void pack_objects_args_release(struct pack_objects_args *args)\n-{\n-\tfree(args->window);\n-\tfree(args->window_memory);\n-\tfree(args->depth);\n-\tfree(args->threads);\n-\tlist_objects_filter_release(&args->filter_options);\n-}\n-\n struct existing_packs {\n \tstruct repository *repo;\n \tstruct string_list kept_packs;\ndiff --git a/meson.build b/meson.build\nindex ec55d6a5fd..4a985ce77e 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -462,6 +462,7 @@ libgit_sources = [\n   'reftable/tree.c',\n   'reftable/writer.c',\n   'remote.c',\n+  'repack.c',\n   'replace-object.c',\n   'repo-settings.c',\n   'repository.c',\ndiff --git a/repack.c b/repack.c\nnew file mode 100644\nindex 0000000000..a1f5b796fb\n--- /dev/null\n+++ b/repack.c\n@@ -0,0 +1,11 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+\n+void pack_objects_args_release(struct pack_objects_args *args)\n+{\n+\tfree(args->window);\n+\tfree(args->window_memory);\n+\tfree(args->depth);\n+\tfree(args->threads);\n+\tlist_objects_filter_release(&args->filter_options);\n+}\ndiff --git a/repack.h b/repack.h\nnew file mode 100644\nindex 0000000000..421d439d5a\n--- /dev/null\n+++ b/repack.h\n@@ -0,0 +1,23 @@\n+#ifndef REPACK_H\n+#define REPACK_H\n+\n+#include \"list-objects-filter-options.h\"\n+\n+struct pack_objects_args {\n+\tchar *window;\n+\tchar *window_memory;\n+\tchar *depth;\n+\tchar *threads;\n+\tunsigned long max_pack_size;\n+\tint no_reuse_delta;\n+\tint no_reuse_object;\n+\tint quiet;\n+\tint local;\n+\tint name_hash_version;\n+\tint path_walk;\n+\tstruct list_objects_filter_options filter_options;\n+};\n+\n+void pack_objects_args_release(struct pack_objects_args *args);\n+\n+#endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528882","messageId":"a812bd3dcb36fe024bb4abc78f40f269c9c04635.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 12/49] builtin/repack.c: pass both pack_objects args to repack_config","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:56Z","receivedAt":"2025-10-15T22:27:58Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"A subsequent commit will remove 'delta_base_offset' as a static variable\nwithin builtin/repack.c, and reintroduce it as a member of the 'struct\npack_objects_args'.\n\nAs a result, the repack_config callback will need to have both the\ncruft- and non-cruft 'struct pack_objects_args's in scope. Introduce a\nnew 'struct repack_config_ctx' to allow the callee to provide both\npointers to the callback.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 15 +++++++++++++--\n 1 file changed, 13 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 6dfcb3327e..af6de8d77a 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -54,10 +54,16 @@ static const char incremental_bitmap_conflict_error[] = N_(\n \"--no-write-bitmap-index or disable the pack.writeBitmaps configuration.\"\n );\n \n+struct repack_config_ctx {\n+\tstruct pack_objects_args *po_args;\n+\tstruct pack_objects_args *cruft_po_args;\n+};\n+\n static int repack_config(const char *var, const char *value,\n \t\t\t const struct config_context *ctx, void *cb)\n {\n-\tstruct pack_objects_args *cruft_po_args = cb;\n+\tstruct repack_config_ctx *repack_ctx = cb;\n+\tstruct pack_objects_args *cruft_po_args = repack_ctx->cruft_po_args;\n \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n \t\tdelta_base_offset = git_config_bool(var, value);\n \t\treturn 0;\n@@ -1260,6 +1266,7 @@ int cmd_repack(int argc,\n \tsize_t midx_pack_names_nr = 0;\n \n \t/* variables to be filled by option parsing */\n+\tstruct repack_config_ctx config_ctx;\n \tint delete_redundant = 0;\n \tconst char *unpack_unreachable = NULL;\n \tint keep_unreachable = 0;\n@@ -1343,7 +1350,11 @@ int cmd_repack(int argc,\n \n \tlist_objects_filter_init(&po_args.filter_options);\n \n-\trepo_config(repo, repack_config, &cruft_po_args);\n+\tmemset(&config_ctx, 0, sizeof(config_ctx));\n+\tconfig_ctx.po_args = &po_args;\n+\tconfig_ctx.cruft_po_args = &cruft_po_args;\n+\n+\trepo_config(repo, repack_config, &config_ctx);\n \n \targc = parse_options(argc, argv, prefix, builtin_repack_options,\n \t\t\t\tgit_repack_usage, 0);\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528883","messageId":"f2d1fa51e36e7500efdd32eebfc3e8016777b715.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 13/49] repack: move 'delta_base_offset' to 'struct pack_objects_args'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:27:58Z","receivedAt":"2025-10-15T22:28:01Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The static variable 'delta_base_offset' determines whether or not we\npass the \"--delta-base-offset\" command-line argument when spawning\npack-objects as a child process. Its introduction dates back to when\nrepack was rewritten in C, all the way back in a1bbc6c017 (repack:\nrewrite the shell script in C, 2013-09-15).\n\n'struct pack_objects_args' was introduced much later on in 4571324b99\n(builtin/repack.c: allow configuring cruft pack generation, 2022-05-20),\nbut did not move the 'delta_base_offset' variable.\n\nSince the 'delta_base_offset' is a property of an individual\npack-objects command, re-introduce that variable as a member of 'struct\npack_objects_args', which will enable further code movement in the\nsubsequent commits.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 11 ++++++-----\n repack.h         |  3 +++\n 2 files changed, 9 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex af6de8d77a..f4af830353 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -34,7 +34,6 @@\n #define RETAIN_PACK 2\n \n static int pack_everything;\n-static int delta_base_offset = 1;\n static int pack_kept_objects = -1;\n static int write_bitmaps = -1;\n static int use_delta_islands;\n@@ -63,9 +62,10 @@ static int repack_config(const char *var, const char *value,\n \t\t\t const struct config_context *ctx, void *cb)\n {\n \tstruct repack_config_ctx *repack_ctx = cb;\n+\tstruct pack_objects_args *po_args = repack_ctx->po_args;\n \tstruct pack_objects_args *cruft_po_args = repack_ctx->cruft_po_args;\n \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n-\t\tdelta_base_offset = git_config_bool(var, value);\n+\t\tpo_args->delta_base_offset = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"repack.packkeptobjects\")) {\n@@ -315,7 +315,7 @@ static void prepare_pack_objects(struct child_process *cmd,\n \t\tstrvec_push(&cmd->args,  \"--local\");\n \tif (args->quiet)\n \t\tstrvec_push(&cmd->args,  \"--quiet\");\n-\tif (delta_base_offset)\n+\tif (args->delta_base_offset)\n \t\tstrvec_push(&cmd->args,  \"--delta-base-offset\");\n \tstrvec_push(&cmd->args, out);\n \tcmd->git_cmd = 1;\n@@ -1271,8 +1271,8 @@ int cmd_repack(int argc,\n \tconst char *unpack_unreachable = NULL;\n \tint keep_unreachable = 0;\n \tstruct string_list keep_pack_list = STRING_LIST_INIT_NODUP;\n-\tstruct pack_objects_args po_args = { 0 };\n-\tstruct pack_objects_args cruft_po_args = { 0 };\n+\tstruct pack_objects_args po_args = PACK_OBJECTS_ARGS_INIT;\n+\tstruct pack_objects_args cruft_po_args = PACK_OBJECTS_ARGS_INIT;\n \tint write_midx = 0;\n \tconst char *cruft_expiration = NULL;\n \tconst char *expire_to = NULL;\n@@ -1567,6 +1567,7 @@ int cmd_repack(int argc,\n \n \t\tcruft_po_args.local = po_args.local;\n \t\tcruft_po_args.quiet = po_args.quiet;\n+\t\tcruft_po_args.delta_base_offset = po_args.delta_base_offset;\n \n \t\tret = write_cruft_pack(&cruft_po_args, packtmp, pack_prefix,\n \t\t\t\t       cruft_expiration,\ndiff --git a/repack.h b/repack.h\nindex 421d439d5a..12632d7fec 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -15,9 +15,12 @@ struct pack_objects_args {\n \tint local;\n \tint name_hash_version;\n \tint path_walk;\n+\tint delta_base_offset;\n \tstruct list_objects_filter_options filter_options;\n };\n \n+#define PACK_OBJECTS_ARGS_INIT { .delta_base_offset = 1 }\n+\n void pack_objects_args_release(struct pack_objects_args *args);\n \n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528884","messageId":"0c32c3c9b6af2e4c5ab3e506a192a9c487d114fb.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 14/49] repack: remove 'prepare_pack_objects' from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:01Z","receivedAt":"2025-10-15T22:28:03Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that the 'prepare_pack_objects' function no longer refers to\nexternal, static variables, move it out to repack.h as generic\nfunctionality.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 34 ----------------------------------\n repack.c         | 35 +++++++++++++++++++++++++++++++++++\n repack.h         |  5 +++++\n 3 files changed, 40 insertions(+), 34 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex f4af830353..ff93654cfe 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -288,40 +288,6 @@ static void collect_pack_filenames(struct existing_packs *existing,\n \tstrbuf_release(&buf);\n }\n \n-static void prepare_pack_objects(struct child_process *cmd,\n-\t\t\t\t const struct pack_objects_args *args,\n-\t\t\t\t const char *out)\n-{\n-\tstrvec_push(&cmd->args, \"pack-objects\");\n-\tif (args->window)\n-\t\tstrvec_pushf(&cmd->args, \"--window=%s\", args->window);\n-\tif (args->window_memory)\n-\t\tstrvec_pushf(&cmd->args, \"--window-memory=%s\", args->window_memory);\n-\tif (args->depth)\n-\t\tstrvec_pushf(&cmd->args, \"--depth=%s\", args->depth);\n-\tif (args->threads)\n-\t\tstrvec_pushf(&cmd->args, \"--threads=%s\", args->threads);\n-\tif (args->max_pack_size)\n-\t\tstrvec_pushf(&cmd->args, \"--max-pack-size=%lu\", args->max_pack_size);\n-\tif (args->no_reuse_delta)\n-\t\tstrvec_pushf(&cmd->args, \"--no-reuse-delta\");\n-\tif (args->no_reuse_object)\n-\t\tstrvec_pushf(&cmd->args, \"--no-reuse-object\");\n-\tif (args->name_hash_version)\n-\t\tstrvec_pushf(&cmd->args, \"--name-hash-version=%d\", args->name_hash_version);\n-\tif (args->path_walk)\n-\t\tstrvec_pushf(&cmd->args, \"--path-walk\");\n-\tif (args->local)\n-\t\tstrvec_push(&cmd->args,  \"--local\");\n-\tif (args->quiet)\n-\t\tstrvec_push(&cmd->args,  \"--quiet\");\n-\tif (args->delta_base_offset)\n-\t\tstrvec_push(&cmd->args,  \"--delta-base-offset\");\n-\tstrvec_push(&cmd->args, out);\n-\tcmd->git_cmd = 1;\n-\tcmd->out = -1;\n-}\n-\n struct write_oid_context {\n \tstruct child_process *cmd;\n \tconst struct git_hash_algo *algop;\ndiff --git a/repack.c b/repack.c\nindex a1f5b796fb..91b6e1cc09 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -1,5 +1,40 @@\n #include \"git-compat-util.h\"\n #include \"repack.h\"\n+#include \"run-command.h\"\n+\n+void prepare_pack_objects(struct child_process *cmd,\n+\t\t\t  const struct pack_objects_args *args,\n+\t\t\t  const char *out)\n+{\n+\tstrvec_push(&cmd->args, \"pack-objects\");\n+\tif (args->window)\n+\t\tstrvec_pushf(&cmd->args, \"--window=%s\", args->window);\n+\tif (args->window_memory)\n+\t\tstrvec_pushf(&cmd->args, \"--window-memory=%s\", args->window_memory);\n+\tif (args->depth)\n+\t\tstrvec_pushf(&cmd->args, \"--depth=%s\", args->depth);\n+\tif (args->threads)\n+\t\tstrvec_pushf(&cmd->args, \"--threads=%s\", args->threads);\n+\tif (args->max_pack_size)\n+\t\tstrvec_pushf(&cmd->args, \"--max-pack-size=%lu\", args->max_pack_size);\n+\tif (args->no_reuse_delta)\n+\t\tstrvec_pushf(&cmd->args, \"--no-reuse-delta\");\n+\tif (args->no_reuse_object)\n+\t\tstrvec_pushf(&cmd->args, \"--no-reuse-object\");\n+\tif (args->name_hash_version)\n+\t\tstrvec_pushf(&cmd->args, \"--name-hash-version=%d\", args->name_hash_version);\n+\tif (args->path_walk)\n+\t\tstrvec_pushf(&cmd->args, \"--path-walk\");\n+\tif (args->local)\n+\t\tstrvec_push(&cmd->args,  \"--local\");\n+\tif (args->quiet)\n+\t\tstrvec_push(&cmd->args,  \"--quiet\");\n+\tif (args->delta_base_offset)\n+\t\tstrvec_push(&cmd->args,  \"--delta-base-offset\");\n+\tstrvec_push(&cmd->args, out);\n+\tcmd->git_cmd = 1;\n+\tcmd->out = -1;\n+}\n \n void pack_objects_args_release(struct pack_objects_args *args)\n {\ndiff --git a/repack.h b/repack.h\nindex 12632d7fec..3f7ec20735 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -21,6 +21,11 @@ struct pack_objects_args {\n \n #define PACK_OBJECTS_ARGS_INIT { .delta_base_offset = 1 }\n \n+struct child_process;\n+\n+void prepare_pack_objects(struct child_process *cmd,\n+\t\t\t  const struct pack_objects_args *args,\n+\t\t\t  const char *out);\n void pack_objects_args_release(struct pack_objects_args *args);\n \n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528885","messageId":"a9767dc42dca66648152d966560cd14699bda81e.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 15/49] builtin/repack.c: rename many 'struct existing_packs' functions","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:04Z","receivedAt":"2025-10-15T22:28:06Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Rename many of the 'struct existing_packs'-related functions according\nto the convention introduced in and described by 541204aabe\n(Documentation: document naming schema for structs and their functions,\n2024-07-30).\n\nNote that some functions which operate over an individual entry in the\nlist of existing packs are prefixed with \"existing_pack_\" instead of the\nplural form.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 66 +++++++++++++++++++++++++-----------------------\n 1 file changed, 34 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex ff93654cfe..f82e6c3930 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -121,39 +121,39 @@ struct existing_packs {\n \t.cruft_packs = STRING_LIST_INIT_DUP, \\\n }\n \n-static int has_existing_non_kept_packs(const struct existing_packs *existing)\n+static int existing_packs_has_non_kept(const struct existing_packs *existing)\n {\n \treturn existing->non_kept_packs.nr || existing->cruft_packs.nr;\n }\n \n-static void pack_mark_for_deletion(struct string_list_item *item)\n+static void existing_pack_mark_for_deletion(struct string_list_item *item)\n {\n \titem->util = (void*)((uintptr_t)item->util | DELETE_PACK);\n }\n \n-static void pack_unmark_for_deletion(struct string_list_item *item)\n+static void existing_pack_unmark_for_deletion(struct string_list_item *item)\n {\n \titem->util = (void*)((uintptr_t)item->util & ~DELETE_PACK);\n }\n \n-static int pack_is_marked_for_deletion(struct string_list_item *item)\n+static int existing_pack_is_marked_for_deletion(struct string_list_item *item)\n {\n \treturn (uintptr_t)item->util & DELETE_PACK;\n }\n \n-static void pack_mark_retained(struct string_list_item *item)\n+static void existing_packs_mark_retained(struct string_list_item *item)\n {\n \titem->util = (void*)((uintptr_t)item->util | RETAIN_PACK);\n }\n \n-static int pack_is_retained(struct string_list_item *item)\n+static int existing_pack_is_retained(struct string_list_item *item)\n {\n \treturn (uintptr_t)item->util & RETAIN_PACK;\n }\n \n-static void mark_packs_for_deletion_1(const struct git_hash_algo *algop,\n-\t\t\t\t      struct string_list *names,\n-\t\t\t\t      struct string_list *list)\n+static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop,\n+\t\t\t\t\t       struct string_list *names,\n+\t\t\t\t\t       struct string_list *list)\n {\n \tstruct string_list_item *item;\n \tconst int hexsz = algop->hexsz;\n@@ -165,8 +165,8 @@ static void mark_packs_for_deletion_1(const struct git_hash_algo *algop,\n \t\t\tcontinue;\n \t\tsha1 = item->string + len - hexsz;\n \n-\t\tif (pack_is_retained(item)) {\n-\t\t\tpack_unmark_for_deletion(item);\n+\t\tif (existing_pack_is_retained(item)) {\n+\t\t\texisting_pack_unmark_for_deletion(item);\n \t\t} else if (!string_list_has_string(names, sha1)) {\n \t\t\t/*\n \t\t\t * Mark this pack for deletion, which ensures\n@@ -175,13 +175,13 @@ static void mark_packs_for_deletion_1(const struct git_hash_algo *algop,\n \t\t\t * will actually delete this pack (if `-d` was\n \t\t\t * given).\n \t\t\t */\n-\t\t\tpack_mark_for_deletion(item);\n+\t\t\texisting_pack_mark_for_deletion(item);\n \t\t}\n \t}\n }\n \n-static void retain_cruft_pack(struct existing_packs *existing,\n-\t\t\t      struct packed_git *cruft)\n+static void existing_packs_retain_cruft(struct existing_packs *existing,\n+\t\t\t\t\tstruct packed_git *cruft)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct string_list_item *item;\n@@ -193,17 +193,19 @@ static void retain_cruft_pack(struct existing_packs *existing,\n \tif (!item)\n \t\tBUG(\"could not find cruft pack '%s'\", pack_basename(cruft));\n \n-\tpack_mark_retained(item);\n+\texisting_packs_mark_retained(item);\n \tstrbuf_release(&buf);\n }\n \n-static void mark_packs_for_deletion(struct existing_packs *existing,\n-\t\t\t\t    struct string_list *names)\n+static void existing_packs_mark_for_deletion(struct existing_packs *existing,\n+\t\t\t\t\t     struct string_list *names)\n \n {\n \tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n-\tmark_packs_for_deletion_1(algop, names, &existing->non_kept_packs);\n-\tmark_packs_for_deletion_1(algop, names, &existing->cruft_packs);\n+\texisting_packs_mark_for_deletion_1(algop, names,\n+\t\t\t\t\t   &existing->non_kept_packs);\n+\texisting_packs_mark_for_deletion_1(algop, names,\n+\t\t\t\t\t   &existing->cruft_packs);\n }\n \n static void remove_redundant_pack(struct repository *repo,\n@@ -225,13 +227,13 @@ static void remove_redundant_packs_1(struct repository *repo,\n {\n \tstruct string_list_item *item;\n \tfor_each_string_list_item(item, packs) {\n-\t\tif (!pack_is_marked_for_deletion(item))\n+\t\tif (!existing_pack_is_marked_for_deletion(item))\n \t\t\tcontinue;\n \t\tremove_redundant_pack(repo, packdir, item->string);\n \t}\n }\n \n-static void remove_redundant_existing_packs(struct existing_packs *existing)\n+static void existing_packs_remove_redundant(struct existing_packs *existing)\n {\n \tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs);\n \tremove_redundant_packs_1(existing->repo, &existing->cruft_packs);\n@@ -250,7 +252,7 @@ static void existing_packs_release(struct existing_packs *existing)\n  * .keep file or not.  Packs without a .keep file are not to be kept\n  * if we are going to pack everything into one file.\n  */\n-static void collect_pack_filenames(struct existing_packs *existing,\n+static void existing_packs_collect(struct existing_packs *existing,\n \t\t\t\t   const struct string_list *extra_keep)\n {\n \tstruct packfile_store *packs = existing->repo->objects->packfiles;\n@@ -721,7 +723,7 @@ static int midx_has_unknown_packs(char **midx_pack_names,\n \n \t\t\titem = string_list_lookup(&existing->non_kept_packs,\n \t\t\t\t\t\t  pack_name);\n-\t\t\tif (item && !pack_is_marked_for_deletion(item))\n+\t\t\tif (item && !existing_pack_is_marked_for_deletion(item))\n \t\t\t\tcontinue;\n \t\t}\n \n@@ -851,7 +853,7 @@ static void midx_included_packs(struct string_list *include,\n \t\t}\n \t} else {\n \t\tfor_each_string_list_item(item, &existing->non_kept_packs) {\n-\t\t\tif (pack_is_marked_for_deletion(item))\n+\t\t\tif (existing_pack_is_marked_for_deletion(item))\n \t\t\t\tcontinue;\n \n \t\t\tstrbuf_reset(&buf);\n@@ -888,10 +890,10 @@ static void midx_included_packs(struct string_list *include,\n \t\t\t * --geometric case, but doing so is unnecessary\n \t\t\t *  since no packs are marked as pending\n \t\t\t *  deletion (since we only call\n-\t\t\t *  `mark_packs_for_deletion()` when doing an\n-\t\t\t *  all-into-one repack).\n+\t\t\t *  `existing_packs_mark_for_deletion()` when\n+\t\t\t *  doing an all-into-one repack).\n \t\t\t */\n-\t\t\tif (pack_is_marked_for_deletion(item))\n+\t\t\tif (existing_pack_is_marked_for_deletion(item))\n \t\t\t\tcontinue;\n \n \t\t\tstrbuf_reset(&buf);\n@@ -1128,7 +1130,7 @@ static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n \t\tif (p->pack_size < combine_cruft_below_size) {\n \t\t\tfprintf(in, \"-%s\\n\", pack_basename(p));\n \t\t} else {\n-\t\t\tretain_cruft_pack(existing, p);\n+\t\t\texisting_packs_retain_cruft(existing, p);\n \t\t\tfprintf(in, \"%s\\n\", pack_basename(p));\n \t\t}\n \t}\n@@ -1382,7 +1384,7 @@ int cmd_repack(int argc,\n \tpacktmp = mkpathdup(\"%s/%s\", packdir, packtmp_name);\n \n \texisting.repo = repo;\n-\tcollect_pack_filenames(&existing, &keep_pack_list);\n+\texisting_packs_collect(&existing, &keep_pack_list);\n \n \tif (geometry.split_factor) {\n \t\tif (pack_everything)\n@@ -1431,7 +1433,7 @@ int cmd_repack(int argc,\n \tif (pack_everything & ALL_INTO_ONE) {\n \t\trepack_promisor_objects(repo, &po_args, &names);\n \n-\t\tif (has_existing_non_kept_packs(&existing) &&\n+\t\tif (existing_packs_has_non_kept(&existing) &&\n \t\t    delete_redundant &&\n \t\t    !(pack_everything & PACK_CRUFT)) {\n \t\t\tfor_each_string_list_item(item, &names) {\n@@ -1647,7 +1649,7 @@ int cmd_repack(int argc,\n \t/* End of pack replacement. */\n \n \tif (delete_redundant && pack_everything & ALL_INTO_ONE)\n-\t\tmark_packs_for_deletion(&existing, &names);\n+\t\texisting_packs_mark_for_deletion(&existing, &names);\n \n \tif (write_midx) {\n \t\tstruct string_list include = STRING_LIST_INIT_DUP;\n@@ -1671,7 +1673,7 @@ int cmd_repack(int argc,\n \n \tif (delete_redundant) {\n \t\tint opts = 0;\n-\t\tremove_redundant_existing_packs(&existing);\n+\t\texisting_packs_remove_redundant(&existing);\n \n \t\tif (geometry.split_factor)\n \t\t\tgeometry_remove_redundant_packs(&geometry, &names,\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528886","messageId":"4edbcb5893a927e6c68a981baf8d409e8bc77084.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 16/49] repack: remove 'remove_redundant_pack' from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:07Z","receivedAt":"2025-10-15T22:28:09Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Extract \"remove_redundant_pack()\" as generic repack-related\nfunctionality by moving its implementation to the repack.[ch]\ncompilation unit.\n\nThis is a prerequisite to moving the \"existing_packs\" API, which is one\nof the callers of this function. (The remaining caller in the pack\ngeometry code will eventually move to its own compilation unit as well,\nand will likewise rely on this function.)\n\nWhile moving it over, prefix the function name with \"repack_\" to\nindicate that it belongs to the repack-subsystem.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 18 ++----------------\n repack.c         | 18 ++++++++++++++++++\n repack.h         |  3 +++\n 3 files changed, 23 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex f82e6c3930..31137cf711 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -208,20 +208,6 @@ static void existing_packs_mark_for_deletion(struct existing_packs *existing,\n \t\t\t\t\t   &existing->cruft_packs);\n }\n \n-static void remove_redundant_pack(struct repository *repo,\n-\t\t\t\t  const char *dir_name, const char *base_name)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tstruct odb_source *source = repo->objects->sources;\n-\tstruct multi_pack_index *m = get_multi_pack_index(source);\n-\tstrbuf_addf(&buf, \"%s.pack\", base_name);\n-\tif (m && source->local && midx_contains_pack(m, buf.buf))\n-\t\tclear_midx_file(repo);\n-\tstrbuf_insertf(&buf, 0, \"%s/\", dir_name);\n-\tunlink_pack_path(buf.buf, 1);\n-\tstrbuf_release(&buf);\n-}\n-\n static void remove_redundant_packs_1(struct repository *repo,\n \t\t\t\t     struct string_list *packs)\n {\n@@ -229,7 +215,7 @@ static void remove_redundant_packs_1(struct repository *repo,\n \tfor_each_string_list_item(item, packs) {\n \t\tif (!existing_pack_is_marked_for_deletion(item))\n \t\t\tcontinue;\n-\t\tremove_redundant_pack(repo, packdir, item->string);\n+\t\trepack_remove_redundant_pack(repo, packdir, item->string);\n \t}\n }\n \n@@ -652,7 +638,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n \t\t    (string_list_has_string(&existing->kept_packs, buf.buf)))\n \t\t\tcontinue;\n \n-\t\tremove_redundant_pack(existing->repo, packdir, buf.buf);\n+\t\trepack_remove_redundant_pack(existing->repo, packdir, buf.buf);\n \t}\n \n \tstrbuf_release(&buf);\ndiff --git a/repack.c b/repack.c\nindex 91b6e1cc09..3aaa351b5b 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -1,5 +1,9 @@\n #include \"git-compat-util.h\"\n+#include \"midx.h\"\n+#include \"odb.h\"\n+#include \"packfile.h\"\n #include \"repack.h\"\n+#include \"repository.h\"\n #include \"run-command.h\"\n \n void prepare_pack_objects(struct child_process *cmd,\n@@ -44,3 +48,17 @@ void pack_objects_args_release(struct pack_objects_args *args)\n \tfree(args->threads);\n \tlist_objects_filter_release(&args->filter_options);\n }\n+\n+void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n+\t\t\t\t  const char *base_name)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct odb_source *source = repo->objects->sources;\n+\tstruct multi_pack_index *m = get_multi_pack_index(source);\n+\tstrbuf_addf(&buf, \"%s.pack\", base_name);\n+\tif (m && source->local && midx_contains_pack(m, buf.buf))\n+\t\tclear_midx_file(repo);\n+\tstrbuf_insertf(&buf, 0, \"%s/\", dir_name);\n+\tunlink_pack_path(buf.buf, 1);\n+\tstrbuf_release(&buf);\n+}\ndiff --git a/repack.h b/repack.h\nindex 3f7ec20735..a62bfa2ff9 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -28,4 +28,7 @@ void prepare_pack_objects(struct child_process *cmd,\n \t\t\t  const char *out);\n void pack_objects_args_release(struct pack_objects_args *args);\n \n+void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n+\t\t\t\t  const char *base_name);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528887","messageId":"4e2a152ebb3fdc592a60e7e68232af056a51113c.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 17/49] builtin/repack.c: pass \"packdir\" when removing packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:10Z","receivedAt":"2025-10-15T22:28:12Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"builtin/repack.c defines a static \"packdir\" to instruct pack-objects on\nwhere to write any new packfiles. This is also the directory scanned\nwhen removing any packfiles which were made redundant by the latest\nrepack.\n\nPrepare to move the \"existing_packs_remove_redundant\" function to its\nown compilation unit by passing in this information as a parameter to\nthat function.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 14 +++++++++-----\n 1 file changed, 9 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 31137cf711..c5a88eda12 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -209,7 +209,8 @@ static void existing_packs_mark_for_deletion(struct existing_packs *existing,\n }\n \n static void remove_redundant_packs_1(struct repository *repo,\n-\t\t\t\t     struct string_list *packs)\n+\t\t\t\t     struct string_list *packs,\n+\t\t\t\t     const char *packdir)\n {\n \tstruct string_list_item *item;\n \tfor_each_string_list_item(item, packs) {\n@@ -219,10 +220,13 @@ static void remove_redundant_packs_1(struct repository *repo,\n \t}\n }\n \n-static void existing_packs_remove_redundant(struct existing_packs *existing)\n+static void existing_packs_remove_redundant(struct existing_packs *existing,\n+\t\t\t\t\t    const char *packdir)\n {\n-\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs);\n-\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs);\n+\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs,\n+\t\t\t\t packdir);\n+\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs,\n+\t\t\t\t packdir);\n }\n \n static void existing_packs_release(struct existing_packs *existing)\n@@ -1659,7 +1663,7 @@ int cmd_repack(int argc,\n \n \tif (delete_redundant) {\n \t\tint opts = 0;\n-\t\texisting_packs_remove_redundant(&existing);\n+\t\texisting_packs_remove_redundant(&existing, packdir);\n \n \t\tif (geometry.split_factor)\n \t\t\tgeometry_remove_redundant_packs(&geometry, &names,\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528888","messageId":"46aa154b762ad68c67117d4072867a62c1144d4a.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 18/49] builtin/repack.c: avoid unnecessary numeric casts in existing_packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:12Z","receivedAt":"2025-10-15T22:28:15Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"There are a couple of spots that cause warnings within the\nexisting_packs API without DISABLE_SIGN_COMPARE_WARNINGS under\nDEVELOPER=1 mode.\n\nIn both cases, we have int values that are being compared against size_t\nones. Neither of these two cases are incorrect, and the cast is\ncompletely OK in practice. But both are unnecessary, since:\n\n - in existing_packs_mark_for_deletion_1(), 'hexsz' should be defined as\n   a size_t anyway, since algop->hexsz is.\n\n - in existing_packs_collect(), 'i' should be defined as a size_t since\n   it is counting up to the value of a string_list's 'nr' field.\n\n(This patch is a little bit of noise, but I would rather see us squelch\nthese warnings ahead of moving the existing_packs API into a separate\ncompilation unit to avoid having to define DISABLE_SIGN_COMPARE_WARNINGS\nin repack.c.)\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex c5a88eda12..e13943b637 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -156,7 +156,7 @@ static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop\n \t\t\t\t\t       struct string_list *list)\n {\n \tstruct string_list_item *item;\n-\tconst int hexsz = algop->hexsz;\n+\tconst size_t hexsz = algop->hexsz;\n \n \tfor_each_string_list_item(item, list) {\n \t\tchar *sha1;\n@@ -250,7 +250,7 @@ static void existing_packs_collect(struct existing_packs *existing,\n \tstruct strbuf buf = STRBUF_INIT;\n \n \tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n-\t\tint i;\n+\t\tsize_t i;\n \t\tconst char *base;\n \n \t\tif (!p->pack_local)\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528889","messageId":"0c6dea280049943f71f42fb33221057469934305.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 19/49] repack: remove 'existing_packs' API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:15Z","receivedAt":"2025-10-15T22:28:17Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The repack builtin defines an API for keeping track of which packs\nwere found in the repository at the beginning of the repack operation.\nThis is used to classify what state a pack was in (kept, non-kept, or\ncruft), and is also used to mark which packs to delete (or keep) at the\nend of a repack operation.\n\nNow that the prerequisite refactoring is complete, this API is isolated\nenough that it can be moved out to repack.[ch] and removed from the\nbuiltin entirely.\n\nAs a result, some of its functions become static within repack.c,\ncleaning up the visible API.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 173 -----------------------------------------------\n repack.c         | 157 ++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  35 ++++++++++\n 3 files changed, 192 insertions(+), 173 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex e13943b637..a168c88791 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -3,7 +3,6 @@\n \n #include \"builtin.h\"\n #include \"config.h\"\n-#include \"dir.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n@@ -108,178 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-struct existing_packs {\n-\tstruct repository *repo;\n-\tstruct string_list kept_packs;\n-\tstruct string_list non_kept_packs;\n-\tstruct string_list cruft_packs;\n-};\n-\n-#define EXISTING_PACKS_INIT { \\\n-\t.kept_packs = STRING_LIST_INIT_DUP, \\\n-\t.non_kept_packs = STRING_LIST_INIT_DUP, \\\n-\t.cruft_packs = STRING_LIST_INIT_DUP, \\\n-}\n-\n-static int existing_packs_has_non_kept(const struct existing_packs *existing)\n-{\n-\treturn existing->non_kept_packs.nr || existing->cruft_packs.nr;\n-}\n-\n-static void existing_pack_mark_for_deletion(struct string_list_item *item)\n-{\n-\titem->util = (void*)((uintptr_t)item->util | DELETE_PACK);\n-}\n-\n-static void existing_pack_unmark_for_deletion(struct string_list_item *item)\n-{\n-\titem->util = (void*)((uintptr_t)item->util & ~DELETE_PACK);\n-}\n-\n-static int existing_pack_is_marked_for_deletion(struct string_list_item *item)\n-{\n-\treturn (uintptr_t)item->util & DELETE_PACK;\n-}\n-\n-static void existing_packs_mark_retained(struct string_list_item *item)\n-{\n-\titem->util = (void*)((uintptr_t)item->util | RETAIN_PACK);\n-}\n-\n-static int existing_pack_is_retained(struct string_list_item *item)\n-{\n-\treturn (uintptr_t)item->util & RETAIN_PACK;\n-}\n-\n-static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop,\n-\t\t\t\t\t       struct string_list *names,\n-\t\t\t\t\t       struct string_list *list)\n-{\n-\tstruct string_list_item *item;\n-\tconst size_t hexsz = algop->hexsz;\n-\n-\tfor_each_string_list_item(item, list) {\n-\t\tchar *sha1;\n-\t\tsize_t len = strlen(item->string);\n-\t\tif (len < hexsz)\n-\t\t\tcontinue;\n-\t\tsha1 = item->string + len - hexsz;\n-\n-\t\tif (existing_pack_is_retained(item)) {\n-\t\t\texisting_pack_unmark_for_deletion(item);\n-\t\t} else if (!string_list_has_string(names, sha1)) {\n-\t\t\t/*\n-\t\t\t * Mark this pack for deletion, which ensures\n-\t\t\t * that this pack won't be included in a MIDX\n-\t\t\t * (if `--write-midx` was given) and that we\n-\t\t\t * will actually delete this pack (if `-d` was\n-\t\t\t * given).\n-\t\t\t */\n-\t\t\texisting_pack_mark_for_deletion(item);\n-\t\t}\n-\t}\n-}\n-\n-static void existing_packs_retain_cruft(struct existing_packs *existing,\n-\t\t\t\t\tstruct packed_git *cruft)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tstruct string_list_item *item;\n-\n-\tstrbuf_addstr(&buf, pack_basename(cruft));\n-\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\titem = string_list_lookup(&existing->cruft_packs, buf.buf);\n-\tif (!item)\n-\t\tBUG(\"could not find cruft pack '%s'\", pack_basename(cruft));\n-\n-\texisting_packs_mark_retained(item);\n-\tstrbuf_release(&buf);\n-}\n-\n-static void existing_packs_mark_for_deletion(struct existing_packs *existing,\n-\t\t\t\t\t     struct string_list *names)\n-\n-{\n-\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n-\texisting_packs_mark_for_deletion_1(algop, names,\n-\t\t\t\t\t   &existing->non_kept_packs);\n-\texisting_packs_mark_for_deletion_1(algop, names,\n-\t\t\t\t\t   &existing->cruft_packs);\n-}\n-\n-static void remove_redundant_packs_1(struct repository *repo,\n-\t\t\t\t     struct string_list *packs,\n-\t\t\t\t     const char *packdir)\n-{\n-\tstruct string_list_item *item;\n-\tfor_each_string_list_item(item, packs) {\n-\t\tif (!existing_pack_is_marked_for_deletion(item))\n-\t\t\tcontinue;\n-\t\trepack_remove_redundant_pack(repo, packdir, item->string);\n-\t}\n-}\n-\n-static void existing_packs_remove_redundant(struct existing_packs *existing,\n-\t\t\t\t\t    const char *packdir)\n-{\n-\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs,\n-\t\t\t\t packdir);\n-\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs,\n-\t\t\t\t packdir);\n-}\n-\n-static void existing_packs_release(struct existing_packs *existing)\n-{\n-\tstring_list_clear(&existing->kept_packs, 0);\n-\tstring_list_clear(&existing->non_kept_packs, 0);\n-\tstring_list_clear(&existing->cruft_packs, 0);\n-}\n-\n-/*\n- * Adds all packs hex strings (pack-$HASH) to either packs->non_kept\n- * or packs->kept based on whether each pack has a corresponding\n- * .keep file or not.  Packs without a .keep file are not to be kept\n- * if we are going to pack everything into one file.\n- */\n-static void existing_packs_collect(struct existing_packs *existing,\n-\t\t\t\t   const struct string_list *extra_keep)\n-{\n-\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n-\tstruct packed_git *p;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n-\t\tsize_t i;\n-\t\tconst char *base;\n-\n-\t\tif (!p->pack_local)\n-\t\t\tcontinue;\n-\n-\t\tbase = pack_basename(p);\n-\n-\t\tfor (i = 0; i < extra_keep->nr; i++)\n-\t\t\tif (!fspathcmp(base, extra_keep->items[i].string))\n-\t\t\t\tbreak;\n-\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addstr(&buf, base);\n-\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\t\tif ((extra_keep->nr > 0 && i < extra_keep->nr) || p->pack_keep)\n-\t\t\tstring_list_append(&existing->kept_packs, buf.buf);\n-\t\telse if (p->is_cruft)\n-\t\t\tstring_list_append(&existing->cruft_packs, buf.buf);\n-\t\telse\n-\t\t\tstring_list_append(&existing->non_kept_packs, buf.buf);\n-\t}\n-\n-\tstring_list_sort(&existing->kept_packs);\n-\tstring_list_sort(&existing->non_kept_packs);\n-\tstring_list_sort(&existing->cruft_packs);\n-\tstrbuf_release(&buf);\n-}\n-\n struct write_oid_context {\n \tstruct child_process *cmd;\n \tconst struct git_hash_algo *algop;\ndiff --git a/repack.c b/repack.c\nindex 3aaa351b5b..9182e1c50b 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -1,4 +1,5 @@\n #include \"git-compat-util.h\"\n+#include \"dir.h\"\n #include \"midx.h\"\n #include \"odb.h\"\n #include \"packfile.h\"\n@@ -62,3 +63,159 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \tunlink_pack_path(buf.buf, 1);\n \tstrbuf_release(&buf);\n }\n+\n+#define DELETE_PACK 1\n+#define RETAIN_PACK 2\n+\n+void existing_packs_collect(struct existing_packs *existing,\n+\t\t\t    const struct string_list *extra_keep)\n+{\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n+\tstruct packed_git *p;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n+\t\tsize_t i;\n+\t\tconst char *base;\n+\n+\t\tif (!p->pack_local)\n+\t\t\tcontinue;\n+\n+\t\tbase = pack_basename(p);\n+\n+\t\tfor (i = 0; i < extra_keep->nr; i++)\n+\t\t\tif (!fspathcmp(base, extra_keep->items[i].string))\n+\t\t\t\tbreak;\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, base);\n+\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\t\tif ((extra_keep->nr > 0 && i < extra_keep->nr) || p->pack_keep)\n+\t\t\tstring_list_append(&existing->kept_packs, buf.buf);\n+\t\telse if (p->is_cruft)\n+\t\t\tstring_list_append(&existing->cruft_packs, buf.buf);\n+\t\telse\n+\t\t\tstring_list_append(&existing->non_kept_packs, buf.buf);\n+\t}\n+\n+\tstring_list_sort(&existing->kept_packs);\n+\tstring_list_sort(&existing->non_kept_packs);\n+\tstring_list_sort(&existing->cruft_packs);\n+\tstrbuf_release(&buf);\n+}\n+\n+int existing_packs_has_non_kept(const struct existing_packs *existing)\n+{\n+\treturn existing->non_kept_packs.nr || existing->cruft_packs.nr;\n+}\n+\n+static void existing_pack_mark_for_deletion(struct string_list_item *item)\n+{\n+\titem->util = (void*)((uintptr_t)item->util | DELETE_PACK);\n+}\n+\n+static void existing_pack_unmark_for_deletion(struct string_list_item *item)\n+{\n+\titem->util = (void*)((uintptr_t)item->util & ~DELETE_PACK);\n+}\n+\n+int existing_pack_is_marked_for_deletion(struct string_list_item *item)\n+{\n+\treturn (uintptr_t)item->util & DELETE_PACK;\n+}\n+\n+static void existing_packs_mark_retained(struct string_list_item *item)\n+{\n+\titem->util = (void*)((uintptr_t)item->util | RETAIN_PACK);\n+}\n+\n+static int existing_pack_is_retained(struct string_list_item *item)\n+{\n+\treturn (uintptr_t)item->util & RETAIN_PACK;\n+}\n+\n+static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop,\n+\t\t\t\t\t       struct string_list *names,\n+\t\t\t\t\t       struct string_list *list)\n+{\n+\tstruct string_list_item *item;\n+\tconst size_t hexsz = algop->hexsz;\n+\n+\tfor_each_string_list_item(item, list) {\n+\t\tchar *sha1;\n+\t\tsize_t len = strlen(item->string);\n+\t\tif (len < hexsz)\n+\t\t\tcontinue;\n+\t\tsha1 = item->string + len - hexsz;\n+\n+\t\tif (existing_pack_is_retained(item)) {\n+\t\t\texisting_pack_unmark_for_deletion(item);\n+\t\t} else if (!string_list_has_string(names, sha1)) {\n+\t\t\t/*\n+\t\t\t * Mark this pack for deletion, which ensures\n+\t\t\t * that this pack won't be included in a MIDX\n+\t\t\t * (if `--write-midx` was given) and that we\n+\t\t\t * will actually delete this pack (if `-d` was\n+\t\t\t * given).\n+\t\t\t */\n+\t\t\texisting_pack_mark_for_deletion(item);\n+\t\t}\n+\t}\n+}\n+\n+void existing_packs_retain_cruft(struct existing_packs *existing,\n+\t\t\t\t struct packed_git *cruft)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\n+\tstrbuf_addstr(&buf, pack_basename(cruft));\n+\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\titem = string_list_lookup(&existing->cruft_packs, buf.buf);\n+\tif (!item)\n+\t\tBUG(\"could not find cruft pack '%s'\", pack_basename(cruft));\n+\n+\texisting_packs_mark_retained(item);\n+\tstrbuf_release(&buf);\n+}\n+\n+void existing_packs_mark_for_deletion(struct existing_packs *existing,\n+\t\t\t\t      struct string_list *names)\n+\n+{\n+\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n+\texisting_packs_mark_for_deletion_1(algop, names,\n+\t\t\t\t\t   &existing->non_kept_packs);\n+\texisting_packs_mark_for_deletion_1(algop, names,\n+\t\t\t\t\t   &existing->cruft_packs);\n+}\n+\n+static void remove_redundant_packs_1(struct repository *repo,\n+\t\t\t\t     struct string_list *packs,\n+\t\t\t\t     const char *packdir)\n+{\n+\tstruct string_list_item *item;\n+\tfor_each_string_list_item(item, packs) {\n+\t\tif (!existing_pack_is_marked_for_deletion(item))\n+\t\t\tcontinue;\n+\t\trepack_remove_redundant_pack(repo, packdir, item->string);\n+\t}\n+}\n+\n+void existing_packs_remove_redundant(struct existing_packs *existing,\n+\t\t\t\t     const char *packdir)\n+{\n+\tremove_redundant_packs_1(existing->repo, &existing->non_kept_packs,\n+\t\t\t\t packdir);\n+\tremove_redundant_packs_1(existing->repo, &existing->cruft_packs,\n+\t\t\t\t packdir);\n+}\n+\n+void existing_packs_release(struct existing_packs *existing)\n+{\n+\tstring_list_clear(&existing->kept_packs, 0);\n+\tstring_list_clear(&existing->non_kept_packs, 0);\n+\tstring_list_clear(&existing->cruft_packs, 0);\n+}\ndiff --git a/repack.h b/repack.h\nindex a62bfa2ff9..19796e2243 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -2,6 +2,7 @@\n #define REPACK_H\n \n #include \"list-objects-filter-options.h\"\n+#include \"string-list.h\"\n \n struct pack_objects_args {\n \tchar *window;\n@@ -31,4 +32,38 @@ void pack_objects_args_release(struct pack_objects_args *args);\n void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \t\t\t\t  const char *base_name);\n \n+struct repository;\n+struct packed_git;\n+\n+struct existing_packs {\n+\tstruct repository *repo;\n+\tstruct string_list kept_packs;\n+\tstruct string_list non_kept_packs;\n+\tstruct string_list cruft_packs;\n+};\n+\n+#define EXISTING_PACKS_INIT { \\\n+\t.kept_packs = STRING_LIST_INIT_DUP, \\\n+\t.non_kept_packs = STRING_LIST_INIT_DUP, \\\n+\t.cruft_packs = STRING_LIST_INIT_DUP, \\\n+}\n+\n+/*\n+ * Adds all packs hex strings (pack-$HASH) to either packs->non_kept\n+ * or packs->kept based on whether each pack has a corresponding\n+ * .keep file or not.  Packs without a .keep file are not to be kept\n+ * if we are going to pack everything into one file.\n+ */\n+void existing_packs_collect(struct existing_packs *existing,\n+\t\t\t    const struct string_list *extra_keep);\n+int existing_packs_has_non_kept(const struct existing_packs *existing);\n+int existing_pack_is_marked_for_deletion(struct string_list_item *item);\n+void existing_packs_retain_cruft(struct existing_packs *existing,\n+\t\t\t\t struct packed_git *cruft);\n+void existing_packs_mark_for_deletion(struct existing_packs *existing,\n+\t\t\t\t      struct string_list *names);\n+void existing_packs_remove_redundant(struct existing_packs *existing,\n+\t\t\t\t     const char *packdir);\n+void existing_packs_release(struct existing_packs *existing);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528890","messageId":"53d5b1053f2075b788804c71771d3123b6158cd3.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 20/49] builtin/repack.c: rename \"struct generated_pack_data\"","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:18Z","receivedAt":"2025-10-15T22:28:20Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The name \"generated_pack_data\" is somewhat redundant, since the contents\nof the struct *is* the data associated with the generated pack.\n\nRename the structure to just \"generated_pack\", resulting in less awkward\nfunction names, like \"generated_pack_has_ext()\" which is preferable to\n\"generated_pack_data_has_ext()\".\n\nRename a few related functions to align with the convention that\nfunctions to do with a struct \"S\" should be prefixed with \"S_\".\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 32 ++++++++++++++++----------------\n 1 file changed, 16 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a168c88791..a4d80b6b04 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -146,15 +146,15 @@ static struct {\n \t{\".idx\"},\n };\n \n-struct generated_pack_data {\n+struct generated_pack {\n \tstruct tempfile *tempfiles[ARRAY_SIZE(exts)];\n };\n \n-static struct generated_pack_data *populate_pack_exts(const char *name)\n+static struct generated_pack *generated_pack_populate(const char *name)\n {\n \tstruct stat statbuf;\n \tstruct strbuf path = STRBUF_INIT;\n-\tstruct generated_pack_data *data = xcalloc(1, sizeof(*data));\n+\tstruct generated_pack *pack = xcalloc(1, sizeof(*pack));\n \tint i;\n \n \tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n@@ -164,21 +164,21 @@ static struct generated_pack_data *populate_pack_exts(const char *name)\n \t\tif (stat(path.buf, &statbuf))\n \t\t\tcontinue;\n \n-\t\tdata->tempfiles[i] = register_tempfile(path.buf);\n+\t\tpack->tempfiles[i] = register_tempfile(path.buf);\n \t}\n \n \tstrbuf_release(&path);\n-\treturn data;\n+\treturn pack;\n }\n \n-static int has_pack_ext(const struct generated_pack_data *data,\n-\t\t\tconst char *ext)\n+static int generated_pack_has_ext(const struct generated_pack *pack,\n+\t\t\t\t  const char *ext)\n {\n \tint i;\n \tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n \t\tif (strcmp(exts[i].name, ext))\n \t\t\tcontinue;\n-\t\treturn !!data->tempfiles[i];\n+\t\treturn !!pack->tempfiles[i];\n \t}\n \tBUG(\"unknown pack extension: '%s'\", ext);\n }\n@@ -239,7 +239,7 @@ static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t\t  line.buf);\n \t\twrite_promisor_file(promisor_name, NULL, 0);\n \n-\t\titem->util = populate_pack_exts(item->string);\n+\t\titem->util = generated_pack_populate(item->string);\n \n \t\tfree(promisor_name);\n \t}\n@@ -780,8 +780,8 @@ static int write_midx_included_packs(struct string_list *include,\n \t\t * will suffice, so pick the first one.)\n \t\t */\n \t\tfor_each_string_list_item(item, names) {\n-\t\t\tstruct generated_pack_data *data = item->util;\n-\t\t\tif (has_pack_ext(data, \".mtimes\"))\n+\t\t\tstruct generated_pack *pack = item->util;\n+\t\t\tif (generated_pack_has_ext(pack, \".mtimes\"))\n \t\t\t\tcontinue;\n \n \t\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=pack-%s.pack\",\n@@ -864,7 +864,7 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t */\n \t\tif (local) {\n \t\t\titem = string_list_append(names, line.buf);\n-\t\t\titem->util = populate_pack_exts(line.buf);\n+\t\t\titem->util = generated_pack_populate(line.buf);\n \t\t}\n \t}\n \tfclose(out);\n@@ -1435,7 +1435,7 @@ int cmd_repack(int argc,\n \t * Ok we have prepared all new packfiles.\n \t */\n \tfor_each_string_list_item(item, &names) {\n-\t\tstruct generated_pack_data *data = item->util;\n+\t\tstruct generated_pack *pack = item->util;\n \n \t\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n \t\t\tchar *fname;\n@@ -1443,8 +1443,8 @@ int cmd_repack(int argc,\n \t\t\tfname = mkpathdup(\"%s/pack-%s%s\",\n \t\t\t\t\tpackdir, item->string, exts[ext].name);\n \n-\t\t\tif (data->tempfiles[ext]) {\n-\t\t\t\tconst char *fname_old = get_tempfile_path(data->tempfiles[ext]);\n+\t\t\tif (pack->tempfiles[ext]) {\n+\t\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n \t\t\t\tstruct stat statbuffer;\n \n \t\t\t\tif (!stat(fname_old, &statbuffer)) {\n@@ -1452,7 +1452,7 @@ int cmd_repack(int argc,\n \t\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n \t\t\t\t}\n \n-\t\t\t\tif (rename_tempfile(&data->tempfiles[ext], fname))\n+\t\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n \t\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"), fname);\n \t\t\t} else if (!exts[ext].optional)\n \t\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528891","messageId":"196b92d05253150e9cf69d83cf64cc74fd1bf523.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 21/49] builtin/repack.c: factor out \"generated_pack_install\"","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:20Z","receivedAt":"2025-10-15T22:28:23Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Once all new packs are known to exist, 'repack' installs their contents\nfrom their temporary location into their permanent one. This is a\nsemi-involved procedure for each pack, since for each extension (e.g.,\n\".idx\", \".pack\", \".mtimes\", and so on) we have to either:\n\n - adjust the filemode of the temporary file before renaming it into\n   place, or\n\n - die() if we are missing a non-optional extension, or\n\n - unlink() any existing file for extensions that we did not generate\n   (e.g., if a non-cruft pack we generated was identical to, say, a\n   cruft pack which existed at the beginning of the process, we have to\n   remove the \".mtimes\" file).\n\nExtract this procedure into its own function, and call it\n\"generated_pack_install\"(). This will set us up for pulling this\nfunction out of the builtin entirely and making it part of the repack.h\nAPI, which will be done in a future commit.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 65 ++++++++++++++++++++++++++----------------------\n 1 file changed, 35 insertions(+), 30 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a4d80b6b04..bf413a6ee2 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -183,6 +183,38 @@ static int generated_pack_has_ext(const struct generated_pack *pack,\n \tBUG(\"unknown pack extension: '%s'\", ext);\n }\n \n+static void generated_pack_install(struct generated_pack *pack,\n+\t\t\t\t   const char *name)\n+{\n+\tint ext;\n+\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n+\t\tchar *fname;\n+\n+\t\tfname = mkpathdup(\"%s/pack-%s%s\", packdir, name,\n+\t\t\t\t  exts[ext].name);\n+\n+\t\tif (pack->tempfiles[ext]) {\n+\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n+\t\t\tstruct stat statbuffer;\n+\n+\t\t\tif (!stat(fname_old, &statbuffer)) {\n+\t\t\t\tstatbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);\n+\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n+\t\t\t}\n+\n+\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n+\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"),\n+\t\t\t\t\t  fname);\n+\t\t} else if (!exts[ext].optional)\n+\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n+\t\t\t    exts[ext].name, packtmp, name);\n+\t\telse if (unlink(fname) < 0 && errno != ENOENT)\n+\t\t\tdie_errno(_(\"could not unlink: %s\"), fname);\n+\n+\t\tfree(fname);\n+\t}\n+}\n+\n static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t    const struct pack_objects_args *args,\n \t\t\t\t    struct string_list *names)\n@@ -1045,7 +1077,7 @@ int cmd_repack(int argc,\n \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n \tstruct pack_geometry geometry = { 0 };\n \tstruct tempfile *refs_snapshot = NULL;\n-\tint i, ext, ret;\n+\tint i, ret;\n \tint show_progress;\n \tchar **midx_pack_names = NULL;\n \tsize_t midx_pack_names_nr = 0;\n@@ -1434,35 +1466,8 @@ int cmd_repack(int argc,\n \t/*\n \t * Ok we have prepared all new packfiles.\n \t */\n-\tfor_each_string_list_item(item, &names) {\n-\t\tstruct generated_pack *pack = item->util;\n-\n-\t\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n-\t\t\tchar *fname;\n-\n-\t\t\tfname = mkpathdup(\"%s/pack-%s%s\",\n-\t\t\t\t\tpackdir, item->string, exts[ext].name);\n-\n-\t\t\tif (pack->tempfiles[ext]) {\n-\t\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n-\t\t\t\tstruct stat statbuffer;\n-\n-\t\t\t\tif (!stat(fname_old, &statbuffer)) {\n-\t\t\t\t\tstatbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);\n-\t\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n-\t\t\t\t}\n-\n-\t\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n-\t\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"), fname);\n-\t\t\t} else if (!exts[ext].optional)\n-\t\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n-\t\t\t\t    exts[ext].name, packtmp, item->string);\n-\t\t\telse if (unlink(fname) < 0 && errno != ENOENT)\n-\t\t\t\tdie_errno(_(\"could not unlink: %s\"), fname);\n-\n-\t\t\tfree(fname);\n-\t\t}\n-\t}\n+\tfor_each_string_list_item(item, &names)\n+\t\tgenerated_pack_install(item->util, item->string);\n \t/* End of pack replacement. */\n \n \tif (delete_redundant && pack_everything & ALL_INTO_ONE)\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528892","messageId":"acd9a83ed035ff0bec330363506acc30ef669672.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 22/49] builtin/repack.c: pass \"packtmp\" to `generated_pack_populate()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:23Z","receivedAt":"2025-10-15T22:28:26Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as previous commits, this function needs to know the\ntemporary pack prefix, which it currently accesses through the static\n\"packtmp\" variable within builtin/repack.c.\n\nPass it explicitly as a function parameter to facilitate moving this\nfunction out of builtin/repack.c entirely.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex bf413a6ee2..bed902adde 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -150,7 +150,8 @@ struct generated_pack {\n \tstruct tempfile *tempfiles[ARRAY_SIZE(exts)];\n };\n \n-static struct generated_pack *generated_pack_populate(const char *name)\n+static struct generated_pack *generated_pack_populate(const char *name,\n+\t\t\t\t\t\t      const char *packtmp)\n {\n \tstruct stat statbuf;\n \tstruct strbuf path = STRBUF_INIT;\n@@ -271,7 +272,7 @@ static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t\t  line.buf);\n \t\twrite_promisor_file(promisor_name, NULL, 0);\n \n-\t\titem->util = generated_pack_populate(item->string);\n+\t\titem->util = generated_pack_populate(item->string, packtmp);\n \n \t\tfree(promisor_name);\n \t}\n@@ -896,7 +897,7 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t */\n \t\tif (local) {\n \t\t\titem = string_list_append(names, line.buf);\n-\t\t\titem->util = generated_pack_populate(line.buf);\n+\t\t\titem->util = generated_pack_populate(line.buf, packtmp);\n \t\t}\n \t}\n \tfclose(out);\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528893","messageId":"2b81a26a843a9d9070bd5a41cdd741067079149a.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 23/49] builtin/repack.c: provide pack locations to `generated_pack_install()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:26Z","receivedAt":"2025-10-15T22:28:29Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Repeat what was done in the preceding commit for the\n`generated_pack_install()` function, which needs both \"packdir\" and\n\"packtmp\".\n\n(As an aside, it is somewhat unfortunate that the final three parameters\nto this function are all \"const char *\", making errors like passing\n\"packdir\" and \"packtmp\" in the wrong order easy. We could define a new\nstructure here, but that may be too heavy-handed.)\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex bed902adde..966db27613 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -185,7 +185,8 @@ static int generated_pack_has_ext(const struct generated_pack *pack,\n }\n \n static void generated_pack_install(struct generated_pack *pack,\n-\t\t\t\t   const char *name)\n+\t\t\t\t   const char *name,\n+\t\t\t\t   const char *packdir, const char *packtmp)\n {\n \tint ext;\n \tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n@@ -1468,7 +1469,8 @@ int cmd_repack(int argc,\n \t * Ok we have prepared all new packfiles.\n \t */\n \tfor_each_string_list_item(item, &names)\n-\t\tgenerated_pack_install(item->util, item->string);\n+\t\tgenerated_pack_install(item->util, item->string, packdir,\n+\t\t\t\t       packtmp);\n \t/* End of pack replacement. */\n \n \tif (delete_redundant && pack_everything & ALL_INTO_ONE)\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528895","messageId":"69003f45612ca282f18ca5ad70768bae5fd60fa0.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 24/49] repack: remove 'generated_pack' API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:29Z","receivedAt":"2025-10-15T22:28:32Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that we have factored the \"generated_pack\" API, we can move it to\nrepack.ch, further slimming down builtin/repack.c.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 83 ------------------------------------------------\n repack.c         | 83 ++++++++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  8 +++++\n 3 files changed, 91 insertions(+), 83 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 966db27613..0e11c3b2c9 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -134,89 +134,6 @@ static int write_oid(const struct object_id *oid,\n \treturn 0;\n }\n \n-static struct {\n-\tconst char *name;\n-\tunsigned optional:1;\n-} exts[] = {\n-\t{\".pack\"},\n-\t{\".rev\", 1},\n-\t{\".mtimes\", 1},\n-\t{\".bitmap\", 1},\n-\t{\".promisor\", 1},\n-\t{\".idx\"},\n-};\n-\n-struct generated_pack {\n-\tstruct tempfile *tempfiles[ARRAY_SIZE(exts)];\n-};\n-\n-static struct generated_pack *generated_pack_populate(const char *name,\n-\t\t\t\t\t\t      const char *packtmp)\n-{\n-\tstruct stat statbuf;\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstruct generated_pack *pack = xcalloc(1, sizeof(*pack));\n-\tint i;\n-\n-\tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n-\t\tstrbuf_reset(&path);\n-\t\tstrbuf_addf(&path, \"%s-%s%s\", packtmp, name, exts[i].name);\n-\n-\t\tif (stat(path.buf, &statbuf))\n-\t\t\tcontinue;\n-\n-\t\tpack->tempfiles[i] = register_tempfile(path.buf);\n-\t}\n-\n-\tstrbuf_release(&path);\n-\treturn pack;\n-}\n-\n-static int generated_pack_has_ext(const struct generated_pack *pack,\n-\t\t\t\t  const char *ext)\n-{\n-\tint i;\n-\tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n-\t\tif (strcmp(exts[i].name, ext))\n-\t\t\tcontinue;\n-\t\treturn !!pack->tempfiles[i];\n-\t}\n-\tBUG(\"unknown pack extension: '%s'\", ext);\n-}\n-\n-static void generated_pack_install(struct generated_pack *pack,\n-\t\t\t\t   const char *name,\n-\t\t\t\t   const char *packdir, const char *packtmp)\n-{\n-\tint ext;\n-\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n-\t\tchar *fname;\n-\n-\t\tfname = mkpathdup(\"%s/pack-%s%s\", packdir, name,\n-\t\t\t\t  exts[ext].name);\n-\n-\t\tif (pack->tempfiles[ext]) {\n-\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n-\t\t\tstruct stat statbuffer;\n-\n-\t\t\tif (!stat(fname_old, &statbuffer)) {\n-\t\t\t\tstatbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);\n-\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n-\t\t\t}\n-\n-\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n-\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"),\n-\t\t\t\t\t  fname);\n-\t\t} else if (!exts[ext].optional)\n-\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n-\t\t\t    exts[ext].name, packtmp, name);\n-\t\telse if (unlink(fname) < 0 && errno != ENOENT)\n-\t\t\tdie_errno(_(\"could not unlink: %s\"), fname);\n-\n-\t\tfree(fname);\n-\t}\n-}\n-\n static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t    const struct pack_objects_args *args,\n \t\t\t\t    struct string_list *names)\ndiff --git a/repack.c b/repack.c\nindex 9182e1c50b..d8afdd352d 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -3,9 +3,11 @@\n #include \"midx.h\"\n #include \"odb.h\"\n #include \"packfile.h\"\n+#include \"path.h\"\n #include \"repack.h\"\n #include \"repository.h\"\n #include \"run-command.h\"\n+#include \"tempfile.h\"\n \n void prepare_pack_objects(struct child_process *cmd,\n \t\t\t  const struct pack_objects_args *args,\n@@ -219,3 +221,84 @@ void existing_packs_release(struct existing_packs *existing)\n \tstring_list_clear(&existing->non_kept_packs, 0);\n \tstring_list_clear(&existing->cruft_packs, 0);\n }\n+\n+static struct {\n+\tconst char *name;\n+\tunsigned optional:1;\n+} exts[] = {\n+\t{\".pack\"},\n+\t{\".rev\", 1},\n+\t{\".mtimes\", 1},\n+\t{\".bitmap\", 1},\n+\t{\".promisor\", 1},\n+\t{\".idx\"},\n+};\n+\n+struct generated_pack {\n+\tstruct tempfile *tempfiles[ARRAY_SIZE(exts)];\n+};\n+\n+struct generated_pack *generated_pack_populate(const char *name,\n+\t\t\t\t\t       const char *packtmp)\n+{\n+\tstruct stat statbuf;\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstruct generated_pack *pack = xcalloc(1, sizeof(*pack));\n+\tsize_t i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addf(&path, \"%s-%s%s\", packtmp, name, exts[i].name);\n+\n+\t\tif (stat(path.buf, &statbuf))\n+\t\t\tcontinue;\n+\n+\t\tpack->tempfiles[i] = register_tempfile(path.buf);\n+\t}\n+\n+\tstrbuf_release(&path);\n+\treturn pack;\n+}\n+\n+int generated_pack_has_ext(const struct generated_pack *pack, const char *ext)\n+{\n+\tsize_t i;\n+\tfor (i = 0; i < ARRAY_SIZE(exts); i++) {\n+\t\tif (strcmp(exts[i].name, ext))\n+\t\t\tcontinue;\n+\t\treturn !!pack->tempfiles[i];\n+\t}\n+\tBUG(\"unknown pack extension: '%s'\", ext);\n+}\n+\n+void generated_pack_install(struct generated_pack *pack, const char *name,\n+\t\t\t    const char *packdir, const char *packtmp)\n+{\n+\tsize_t ext;\n+\tfor (ext = 0; ext < ARRAY_SIZE(exts); ext++) {\n+\t\tchar *fname;\n+\n+\t\tfname = mkpathdup(\"%s/pack-%s%s\", packdir, name,\n+\t\t\t\t  exts[ext].name);\n+\n+\t\tif (pack->tempfiles[ext]) {\n+\t\t\tconst char *fname_old = get_tempfile_path(pack->tempfiles[ext]);\n+\t\t\tstruct stat statbuffer;\n+\n+\t\t\tif (!stat(fname_old, &statbuffer)) {\n+\t\t\t\tstatbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);\n+\t\t\t\tchmod(fname_old, statbuffer.st_mode);\n+\t\t\t}\n+\n+\t\t\tif (rename_tempfile(&pack->tempfiles[ext], fname))\n+\t\t\t\tdie_errno(_(\"renaming pack to '%s' failed\"),\n+\t\t\t\t\t  fname);\n+\t\t} else if (!exts[ext].optional)\n+\t\t\tdie(_(\"pack-objects did not write a '%s' file for pack %s-%s\"),\n+\t\t\t    exts[ext].name, packtmp, name);\n+\t\telse if (unlink(fname) < 0 && errno != ENOENT)\n+\t\t\tdie_errno(_(\"could not unlink: %s\"), fname);\n+\n+\t\tfree(fname);\n+\t}\n+}\ndiff --git a/repack.h b/repack.h\nindex 19796e2243..f37eb49524 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -66,4 +66,12 @@ void existing_packs_remove_redundant(struct existing_packs *existing,\n \t\t\t\t     const char *packdir);\n void existing_packs_release(struct existing_packs *existing);\n \n+struct generated_pack;\n+\n+struct generated_pack *generated_pack_populate(const char *name,\n+\t\t\t\t\t       const char *packtmp);\n+int generated_pack_has_ext(const struct generated_pack *pack, const char *ext);\n+void generated_pack_install(struct generated_pack *pack, const char *name,\n+\t\t\t    const char *packdir, const char *packtmp);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528896","messageId":"ca030322cf05a2c44463f94e9100e7163d8cdd67.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 25/49] builtin/repack.c: pass \"packtmp\" to `repack_promisor_objects()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:32Z","receivedAt":"2025-10-15T22:28:35Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as previous commit(s), pass the \"packtmp\" variable\nto \"repack_promisor_objects()\" as an explicit parameter of the function,\npreparing us to move this function in a following commit.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 0e11c3b2c9..2c67111b33 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -136,7 +136,8 @@ static int write_oid(const struct object_id *oid,\n \n static void repack_promisor_objects(struct repository *repo,\n \t\t\t\t    const struct pack_objects_args *args,\n-\t\t\t\t    struct string_list *names)\n+\t\t\t\t    struct string_list *names,\n+\t\t\t\t    const char *packtmp)\n {\n \tstruct write_oid_context ctx;\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -1199,7 +1200,7 @@ int cmd_repack(int argc,\n \t\tstrvec_push(&cmd.args, \"--delta-islands\");\n \n \tif (pack_everything & ALL_INTO_ONE) {\n-\t\trepack_promisor_objects(repo, &po_args, &names);\n+\t\trepack_promisor_objects(repo, &po_args, &names, packtmp);\n \n \t\tif (existing_packs_has_non_kept(&existing) &&\n \t\t    delete_redundant &&\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528897","messageId":"1050b856a5414a79cca49d3beb569c85734c7f89.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 26/49] builtin/repack.c: remove \"repack_promisor_objects()\" from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:35Z","receivedAt":"2025-10-15T22:28:38Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that we have properly factored the portion of the builtin which is\nresponsible for repacking promisor objects, we can move that function\n(and associated dependencies) out of the builtin entirely.\n\nSimilar to previous extractions, this function is declared in repack.h,\nbut implemented in a separate repack-promisor.c file. This is done to\nseparate promisor-specific repacking functionality from generic repack\nutilities (like \"existing_packs\", and \"generated_pack\" APIs).\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile          |   1 +\n builtin/repack.c  |  95 ------------------------------------------\n meson.build       |   1 +\n repack-promisor.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++\n repack.h          |   4 ++\n 5 files changed, 108 insertions(+), 95 deletions(-)\n create mode 100644 repack-promisor.c\n\ndiff --git a/Makefile b/Makefile\nindex 58fc05030b..01597af359 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1250,6 +1250,7 @@ LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n+LIB_OBJS += repack-promisor.o\n LIB_OBJS += replace-object.o\n LIB_OBJS += repo-settings.o\n LIB_OBJS += repository.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 2c67111b33..24b5e5049b 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,101 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-struct write_oid_context {\n-\tstruct child_process *cmd;\n-\tconst struct git_hash_algo *algop;\n-};\n-\n-/*\n- * Write oid to the given struct child_process's stdin, starting it first if\n- * necessary.\n- */\n-static int write_oid(const struct object_id *oid,\n-\t\t     struct packed_git *pack UNUSED,\n-\t\t     uint32_t pos UNUSED, void *data)\n-{\n-\tstruct write_oid_context *ctx = data;\n-\tstruct child_process *cmd = ctx->cmd;\n-\n-\tif (cmd->in == -1) {\n-\t\tif (start_command(cmd))\n-\t\t\tdie(_(\"could not start pack-objects to repack promisor objects\"));\n-\t}\n-\n-\tif (write_in_full(cmd->in, oid_to_hex(oid), ctx->algop->hexsz) < 0 ||\n-\t    write_in_full(cmd->in, \"\\n\", 1) < 0)\n-\t\tdie(_(\"failed to feed promisor objects to pack-objects\"));\n-\treturn 0;\n-}\n-\n-static void repack_promisor_objects(struct repository *repo,\n-\t\t\t\t    const struct pack_objects_args *args,\n-\t\t\t\t    struct string_list *names,\n-\t\t\t\t    const char *packtmp)\n-{\n-\tstruct write_oid_context ctx;\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tFILE *out;\n-\tstruct strbuf line = STRBUF_INIT;\n-\n-\tprepare_pack_objects(&cmd, args, packtmp);\n-\tcmd.in = -1;\n-\n-\t/*\n-\t * NEEDSWORK: Giving pack-objects only the OIDs without any ordering\n-\t * hints may result in suboptimal deltas in the resulting pack. See if\n-\t * the OIDs can be sent with fake paths such that pack-objects can use a\n-\t * {type -> existing pack order} ordering when computing deltas instead\n-\t * of a {type -> size} ordering, which may produce better deltas.\n-\t */\n-\tctx.cmd = &cmd;\n-\tctx.algop = repo->hash_algo;\n-\tfor_each_packed_object(repo, write_oid, &ctx,\n-\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n-\n-\tif (cmd.in == -1) {\n-\t\t/* No packed objects; cmd was never started */\n-\t\tchild_process_clear(&cmd);\n-\t\treturn;\n-\t}\n-\n-\tclose(cmd.in);\n-\n-\tout = xfdopen(cmd.out, \"r\");\n-\twhile (strbuf_getline_lf(&line, out) != EOF) {\n-\t\tstruct string_list_item *item;\n-\t\tchar *promisor_name;\n-\n-\t\tif (line.len != repo->hash_algo->hexsz)\n-\t\t\tdie(_(\"repack: Expecting full hex object ID lines only from pack-objects.\"));\n-\t\titem = string_list_append(names, line.buf);\n-\n-\t\t/*\n-\t\t * pack-objects creates the .pack and .idx files, but not the\n-\t\t * .promisor file. Create the .promisor file, which is empty.\n-\t\t *\n-\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n-\t\t * .promisor files containing the ref names and associated\n-\t\t * hashes at the point of generation of the corresponding\n-\t\t * packfile, but this would not preserve their contents. Maybe\n-\t\t * concatenate the contents of all .promisor files instead of\n-\t\t * just creating a new empty file.\n-\t\t */\n-\t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n-\t\t\t\t\t  line.buf);\n-\t\twrite_promisor_file(promisor_name, NULL, 0);\n-\n-\t\titem->util = generated_pack_populate(item->string, packtmp);\n-\n-\t\tfree(promisor_name);\n-\t}\n-\n-\tfclose(out);\n-\tif (finish_command(&cmd))\n-\t\tdie(_(\"could not finish pack-objects to repack promisor objects\"));\n-\tstrbuf_release(&line);\n-}\n-\n struct pack_geometry {\n \tstruct packed_git **pack;\n \tuint32_t pack_nr, pack_alloc;\ndiff --git a/meson.build b/meson.build\nindex 4a985ce77e..b37e3d192d 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -463,6 +463,7 @@ libgit_sources = [\n   'reftable/writer.c',\n   'remote.c',\n   'repack.c',\n+  'repack-promisor.c',\n   'replace-object.c',\n   'repo-settings.c',\n   'repository.c',\ndiff --git a/repack-promisor.c b/repack-promisor.c\nnew file mode 100644\nindex 0000000000..ee6e0669f6\n--- /dev/null\n+++ b/repack-promisor.c\n@@ -0,0 +1,102 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"hex.h\"\n+#include \"pack.h\"\n+#include \"packfile.h\"\n+#include \"path.h\"\n+#include \"repository.h\"\n+#include \"run-command.h\"\n+\n+struct write_oid_context {\n+\tstruct child_process *cmd;\n+\tconst struct git_hash_algo *algop;\n+};\n+\n+/*\n+ * Write oid to the given struct child_process's stdin, starting it first if\n+ * necessary.\n+ */\n+static int write_oid(const struct object_id *oid,\n+\t\t     struct packed_git *pack UNUSED,\n+\t\t     uint32_t pos UNUSED, void *data)\n+{\n+\tstruct write_oid_context *ctx = data;\n+\tstruct child_process *cmd = ctx->cmd;\n+\n+\tif (cmd->in == -1) {\n+\t\tif (start_command(cmd))\n+\t\t\tdie(_(\"could not start pack-objects to repack promisor objects\"));\n+\t}\n+\n+\tif (write_in_full(cmd->in, oid_to_hex(oid), ctx->algop->hexsz) < 0 ||\n+\t    write_in_full(cmd->in, \"\\n\", 1) < 0)\n+\t\tdie(_(\"failed to feed promisor objects to pack-objects\"));\n+\treturn 0;\n+}\n+\n+void repack_promisor_objects(struct repository *repo,\n+\t\t\t     const struct pack_objects_args *args,\n+\t\t\t     struct string_list *names, const char *packtmp)\n+{\n+\tstruct write_oid_context ctx;\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tFILE *out;\n+\tstruct strbuf line = STRBUF_INIT;\n+\n+\tprepare_pack_objects(&cmd, args, packtmp);\n+\tcmd.in = -1;\n+\n+\t/*\n+\t * NEEDSWORK: Giving pack-objects only the OIDs without any ordering\n+\t * hints may result in suboptimal deltas in the resulting pack. See if\n+\t * the OIDs can be sent with fake paths such that pack-objects can use a\n+\t * {type -> existing pack order} ordering when computing deltas instead\n+\t * of a {type -> size} ordering, which may produce better deltas.\n+\t */\n+\tctx.cmd = &cmd;\n+\tctx.algop = repo->hash_algo;\n+\tfor_each_packed_object(repo, write_oid, &ctx,\n+\t\t\t       FOR_EACH_OBJECT_PROMISOR_ONLY);\n+\n+\tif (cmd.in == -1) {\n+\t\t/* No packed objects; cmd was never started */\n+\t\tchild_process_clear(&cmd);\n+\t\treturn;\n+\t}\n+\n+\tclose(cmd.in);\n+\n+\tout = xfdopen(cmd.out, \"r\");\n+\twhile (strbuf_getline_lf(&line, out) != EOF) {\n+\t\tstruct string_list_item *item;\n+\t\tchar *promisor_name;\n+\n+\t\tif (line.len != repo->hash_algo->hexsz)\n+\t\t\tdie(_(\"repack: Expecting full hex object ID lines only from pack-objects.\"));\n+\t\titem = string_list_append(names, line.buf);\n+\n+\t\t/*\n+\t\t * pack-objects creates the .pack and .idx files, but not the\n+\t\t * .promisor file. Create the .promisor file, which is empty.\n+\t\t *\n+\t\t * NEEDSWORK: fetch-pack sometimes generates non-empty\n+\t\t * .promisor files containing the ref names and associated\n+\t\t * hashes at the point of generation of the corresponding\n+\t\t * packfile, but this would not preserve their contents. Maybe\n+\t\t * concatenate the contents of all .promisor files instead of\n+\t\t * just creating a new empty file.\n+\t\t */\n+\t\tpromisor_name = mkpathdup(\"%s-%s.promisor\", packtmp,\n+\t\t\t\t\t  line.buf);\n+\t\twrite_promisor_file(promisor_name, NULL, 0);\n+\n+\t\titem->util = generated_pack_populate(item->string, packtmp);\n+\n+\t\tfree(promisor_name);\n+\t}\n+\n+\tfclose(out);\n+\tif (finish_command(&cmd))\n+\t\tdie(_(\"could not finish pack-objects to repack promisor objects\"));\n+\tstrbuf_release(&line);\n+}\ndiff --git a/repack.h b/repack.h\nindex f37eb49524..19dc4fd738 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -74,4 +74,8 @@ int generated_pack_has_ext(const struct generated_pack *pack, const char *ext);\n void generated_pack_install(struct generated_pack *pack, const char *name,\n \t\t\t    const char *packdir, const char *packtmp);\n \n+void repack_promisor_objects(struct repository *repo,\n+\t\t\t     const struct pack_objects_args *args,\n+\t\t\t     struct string_list *names, const char *packtmp);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528898","messageId":"0b2fa0cf625caa3a31857295dfb237ca0c5192b7.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 27/49] builtin/repack.c: rename various pack_geometry functions","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:38Z","receivedAt":"2025-10-15T22:28:41Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Rename functions which work with 'struct pack_geometry' to begin with\n\"pack_geometry_\". While we're at it, change `free_pack_geometry()` to\ninstead be named `pack_geometry_release()` to match our conventions, and\nmake clear that that function frees the contents of the struct, not the\nmemory allocated to hold the struct itself.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 52 ++++++++++++++++++++++++------------------------\n 1 file changed, 26 insertions(+), 26 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 24b5e5049b..42f05d2ebf 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -115,17 +115,17 @@ struct pack_geometry {\n \tint split_factor;\n };\n \n-static uint32_t geometry_pack_weight(struct packed_git *p)\n+static uint32_t pack_geometry_weight(struct packed_git *p)\n {\n \tif (open_pack_index(p))\n \t\tdie(_(\"cannot open index for %s\"), p->pack_name);\n \treturn p->num_objects;\n }\n \n-static int geometry_cmp(const void *va, const void *vb)\n+static int pack_geometry_cmp(const void *va, const void *vb)\n {\n-\tuint32_t aw = geometry_pack_weight(*(struct packed_git **)va),\n-\t\t bw = geometry_pack_weight(*(struct packed_git **)vb);\n+\tuint32_t aw = pack_geometry_weight(*(struct packed_git **)va),\n+\t\t bw = pack_geometry_weight(*(struct packed_git **)vb);\n \n \tif (aw < bw)\n \t\treturn -1;\n@@ -134,7 +134,7 @@ static int geometry_cmp(const void *va, const void *vb)\n \treturn 0;\n }\n \n-static void init_pack_geometry(struct pack_geometry *geometry,\n+static void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\t       struct existing_packs *existing,\n \t\t\t       const struct pack_objects_args *args)\n {\n@@ -184,11 +184,11 @@ static void init_pack_geometry(struct pack_geometry *geometry,\n \t\tgeometry->pack_nr++;\n \t}\n \n-\tQSORT(geometry->pack, geometry->pack_nr, geometry_cmp);\n+\tQSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);\n \tstrbuf_release(&buf);\n }\n \n-static void split_pack_geometry(struct pack_geometry *geometry)\n+static void pack_geometry_split(struct pack_geometry *geometry)\n {\n \tuint32_t i;\n \tuint32_t split;\n@@ -208,13 +208,13 @@ static void split_pack_geometry(struct pack_geometry *geometry)\n \t\tstruct packed_git *prev = geometry->pack[i - 1];\n \n \t\tif (unsigned_mult_overflows(geometry->split_factor,\n-\t\t\t\t\t    geometry_pack_weight(prev)))\n+\t\t\t\t\t    pack_geometry_weight(prev)))\n \t\t\tdie(_(\"pack %s too large to consider in geometric \"\n \t\t\t      \"progression\"),\n \t\t\t    prev->pack_name);\n \n-\t\tif (geometry_pack_weight(ours) <\n-\t\t    geometry->split_factor * geometry_pack_weight(prev))\n+\t\tif (pack_geometry_weight(ours) <\n+\t\t    geometry->split_factor * pack_geometry_weight(prev))\n \t\t\tbreak;\n \t}\n \n@@ -242,9 +242,9 @@ static void split_pack_geometry(struct pack_geometry *geometry)\n \tfor (i = 0; i < split; i++) {\n \t\tstruct packed_git *p = geometry->pack[i];\n \n-\t\tif (unsigned_add_overflows(total_size, geometry_pack_weight(p)))\n+\t\tif (unsigned_add_overflows(total_size, pack_geometry_weight(p)))\n \t\t\tdie(_(\"pack %s too large to roll up\"), p->pack_name);\n-\t\ttotal_size += geometry_pack_weight(p);\n+\t\ttotal_size += pack_geometry_weight(p);\n \t}\n \tfor (i = split; i < geometry->pack_nr; i++) {\n \t\tstruct packed_git *ours = geometry->pack[i];\n@@ -253,15 +253,15 @@ static void split_pack_geometry(struct pack_geometry *geometry)\n \t\t\t\t\t    total_size))\n \t\t\tdie(_(\"pack %s too large to roll up\"), ours->pack_name);\n \n-\t\tif (geometry_pack_weight(ours) <\n+\t\tif (pack_geometry_weight(ours) <\n \t\t    geometry->split_factor * total_size) {\n \t\t\tif (unsigned_add_overflows(total_size,\n-\t\t\t\t\t\t   geometry_pack_weight(ours)))\n+\t\t\t\t\t\t   pack_geometry_weight(ours)))\n \t\t\t\tdie(_(\"pack %s too large to roll up\"),\n \t\t\t\t    ours->pack_name);\n \n \t\t\tsplit++;\n-\t\t\ttotal_size += geometry_pack_weight(ours);\n+\t\t\ttotal_size += pack_geometry_weight(ours);\n \t\t} else\n \t\t\tbreak;\n \t}\n@@ -269,7 +269,7 @@ static void split_pack_geometry(struct pack_geometry *geometry)\n \tgeometry->split = split;\n }\n \n-static struct packed_git *get_preferred_pack(struct pack_geometry *geometry)\n+static struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)\n {\n \tuint32_t i;\n \n@@ -304,9 +304,9 @@ static struct packed_git *get_preferred_pack(struct pack_geometry *geometry)\n \treturn NULL;\n }\n \n-static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n-\t\t\t\t\t    struct string_list *names,\n-\t\t\t\t\t    struct existing_packs *existing)\n+static void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n+\t\t\t\t\t   struct string_list *names,\n+\t\t\t\t\t   struct existing_packs *existing)\n {\n \tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -332,7 +332,7 @@ static void geometry_remove_redundant_packs(struct pack_geometry *geometry,\n \tstrbuf_release(&buf);\n }\n \n-static void free_pack_geometry(struct pack_geometry *geometry)\n+static void pack_geometry_release(struct pack_geometry *geometry)\n {\n \tif (!geometry)\n \t\treturn;\n@@ -599,7 +599,7 @@ static int write_midx_included_packs(struct string_list *include,\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n-\tstruct packed_git *preferred = get_preferred_pack(geometry);\n+\tstruct packed_git *preferred = pack_geometry_preferred_pack(geometry);\n \tFILE *in;\n \tint ret;\n \n@@ -1063,8 +1063,8 @@ int cmd_repack(int argc,\n \tif (geometry.split_factor) {\n \t\tif (pack_everything)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--geometric\", \"-A/-a\");\n-\t\tinit_pack_geometry(&geometry, &existing, &po_args);\n-\t\tsplit_pack_geometry(&geometry);\n+\t\tpack_geometry_init(&geometry, &existing, &po_args);\n+\t\tpack_geometry_split(&geometry);\n \t}\n \n \tprepare_pack_objects(&cmd, &po_args, packtmp);\n@@ -1324,8 +1324,8 @@ int cmd_repack(int argc,\n \t\texisting_packs_remove_redundant(&existing, packdir);\n \n \t\tif (geometry.split_factor)\n-\t\t\tgeometry_remove_redundant_packs(&geometry, &names,\n-\t\t\t\t\t\t\t&existing);\n+\t\t\tpack_geometry_remove_redundant(&geometry, &names,\n+\t\t\t\t\t\t       &existing);\n \t\tif (show_progress)\n \t\t\topts |= PRUNE_PACKED_VERBOSE;\n \t\tprune_packed_objects(opts);\n@@ -1352,7 +1352,7 @@ int cmd_repack(int argc,\n \tstring_list_clear(&keep_pack_list, 0);\n \tstring_list_clear(&names, 1);\n \texisting_packs_release(&existing);\n-\tfree_pack_geometry(&geometry);\n+\tpack_geometry_release(&geometry);\n \tfor (size_t i = 0; i < midx_pack_names_nr; i++)\n \t\tfree(midx_pack_names[i]);\n \tfree(midx_pack_names);\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528899","messageId":"a3d99c485299221bc6fe6c012e1efaaf38ac41d6.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 28/49] builtin/repack.c: pass 'pack_kept_objects' to `pack_geometry_init()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:41Z","receivedAt":"2025-10-15T22:28:43Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Prepare to move pack_geometry-related APIs to their own compilation unit\nby passing in the static \"pack_kept_objects\" variable directly as a\nparameter to the 'pack_geometry_init()' function.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 42f05d2ebf..ac8c80d0a5 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -136,7 +136,8 @@ static int pack_geometry_cmp(const void *va, const void *vb)\n \n static void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\t       struct existing_packs *existing,\n-\t\t\t       const struct pack_objects_args *args)\n+\t\t\t       const struct pack_objects_args *args,\n+\t\t\t       int pack_kept_objects)\n {\n \tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n@@ -1063,7 +1064,8 @@ int cmd_repack(int argc,\n \tif (geometry.split_factor) {\n \t\tif (pack_everything)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--geometric\", \"-A/-a\");\n-\t\tpack_geometry_init(&geometry, &existing, &po_args);\n+\t\tpack_geometry_init(&geometry, &existing, &po_args,\n+\t\t\t\t   pack_kept_objects);\n \t\tpack_geometry_split(&geometry);\n \t}\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528900","messageId":"0a2a72ea22d5e4ad2fe5c93c2691a5d089b32589.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 29/49] builtin/repack.c: pass 'packdir' to `pack_geometry_remove_redundant()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:44Z","receivedAt":"2025-10-15T22:28:46Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"For similar reasons as the preceding commit, pass the \"packdir\" variable\ndirectly to `pack_geometry_remove_redundant()` as a parameter to the\nfunction.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex ac8c80d0a5..f6d04b33a7 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -307,7 +307,8 @@ static struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geo\n \n static void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n \t\t\t\t\t   struct string_list *names,\n-\t\t\t\t\t   struct existing_packs *existing)\n+\t\t\t\t\t   struct existing_packs *existing,\n+\t\t\t\t\t   const char *packdir)\n {\n \tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -1327,7 +1328,7 @@ int cmd_repack(int argc,\n \n \t\tif (geometry.split_factor)\n \t\t\tpack_geometry_remove_redundant(&geometry, &names,\n-\t\t\t\t\t\t       &existing);\n+\t\t\t\t\t\t       &existing, packdir);\n \t\tif (show_progress)\n \t\t\topts |= PRUNE_PACKED_VERBOSE;\n \t\tprune_packed_objects(opts);\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528901","messageId":"9172e9a515084a1b513b004d71acefcf3fb68dda.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 30/49] repack: remove pack_geometry API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:47Z","receivedAt":"2025-10-15T22:28:49Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that the pack_geometry API is fully factored and isolated from the\nrest of the builtin, declare it within repack.h and move its\nimplementation to \"repack-geometry.c\" as a separate component.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile          |   1 +\n builtin/repack.c  | 235 ----------------------------------------------\n meson.build       |   1 +\n repack-geometry.c | 234 +++++++++++++++++++++++++++++++++++++++++++++\n repack.h          |  20 ++++\n 5 files changed, 256 insertions(+), 235 deletions(-)\n create mode 100644 repack-geometry.c\n\ndiff --git a/Makefile b/Makefile\nindex 01597af359..a1ccac235d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1250,6 +1250,7 @@ LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n+LIB_OBJS += repack-geometry.o\n LIB_OBJS += repack-promisor.o\n LIB_OBJS += replace-object.o\n LIB_OBJS += repo-settings.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex f6d04b33a7..e2313c80c3 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,241 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-struct pack_geometry {\n-\tstruct packed_git **pack;\n-\tuint32_t pack_nr, pack_alloc;\n-\tuint32_t split;\n-\n-\tint split_factor;\n-};\n-\n-static uint32_t pack_geometry_weight(struct packed_git *p)\n-{\n-\tif (open_pack_index(p))\n-\t\tdie(_(\"cannot open index for %s\"), p->pack_name);\n-\treturn p->num_objects;\n-}\n-\n-static int pack_geometry_cmp(const void *va, const void *vb)\n-{\n-\tuint32_t aw = pack_geometry_weight(*(struct packed_git **)va),\n-\t\t bw = pack_geometry_weight(*(struct packed_git **)vb);\n-\n-\tif (aw < bw)\n-\t\treturn -1;\n-\tif (aw > bw)\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n-static void pack_geometry_init(struct pack_geometry *geometry,\n-\t\t\t       struct existing_packs *existing,\n-\t\t\t       const struct pack_objects_args *args,\n-\t\t\t       int pack_kept_objects)\n-{\n-\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n-\tstruct packed_git *p;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n-\t\tif (args->local && !p->pack_local)\n-\t\t\t/*\n-\t\t\t * When asked to only repack local packfiles we skip\n-\t\t\t * over any packfiles that are borrowed from alternate\n-\t\t\t * object directories.\n-\t\t\t */\n-\t\t\tcontinue;\n-\n-\t\tif (!pack_kept_objects) {\n-\t\t\t/*\n-\t\t\t * Any pack that has its pack_keep bit set will\n-\t\t\t * appear in existing->kept_packs below, but\n-\t\t\t * this saves us from doing a more expensive\n-\t\t\t * check.\n-\t\t\t */\n-\t\t\tif (p->pack_keep)\n-\t\t\t\tcontinue;\n-\n-\t\t\t/*\n-\t\t\t * The pack may be kept via the --keep-pack\n-\t\t\t * option; check 'existing->kept_packs' to\n-\t\t\t * determine whether to ignore it.\n-\t\t\t */\n-\t\t\tstrbuf_reset(&buf);\n-\t\t\tstrbuf_addstr(&buf, pack_basename(p));\n-\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\t\t\tif (string_list_has_string(&existing->kept_packs, buf.buf))\n-\t\t\t\tcontinue;\n-\t\t}\n-\t\tif (p->is_cruft)\n-\t\t\tcontinue;\n-\n-\t\tALLOC_GROW(geometry->pack,\n-\t\t\t   geometry->pack_nr + 1,\n-\t\t\t   geometry->pack_alloc);\n-\n-\t\tgeometry->pack[geometry->pack_nr] = p;\n-\t\tgeometry->pack_nr++;\n-\t}\n-\n-\tQSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);\n-\tstrbuf_release(&buf);\n-}\n-\n-static void pack_geometry_split(struct pack_geometry *geometry)\n-{\n-\tuint32_t i;\n-\tuint32_t split;\n-\toff_t total_size = 0;\n-\n-\tif (!geometry->pack_nr) {\n-\t\tgeometry->split = geometry->pack_nr;\n-\t\treturn;\n-\t}\n-\n-\t/*\n-\t * First, count the number of packs (in descending order of size) which\n-\t * already form a geometric progression.\n-\t */\n-\tfor (i = geometry->pack_nr - 1; i > 0; i--) {\n-\t\tstruct packed_git *ours = geometry->pack[i];\n-\t\tstruct packed_git *prev = geometry->pack[i - 1];\n-\n-\t\tif (unsigned_mult_overflows(geometry->split_factor,\n-\t\t\t\t\t    pack_geometry_weight(prev)))\n-\t\t\tdie(_(\"pack %s too large to consider in geometric \"\n-\t\t\t      \"progression\"),\n-\t\t\t    prev->pack_name);\n-\n-\t\tif (pack_geometry_weight(ours) <\n-\t\t    geometry->split_factor * pack_geometry_weight(prev))\n-\t\t\tbreak;\n-\t}\n-\n-\tsplit = i;\n-\n-\tif (split) {\n-\t\t/*\n-\t\t * Move the split one to the right, since the top element in the\n-\t\t * last-compared pair can't be in the progression. Only do this\n-\t\t * when we split in the middle of the array (otherwise if we got\n-\t\t * to the end, then the split is in the right place).\n-\t\t */\n-\t\tsplit++;\n-\t}\n-\n-\t/*\n-\t * Then, anything to the left of 'split' must be in a new pack. But,\n-\t * creating that new pack may cause packs in the heavy half to no longer\n-\t * form a geometric progression.\n-\t *\n-\t * Compute an expected size of the new pack, and then determine how many\n-\t * packs in the heavy half need to be joined into it (if any) to restore\n-\t * the geometric progression.\n-\t */\n-\tfor (i = 0; i < split; i++) {\n-\t\tstruct packed_git *p = geometry->pack[i];\n-\n-\t\tif (unsigned_add_overflows(total_size, pack_geometry_weight(p)))\n-\t\t\tdie(_(\"pack %s too large to roll up\"), p->pack_name);\n-\t\ttotal_size += pack_geometry_weight(p);\n-\t}\n-\tfor (i = split; i < geometry->pack_nr; i++) {\n-\t\tstruct packed_git *ours = geometry->pack[i];\n-\n-\t\tif (unsigned_mult_overflows(geometry->split_factor,\n-\t\t\t\t\t    total_size))\n-\t\t\tdie(_(\"pack %s too large to roll up\"), ours->pack_name);\n-\n-\t\tif (pack_geometry_weight(ours) <\n-\t\t    geometry->split_factor * total_size) {\n-\t\t\tif (unsigned_add_overflows(total_size,\n-\t\t\t\t\t\t   pack_geometry_weight(ours)))\n-\t\t\t\tdie(_(\"pack %s too large to roll up\"),\n-\t\t\t\t    ours->pack_name);\n-\n-\t\t\tsplit++;\n-\t\t\ttotal_size += pack_geometry_weight(ours);\n-\t\t} else\n-\t\t\tbreak;\n-\t}\n-\n-\tgeometry->split = split;\n-}\n-\n-static struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)\n-{\n-\tuint32_t i;\n-\n-\tif (!geometry) {\n-\t\t/*\n-\t\t * No geometry means either an all-into-one repack (in which\n-\t\t * case there is only one pack left and it is the largest) or an\n-\t\t * incremental one.\n-\t\t *\n-\t\t * If repacking incrementally, then we could check the size of\n-\t\t * all packs to determine which should be preferred, but leave\n-\t\t * this for later.\n-\t\t */\n-\t\treturn NULL;\n-\t}\n-\tif (geometry->split == geometry->pack_nr)\n-\t\treturn NULL;\n-\n-\t/*\n-\t * The preferred pack is the largest pack above the split line. In\n-\t * other words, it is the largest pack that does not get rolled up in\n-\t * the geometric repack.\n-\t */\n-\tfor (i = geometry->pack_nr; i > geometry->split; i--)\n-\t\t/*\n-\t\t * A pack that is not local would never be included in a\n-\t\t * multi-pack index. We thus skip over any non-local packs.\n-\t\t */\n-\t\tif (geometry->pack[i - 1]->pack_local)\n-\t\t\treturn geometry->pack[i - 1];\n-\n-\treturn NULL;\n-}\n-\n-static void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n-\t\t\t\t\t   struct string_list *names,\n-\t\t\t\t\t   struct existing_packs *existing,\n-\t\t\t\t\t   const char *packdir)\n-{\n-\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tuint32_t i;\n-\n-\tfor (i = 0; i < geometry->split; i++) {\n-\t\tstruct packed_git *p = geometry->pack[i];\n-\t\tif (string_list_has_string(names, hash_to_hex_algop(p->hash,\n-\t\t\t\t\t\t\t\t    algop)))\n-\t\t\tcontinue;\n-\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addstr(&buf, pack_basename(p));\n-\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\t\tif ((p->pack_keep) ||\n-\t\t    (string_list_has_string(&existing->kept_packs, buf.buf)))\n-\t\t\tcontinue;\n-\n-\t\trepack_remove_redundant_pack(existing->repo, packdir, buf.buf);\n-\t}\n-\n-\tstrbuf_release(&buf);\n-}\n-\n-static void pack_geometry_release(struct pack_geometry *geometry)\n-{\n-\tif (!geometry)\n-\t\treturn;\n-\n-\tfree(geometry->pack);\n-}\n-\n static int midx_has_unknown_packs(char **midx_pack_names,\n \t\t\t\t  size_t midx_pack_names_nr,\n \t\t\t\t  struct string_list *include,\ndiff --git a/meson.build b/meson.build\nindex b37e3d192d..9afd45ea45 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -463,6 +463,7 @@ libgit_sources = [\n   'reftable/writer.c',\n   'remote.c',\n   'repack.c',\n+  'repack-geometry.c',\n   'repack-promisor.c',\n   'replace-object.c',\n   'repo-settings.c',\ndiff --git a/repack-geometry.c b/repack-geometry.c\nnew file mode 100644\nindex 0000000000..f58f1fc7f0\n--- /dev/null\n+++ b/repack-geometry.c\n@@ -0,0 +1,234 @@\n+#define DISABLE_SIGN_COMPARE_WARNINGS\n+\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"repository.h\"\n+#include \"hex.h\"\n+#include \"packfile.h\"\n+\n+static uint32_t pack_geometry_weight(struct packed_git *p)\n+{\n+\tif (open_pack_index(p))\n+\t\tdie(_(\"cannot open index for %s\"), p->pack_name);\n+\treturn p->num_objects;\n+}\n+\n+static int pack_geometry_cmp(const void *va, const void *vb)\n+{\n+\tuint32_t aw = pack_geometry_weight(*(struct packed_git **)va),\n+\t\t bw = pack_geometry_weight(*(struct packed_git **)vb);\n+\n+\tif (aw < bw)\n+\t\treturn -1;\n+\tif (aw > bw)\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+void pack_geometry_init(struct pack_geometry *geometry,\n+\t\t\tstruct existing_packs *existing,\n+\t\t\tconst struct pack_objects_args *args,\n+\t\t\tint pack_kept_objects)\n+{\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n+\tstruct packed_git *p;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n+\t\tif (args->local && !p->pack_local)\n+\t\t\t/*\n+\t\t\t * When asked to only repack local packfiles we skip\n+\t\t\t * over any packfiles that are borrowed from alternate\n+\t\t\t * object directories.\n+\t\t\t */\n+\t\t\tcontinue;\n+\n+\t\tif (!pack_kept_objects) {\n+\t\t\t/*\n+\t\t\t * Any pack that has its pack_keep bit set will\n+\t\t\t * appear in existing->kept_packs below, but\n+\t\t\t * this saves us from doing a more expensive\n+\t\t\t * check.\n+\t\t\t */\n+\t\t\tif (p->pack_keep)\n+\t\t\t\tcontinue;\n+\n+\t\t\t/*\n+\t\t\t * The pack may be kept via the --keep-pack\n+\t\t\t * option; check 'existing->kept_packs' to\n+\t\t\t * determine whether to ignore it.\n+\t\t\t */\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addstr(&buf, pack_basename(p));\n+\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\t\t\tif (string_list_has_string(&existing->kept_packs, buf.buf))\n+\t\t\t\tcontinue;\n+\t\t}\n+\t\tif (p->is_cruft)\n+\t\t\tcontinue;\n+\n+\t\tALLOC_GROW(geometry->pack,\n+\t\t\t   geometry->pack_nr + 1,\n+\t\t\t   geometry->pack_alloc);\n+\n+\t\tgeometry->pack[geometry->pack_nr] = p;\n+\t\tgeometry->pack_nr++;\n+\t}\n+\n+\tQSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);\n+\tstrbuf_release(&buf);\n+}\n+\n+void pack_geometry_split(struct pack_geometry *geometry)\n+{\n+\tuint32_t i;\n+\tuint32_t split;\n+\toff_t total_size = 0;\n+\n+\tif (!geometry->pack_nr) {\n+\t\tgeometry->split = geometry->pack_nr;\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * First, count the number of packs (in descending order of size) which\n+\t * already form a geometric progression.\n+\t */\n+\tfor (i = geometry->pack_nr - 1; i > 0; i--) {\n+\t\tstruct packed_git *ours = geometry->pack[i];\n+\t\tstruct packed_git *prev = geometry->pack[i - 1];\n+\n+\t\tif (unsigned_mult_overflows(geometry->split_factor,\n+\t\t\t\t\t    pack_geometry_weight(prev)))\n+\t\t\tdie(_(\"pack %s too large to consider in geometric \"\n+\t\t\t      \"progression\"),\n+\t\t\t    prev->pack_name);\n+\n+\t\tif (pack_geometry_weight(ours) <\n+\t\t    geometry->split_factor * pack_geometry_weight(prev))\n+\t\t\tbreak;\n+\t}\n+\n+\tsplit = i;\n+\n+\tif (split) {\n+\t\t/*\n+\t\t * Move the split one to the right, since the top element in the\n+\t\t * last-compared pair can't be in the progression. Only do this\n+\t\t * when we split in the middle of the array (otherwise if we got\n+\t\t * to the end, then the split is in the right place).\n+\t\t */\n+\t\tsplit++;\n+\t}\n+\n+\t/*\n+\t * Then, anything to the left of 'split' must be in a new pack. But,\n+\t * creating that new pack may cause packs in the heavy half to no longer\n+\t * form a geometric progression.\n+\t *\n+\t * Compute an expected size of the new pack, and then determine how many\n+\t * packs in the heavy half need to be joined into it (if any) to restore\n+\t * the geometric progression.\n+\t */\n+\tfor (i = 0; i < split; i++) {\n+\t\tstruct packed_git *p = geometry->pack[i];\n+\n+\t\tif (unsigned_add_overflows(total_size, pack_geometry_weight(p)))\n+\t\t\tdie(_(\"pack %s too large to roll up\"), p->pack_name);\n+\t\ttotal_size += pack_geometry_weight(p);\n+\t}\n+\tfor (i = split; i < geometry->pack_nr; i++) {\n+\t\tstruct packed_git *ours = geometry->pack[i];\n+\n+\t\tif (unsigned_mult_overflows(geometry->split_factor,\n+\t\t\t\t\t    total_size))\n+\t\t\tdie(_(\"pack %s too large to roll up\"), ours->pack_name);\n+\n+\t\tif (pack_geometry_weight(ours) <\n+\t\t    geometry->split_factor * total_size) {\n+\t\t\tif (unsigned_add_overflows(total_size,\n+\t\t\t\t\t\t   pack_geometry_weight(ours)))\n+\t\t\t\tdie(_(\"pack %s too large to roll up\"),\n+\t\t\t\t    ours->pack_name);\n+\n+\t\t\tsplit++;\n+\t\t\ttotal_size += pack_geometry_weight(ours);\n+\t\t} else\n+\t\t\tbreak;\n+\t}\n+\n+\tgeometry->split = split;\n+}\n+\n+struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)\n+{\n+\tuint32_t i;\n+\n+\tif (!geometry) {\n+\t\t/*\n+\t\t * No geometry means either an all-into-one repack (in which\n+\t\t * case there is only one pack left and it is the largest) or an\n+\t\t * incremental one.\n+\t\t *\n+\t\t * If repacking incrementally, then we could check the size of\n+\t\t * all packs to determine which should be preferred, but leave\n+\t\t * this for later.\n+\t\t */\n+\t\treturn NULL;\n+\t}\n+\tif (geometry->split == geometry->pack_nr)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * The preferred pack is the largest pack above the split line. In\n+\t * other words, it is the largest pack that does not get rolled up in\n+\t * the geometric repack.\n+\t */\n+\tfor (i = geometry->pack_nr; i > geometry->split; i--)\n+\t\t/*\n+\t\t * A pack that is not local would never be included in a\n+\t\t * multi-pack index. We thus skip over any non-local packs.\n+\t\t */\n+\t\tif (geometry->pack[i - 1]->pack_local)\n+\t\t\treturn geometry->pack[i - 1];\n+\n+\treturn NULL;\n+}\n+\n+void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n+\t\t\t\t    struct string_list *names,\n+\t\t\t\t    struct existing_packs *existing,\n+\t\t\t\t    const char *packdir)\n+{\n+\tconst struct git_hash_algo *algop = existing->repo->hash_algo;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tuint32_t i;\n+\n+\tfor (i = 0; i < geometry->split; i++) {\n+\t\tstruct packed_git *p = geometry->pack[i];\n+\t\tif (string_list_has_string(names, hash_to_hex_algop(p->hash,\n+\t\t\t\t\t\t\t\t    algop)))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, pack_basename(p));\n+\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\t\tif ((p->pack_keep) ||\n+\t\t    (string_list_has_string(&existing->kept_packs, buf.buf)))\n+\t\t\tcontinue;\n+\n+\t\trepack_remove_redundant_pack(existing->repo, packdir, buf.buf);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+void pack_geometry_release(struct pack_geometry *geometry)\n+{\n+\tif (!geometry)\n+\t\treturn;\n+\n+\tfree(geometry->pack);\n+}\ndiff --git a/repack.h b/repack.h\nindex 19dc4fd738..cea7969ae4 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -78,4 +78,24 @@ void repack_promisor_objects(struct repository *repo,\n \t\t\t     const struct pack_objects_args *args,\n \t\t\t     struct string_list *names, const char *packtmp);\n \n+struct pack_geometry {\n+\tstruct packed_git **pack;\n+\tuint32_t pack_nr, pack_alloc;\n+\tuint32_t split;\n+\n+\tint split_factor;\n+};\n+\n+void pack_geometry_init(struct pack_geometry *geometry,\n+\t\t\tstruct existing_packs *existing,\n+\t\t\tconst struct pack_objects_args *args,\n+\t\t\tint pack_kept_objects);\n+void pack_geometry_split(struct pack_geometry *geometry);\n+struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry);\n+void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n+\t\t\t\t    struct string_list *names,\n+\t\t\t\t    struct existing_packs *existing,\n+\t\t\t\t    const char *packdir);\n+void pack_geometry_release(struct pack_geometry *geometry);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528902","messageId":"5b15d83e65b1de52cfa4fad6f39ca707aae72da6.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 31/49] builtin/repack.c: remove ref snapshotting from builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:50Z","receivedAt":"2025-10-15T22:28:52Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"When writing a MIDX, 'git repack' takes a snapshot of the repository's\nreferences and writes the result out to a file, which it then passes to\n'git multi-pack-index write' via the '--refs-snapshot'.\n\nThis is done in order to make bitmap selections with respect to what we\nare packing, thus avoiding a race where an incoming reference update\ncauses us to try and write a bitmap for a commit not present in the\nMIDX.\n\nExtract this functionality out into a new repack-midx.c compilation\nunit, and expose the necessary functions via the repack.h API.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile         |  1 +\n builtin/repack.c | 68 ------------------------------------------\n meson.build      |  1 +\n repack-midx.c    | 77 ++++++++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  4 +++\n 5 files changed, 83 insertions(+), 68 deletions(-)\n create mode 100644 repack-midx.c\n\ndiff --git a/Makefile b/Makefile\nindex a1ccac235d..4b0a122428 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1251,6 +1251,7 @@ LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n LIB_OBJS += repack-geometry.o\n+LIB_OBJS += repack-midx.o\n LIB_OBJS += repack-promisor.o\n LIB_OBJS += replace-object.o\n LIB_OBJS += repo-settings.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex e2313c80c3..7713721826 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -178,74 +178,6 @@ static int midx_has_unknown_packs(char **midx_pack_names,\n \treturn 0;\n }\n \n-struct midx_snapshot_ref_data {\n-\tstruct repository *repo;\n-\tstruct tempfile *f;\n-\tstruct oidset seen;\n-\tint preferred;\n-};\n-\n-static int midx_snapshot_ref_one(const char *refname UNUSED,\n-\t\t\t\t const char *referent UNUSED,\n-\t\t\t\t const struct object_id *oid,\n-\t\t\t\t int flag UNUSED, void *_data)\n-{\n-\tstruct midx_snapshot_ref_data *data = _data;\n-\tstruct object_id peeled;\n-\n-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n-\t\toid = &peeled;\n-\n-\tif (oidset_insert(&data->seen, oid))\n-\t\treturn 0; /* already seen */\n-\n-\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n-\t\treturn 0;\n-\n-\tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n-\t\toid_to_hex(oid));\n-\n-\treturn 0;\n-}\n-\n-static void midx_snapshot_refs(struct repository *repo, struct tempfile *f)\n-{\n-\tstruct midx_snapshot_ref_data data;\n-\tconst struct string_list *preferred = bitmap_preferred_tips(repo);\n-\n-\tdata.repo = repo;\n-\tdata.f = f;\n-\tdata.preferred = 0;\n-\toidset_init(&data.seen, 0);\n-\n-\tif (!fdopen_tempfile(f, \"w\"))\n-\t\t die(_(\"could not open tempfile %s for writing\"),\n-\t\t     get_tempfile_path(f));\n-\n-\tif (preferred) {\n-\t\tstruct string_list_item *item;\n-\n-\t\tdata.preferred = 1;\n-\t\tfor_each_string_list_item(item, preferred)\n-\t\t\trefs_for_each_ref_in(get_main_ref_store(repo),\n-\t\t\t\t\t     item->string,\n-\t\t\t\t\t     midx_snapshot_ref_one, &data);\n-\t\tdata.preferred = 0;\n-\t}\n-\n-\trefs_for_each_ref(get_main_ref_store(repo),\n-\t\t\t  midx_snapshot_ref_one, &data);\n-\n-\tif (close_tempfile_gently(f)) {\n-\t\tint save_errno = errno;\n-\t\tdelete_tempfile(&f);\n-\t\terrno = save_errno;\n-\t\tdie_errno(_(\"could not close refs snapshot tempfile\"));\n-\t}\n-\n-\toidset_clear(&data.seen);\n-}\n-\n static void midx_included_packs(struct string_list *include,\n \t\t\t\tstruct existing_packs *existing,\n \t\t\t\tchar **midx_pack_names,\ndiff --git a/meson.build b/meson.build\nindex 9afd45ea45..0373d5c454 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -464,6 +464,7 @@ libgit_sources = [\n   'remote.c',\n   'repack.c',\n   'repack-geometry.c',\n+  'repack-midx.c',\n   'repack-promisor.c',\n   'replace-object.c',\n   'repo-settings.c',\ndiff --git a/repack-midx.c b/repack-midx.c\nnew file mode 100644\nindex 0000000000..354df729a5\n--- /dev/null\n+++ b/repack-midx.c\n@@ -0,0 +1,77 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"hash.h\"\n+#include \"hex.h\"\n+#include \"odb.h\"\n+#include \"oidset.h\"\n+#include \"pack-bitmap.h\"\n+#include \"refs.h\"\n+#include \"tempfile.h\"\n+\n+struct midx_snapshot_ref_data {\n+\tstruct repository *repo;\n+\tstruct tempfile *f;\n+\tstruct oidset seen;\n+\tint preferred;\n+};\n+\n+static int midx_snapshot_ref_one(const char *refname UNUSED,\n+\t\t\t\t const char *referent UNUSED,\n+\t\t\t\t const struct object_id *oid,\n+\t\t\t\t int flag UNUSED, void *_data)\n+{\n+\tstruct midx_snapshot_ref_data *data = _data;\n+\tstruct object_id peeled;\n+\n+\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n+\t\toid = &peeled;\n+\n+\tif (oidset_insert(&data->seen, oid))\n+\t\treturn 0; /* already seen */\n+\n+\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n+\t\treturn 0;\n+\n+\tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n+\t\toid_to_hex(oid));\n+\n+\treturn 0;\n+}\n+\n+void midx_snapshot_refs(struct repository *repo, struct tempfile *f)\n+{\n+\tstruct midx_snapshot_ref_data data;\n+\tconst struct string_list *preferred = bitmap_preferred_tips(repo);\n+\n+\tdata.repo = repo;\n+\tdata.f = f;\n+\tdata.preferred = 0;\n+\toidset_init(&data.seen, 0);\n+\n+\tif (!fdopen_tempfile(f, \"w\"))\n+\t\t die(_(\"could not open tempfile %s for writing\"),\n+\t\t     get_tempfile_path(f));\n+\n+\tif (preferred) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tdata.preferred = 1;\n+\t\tfor_each_string_list_item(item, preferred)\n+\t\t\trefs_for_each_ref_in(get_main_ref_store(repo),\n+\t\t\t\t\t     item->string,\n+\t\t\t\t\t     midx_snapshot_ref_one, &data);\n+\t\tdata.preferred = 0;\n+\t}\n+\n+\trefs_for_each_ref(get_main_ref_store(repo),\n+\t\t\t  midx_snapshot_ref_one, &data);\n+\n+\tif (close_tempfile_gently(f)) {\n+\t\tint save_errno = errno;\n+\t\tdelete_tempfile(&f);\n+\t\terrno = save_errno;\n+\t\tdie_errno(_(\"could not close refs snapshot tempfile\"));\n+\t}\n+\n+\toidset_clear(&data.seen);\n+}\ndiff --git a/repack.h b/repack.h\nindex cea7969ae4..803e129224 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -98,4 +98,8 @@ void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n \t\t\t\t    const char *packdir);\n void pack_geometry_release(struct pack_geometry *geometry);\n \n+struct tempfile;\n+\n+void midx_snapshot_refs(struct repository *repo, struct tempfile *f);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528903","messageId":"077f06c935ad268186cd9162fcef82fdc2a76b81.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 32/49] builtin/repack.c: extract opts struct for 'write_midx_included_packs()'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:53Z","receivedAt":"2025-10-15T22:28:55Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The function 'write_midx_included_packs()', which is responsible for\nwriting a new MIDX with a given set of included packs, currently takes a\nlist of six arguments.\n\nIn order to extract this function out of the builtin, we have to pass\nin a few additional parameters, like 'midx_must_contain_cruft' and\n'packdir', which are currently declared as static variables within the\nbuiltin/repack.c compilation unit.\n\nInstead of adding additional parameters to `write_midx_included_packs()`\nextract out an \"opts\" struct that names these parameters, and pass a\npointer to that, making it less cumbersome to add additional parameters.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 52 +++++++++++++++++++++++++++++++-----------------\n 1 file changed, 34 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 7713721826..1a79d1d834 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,6 +107,17 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n+struct repack_write_midx_opts {\n+\tstruct string_list *include;\n+\tstruct pack_geometry *geometry;\n+\tstruct string_list *names;\n+\tconst char *refs_snapshot;\n+\tconst char *packdir;\n+\tint show_progress;\n+\tint write_bitmaps;\n+\tint midx_must_contain_cruft;\n+};\n+\n static int midx_has_unknown_packs(char **midx_pack_names,\n \t\t\t\t  size_t midx_pack_names_nr,\n \t\t\t\t  struct string_list *include,\n@@ -290,19 +301,15 @@ static void midx_included_packs(struct string_list *include,\n \tstrbuf_release(&buf);\n }\n \n-static int write_midx_included_packs(struct string_list *include,\n-\t\t\t\t     struct pack_geometry *geometry,\n-\t\t\t\t     struct string_list *names,\n-\t\t\t\t     const char *refs_snapshot,\n-\t\t\t\t     int show_progress, int write_bitmaps)\n+static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n-\tstruct packed_git *preferred = pack_geometry_preferred_pack(geometry);\n+\tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n \tFILE *in;\n \tint ret;\n \n-\tif (!include->nr)\n+\tif (!opts->include->nr)\n \t\treturn 0;\n \n \tcmd.in = -1;\n@@ -311,18 +318,18 @@ static int write_midx_included_packs(struct string_list *include,\n \tstrvec_push(&cmd.args, \"multi-pack-index\");\n \tstrvec_pushl(&cmd.args, \"write\", \"--stdin-packs\", NULL);\n \n-\tif (show_progress)\n+\tif (opts->show_progress)\n \t\tstrvec_push(&cmd.args, \"--progress\");\n \telse\n \t\tstrvec_push(&cmd.args, \"--no-progress\");\n \n-\tif (write_bitmaps)\n+\tif (opts->write_bitmaps)\n \t\tstrvec_push(&cmd.args, \"--bitmap\");\n \n \tif (preferred)\n \t\tstrvec_pushf(&cmd.args, \"--preferred-pack=%s\",\n \t\t\t     pack_basename(preferred));\n-\telse if (names->nr) {\n+\telse if (opts->names->nr) {\n \t\t/* The largest pack was repacked, meaning that either\n \t\t * one or two packs exist depending on whether the\n \t\t * repository has a cruft pack or not.\n@@ -335,7 +342,7 @@ static int write_midx_included_packs(struct string_list *include,\n \t\t * `--max-pack-size` was given, but any one of them\n \t\t * will suffice, so pick the first one.)\n \t\t */\n-\t\tfor_each_string_list_item(item, names) {\n+\t\tfor_each_string_list_item(item, opts->names) {\n \t\t\tstruct generated_pack *pack = item->util;\n \t\t\tif (generated_pack_has_ext(pack, \".mtimes\"))\n \t\t\t\tcontinue;\n@@ -355,15 +362,16 @@ static int write_midx_included_packs(struct string_list *include,\n \t\t;\n \t}\n \n-\tif (refs_snapshot)\n-\t\tstrvec_pushf(&cmd.args, \"--refs-snapshot=%s\", refs_snapshot);\n+\tif (opts->refs_snapshot)\n+\t\tstrvec_pushf(&cmd.args, \"--refs-snapshot=%s\",\n+\t\t\t     opts->refs_snapshot);\n \n \tret = start_command(&cmd);\n \tif (ret)\n \t\treturn ret;\n \n \tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, include)\n+\tfor_each_string_list_item(item, opts->include)\n \t\tfprintf(in, \"%s\\n\", item->string);\n \tfclose(in);\n \n@@ -1001,15 +1009,23 @@ int cmd_repack(int argc,\n \n \tif (write_midx) {\n \t\tstruct string_list include = STRING_LIST_INIT_DUP;\n+\t\tstruct repack_write_midx_opts opts = {\n+\t\t\t.include = &include,\n+\t\t\t.geometry = &geometry,\n+\t\t\t.names = &names,\n+\t\t\t.refs_snapshot = refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,\n+\t\t\t.packdir = packdir,\n+\t\t\t.show_progress = show_progress,\n+\t\t\t.write_bitmaps = write_bitmaps > 0,\n+\t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n+\t\t};\n \t\tmidx_included_packs(&include, &existing, midx_pack_names,\n \t\t\t\t    midx_pack_names_nr, &names, &geometry);\n \n-\t\tret = write_midx_included_packs(&include, &geometry, &names,\n-\t\t\t\t\t\trefs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,\n-\t\t\t\t\t\tshow_progress, write_bitmaps > 0);\n+\t\tret = write_midx_included_packs(&opts);\n \n \t\tif (!ret && write_bitmaps)\n-\t\t\tremove_redundant_bitmaps(&include, packdir);\n+\t\t\tremove_redundant_bitmaps(&include, opts.packdir);\n \n \t\tstring_list_clear(&include, 0);\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528904","messageId":"40ce496de234c432f8fe0c322b837d721ac7607d.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 33/49] builtin/repack.c: use a string_list for 'midx_pack_names'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:56Z","receivedAt":"2025-10-15T22:28:58Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"When writing a new MIDX, repack must determine whether or not there are\nany packs in the MIDX it is replacing (if one exists) that are not\nsomehow represented in the new MIDX (e.g., either by preserving the pack\nverbatim, or rolling it up as part of a geometric repack, etc.).\n\nIn order to do this, it keeps track of a list of pack names from the\nMIDX present in the repository at the start of the repack operation.\nSince we manipulate and close the object store, we cannot rely on the\nrepository's in-core representation of the MIDX, since this is subject\nto change and/or go away.\n\nWhen this behavior was introduced in 5ee86c273b (repack: exclude cruft\npack(s) from the MIDX where possible, 2025-06-23), we maintained an\narray of character pointers instead of using a convenience API, such as\nstring-list.h.\n\nStore the list of MIDX pack names in a string_list, thereby reducing the\nnumber of parameters we have to pass to `midx_has_unknown_packs()`.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 40 +++++++++++++++++-----------------------\n 1 file changed, 17 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 1a79d1d834..dda533f171 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -118,18 +118,17 @@ struct repack_write_midx_opts {\n \tint midx_must_contain_cruft;\n };\n \n-static int midx_has_unknown_packs(char **midx_pack_names,\n-\t\t\t\t  size_t midx_pack_names_nr,\n+static int midx_has_unknown_packs(struct string_list *midx_pack_names,\n \t\t\t\t  struct string_list *include,\n \t\t\t\t  struct pack_geometry *geometry,\n \t\t\t\t  struct existing_packs *existing)\n {\n-\tsize_t i;\n+\tstruct string_list_item *item;\n \n \tstring_list_sort(include);\n \n-\tfor (i = 0; i < midx_pack_names_nr; i++) {\n-\t\tconst char *pack_name = midx_pack_names[i];\n+\tfor_each_string_list_item(item, midx_pack_names) {\n+\t\tconst char *pack_name = item->string;\n \n \t\t/*\n \t\t * Determine whether or not each MIDX'd pack from the existing\n@@ -191,8 +190,7 @@ static int midx_has_unknown_packs(char **midx_pack_names,\n \n static void midx_included_packs(struct string_list *include,\n \t\t\t\tstruct existing_packs *existing,\n-\t\t\t\tchar **midx_pack_names,\n-\t\t\t\tsize_t midx_pack_names_nr,\n+\t\t\t\tstruct string_list *midx_pack_names,\n \t\t\t\tstruct string_list *names,\n \t\t\t\tstruct pack_geometry *geometry)\n {\n@@ -247,8 +245,8 @@ static void midx_included_packs(struct string_list *include,\n \t}\n \n \tif (midx_must_contain_cruft ||\n-\t    midx_has_unknown_packs(midx_pack_names, midx_pack_names_nr,\n-\t\t\t\t   include, geometry, existing)) {\n+\t    midx_has_unknown_packs(midx_pack_names, include, geometry,\n+\t\t\t\t   existing)) {\n \t\t/*\n \t\t * If there are one or more unknown pack(s) present (see\n \t\t * midx_has_unknown_packs() for what makes a pack\n@@ -606,13 +604,12 @@ int cmd_repack(int argc,\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n \tstruct string_list names = STRING_LIST_INIT_DUP;\n+\tstruct string_list midx_pack_names = STRING_LIST_INIT_DUP;\n \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n \tstruct pack_geometry geometry = { 0 };\n \tstruct tempfile *refs_snapshot = NULL;\n \tint i, ret;\n \tint show_progress;\n-\tchar **midx_pack_names = NULL;\n-\tsize_t midx_pack_names_nr = 0;\n \n \t/* variables to be filled by option parsing */\n \tstruct repack_config_ctx config_ctx;\n@@ -985,13 +982,12 @@ int cmd_repack(int argc,\n \t\tstruct multi_pack_index *m =\n \t\t\tget_multi_pack_index(repo->objects->sources);\n \n-\t\tALLOC_ARRAY(midx_pack_names,\n-\t\t\t    m->num_packs + m->num_packs_in_base);\n-\n-\t\tfor (; m; m = m->base_midx)\n-\t\t\tfor (uint32_t i = 0; i < m->num_packs; i++)\n-\t\t\t\tmidx_pack_names[midx_pack_names_nr++] =\n-\t\t\t\t\txstrdup(m->pack_names[i]);\n+\t\tfor (; m; m = m->base_midx) {\n+\t\t\tfor (uint32_t i = 0; i < m->num_packs; i++) {\n+\t\t\t\tstring_list_append(&midx_pack_names,\n+\t\t\t\t\t\t   m->pack_names[i]);\n+\t\t\t}\n+\t\t}\n \t}\n \n \tclose_object_store(repo->objects);\n@@ -1019,8 +1015,8 @@ int cmd_repack(int argc,\n \t\t\t.write_bitmaps = write_bitmaps > 0,\n \t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n \t\t};\n-\t\tmidx_included_packs(&include, &existing, midx_pack_names,\n-\t\t\t\t    midx_pack_names_nr, &names, &geometry);\n+\t\tmidx_included_packs(&include, &existing, &midx_pack_names,\n+\t\t\t\t    &names, &geometry);\n \n \t\tret = write_midx_included_packs(&opts);\n \n@@ -1067,11 +1063,9 @@ int cmd_repack(int argc,\n cleanup:\n \tstring_list_clear(&keep_pack_list, 0);\n \tstring_list_clear(&names, 1);\n+\tstring_list_clear(&midx_pack_names, 0);\n \texisting_packs_release(&existing);\n \tpack_geometry_release(&geometry);\n-\tfor (size_t i = 0; i < midx_pack_names_nr; i++)\n-\t\tfree(midx_pack_names[i]);\n-\tfree(midx_pack_names);\n \tpack_objects_args_release(&po_args);\n \tpack_objects_args_release(&cruft_po_args);\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528905","messageId":"ec3a8a7dd29c18c71947f1d3c4b4fffe1fe799d6.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 34/49] repack: keep track of MIDX pack names using existing_packs","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:28:59Z","receivedAt":"2025-10-15T22:29:01Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Instead of storing the list of MIDX pack names separately, let's inline\nit into the existing_packs struct, further reducing the number of\nparameters we have to pass around.\n\nThis amounts to adding a new string_list to the existing_packs struct,\nand populating it via `existing_packs_collect()`. This is fairly\nstraightforward to do, since we are already looping over all packs, all\nwe need to do is:\n\n    if (p->multi_pack_index)\n        string_list_append(&existing->midx_packs, pack_basename(p));\n\nNote, however, that this check *must* come before other conditions where\nwe discard and do not keep track of a pack, including the condition \"if\n(!p->pack_local)\" immediately below. This is because the existing\nroutine which collects MIDX pack names does so blindly, and does not\ndiscard, for example, non-local packs.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 26 ++++----------------------\n repack.c         |  5 +++++\n repack.h         |  1 +\n 3 files changed, 10 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex dda533f171..a57a14ef60 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -118,8 +118,7 @@ struct repack_write_midx_opts {\n \tint midx_must_contain_cruft;\n };\n \n-static int midx_has_unknown_packs(struct string_list *midx_pack_names,\n-\t\t\t\t  struct string_list *include,\n+static int midx_has_unknown_packs(struct string_list *include,\n \t\t\t\t  struct pack_geometry *geometry,\n \t\t\t\t  struct existing_packs *existing)\n {\n@@ -127,7 +126,7 @@ static int midx_has_unknown_packs(struct string_list *midx_pack_names,\n \n \tstring_list_sort(include);\n \n-\tfor_each_string_list_item(item, midx_pack_names) {\n+\tfor_each_string_list_item(item, &existing->midx_packs) {\n \t\tconst char *pack_name = item->string;\n \n \t\t/*\n@@ -190,7 +189,6 @@ static int midx_has_unknown_packs(struct string_list *midx_pack_names,\n \n static void midx_included_packs(struct string_list *include,\n \t\t\t\tstruct existing_packs *existing,\n-\t\t\t\tstruct string_list *midx_pack_names,\n \t\t\t\tstruct string_list *names,\n \t\t\t\tstruct pack_geometry *geometry)\n {\n@@ -245,8 +243,7 @@ static void midx_included_packs(struct string_list *include,\n \t}\n \n \tif (midx_must_contain_cruft ||\n-\t    midx_has_unknown_packs(midx_pack_names, include, geometry,\n-\t\t\t\t   existing)) {\n+\t    midx_has_unknown_packs(include, geometry, existing)) {\n \t\t/*\n \t\t * If there are one or more unknown pack(s) present (see\n \t\t * midx_has_unknown_packs() for what makes a pack\n@@ -604,7 +601,6 @@ int cmd_repack(int argc,\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tstruct string_list_item *item;\n \tstruct string_list names = STRING_LIST_INIT_DUP;\n-\tstruct string_list midx_pack_names = STRING_LIST_INIT_DUP;\n \tstruct existing_packs existing = EXISTING_PACKS_INIT;\n \tstruct pack_geometry geometry = { 0 };\n \tstruct tempfile *refs_snapshot = NULL;\n@@ -978,18 +974,6 @@ int cmd_repack(int argc,\n \n \tstring_list_sort(&names);\n \n-\tif (get_multi_pack_index(repo->objects->sources)) {\n-\t\tstruct multi_pack_index *m =\n-\t\t\tget_multi_pack_index(repo->objects->sources);\n-\n-\t\tfor (; m; m = m->base_midx) {\n-\t\t\tfor (uint32_t i = 0; i < m->num_packs; i++) {\n-\t\t\t\tstring_list_append(&midx_pack_names,\n-\t\t\t\t\t\t   m->pack_names[i]);\n-\t\t\t}\n-\t\t}\n-\t}\n-\n \tclose_object_store(repo->objects);\n \n \t/*\n@@ -1015,8 +999,7 @@ int cmd_repack(int argc,\n \t\t\t.write_bitmaps = write_bitmaps > 0,\n \t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n \t\t};\n-\t\tmidx_included_packs(&include, &existing, &midx_pack_names,\n-\t\t\t\t    &names, &geometry);\n+\t\tmidx_included_packs(&include, &existing, &names, &geometry);\n \n \t\tret = write_midx_included_packs(&opts);\n \n@@ -1063,7 +1046,6 @@ int cmd_repack(int argc,\n cleanup:\n \tstring_list_clear(&keep_pack_list, 0);\n \tstring_list_clear(&names, 1);\n-\tstring_list_clear(&midx_pack_names, 0);\n \texisting_packs_release(&existing);\n \tpack_geometry_release(&geometry);\n \tpack_objects_args_release(&po_args);\ndiff --git a/repack.c b/repack.c\nindex d8afdd352d..1d485e0112 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -80,6 +80,9 @@ void existing_packs_collect(struct existing_packs *existing,\n \t\tsize_t i;\n \t\tconst char *base;\n \n+\t\tif (p->multi_pack_index)\n+\t\t\tstring_list_append(&existing->midx_packs,\n+\t\t\t\t\t    pack_basename(p));\n \t\tif (!p->pack_local)\n \t\t\tcontinue;\n \n@@ -104,6 +107,7 @@ void existing_packs_collect(struct existing_packs *existing,\n \tstring_list_sort(&existing->kept_packs);\n \tstring_list_sort(&existing->non_kept_packs);\n \tstring_list_sort(&existing->cruft_packs);\n+\tstring_list_sort(&existing->midx_packs);\n \tstrbuf_release(&buf);\n }\n \n@@ -220,6 +224,7 @@ void existing_packs_release(struct existing_packs *existing)\n \tstring_list_clear(&existing->kept_packs, 0);\n \tstring_list_clear(&existing->non_kept_packs, 0);\n \tstring_list_clear(&existing->cruft_packs, 0);\n+\tstring_list_clear(&existing->midx_packs, 0);\n }\n \n static struct {\ndiff --git a/repack.h b/repack.h\nindex 803e129224..6aa5b4e0f0 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -40,6 +40,7 @@ struct existing_packs {\n \tstruct string_list kept_packs;\n \tstruct string_list non_kept_packs;\n \tstruct string_list cruft_packs;\n+\tstruct string_list midx_packs;\n };\n \n #define EXISTING_PACKS_INIT { \\\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528906","messageId":"8042860b26230b50c51e98db30960272392a6b12.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 35/49] builtin/repack.c: reorder `remove_redundant_bitmaps()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:02Z","receivedAt":"2025-10-15T22:29:04Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The next commit will inline the call to `remove_redundant_bitmaps()`\ninto `write_midx_included_packs()`. Reorder these two functions to avoid\na forward declaration to `remove_redundant_bitmaps()`.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 58 ++++++++++++++++++++++++------------------------\n 1 file changed, 29 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a57a14ef60..865e0af039 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -296,6 +296,35 @@ static void midx_included_packs(struct string_list *include,\n \tstrbuf_release(&buf);\n }\n \n+static void remove_redundant_bitmaps(struct string_list *include,\n+\t\t\t\t     const char *packdir)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\tsize_t packdir_len;\n+\n+\tstrbuf_addstr(&path, packdir);\n+\tstrbuf_addch(&path, '/');\n+\tpackdir_len = path.len;\n+\n+\t/*\n+\t * Remove any pack bitmaps corresponding to packs which are now\n+\t * included in the MIDX.\n+\t */\n+\tfor_each_string_list_item(item, include) {\n+\t\tstrbuf_addstr(&path, item->string);\n+\t\tstrbuf_strip_suffix(&path, \".idx\");\n+\t\tstrbuf_addstr(&path, \".bitmap\");\n+\n+\t\tif (unlink(path.buf) && errno != ENOENT)\n+\t\t\twarning_errno(_(\"could not remove stale bitmap: %s\"),\n+\t\t\t\t      path.buf);\n+\n+\t\tstrbuf_setlen(&path, packdir_len);\n+\t}\n+\tstrbuf_release(&path);\n+}\n+\n static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -373,35 +402,6 @@ static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n \treturn finish_command(&cmd);\n }\n \n-static void remove_redundant_bitmaps(struct string_list *include,\n-\t\t\t\t     const char *packdir)\n-{\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstruct string_list_item *item;\n-\tsize_t packdir_len;\n-\n-\tstrbuf_addstr(&path, packdir);\n-\tstrbuf_addch(&path, '/');\n-\tpackdir_len = path.len;\n-\n-\t/*\n-\t * Remove any pack bitmaps corresponding to packs which are now\n-\t * included in the MIDX.\n-\t */\n-\tfor_each_string_list_item(item, include) {\n-\t\tstrbuf_addstr(&path, item->string);\n-\t\tstrbuf_strip_suffix(&path, \".idx\");\n-\t\tstrbuf_addstr(&path, \".bitmap\");\n-\n-\t\tif (unlink(path.buf) && errno != ENOENT)\n-\t\t\twarning_errno(_(\"could not remove stale bitmap: %s\"),\n-\t\t\t\t      path.buf);\n-\n-\t\tstrbuf_setlen(&path, packdir_len);\n-\t}\n-\tstrbuf_release(&path);\n-}\n-\n static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t\t\t   struct child_process *cmd,\n \t\t\t\t   struct string_list *names,\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528907","messageId":"af06f60e639827038fb9f75e3d884303e9b20416.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 36/49] builtin/repack.c: inline `remove_redundant_bitmaps()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:05Z","receivedAt":"2025-10-15T22:29:07Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"After writing a new MIDX, the repack command removes any bitmaps\nbelonging to packs which were written into the MIDX.\n\nThis is currently done in a separate function outside of\n`write_midx_included_packs()`, which forces the caller to keep track of\nthe set of packs written into the MIDX.\n\nPrepare to no longer require the caller to keep track of such\ninformation by inlining the clean-up into `write_midx_included_packs()`.\nFuture commits will make the caller oblivious to the set of packs\nincluded in the MIDX altogether.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 15 ++++++++-------\n 1 file changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 865e0af039..271c869268 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -331,10 +331,10 @@ static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n \tstruct string_list_item *item;\n \tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n \tFILE *in;\n-\tint ret;\n+\tint ret = 0;\n \n \tif (!opts->include->nr)\n-\t\treturn 0;\n+\t\tgoto done;\n \n \tcmd.in = -1;\n \tcmd.git_cmd = 1;\n@@ -392,14 +392,18 @@ static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n \n \tret = start_command(&cmd);\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto done;\n \n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, opts->include)\n \t\tfprintf(in, \"%s\\n\", item->string);\n \tfclose(in);\n \n-\treturn finish_command(&cmd);\n+\tret = finish_command(&cmd);\n+done:\n+\tif (!ret && opts->write_bitmaps)\n+\t\tremove_redundant_bitmaps(opts->include, opts->packdir);\n+\treturn ret;\n }\n \n static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n@@ -1003,9 +1007,6 @@ int cmd_repack(int argc,\n \n \t\tret = write_midx_included_packs(&opts);\n \n-\t\tif (!ret && write_bitmaps)\n-\t\t\tremove_redundant_bitmaps(&include, opts.packdir);\n-\n \t\tstring_list_clear(&include, 0);\n \n \t\tif (ret)\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528908","messageId":"668f4edc9e935f468f5630c74c7d4744a30b476d.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 37/49] builtin/repack.c: pass `repack_write_midx_opts` to `midx_included_packs`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:08Z","receivedAt":"2025-10-15T22:29:10Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Instead of passing individual parameters (in this case, \"existing\",\n\"names\", and \"geometry\") to `midx_included_packs()`, pass a pointer to a\n`repack_write_midx_opts` structure instead.\n\nBesides reducing the number of parameters necessary to call the\n`midx_included_packs` function, this refactoring sets us up nicely to\ninline the call to `midx_included_packs()` into\n`write_midx_included_packs()`, thus making the caller (in this case,\n`cmd_repack()`) oblivious to the set of packs being written into the\nMIDX.\n\nIn order to do this, `repack_write_midx_opts` has to keep track of the\nset of existing packs, so add an additional field to point to that set.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 13 ++++++++-----\n 1 file changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 271c869268..a518a2b2f3 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -108,6 +108,7 @@ static int repack_config(const char *var, const char *value,\n }\n \n struct repack_write_midx_opts {\n+\tstruct existing_packs *existing;\n \tstruct string_list *include;\n \tstruct pack_geometry *geometry;\n \tstruct string_list *names;\n@@ -188,10 +189,11 @@ static int midx_has_unknown_packs(struct string_list *include,\n }\n \n static void midx_included_packs(struct string_list *include,\n-\t\t\t\tstruct existing_packs *existing,\n-\t\t\t\tstruct string_list *names,\n-\t\t\t\tstruct pack_geometry *geometry)\n+\t\t\t\tstruct repack_write_midx_opts *opts)\n {\n+\tstruct existing_packs *existing = opts->existing;\n+\tstruct pack_geometry *geometry = opts->geometry;\n+\tstruct string_list *names = opts->names;\n \tstruct string_list_item *item;\n \tstruct strbuf buf = STRBUF_INIT;\n \n@@ -242,7 +244,7 @@ static void midx_included_packs(struct string_list *include,\n \t\t}\n \t}\n \n-\tif (midx_must_contain_cruft ||\n+\tif (opts->midx_must_contain_cruft ||\n \t    midx_has_unknown_packs(include, geometry, existing)) {\n \t\t/*\n \t\t * If there are one or more unknown pack(s) present (see\n@@ -994,6 +996,7 @@ int cmd_repack(int argc,\n \tif (write_midx) {\n \t\tstruct string_list include = STRING_LIST_INIT_DUP;\n \t\tstruct repack_write_midx_opts opts = {\n+\t\t\t.existing = &existing,\n \t\t\t.include = &include,\n \t\t\t.geometry = &geometry,\n \t\t\t.names = &names,\n@@ -1003,7 +1006,7 @@ int cmd_repack(int argc,\n \t\t\t.write_bitmaps = write_bitmaps > 0,\n \t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n \t\t};\n-\t\tmidx_included_packs(&include, &existing, &names, &geometry);\n+\t\tmidx_included_packs(&include, &opts);\n \n \t\tret = write_midx_included_packs(&opts);\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528909","messageId":"5bd43e96f29038f1a5fe5064ce40053997125cd9.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 38/49] builtin/repack.c: inline packs within `write_midx_included_packs()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:10Z","receivedAt":"2025-10-15T22:29:13Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"To write a MIDX at the end of a repack operation, 'git repack' presently\ncomputes the set of packs to write into the MIDX, before invoking\n`write_midx_included_packs()` with a `string_list` containing those\npacks.\n\nThe logic for computing which packs are supposed to appear in the\nresulting MIDX is within `midx_included_packs()`, where it is aware of\ndetails like which cruft pack(s) were written/combined, if/how we did a\ngeometric repack, etc.\n\nComputing this list ourselves before providing it to the sole function\nto make use of that list `write_midx_included_packs()` is somewhat\nawkward. In the future, repack will learn how to write incremental\nMIDXs, which will use a very different pack selection routine.\n\nInstead of doing something like:\n\n    struct string_list included_packs = STRING_LIST_INIT_DUP;\n    if (incremental) {\n        midx_incremental_included_packs(&included_packs, ...):\n        write_midx_incremental_included_packs(&included_packs, ...);\n    } else {\n        midx_included_packs(&included_packs, ...):\n        write_midx_included_packs(&included_packs, ...);\n    }\n\nin the future, let's have each function that writes a MIDX be\nresponsible for itself computing the list of included packs. Inline the\ndeclaration and initialization of `included_packs` into the\n`write_midx_included_packs()` function itself, and repeat that pattern\nin the future when we introduce new ways to write MIDXs.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 17 ++++++++---------\n 1 file changed, 8 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a518a2b2f3..fad10be42a 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -109,7 +109,6 @@ static int repack_config(const char *var, const char *value,\n \n struct repack_write_midx_opts {\n \tstruct existing_packs *existing;\n-\tstruct string_list *include;\n \tstruct pack_geometry *geometry;\n \tstruct string_list *names;\n \tconst char *refs_snapshot;\n@@ -330,12 +329,14 @@ static void remove_redundant_bitmaps(struct string_list *include,\n static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct string_list include = STRING_LIST_INIT_DUP;\n \tstruct string_list_item *item;\n \tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n \tFILE *in;\n \tint ret = 0;\n \n-\tif (!opts->include->nr)\n+\tmidx_included_packs(&include, opts);\n+\tif (!include.nr)\n \t\tgoto done;\n \n \tcmd.in = -1;\n@@ -397,14 +398,17 @@ static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n \t\tgoto done;\n \n \tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, opts->include)\n+\tfor_each_string_list_item(item, &include)\n \t\tfprintf(in, \"%s\\n\", item->string);\n \tfclose(in);\n \n \tret = finish_command(&cmd);\n done:\n \tif (!ret && opts->write_bitmaps)\n-\t\tremove_redundant_bitmaps(opts->include, opts->packdir);\n+\t\tremove_redundant_bitmaps(&include, opts->packdir);\n+\n+\tstring_list_clear(&include, 0);\n+\n \treturn ret;\n }\n \n@@ -994,10 +998,8 @@ int cmd_repack(int argc,\n \t\texisting_packs_mark_for_deletion(&existing, &names);\n \n \tif (write_midx) {\n-\t\tstruct string_list include = STRING_LIST_INIT_DUP;\n \t\tstruct repack_write_midx_opts opts = {\n \t\t\t.existing = &existing,\n-\t\t\t.include = &include,\n \t\t\t.geometry = &geometry,\n \t\t\t.names = &names,\n \t\t\t.refs_snapshot = refs_snapshot ? get_tempfile_path(refs_snapshot) : NULL,\n@@ -1006,12 +1008,9 @@ int cmd_repack(int argc,\n \t\t\t.write_bitmaps = write_bitmaps > 0,\n \t\t\t.midx_must_contain_cruft = midx_must_contain_cruft\n \t\t};\n-\t\tmidx_included_packs(&include, &opts);\n \n \t\tret = write_midx_included_packs(&opts);\n \n-\t\tstring_list_clear(&include, 0);\n-\n \t\tif (ret)\n \t\t\tgoto cleanup;\n \t}\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528910","messageId":"794122266d64ec8fbd21532b0ab080cdf8fc6fa7.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 39/49] repack: 'write_midx_included_packs' API from the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:13Z","receivedAt":"2025-10-15T22:29:16Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Now that we have sufficiently cleaned up the write_midx_included_packs()\nfunction, we can move it (along with the struct repack_write_midx_opts)\nout of the builtin, and into the repack.h header.\n\nSince this function (and the static ones that it depends on) are\nMIDX-specific details of the repacking process, move them to the\nrepack-midx.c compilation unit instead of the general repack.c one.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 305 -----------------------------------------------\n repack-midx.c    | 295 +++++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  12 ++\n 3 files changed, 307 insertions(+), 305 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex fad10be42a..2f49a18283 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,311 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-struct repack_write_midx_opts {\n-\tstruct existing_packs *existing;\n-\tstruct pack_geometry *geometry;\n-\tstruct string_list *names;\n-\tconst char *refs_snapshot;\n-\tconst char *packdir;\n-\tint show_progress;\n-\tint write_bitmaps;\n-\tint midx_must_contain_cruft;\n-};\n-\n-static int midx_has_unknown_packs(struct string_list *include,\n-\t\t\t\t  struct pack_geometry *geometry,\n-\t\t\t\t  struct existing_packs *existing)\n-{\n-\tstruct string_list_item *item;\n-\n-\tstring_list_sort(include);\n-\n-\tfor_each_string_list_item(item, &existing->midx_packs) {\n-\t\tconst char *pack_name = item->string;\n-\n-\t\t/*\n-\t\t * Determine whether or not each MIDX'd pack from the existing\n-\t\t * MIDX (if any) is represented in the new MIDX. For each pack\n-\t\t * in the MIDX, it must either be:\n-\t\t *\n-\t\t *  - In the \"include\" list of packs to be included in the new\n-\t\t *    MIDX. Note this function is called before the include\n-\t\t *    list is populated with any cruft pack(s).\n-\t\t *\n-\t\t *  - Below the geometric split line (if using pack geometry),\n-\t\t *    indicating that the pack won't be included in the new\n-\t\t *    MIDX, but its contents were rolled up as part of the\n-\t\t *    geometric repack.\n-\t\t *\n-\t\t *  - In the existing non-kept packs list (if not using pack\n-\t\t *    geometry), and marked as non-deleted.\n-\t\t */\n-\t\tif (string_list_has_string(include, pack_name)) {\n-\t\t\tcontinue;\n-\t\t} else if (geometry) {\n-\t\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\t\tuint32_t j;\n-\n-\t\t\tfor (j = 0; j < geometry->split; j++) {\n-\t\t\t\tstrbuf_reset(&buf);\n-\t\t\t\tstrbuf_addstr(&buf, pack_basename(geometry->pack[j]));\n-\t\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\t\t\t\tstrbuf_addstr(&buf, \".idx\");\n-\n-\t\t\t\tif (!strcmp(pack_name, buf.buf)) {\n-\t\t\t\t\tstrbuf_release(&buf);\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t\t}\n-\n-\t\t\tstrbuf_release(&buf);\n-\n-\t\t\tif (j < geometry->split)\n-\t\t\t\tcontinue;\n-\t\t} else {\n-\t\t\tstruct string_list_item *item;\n-\n-\t\t\titem = string_list_lookup(&existing->non_kept_packs,\n-\t\t\t\t\t\t  pack_name);\n-\t\t\tif (item && !existing_pack_is_marked_for_deletion(item))\n-\t\t\t\tcontinue;\n-\t\t}\n-\n-\t\t/*\n-\t\t * If we got to this point, the MIDX includes some pack that we\n-\t\t * don't know about.\n-\t\t */\n-\t\treturn 1;\n-\t}\n-\n-\treturn 0;\n-}\n-\n-static void midx_included_packs(struct string_list *include,\n-\t\t\t\tstruct repack_write_midx_opts *opts)\n-{\n-\tstruct existing_packs *existing = opts->existing;\n-\tstruct pack_geometry *geometry = opts->geometry;\n-\tstruct string_list *names = opts->names;\n-\tstruct string_list_item *item;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tfor_each_string_list_item(item, &existing->kept_packs) {\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n-\t\tstring_list_insert(include, buf.buf);\n-\t}\n-\n-\tfor_each_string_list_item(item, names) {\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"pack-%s.idx\", item->string);\n-\t\tstring_list_insert(include, buf.buf);\n-\t}\n-\n-\tif (geometry->split_factor) {\n-\t\tuint32_t i;\n-\n-\t\tfor (i = geometry->split; i < geometry->pack_nr; i++) {\n-\t\t\tstruct packed_git *p = geometry->pack[i];\n-\n-\t\t\t/*\n-\t\t\t * The multi-pack index never refers to packfiles part\n-\t\t\t * of an alternate object database, so we skip these.\n-\t\t\t * While git-multi-pack-index(1) would silently ignore\n-\t\t\t * them anyway, this allows us to skip executing the\n-\t\t\t * command completely when we have only non-local\n-\t\t\t * packfiles.\n-\t\t\t */\n-\t\t\tif (!p->pack_local)\n-\t\t\t\tcontinue;\n-\n-\t\t\tstrbuf_reset(&buf);\n-\t\t\tstrbuf_addstr(&buf, pack_basename(p));\n-\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\t\t\tstrbuf_addstr(&buf, \".idx\");\n-\n-\t\t\tstring_list_insert(include, buf.buf);\n-\t\t}\n-\t} else {\n-\t\tfor_each_string_list_item(item, &existing->non_kept_packs) {\n-\t\t\tif (existing_pack_is_marked_for_deletion(item))\n-\t\t\t\tcontinue;\n-\n-\t\t\tstrbuf_reset(&buf);\n-\t\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n-\t\t\tstring_list_insert(include, buf.buf);\n-\t\t}\n-\t}\n-\n-\tif (opts->midx_must_contain_cruft ||\n-\t    midx_has_unknown_packs(include, geometry, existing)) {\n-\t\t/*\n-\t\t * If there are one or more unknown pack(s) present (see\n-\t\t * midx_has_unknown_packs() for what makes a pack\n-\t\t * \"unknown\") in the MIDX before the repack, keep them\n-\t\t * as they may be required to form a reachability\n-\t\t * closure if the MIDX is bitmapped.\n-\t\t *\n-\t\t * For example, a cruft pack can be required to form a\n-\t\t * reachability closure if the MIDX is bitmapped and one\n-\t\t * or more of the bitmap's selected commits reaches a\n-\t\t * once-cruft object that was later made reachable.\n-\t\t */\n-\t\tfor_each_string_list_item(item, &existing->cruft_packs) {\n-\t\t\t/*\n-\t\t\t * When doing a --geometric repack, there is no\n-\t\t\t * need to check for deleted packs, since we're\n-\t\t\t * by definition not doing an ALL_INTO_ONE\n-\t\t\t * repack (hence no packs will be deleted).\n-\t\t\t * Otherwise we must check for and exclude any\n-\t\t\t * packs which are enqueued for deletion.\n-\t\t\t *\n-\t\t\t * So we could omit the conditional below in the\n-\t\t\t * --geometric case, but doing so is unnecessary\n-\t\t\t *  since no packs are marked as pending\n-\t\t\t *  deletion (since we only call\n-\t\t\t *  `existing_packs_mark_for_deletion()` when\n-\t\t\t *  doing an all-into-one repack).\n-\t\t\t */\n-\t\t\tif (existing_pack_is_marked_for_deletion(item))\n-\t\t\t\tcontinue;\n-\n-\t\t\tstrbuf_reset(&buf);\n-\t\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n-\t\t\tstring_list_insert(include, buf.buf);\n-\t\t}\n-\t} else {\n-\t\t/*\n-\t\t * Modern versions of Git (with the appropriate\n-\t\t * configuration setting) will write new copies of\n-\t\t * once-cruft objects when doing a --geometric repack.\n-\t\t *\n-\t\t * If the MIDX has no cruft pack, new packs written\n-\t\t * during a --geometric repack will not rely on the\n-\t\t * cruft pack to form a reachability closure, so we can\n-\t\t * avoid including them in the MIDX in that case.\n-\t\t */\n-\t\t;\n-\t}\n-\n-\tstrbuf_release(&buf);\n-}\n-\n-static void remove_redundant_bitmaps(struct string_list *include,\n-\t\t\t\t     const char *packdir)\n-{\n-\tstruct strbuf path = STRBUF_INIT;\n-\tstruct string_list_item *item;\n-\tsize_t packdir_len;\n-\n-\tstrbuf_addstr(&path, packdir);\n-\tstrbuf_addch(&path, '/');\n-\tpackdir_len = path.len;\n-\n-\t/*\n-\t * Remove any pack bitmaps corresponding to packs which are now\n-\t * included in the MIDX.\n-\t */\n-\tfor_each_string_list_item(item, include) {\n-\t\tstrbuf_addstr(&path, item->string);\n-\t\tstrbuf_strip_suffix(&path, \".idx\");\n-\t\tstrbuf_addstr(&path, \".bitmap\");\n-\n-\t\tif (unlink(path.buf) && errno != ENOENT)\n-\t\t\twarning_errno(_(\"could not remove stale bitmap: %s\"),\n-\t\t\t\t      path.buf);\n-\n-\t\tstrbuf_setlen(&path, packdir_len);\n-\t}\n-\tstrbuf_release(&path);\n-}\n-\n-static int write_midx_included_packs(struct repack_write_midx_opts *opts)\n-{\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tstruct string_list include = STRING_LIST_INIT_DUP;\n-\tstruct string_list_item *item;\n-\tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n-\tFILE *in;\n-\tint ret = 0;\n-\n-\tmidx_included_packs(&include, opts);\n-\tif (!include.nr)\n-\t\tgoto done;\n-\n-\tcmd.in = -1;\n-\tcmd.git_cmd = 1;\n-\n-\tstrvec_push(&cmd.args, \"multi-pack-index\");\n-\tstrvec_pushl(&cmd.args, \"write\", \"--stdin-packs\", NULL);\n-\n-\tif (opts->show_progress)\n-\t\tstrvec_push(&cmd.args, \"--progress\");\n-\telse\n-\t\tstrvec_push(&cmd.args, \"--no-progress\");\n-\n-\tif (opts->write_bitmaps)\n-\t\tstrvec_push(&cmd.args, \"--bitmap\");\n-\n-\tif (preferred)\n-\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=%s\",\n-\t\t\t     pack_basename(preferred));\n-\telse if (opts->names->nr) {\n-\t\t/* The largest pack was repacked, meaning that either\n-\t\t * one or two packs exist depending on whether the\n-\t\t * repository has a cruft pack or not.\n-\t\t *\n-\t\t * Select the non-cruft one as preferred to encourage\n-\t\t * pack-reuse among packs containing reachable objects\n-\t\t * over unreachable ones.\n-\t\t *\n-\t\t * (Note we could write multiple packs here if\n-\t\t * `--max-pack-size` was given, but any one of them\n-\t\t * will suffice, so pick the first one.)\n-\t\t */\n-\t\tfor_each_string_list_item(item, opts->names) {\n-\t\t\tstruct generated_pack *pack = item->util;\n-\t\t\tif (generated_pack_has_ext(pack, \".mtimes\"))\n-\t\t\t\tcontinue;\n-\n-\t\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=pack-%s.pack\",\n-\t\t\t\t     item->string);\n-\t\t\tbreak;\n-\t\t}\n-\t} else {\n-\t\t/*\n-\t\t * No packs were kept, and no packs were written. The\n-\t\t * only thing remaining are .keep packs (unless\n-\t\t * --pack-kept-objects was given).\n-\t\t *\n-\t\t * Set the `--preferred-pack` arbitrarily here.\n-\t\t */\n-\t\t;\n-\t}\n-\n-\tif (opts->refs_snapshot)\n-\t\tstrvec_pushf(&cmd.args, \"--refs-snapshot=%s\",\n-\t\t\t     opts->refs_snapshot);\n-\n-\tret = start_command(&cmd);\n-\tif (ret)\n-\t\tgoto done;\n-\n-\tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, &include)\n-\t\tfprintf(in, \"%s\\n\", item->string);\n-\tfclose(in);\n-\n-\tret = finish_command(&cmd);\n-done:\n-\tif (!ret && opts->write_bitmaps)\n-\t\tremove_redundant_bitmaps(&include, opts->packdir);\n-\n-\tstring_list_clear(&include, 0);\n-\n-\treturn ret;\n-}\n-\n static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t\t\t   struct child_process *cmd,\n \t\t\t\t   struct string_list *names,\ndiff --git a/repack-midx.c b/repack-midx.c\nindex 354df729a5..6f6202c5bc 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -6,6 +6,7 @@\n #include \"oidset.h\"\n #include \"pack-bitmap.h\"\n #include \"refs.h\"\n+#include \"run-command.h\"\n #include \"tempfile.h\"\n \n struct midx_snapshot_ref_data {\n@@ -75,3 +76,297 @@ void midx_snapshot_refs(struct repository *repo, struct tempfile *f)\n \n \toidset_clear(&data.seen);\n }\n+\n+static int midx_has_unknown_packs(struct string_list *include,\n+\t\t\t\t  struct pack_geometry *geometry,\n+\t\t\t\t  struct existing_packs *existing)\n+{\n+\tstruct string_list_item *item;\n+\n+\tstring_list_sort(include);\n+\n+\tfor_each_string_list_item(item, &existing->midx_packs) {\n+\t\tconst char *pack_name = item->string;\n+\n+\t\t/*\n+\t\t * Determine whether or not each MIDX'd pack from the existing\n+\t\t * MIDX (if any) is represented in the new MIDX. For each pack\n+\t\t * in the MIDX, it must either be:\n+\t\t *\n+\t\t *  - In the \"include\" list of packs to be included in the new\n+\t\t *    MIDX. Note this function is called before the include\n+\t\t *    list is populated with any cruft pack(s).\n+\t\t *\n+\t\t *  - Below the geometric split line (if using pack geometry),\n+\t\t *    indicating that the pack won't be included in the new\n+\t\t *    MIDX, but its contents were rolled up as part of the\n+\t\t *    geometric repack.\n+\t\t *\n+\t\t *  - In the existing non-kept packs list (if not using pack\n+\t\t *    geometry), and marked as non-deleted.\n+\t\t */\n+\t\tif (string_list_has_string(include, pack_name)) {\n+\t\t\tcontinue;\n+\t\t} else if (geometry) {\n+\t\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\t\tuint32_t j;\n+\n+\t\t\tfor (j = 0; j < geometry->split; j++) {\n+\t\t\t\tstrbuf_reset(&buf);\n+\t\t\t\tstrbuf_addstr(&buf, pack_basename(geometry->pack[j]));\n+\t\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\t\t\t\tstrbuf_addstr(&buf, \".idx\");\n+\n+\t\t\t\tif (!strcmp(pack_name, buf.buf)) {\n+\t\t\t\t\tstrbuf_release(&buf);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tstrbuf_release(&buf);\n+\n+\t\t\tif (j < geometry->split)\n+\t\t\t\tcontinue;\n+\t\t} else {\n+\t\t\tstruct string_list_item *item;\n+\n+\t\t\titem = string_list_lookup(&existing->non_kept_packs,\n+\t\t\t\t\t\t  pack_name);\n+\t\t\tif (item && !existing_pack_is_marked_for_deletion(item))\n+\t\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/*\n+\t\t * If we got to this point, the MIDX includes some pack that we\n+\t\t * don't know about.\n+\t\t */\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void midx_included_packs(struct string_list *include,\n+\t\t\t\tstruct repack_write_midx_opts *opts)\n+{\n+\tstruct existing_packs *existing = opts->existing;\n+\tstruct pack_geometry *geometry = opts->geometry;\n+\tstruct string_list *names = opts->names;\n+\tstruct string_list_item *item;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tfor_each_string_list_item(item, &existing->kept_packs) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n+\t\tstring_list_insert(include, buf.buf);\n+\t}\n+\n+\tfor_each_string_list_item(item, names) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addf(&buf, \"pack-%s.idx\", item->string);\n+\t\tstring_list_insert(include, buf.buf);\n+\t}\n+\n+\tif (geometry->split_factor) {\n+\t\tuint32_t i;\n+\n+\t\tfor (i = geometry->split; i < geometry->pack_nr; i++) {\n+\t\t\tstruct packed_git *p = geometry->pack[i];\n+\n+\t\t\t/*\n+\t\t\t * The multi-pack index never refers to packfiles part\n+\t\t\t * of an alternate object database, so we skip these.\n+\t\t\t * While git-multi-pack-index(1) would silently ignore\n+\t\t\t * them anyway, this allows us to skip executing the\n+\t\t\t * command completely when we have only non-local\n+\t\t\t * packfiles.\n+\t\t\t */\n+\t\t\tif (!p->pack_local)\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addstr(&buf, pack_basename(p));\n+\t\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\t\t\tstrbuf_addstr(&buf, \".idx\");\n+\n+\t\t\tstring_list_insert(include, buf.buf);\n+\t\t}\n+\t} else {\n+\t\tfor_each_string_list_item(item, &existing->non_kept_packs) {\n+\t\t\tif (existing_pack_is_marked_for_deletion(item))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n+\t\t\tstring_list_insert(include, buf.buf);\n+\t\t}\n+\t}\n+\n+\tif (opts->midx_must_contain_cruft ||\n+\t    midx_has_unknown_packs(include, geometry, existing)) {\n+\t\t/*\n+\t\t * If there are one or more unknown pack(s) present (see\n+\t\t * midx_has_unknown_packs() for what makes a pack\n+\t\t * \"unknown\") in the MIDX before the repack, keep them\n+\t\t * as they may be required to form a reachability\n+\t\t * closure if the MIDX is bitmapped.\n+\t\t *\n+\t\t * For example, a cruft pack can be required to form a\n+\t\t * reachability closure if the MIDX is bitmapped and one\n+\t\t * or more of the bitmap's selected commits reaches a\n+\t\t * once-cruft object that was later made reachable.\n+\t\t */\n+\t\tfor_each_string_list_item(item, &existing->cruft_packs) {\n+\t\t\t/*\n+\t\t\t * When doing a --geometric repack, there is no\n+\t\t\t * need to check for deleted packs, since we're\n+\t\t\t * by definition not doing an ALL_INTO_ONE\n+\t\t\t * repack (hence no packs will be deleted).\n+\t\t\t * Otherwise we must check for and exclude any\n+\t\t\t * packs which are enqueued for deletion.\n+\t\t\t *\n+\t\t\t * So we could omit the conditional below in the\n+\t\t\t * --geometric case, but doing so is unnecessary\n+\t\t\t *  since no packs are marked as pending\n+\t\t\t *  deletion (since we only call\n+\t\t\t *  `existing_packs_mark_for_deletion()` when\n+\t\t\t *  doing an all-into-one repack).\n+\t\t\t */\n+\t\t\tif (existing_pack_is_marked_for_deletion(item))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_reset(&buf);\n+\t\t\tstrbuf_addf(&buf, \"%s.idx\", item->string);\n+\t\t\tstring_list_insert(include, buf.buf);\n+\t\t}\n+\t} else {\n+\t\t/*\n+\t\t * Modern versions of Git (with the appropriate\n+\t\t * configuration setting) will write new copies of\n+\t\t * once-cruft objects when doing a --geometric repack.\n+\t\t *\n+\t\t * If the MIDX has no cruft pack, new packs written\n+\t\t * during a --geometric repack will not rely on the\n+\t\t * cruft pack to form a reachability closure, so we can\n+\t\t * avoid including them in the MIDX in that case.\n+\t\t */\n+\t\t;\n+\t}\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+static void remove_redundant_bitmaps(struct string_list *include,\n+\t\t\t\t     const char *packdir)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\tsize_t packdir_len;\n+\n+\tstrbuf_addstr(&path, packdir);\n+\tstrbuf_addch(&path, '/');\n+\tpackdir_len = path.len;\n+\n+\t/*\n+\t * Remove any pack bitmaps corresponding to packs which are now\n+\t * included in the MIDX.\n+\t */\n+\tfor_each_string_list_item(item, include) {\n+\t\tstrbuf_addstr(&path, item->string);\n+\t\tstrbuf_strip_suffix(&path, \".idx\");\n+\t\tstrbuf_addstr(&path, \".bitmap\");\n+\n+\t\tif (unlink(path.buf) && errno != ENOENT)\n+\t\t\twarning_errno(_(\"could not remove stale bitmap: %s\"),\n+\t\t\t\t      path.buf);\n+\n+\t\tstrbuf_setlen(&path, packdir_len);\n+\t}\n+\tstrbuf_release(&path);\n+}\n+\n+int write_midx_included_packs(struct repack_write_midx_opts *opts)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct string_list include = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct packed_git *preferred = pack_geometry_preferred_pack(opts->geometry);\n+\tFILE *in;\n+\tint ret = 0;\n+\n+\tmidx_included_packs(&include, opts);\n+\tif (!include.nr)\n+\t\tgoto done;\n+\n+\tcmd.in = -1;\n+\tcmd.git_cmd = 1;\n+\n+\tstrvec_push(&cmd.args, \"multi-pack-index\");\n+\tstrvec_pushl(&cmd.args, \"write\", \"--stdin-packs\", NULL);\n+\n+\tif (opts->show_progress)\n+\t\tstrvec_push(&cmd.args, \"--progress\");\n+\telse\n+\t\tstrvec_push(&cmd.args, \"--no-progress\");\n+\n+\tif (opts->write_bitmaps)\n+\t\tstrvec_push(&cmd.args, \"--bitmap\");\n+\n+\tif (preferred)\n+\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=%s\",\n+\t\t\t     pack_basename(preferred));\n+\telse if (opts->names->nr) {\n+\t\t/* The largest pack was repacked, meaning that either\n+\t\t * one or two packs exist depending on whether the\n+\t\t * repository has a cruft pack or not.\n+\t\t *\n+\t\t * Select the non-cruft one as preferred to encourage\n+\t\t * pack-reuse among packs containing reachable objects\n+\t\t * over unreachable ones.\n+\t\t *\n+\t\t * (Note we could write multiple packs here if\n+\t\t * `--max-pack-size` was given, but any one of them\n+\t\t * will suffice, so pick the first one.)\n+\t\t */\n+\t\tfor_each_string_list_item(item, opts->names) {\n+\t\t\tstruct generated_pack *pack = item->util;\n+\t\t\tif (generated_pack_has_ext(pack, \".mtimes\"))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrvec_pushf(&cmd.args, \"--preferred-pack=pack-%s.pack\",\n+\t\t\t\t     item->string);\n+\t\t\tbreak;\n+\t\t}\n+\t} else {\n+\t\t/*\n+\t\t * No packs were kept, and no packs were written. The\n+\t\t * only thing remaining are .keep packs (unless\n+\t\t * --pack-kept-objects was given).\n+\t\t *\n+\t\t * Set the `--preferred-pack` arbitrarily here.\n+\t\t */\n+\t\t;\n+\t}\n+\n+\tif (opts->refs_snapshot)\n+\t\tstrvec_pushf(&cmd.args, \"--refs-snapshot=%s\",\n+\t\t\t     opts->refs_snapshot);\n+\n+\tret = start_command(&cmd);\n+\tif (ret)\n+\t\tgoto done;\n+\n+\tin = xfdopen(cmd.in, \"w\");\n+\tfor_each_string_list_item(item, &include)\n+\t\tfprintf(in, \"%s\\n\", item->string);\n+\tfclose(in);\n+\n+\tret = finish_command(&cmd);\n+done:\n+\tif (!ret && opts->write_bitmaps)\n+\t\tremove_redundant_bitmaps(&include, opts->packdir);\n+\n+\tstring_list_clear(&include, 0);\n+\n+\treturn ret;\n+}\ndiff --git a/repack.h b/repack.h\nindex 6aa5b4e0f0..25a31ac0a0 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -101,6 +101,18 @@ void pack_geometry_release(struct pack_geometry *geometry);\n \n struct tempfile;\n \n+struct repack_write_midx_opts {\n+\tstruct existing_packs *existing;\n+\tstruct pack_geometry *geometry;\n+\tstruct string_list *names;\n+\tconst char *refs_snapshot;\n+\tconst char *packdir;\n+\tint show_progress;\n+\tint write_bitmaps;\n+\tint midx_must_contain_cruft;\n+};\n+\n void midx_snapshot_refs(struct repository *repo, struct tempfile *f);\n+int write_midx_included_packs(struct repack_write_midx_opts *opts);\n \n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528911","messageId":"0cb6e78856c8e365668e21f7cd8ad6de5f5b3922.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 40/49] builtin/repack.c: introduce `struct write_pack_opts`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:16Z","receivedAt":"2025-10-15T22:29:18Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"There are various functions within the 'repack' builtin which are\nresponsible for writing different kinds of packs. They include:\n\n - `static int write_filtered_pack(...)`\n - `static int write_cruft_pack(...)`\n\nas well as the function `finish_pack_objects_cmd()`, which is\nresponsible for finalizing a new pack write, and recording the checksum\nof its contents in the 'names' list.\n\nBoth of these `write_` functions have a few things in common. They both\ntake a pointer to the 'pack_objects_args' struct, as well as a pair of\ncharacter pointers for `destination` and `pack_prefix`.\n\nInstead of repeating those arguments for each function, let's extract an\noptions struct called \"write_pack_opts\" which has these three parameters\nas member fields. While we're at it, add fields for \"packdir,\" and\n\"packtmp\", both of which are static variables within the builtin, and\nneed to be read from within these two functions.\n\nThis will shorten the list of parameters that callers have to provide to\n`write_filtered_pack()`, avoid ambiguity when passing multiple variables\nof the same type, and provide a unified interface for the two functions\nmentioned earlier.\n\n(Note that \"pack_prefix\" can be derived on the fly as a function of\n\"packdir\" and \"packtmp\", making it unnecessary to store \"pack_prefix\"\nexplicitly. This commit ignores that potential cleanup in the name of\ndoing as few things as possible, but a later commit will make that\nchange.)\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 28 +++++++++++++++-------------\n repack.h         |  8 ++++++++\n 2 files changed, 23 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 2f49a18283..45ce469898 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -138,9 +138,7 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \treturn finish_command(cmd);\n }\n \n-static int write_filtered_pack(const struct pack_objects_args *args,\n-\t\t\t       const char *destination,\n-\t\t\t       const char *pack_prefix,\n+static int write_filtered_pack(const struct write_pack_opts *opts,\n \t\t\t       struct existing_packs *existing,\n \t\t\t       struct string_list *names)\n {\n@@ -150,9 +148,9 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n \tint ret;\n \tconst char *caret;\n \tconst char *scratch;\n-\tint local = skip_prefix(destination, packdir, &scratch);\n+\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n \n-\tprepare_pack_objects(&cmd, args, destination);\n+\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n \n \tstrvec_push(&cmd.args, \"--stdin-packs\");\n \n@@ -175,7 +173,7 @@ static int write_filtered_pack(const struct pack_objects_args *args,\n \t */\n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"^%s-%s.pack\\n\", pack_prefix, item->string);\n+\t\tfprintf(in, \"^%s-%s.pack\\n\", opts->pack_prefix, item->string);\n \tfor_each_string_list_item(item, &existing->non_kept_packs)\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfor_each_string_list_item(item, &existing->cruft_packs)\n@@ -665,14 +663,18 @@ int cmd_repack(int argc,\n \t}\n \n \tif (po_args.filter_options.choice) {\n-\t\tif (!filter_to)\n-\t\t\tfilter_to = packtmp;\n+\t\tstruct write_pack_opts opts = {\n+\t\t\t.po_args = &po_args,\n+\t\t\t.destination = filter_to,\n+\t\t\t.pack_prefix = find_pack_prefix(packdir, packtmp),\n+\t\t\t.packdir = packdir,\n+\t\t\t.packtmp = packtmp,\n+\t\t};\n \n-\t\tret = write_filtered_pack(&po_args,\n-\t\t\t\t\t  filter_to,\n-\t\t\t\t\t  find_pack_prefix(packdir, packtmp),\n-\t\t\t\t\t  &existing,\n-\t\t\t\t\t  &names);\n+\t\tif (!opts.destination)\n+\t\t\topts.destination = packtmp;\n+\n+\t\tret = write_filtered_pack(&opts, &existing, &names);\n \t\tif (ret)\n \t\t\tgoto cleanup;\n \t}\ndiff --git a/repack.h b/repack.h\nindex 25a31ac0a0..6ef503f623 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -32,6 +32,14 @@ void pack_objects_args_release(struct pack_objects_args *args);\n void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \t\t\t\t  const char *base_name);\n \n+struct write_pack_opts {\n+\tstruct pack_objects_args *po_args;\n+\tconst char *destination;\n+\tconst char *pack_prefix;\n+\tconst char *packdir;\n+\tconst char *packtmp;\n+};\n+\n struct repository;\n struct packed_git;\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528912","messageId":"1a9b578fdd62464fb8a26b0c1668e80dc62a8dfb.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 41/49] builtin/repack.c: use `write_pack_opts` within `write_cruft_pack()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:19Z","receivedAt":"2025-10-15T22:29:21Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Similar to the changes made in the previous commit to\n`write_filtered_pack()`, teach `write_cruft_pack()` to take a\n`write_pack_opts` struct and use that where possible.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 27 ++++++++++++++-------------\n 1 file changed, 14 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 45ce469898..7295135ec2 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -221,9 +221,7 @@ static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n \tstrbuf_release(&buf);\n }\n \n-static int write_cruft_pack(const struct pack_objects_args *args,\n-\t\t\t    const char *destination,\n-\t\t\t    const char *pack_prefix,\n+static int write_cruft_pack(const struct write_pack_opts *opts,\n \t\t\t    const char *cruft_expiration,\n \t\t\t    unsigned long combine_cruft_below_size,\n \t\t\t    struct string_list *names,\n@@ -234,9 +232,9 @@ static int write_cruft_pack(const struct pack_objects_args *args,\n \tFILE *in;\n \tint ret;\n \tconst char *scratch;\n-\tint local = skip_prefix(destination, packdir, &scratch);\n+\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n \n-\tprepare_pack_objects(&cmd, args, destination);\n+\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n \n \tstrvec_push(&cmd.args, \"--cruft\");\n \tif (cruft_expiration)\n@@ -267,7 +265,7 @@ static int write_cruft_pack(const struct pack_objects_args *args,\n \t */\n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"%s-%s.pack\\n\", pack_prefix, item->string);\n+\t\tfprintf(in, \"%s-%s.pack\\n\", opts->pack_prefix, item->string);\n \tif (combine_cruft_below_size && !cruft_expiration) {\n \t\tcombine_small_cruft_packs(in, combine_cruft_below_size,\n \t\t\t\t\t  existing);\n@@ -599,6 +597,13 @@ int cmd_repack(int argc,\n \n \tif (pack_everything & PACK_CRUFT) {\n \t\tconst char *pack_prefix = find_pack_prefix(packdir, packtmp);\n+\t\tstruct write_pack_opts opts = {\n+\t\t\t.po_args = &cruft_po_args,\n+\t\t\t.destination = packtmp,\n+\t\t\t.pack_prefix = pack_prefix,\n+\t\t\t.packtmp = packtmp,\n+\t\t\t.packdir = packdir,\n+\t\t};\n \n \t\tif (!cruft_po_args.window)\n \t\t\tcruft_po_args.window = xstrdup_or_null(po_args.window);\n@@ -615,8 +620,7 @@ int cmd_repack(int argc,\n \t\tcruft_po_args.quiet = po_args.quiet;\n \t\tcruft_po_args.delta_base_offset = po_args.delta_base_offset;\n \n-\t\tret = write_cruft_pack(&cruft_po_args, packtmp, pack_prefix,\n-\t\t\t\t       cruft_expiration,\n+\t\tret = write_cruft_pack(&opts, cruft_expiration,\n \t\t\t\t       combine_cruft_below_size, &names,\n \t\t\t\t       &existing);\n \t\tif (ret)\n@@ -651,11 +655,8 @@ int cmd_repack(int argc,\n \t\t\t * pack, but rather removing all cruft packs from the\n \t\t\t * main repository regardless of size.\n \t\t\t */\n-\t\t\tret = write_cruft_pack(&cruft_po_args, expire_to,\n-\t\t\t\t\t       pack_prefix,\n-\t\t\t\t\t       NULL,\n-\t\t\t\t\t       0ul,\n-\t\t\t\t\t       &names,\n+\t\t\topts.destination = expire_to;\n+\t\t\tret = write_cruft_pack(&opts, NULL, 0ul, &names,\n \t\t\t\t\t       &existing);\n \t\t\tif (ret)\n \t\t\t\tgoto cleanup;\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528913","messageId":"43116b8942a487724d0172a7977d7c92533143e3.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 42/49] repack: move `find_pack_prefix()` out of the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:21Z","receivedAt":"2025-10-15T22:29:24Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Both callers within the repack builtin which call functions that take a\n'write_pack_opts' structure have the following pattern:\n\n    struct write_pack_opts opts = {\n        .packdir = packdir,\n        .packtmp = packtmp,\n        .pack_prefix = find_pack_prefix(packdir, packtmp),\n        /* ... */\n    };\n    int ret = write_some_kind_of_pack(&opts, /* ... */);\n\n, but both \"packdir\" and \"packtmp\" are fields within the write_pack_opts\nstruct itself!\n\nInstead of also computing the pack_prefix ahead of time, let's have the\ncallees compute it themselves by moving `find_pack_prefix()` out of the\nrepack builtin, and have it take a write_pack_opts pointer instead of\nthe \"packdir\" and \"packtmp\" fields directly.\n\nThis avoids the callers having to do some prep work that is common\nbetween the two of them, but also avoids the potential pitfall of\naccidentally writing:\n\n    .pack_prefix = find_pack_prefix(packtmp, packdir),\n\n(which is well-typed) when the caller meant to instead write:\n\n    .pack_prefix = find_pack_prefix(packdir, packtmp),\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 20 ++++----------------\n repack.c         | 11 +++++++++++\n repack.h         |  3 ++-\n 3 files changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 7295135ec2..b21799c650 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -149,6 +149,7 @@ static int write_filtered_pack(const struct write_pack_opts *opts,\n \tconst char *caret;\n \tconst char *scratch;\n \tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n+\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n \n@@ -173,7 +174,7 @@ static int write_filtered_pack(const struct write_pack_opts *opts,\n \t */\n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"^%s-%s.pack\\n\", opts->pack_prefix, item->string);\n+\t\tfprintf(in, \"^%s-%s.pack\\n\", pack_prefix, item->string);\n \tfor_each_string_list_item(item, &existing->non_kept_packs)\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfor_each_string_list_item(item, &existing->cruft_packs)\n@@ -233,6 +234,7 @@ static int write_cruft_pack(const struct write_pack_opts *opts,\n \tint ret;\n \tconst char *scratch;\n \tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n+\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n \n@@ -265,7 +267,7 @@ static int write_cruft_pack(const struct write_pack_opts *opts,\n \t */\n \tin = xfdopen(cmd.in, \"w\");\n \tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"%s-%s.pack\\n\", opts->pack_prefix, item->string);\n+\t\tfprintf(in, \"%s-%s.pack\\n\", pack_prefix, item->string);\n \tif (combine_cruft_below_size && !cruft_expiration) {\n \t\tcombine_small_cruft_packs(in, combine_cruft_below_size,\n \t\t\t\t\t  existing);\n@@ -283,17 +285,6 @@ static int write_cruft_pack(const struct write_pack_opts *opts,\n \t\t\t\t       local);\n }\n \n-static const char *find_pack_prefix(const char *packdir, const char *packtmp)\n-{\n-\tconst char *pack_prefix;\n-\tif (!skip_prefix(packtmp, packdir, &pack_prefix))\n-\t\tdie(_(\"pack prefix %s does not begin with objdir %s\"),\n-\t\t    packtmp, packdir);\n-\tif (*pack_prefix == '/')\n-\t\tpack_prefix++;\n-\treturn pack_prefix;\n-}\n-\n int cmd_repack(int argc,\n \t       const char **argv,\n \t       const char *prefix,\n@@ -596,11 +587,9 @@ int cmd_repack(int argc,\n \t}\n \n \tif (pack_everything & PACK_CRUFT) {\n-\t\tconst char *pack_prefix = find_pack_prefix(packdir, packtmp);\n \t\tstruct write_pack_opts opts = {\n \t\t\t.po_args = &cruft_po_args,\n \t\t\t.destination = packtmp,\n-\t\t\t.pack_prefix = pack_prefix,\n \t\t\t.packtmp = packtmp,\n \t\t\t.packdir = packdir,\n \t\t};\n@@ -667,7 +656,6 @@ int cmd_repack(int argc,\n \t\tstruct write_pack_opts opts = {\n \t\t\t.po_args = &po_args,\n \t\t\t.destination = filter_to,\n-\t\t\t.pack_prefix = find_pack_prefix(packdir, packtmp),\n \t\t\t.packdir = packdir,\n \t\t\t.packtmp = packtmp,\n \t\t};\ndiff --git a/repack.c b/repack.c\nindex 1d485e0112..19fd1d6d5b 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -66,6 +66,17 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n \tstrbuf_release(&buf);\n }\n \n+const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts)\n+{\n+\tconst char *pack_prefix;\n+\tif (!skip_prefix(opts->packtmp, opts->packdir, &pack_prefix))\n+\t\tdie(_(\"pack prefix %s does not begin with objdir %s\"),\n+\t\t    opts->packtmp, opts->packdir);\n+\tif (*pack_prefix == '/')\n+\t\tpack_prefix++;\n+\treturn pack_prefix;\n+}\n+\n #define DELETE_PACK 1\n #define RETAIN_PACK 2\n \ndiff --git a/repack.h b/repack.h\nindex 6ef503f623..5852e2407f 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -35,11 +35,12 @@ void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,\n struct write_pack_opts {\n \tstruct pack_objects_args *po_args;\n \tconst char *destination;\n-\tconst char *pack_prefix;\n \tconst char *packdir;\n \tconst char *packtmp;\n };\n \n+const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts);\n+\n struct repository;\n struct packed_git;\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528914","messageId":"5e3b615993e7f672a71eeca53235ee34aa799609.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 43/49] repack: extract `write_pack_opts_is_local()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:24Z","receivedAt":"2025-10-15T22:29:26Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Similar to the previous commit, the functions `write_cruft_pack()` and\n`write_filtered_pack()` both compute a \"local\" variable via the exact\nsame mechanism:\n\n    const char *scratch;\n    int local = skip_prefix(opts->destination, opts->packdir, &scratch);\n\nNot only does this cause us to repeat the same pair of lines, it also\nintroduces an unnecessary \"scratch\" variable that is common between both\nfunctions.\n\nInstead of repeating ourselves, let's extract that functionality into a\nnew function in the repack.h API called \"write_pack_opts_is_local()\".\nThat function takes a pointer to a \"struct write_pack_opts\" (which has\nas fields both \"destination\" and \"packdir\"), and can encapsulate the\ndangling \"scratch\" field.\n\nExtract that function and make it visible within the repack.h API, and\nuse it within both `write_cruft_pack()` and `write_filtered_pack()`.\nWhile we're at it, match our modern conventions by returning a \"bool\"\ninstead of \"int\", and use `starts_with()` instead of `skip_prefix()` to\navoid storing the dummy \"scratch\" variable.\n\nThe remaining duplication (that is, that both `write_cruft_pack()` and\n`write_filtered_pack()` still both call `write_pack_opts_is_local()`)\nwill be addressed in the following commit.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 6 ++----\n repack.c         | 5 +++++\n repack.h         | 1 +\n 3 files changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex b21799c650..d1449cfe13 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -147,8 +147,7 @@ static int write_filtered_pack(const struct write_pack_opts *opts,\n \tFILE *in;\n \tint ret;\n \tconst char *caret;\n-\tconst char *scratch;\n-\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n+\tbool local = write_pack_opts_is_local(opts);\n \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n@@ -232,8 +231,7 @@ static int write_cruft_pack(const struct write_pack_opts *opts,\n \tstruct string_list_item *item;\n \tFILE *in;\n \tint ret;\n-\tconst char *scratch;\n-\tint local = skip_prefix(opts->destination, opts->packdir, &scratch);\n+\tbool local = write_pack_opts_is_local(opts);\n \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\ndiff --git a/repack.c b/repack.c\nindex 19fd1d6d5b..d2ee9f2460 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -77,6 +77,11 @@ const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts)\n \treturn pack_prefix;\n }\n \n+bool write_pack_opts_is_local(const struct write_pack_opts *opts)\n+{\n+\treturn starts_with(opts->destination, opts->packdir);\n+}\n+\n #define DELETE_PACK 1\n #define RETAIN_PACK 2\n \ndiff --git a/repack.h b/repack.h\nindex 5852e2407f..26d1954ae2 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -40,6 +40,7 @@ struct write_pack_opts {\n };\n \n const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts);\n+bool write_pack_opts_is_local(const struct write_pack_opts *opts);\n \n struct repository;\n struct packed_git;\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528915","messageId":"fcbe44d8bb815e87d21589299499681f9aa990de.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 44/49] builtin/repack.c: pass `write_pack_opts` to `finish_pack_objects_cmd()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:27Z","receivedAt":"2025-10-15T22:29:29Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"To prepare to move the `finish_pack_objects_cmd()` function out of the\nbuiltin and into the repack.h API, there are a couple of things we need\nto do first:\n\n - First, let's take advantage of `write_pack_opts_is_local()` function\n   introduced in the previous commit instead of passing \"local\"\n   explicitly.\n\n - Let's also avoid referring to the static 'packtmp' field within\n   builtin/repack.c by instead accessing it through the write_pack_opts\n   argument.\n\nThere are three callers which need to adjust themselves in order to\naccount for this change. The callers which reside in write_cruft_pack()\nand write_filtered_pack() both already have an \"opts\" in scope, so they\ncan pass it through transparently.\n\nThe other call (at the bottom of `cmd_repack()`) needs to initialize its\nown write_pack_opts to pass the necessary fields over to the direct call\nto `finish_pack_objects_cmd()`.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 32 ++++++++++++++++++++------------\n 1 file changed, 20 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex d1449cfe13..5f382aaf19 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -108,11 +108,12 @@ static int repack_config(const char *var, const char *value,\n }\n \n static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n+\t\t\t\t   const struct write_pack_opts *opts,\n \t\t\t\t   struct child_process *cmd,\n-\t\t\t\t   struct string_list *names,\n-\t\t\t\t   int local)\n+\t\t\t\t   struct string_list *names)\n {\n \tFILE *out;\n+\tbool local = write_pack_opts_is_local(opts);\n \tstruct strbuf line = STRBUF_INIT;\n \n \tout = xfdopen(cmd->out, \"r\");\n@@ -128,7 +129,8 @@ static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n \t\t */\n \t\tif (local) {\n \t\t\titem = string_list_append(names, line.buf);\n-\t\t\titem->util = generated_pack_populate(line.buf, packtmp);\n+\t\t\titem->util = generated_pack_populate(line.buf,\n+\t\t\t\t\t\t\t     opts->packtmp);\n \t\t}\n \t}\n \tfclose(out);\n@@ -147,7 +149,6 @@ static int write_filtered_pack(const struct write_pack_opts *opts,\n \tFILE *in;\n \tint ret;\n \tconst char *caret;\n-\tbool local = write_pack_opts_is_local(opts);\n \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n@@ -183,8 +184,8 @@ static int write_filtered_pack(const struct write_pack_opts *opts,\n \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n \tfclose(in);\n \n-\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n-\t\t\t\t       local);\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n+\t\t\t\t       names);\n }\n \n static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n@@ -231,7 +232,6 @@ static int write_cruft_pack(const struct write_pack_opts *opts,\n \tstruct string_list_item *item;\n \tFILE *in;\n \tint ret;\n-\tbool local = write_pack_opts_is_local(opts);\n \tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n \n \tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n@@ -279,8 +279,8 @@ static int write_cruft_pack(const struct write_pack_opts *opts,\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfclose(in);\n \n-\treturn finish_pack_objects_cmd(existing->repo->hash_algo, &cmd, names,\n-\t\t\t\t       local);\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n+\t\t\t\t       names);\n }\n \n int cmd_repack(int argc,\n@@ -560,9 +560,17 @@ int cmd_repack(int argc,\n \t\tfclose(in);\n \t}\n \n-\tret = finish_pack_objects_cmd(repo->hash_algo, &cmd, &names, 1);\n-\tif (ret)\n-\t\tgoto cleanup;\n+\t{\n+\t\tstruct write_pack_opts opts = {\n+\t\t\t.packdir = packdir,\n+\t\t\t.destination = packdir,\n+\t\t\t.packtmp = packtmp,\n+\t\t};\n+\t\tret = finish_pack_objects_cmd(repo->hash_algo, &opts, &cmd,\n+\t\t\t\t\t      &names);\n+\t\tif (ret)\n+\t\t\tgoto cleanup;\n+\t}\n \n \tif (!names.nr) {\n \t\tif (!po_args.quiet)\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528916","messageId":"244260c6abfdd72e95dc19c965cf20b81ac91576.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 45/49] repack: move `finish_pack_objects_cmd()` out of the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:30Z","receivedAt":"2025-10-15T22:29:32Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar spirit as the previous commit(s), now that the function\n`finish_pack_objects_cmd()` has no explicit dependencies within the\nrepack builtin, let's extract it.\n\nThis prepares us to extract the remaining two functions within the\nrepack builtin that explicitly write packfiles, which are\n`write_cruft_pack()` and `write_filtered_pack()`, which will be done in\nthe future commits.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 33 ---------------------------------\n repack.c         | 33 +++++++++++++++++++++++++++++++++\n repack.h         |  5 +++++\n 3 files changed, 38 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 5f382aaf19..71abcfa0b7 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -107,39 +107,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-static int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n-\t\t\t\t   const struct write_pack_opts *opts,\n-\t\t\t\t   struct child_process *cmd,\n-\t\t\t\t   struct string_list *names)\n-{\n-\tFILE *out;\n-\tbool local = write_pack_opts_is_local(opts);\n-\tstruct strbuf line = STRBUF_INIT;\n-\n-\tout = xfdopen(cmd->out, \"r\");\n-\twhile (strbuf_getline_lf(&line, out) != EOF) {\n-\t\tstruct string_list_item *item;\n-\n-\t\tif (line.len != algop->hexsz)\n-\t\t\tdie(_(\"repack: Expecting full hex object ID lines only \"\n-\t\t\t      \"from pack-objects.\"));\n-\t\t/*\n-\t\t * Avoid putting packs written outside of the repository in the\n-\t\t * list of names.\n-\t\t */\n-\t\tif (local) {\n-\t\t\titem = string_list_append(names, line.buf);\n-\t\t\titem->util = generated_pack_populate(line.buf,\n-\t\t\t\t\t\t\t     opts->packtmp);\n-\t\t}\n-\t}\n-\tfclose(out);\n-\n-\tstrbuf_release(&line);\n-\n-\treturn finish_command(cmd);\n-}\n-\n static int write_filtered_pack(const struct write_pack_opts *opts,\n \t\t\t       struct existing_packs *existing,\n \t\t\t       struct string_list *names)\ndiff --git a/repack.c b/repack.c\nindex d2ee9f2460..2c478970f3 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -82,6 +82,39 @@ bool write_pack_opts_is_local(const struct write_pack_opts *opts)\n \treturn starts_with(opts->destination, opts->packdir);\n }\n \n+int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n+\t\t\t    const struct write_pack_opts *opts,\n+\t\t\t    struct child_process *cmd,\n+\t\t\t    struct string_list *names)\n+{\n+\tFILE *out;\n+\tbool local = write_pack_opts_is_local(opts);\n+\tstruct strbuf line = STRBUF_INIT;\n+\n+\tout = xfdopen(cmd->out, \"r\");\n+\twhile (strbuf_getline_lf(&line, out) != EOF) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tif (line.len != algop->hexsz)\n+\t\t\tdie(_(\"repack: Expecting full hex object ID lines only \"\n+\t\t\t      \"from pack-objects.\"));\n+\t\t/*\n+\t\t * Avoid putting packs written outside of the repository in the\n+\t\t * list of names.\n+\t\t */\n+\t\tif (local) {\n+\t\t\titem = string_list_append(names, line.buf);\n+\t\t\titem->util = generated_pack_populate(line.buf,\n+\t\t\t\t\t\t\t     opts->packtmp);\n+\t\t}\n+\t}\n+\tfclose(out);\n+\n+\tstrbuf_release(&line);\n+\n+\treturn finish_command(cmd);\n+}\n+\n #define DELETE_PACK 1\n #define RETAIN_PACK 2\n \ndiff --git a/repack.h b/repack.h\nindex 26d1954ae2..3244f601e2 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -42,6 +42,11 @@ struct write_pack_opts {\n const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts);\n bool write_pack_opts_is_local(const struct write_pack_opts *opts);\n \n+int finish_pack_objects_cmd(const struct git_hash_algo *algop,\n+\t\t\t    const struct write_pack_opts *opts,\n+\t\t\t    struct child_process *cmd,\n+\t\t\t    struct string_list *names);\n+\n struct repository;\n struct packed_git;\n \n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528917","messageId":"dbc317af27c58484dd462b433653a7a65b7b4e64.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 46/49] repack: move `pack_kept_objects` to `struct pack_objects_args`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:33Z","receivedAt":"2025-10-15T22:29:35Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The \"pack_kept_objects\" variable is defined as static to the repack\nbuiltin, but is inherently related to the pack-objects arguments that\nthe builtin uses when generating new packs.\n\nMove that field into the \"struct pack_objects_args\", and shuffle around\nwhere we append the corresponding command-line option when preparing a\npack-objects process. Specifically:\n\n - `write_cruft_pack()` always wants to pass \"--honor-pack-keep\", so\n   explicitly set the `pack_kept_objects` field to \"0\" when initializing\n   the `write_pack_opts` struct before calling `write_cruft_pack()`.\n\n - `write_filtered_pack()` no longer needs to handle writing the\n   command-line option \"--honor-pack-keep\" when preparing a pack-objects\n   process, since its call to `prepare_pack_objects()` will have already\n   taken care of that.\n\n   `write_filtered_pack()` also reads the `pack_kept_objects` field to\n   determine whether to write the existing kept packs with a leading \"^\"\n   character, so update that to read through the `po_args` pointer\n   instead.\n\n - `cmd_repack()` also no longer has to write the \"--honor-pack-keep\"\n   flag explicitly, since this is also handled via its call to\n   `prepare_pack_objects()`.\n\nSince there is a default value for \"pack_kept_objects\" that relies on\nwhether or not we are writing a bitmap (and not writing a MIDX), extract\na default initializer for `struct pack_objects_args` that keeps this\nconditional default behavior.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c  | 20 +++++++-------------\n repack-geometry.c |  5 ++---\n repack.c          |  2 ++\n repack.h          |  9 ++++++---\n 4 files changed, 17 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 71abcfa0b7..3c6d7e91fd 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -33,7 +33,6 @@\n #define RETAIN_PACK 2\n \n static int pack_everything;\n-static int pack_kept_objects = -1;\n static int write_bitmaps = -1;\n static int use_delta_islands;\n static int run_update_server_info = 1;\n@@ -68,7 +67,7 @@ static int repack_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"repack.packkeptobjects\")) {\n-\t\tpack_kept_objects = git_config_bool(var, value);\n+\t\tpo_args->pack_kept_objects = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"repack.writebitmaps\") ||\n@@ -122,8 +121,6 @@ static int write_filtered_pack(const struct write_pack_opts *opts,\n \n \tstrvec_push(&cmd.args, \"--stdin-packs\");\n \n-\tif (!pack_kept_objects)\n-\t\tstrvec_push(&cmd.args, \"--honor-pack-keep\");\n \tfor_each_string_list_item(item, &existing->kept_packs)\n \t\tstrvec_pushf(&cmd.args, \"--keep-pack=%s\", item->string);\n \n@@ -146,7 +143,7 @@ static int write_filtered_pack(const struct write_pack_opts *opts,\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n \tfor_each_string_list_item(item, &existing->cruft_packs)\n \t\tfprintf(in, \"%s.pack\\n\", item->string);\n-\tcaret = pack_kept_objects ? \"\" : \"^\";\n+\tcaret = opts->po_args->pack_kept_objects ? \"\" : \"^\";\n \tfor_each_string_list_item(item, &existing->kept_packs)\n \t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n \tfclose(in);\n@@ -208,7 +205,6 @@ static int write_cruft_pack(const struct write_pack_opts *opts,\n \t\tstrvec_pushf(&cmd.args, \"--cruft-expiration=%s\",\n \t\t\t     cruft_expiration);\n \n-\tstrvec_push(&cmd.args, \"--honor-pack-keep\");\n \tstrvec_push(&cmd.args, \"--non-empty\");\n \n \tcmd.in = -1;\n@@ -332,7 +328,7 @@ int cmd_repack(int argc,\n \t\tOPT_UNSIGNED(0, \"max-pack-size\", &po_args.max_pack_size,\n \t\t\t     N_(\"maximum size of each packfile\")),\n \t\tOPT_PARSE_LIST_OBJECTS_FILTER(&po_args.filter_options),\n-\t\tOPT_BOOL(0, \"pack-kept-objects\", &pack_kept_objects,\n+\t\tOPT_BOOL(0, \"pack-kept-objects\", &po_args.pack_kept_objects,\n \t\t\t\tN_(\"repack objects in packs marked with .keep\")),\n \t\tOPT_STRING_LIST(0, \"keep-pack\", &keep_pack_list, N_(\"name\"),\n \t\t\t\tN_(\"do not repack this pack\")),\n@@ -378,8 +374,8 @@ int cmd_repack(int argc,\n \t\t    (!(pack_everything & ALL_INTO_ONE) || !is_bare_repository()))\n \t\t\twrite_bitmaps = 0;\n \t}\n-\tif (pack_kept_objects < 0)\n-\t\tpack_kept_objects = write_bitmaps > 0 && !write_midx;\n+\tif (po_args.pack_kept_objects < 0)\n+\t\tpo_args.pack_kept_objects = write_bitmaps > 0 && !write_midx;\n \n \tif (write_bitmaps && !(pack_everything & ALL_INTO_ONE) && !write_midx)\n \t\tdie(_(incremental_bitmap_conflict_error));\n@@ -420,8 +416,7 @@ int cmd_repack(int argc,\n \tif (geometry.split_factor) {\n \t\tif (pack_everything)\n \t\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--geometric\", \"-A/-a\");\n-\t\tpack_geometry_init(&geometry, &existing, &po_args,\n-\t\t\t\t   pack_kept_objects);\n+\t\tpack_geometry_init(&geometry, &existing, &po_args);\n \t\tpack_geometry_split(&geometry);\n \t}\n \n@@ -430,8 +425,6 @@ int cmd_repack(int argc,\n \tshow_progress = !po_args.quiet && isatty(2);\n \n \tstrvec_push(&cmd.args, \"--keep-true-parents\");\n-\tif (!pack_kept_objects)\n-\t\tstrvec_push(&cmd.args, \"--honor-pack-keep\");\n \tfor (i = 0; i < keep_pack_list.nr; i++)\n \t\tstrvec_pushf(&cmd.args, \"--keep-pack=%s\",\n \t\t\t     keep_pack_list.items[i].string);\n@@ -581,6 +574,7 @@ int cmd_repack(int argc,\n \t\tcruft_po_args.local = po_args.local;\n \t\tcruft_po_args.quiet = po_args.quiet;\n \t\tcruft_po_args.delta_base_offset = po_args.delta_base_offset;\n+\t\tcruft_po_args.pack_kept_objects = 0;\n \n \t\tret = write_cruft_pack(&opts, cruft_expiration,\n \t\t\t\t       combine_cruft_below_size, &names,\ndiff --git a/repack-geometry.c b/repack-geometry.c\nindex f58f1fc7f0..e2f9794d7d 100644\n--- a/repack-geometry.c\n+++ b/repack-geometry.c\n@@ -27,8 +27,7 @@ static int pack_geometry_cmp(const void *va, const void *vb)\n \n void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\tstruct existing_packs *existing,\n-\t\t\tconst struct pack_objects_args *args,\n-\t\t\tint pack_kept_objects)\n+\t\t\tconst struct pack_objects_args *args)\n {\n \tstruct packfile_store *packs = existing->repo->objects->packfiles;\n \tstruct packed_git *p;\n@@ -43,7 +42,7 @@ void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\t */\n \t\t\tcontinue;\n \n-\t\tif (!pack_kept_objects) {\n+\t\tif (!args->pack_kept_objects) {\n \t\t\t/*\n \t\t\t * Any pack that has its pack_keep bit set will\n \t\t\t * appear in existing->kept_packs below, but\ndiff --git a/repack.c b/repack.c\nindex 2c478970f3..2ab33c665a 100644\n--- a/repack.c\n+++ b/repack.c\n@@ -38,6 +38,8 @@ void prepare_pack_objects(struct child_process *cmd,\n \t\tstrvec_push(&cmd->args,  \"--quiet\");\n \tif (args->delta_base_offset)\n \t\tstrvec_push(&cmd->args,  \"--delta-base-offset\");\n+\tif (!args->pack_kept_objects)\n+\t\tstrvec_push(&cmd->args,  \"--honor-pack-keep\");\n \tstrvec_push(&cmd->args, out);\n \tcmd->git_cmd = 1;\n \tcmd->out = -1;\ndiff --git a/repack.h b/repack.h\nindex 3244f601e2..0432379815 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -17,10 +17,14 @@ struct pack_objects_args {\n \tint name_hash_version;\n \tint path_walk;\n \tint delta_base_offset;\n+\tint pack_kept_objects;\n \tstruct list_objects_filter_options filter_options;\n };\n \n-#define PACK_OBJECTS_ARGS_INIT { .delta_base_offset = 1 }\n+#define PACK_OBJECTS_ARGS_INIT { \\\n+\t.delta_base_offset = 1, \\\n+\t.pack_kept_objects = -1, \\\n+}\n \n struct child_process;\n \n@@ -104,8 +108,7 @@ struct pack_geometry {\n \n void pack_geometry_init(struct pack_geometry *geometry,\n \t\t\tstruct existing_packs *existing,\n-\t\t\tconst struct pack_objects_args *args,\n-\t\t\tint pack_kept_objects);\n+\t\t\tconst struct pack_objects_args *args);\n void pack_geometry_split(struct pack_geometry *geometry);\n struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry);\n void pack_geometry_remove_redundant(struct pack_geometry *geometry,\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528918","messageId":"75233450562c585634bafb5a979337ebf5c2010d.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 47/49] repack: move `write_filtered_pack()` out of the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:35Z","receivedAt":"2025-10-15T22:29:38Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In a similar fashion as in previous commits, move the function\n`write_filtered_pack()` out of the builtin and into its own compilation\nunit.\n\nThis function is now part of the repack.h API, but implemented in its\nown \"repack-filtered.c\" unit as it is a separate component from other\nkinds of repacking operations.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile          |  1 +\n builtin/repack.c  | 46 ------------------------------------------\n meson.build       |  1 +\n repack-filtered.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++\n repack.h          |  4 ++++\n 5 files changed, 57 insertions(+), 46 deletions(-)\n create mode 100644 repack-filtered.c\n\ndiff --git a/Makefile b/Makefile\nindex 4b0a122428..42461ea91f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1250,6 +1250,7 @@ LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n+LIB_OBJS += repack-filtered.o\n LIB_OBJS += repack-geometry.o\n LIB_OBJS += repack-midx.o\n LIB_OBJS += repack-promisor.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 3c6d7e91fd..f65880d8f6 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -106,52 +106,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-static int write_filtered_pack(const struct write_pack_opts *opts,\n-\t\t\t       struct existing_packs *existing,\n-\t\t\t       struct string_list *names)\n-{\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tstruct string_list_item *item;\n-\tFILE *in;\n-\tint ret;\n-\tconst char *caret;\n-\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n-\n-\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n-\n-\tstrvec_push(&cmd.args, \"--stdin-packs\");\n-\n-\tfor_each_string_list_item(item, &existing->kept_packs)\n-\t\tstrvec_pushf(&cmd.args, \"--keep-pack=%s\", item->string);\n-\n-\tcmd.in = -1;\n-\n-\tret = start_command(&cmd);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/*\n-\t * Here 'names' contains only the pack(s) that were just\n-\t * written, which is exactly the packs we want to keep. Also\n-\t * 'existing_kept_packs' already contains the packs in\n-\t * 'keep_pack_list'.\n-\t */\n-\tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"^%s-%s.pack\\n\", pack_prefix, item->string);\n-\tfor_each_string_list_item(item, &existing->non_kept_packs)\n-\t\tfprintf(in, \"%s.pack\\n\", item->string);\n-\tfor_each_string_list_item(item, &existing->cruft_packs)\n-\t\tfprintf(in, \"%s.pack\\n\", item->string);\n-\tcaret = opts->po_args->pack_kept_objects ? \"\" : \"^\";\n-\tfor_each_string_list_item(item, &existing->kept_packs)\n-\t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n-\tfclose(in);\n-\n-\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n-\t\t\t\t       names);\n-}\n-\n static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n \t\t\t\t      struct existing_packs *existing)\n {\ndiff --git a/meson.build b/meson.build\nindex 0373d5c454..f5325b5941 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -463,6 +463,7 @@ libgit_sources = [\n   'reftable/writer.c',\n   'remote.c',\n   'repack.c',\n+  'repack-filtered.c',\n   'repack-geometry.c',\n   'repack-midx.c',\n   'repack-promisor.c',\ndiff --git a/repack-filtered.c b/repack-filtered.c\nnew file mode 100644\nindex 0000000000..edcf7667c5\n--- /dev/null\n+++ b/repack-filtered.c\n@@ -0,0 +1,51 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"repository.h\"\n+#include \"run-command.h\"\n+#include \"string-list.h\"\n+\n+int write_filtered_pack(const struct write_pack_opts *opts,\n+\t\t\tstruct existing_packs *existing,\n+\t\t\tstruct string_list *names)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct string_list_item *item;\n+\tFILE *in;\n+\tint ret;\n+\tconst char *caret;\n+\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n+\n+\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n+\n+\tstrvec_push(&cmd.args, \"--stdin-packs\");\n+\n+\tfor_each_string_list_item(item, &existing->kept_packs)\n+\t\tstrvec_pushf(&cmd.args, \"--keep-pack=%s\", item->string);\n+\n+\tcmd.in = -1;\n+\n+\tret = start_command(&cmd);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Here 'names' contains only the pack(s) that were just\n+\t * written, which is exactly the packs we want to keep. Also\n+\t * 'existing_kept_packs' already contains the packs in\n+\t * 'keep_pack_list'.\n+\t */\n+\tin = xfdopen(cmd.in, \"w\");\n+\tfor_each_string_list_item(item, names)\n+\t\tfprintf(in, \"^%s-%s.pack\\n\", pack_prefix, item->string);\n+\tfor_each_string_list_item(item, &existing->non_kept_packs)\n+\t\tfprintf(in, \"%s.pack\\n\", item->string);\n+\tfor_each_string_list_item(item, &existing->cruft_packs)\n+\t\tfprintf(in, \"%s.pack\\n\", item->string);\n+\tcaret = opts->po_args->pack_kept_objects ? \"\" : \"^\";\n+\tfor_each_string_list_item(item, &existing->kept_packs)\n+\t\tfprintf(in, \"%s%s.pack\\n\", caret, item->string);\n+\tfclose(in);\n+\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n+\t\t\t\t       names);\n+}\ndiff --git a/repack.h b/repack.h\nindex 0432379815..c790c90ef8 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -133,4 +133,8 @@ struct repack_write_midx_opts {\n void midx_snapshot_refs(struct repository *repo, struct tempfile *f);\n int write_midx_included_packs(struct repack_write_midx_opts *opts);\n \n+int write_filtered_pack(const struct write_pack_opts *opts,\n+\t\t\tstruct existing_packs *existing,\n+\t\t\tstruct string_list *names);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528919","messageId":"567ccd3d3e4e197a0fbf78780470ec4e5773970a.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 48/49] repack: move `write_cruft_pack()` out of the builtin","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:38Z","receivedAt":"2025-10-15T22:29:40Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"In an identical fashion as the previous commit, move the function\n`write_cruft_pack()` into its own compilation unit, and make the\nfunction visible through the repack.h API.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Makefile         |  1 +\n builtin/repack.c | 94 ---------------------------------------------\n meson.build      |  1 +\n repack-cruft.c   | 99 ++++++++++++++++++++++++++++++++++++++++++++++++\n repack.h         |  6 +++\n 5 files changed, 107 insertions(+), 94 deletions(-)\n create mode 100644 repack-cruft.c\n\ndiff --git a/Makefile b/Makefile\nindex 42461ea91f..fb965e6acb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1250,6 +1250,7 @@ LIB_OBJS += refs/ref-cache.o\n LIB_OBJS += refspec.o\n LIB_OBJS += remote.o\n LIB_OBJS += repack.o\n+LIB_OBJS += repack-cruft.o\n LIB_OBJS += repack-filtered.o\n LIB_OBJS += repack-geometry.o\n LIB_OBJS += repack-midx.o\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex f65880d8f6..a68c22f605 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -106,100 +106,6 @@ static int repack_config(const char *var, const char *value,\n \treturn git_default_config(var, value, ctx, cb);\n }\n \n-static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,\n-\t\t\t\t      struct existing_packs *existing)\n-{\n-\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n-\tstruct packed_git *p;\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tsize_t i;\n-\n-\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n-\t\tif (!(p->is_cruft && p->pack_local))\n-\t\t\tcontinue;\n-\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addstr(&buf, pack_basename(p));\n-\t\tstrbuf_strip_suffix(&buf, \".pack\");\n-\n-\t\tif (!string_list_has_string(&existing->cruft_packs, buf.buf))\n-\t\t\tcontinue;\n-\n-\t\tif (p->pack_size < combine_cruft_below_size) {\n-\t\t\tfprintf(in, \"-%s\\n\", pack_basename(p));\n-\t\t} else {\n-\t\t\texisting_packs_retain_cruft(existing, p);\n-\t\t\tfprintf(in, \"%s\\n\", pack_basename(p));\n-\t\t}\n-\t}\n-\n-\tfor (i = 0; i < existing->non_kept_packs.nr; i++)\n-\t\tfprintf(in, \"-%s.pack\\n\",\n-\t\t\texisting->non_kept_packs.items[i].string);\n-\n-\tstrbuf_release(&buf);\n-}\n-\n-static int write_cruft_pack(const struct write_pack_opts *opts,\n-\t\t\t    const char *cruft_expiration,\n-\t\t\t    unsigned long combine_cruft_below_size,\n-\t\t\t    struct string_list *names,\n-\t\t\t    struct existing_packs *existing)\n-{\n-\tstruct child_process cmd = CHILD_PROCESS_INIT;\n-\tstruct string_list_item *item;\n-\tFILE *in;\n-\tint ret;\n-\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n-\n-\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n-\n-\tstrvec_push(&cmd.args, \"--cruft\");\n-\tif (cruft_expiration)\n-\t\tstrvec_pushf(&cmd.args, \"--cruft-expiration=%s\",\n-\t\t\t     cruft_expiration);\n-\n-\tstrvec_push(&cmd.args, \"--non-empty\");\n-\n-\tcmd.in = -1;\n-\n-\tret = start_command(&cmd);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/*\n-\t * names has a confusing double use: it both provides the list\n-\t * of just-written new packs, and accepts the name of the cruft\n-\t * pack we are writing.\n-\t *\n-\t * By the time it is read here, it contains only the pack(s)\n-\t * that were just written, which is exactly the set of packs we\n-\t * want to consider kept.\n-\t *\n-\t * If `--expire-to` is given, the double-use served by `names`\n-\t * ensures that the pack written to `--expire-to` excludes any\n-\t * objects contained in the cruft pack.\n-\t */\n-\tin = xfdopen(cmd.in, \"w\");\n-\tfor_each_string_list_item(item, names)\n-\t\tfprintf(in, \"%s-%s.pack\\n\", pack_prefix, item->string);\n-\tif (combine_cruft_below_size && !cruft_expiration) {\n-\t\tcombine_small_cruft_packs(in, combine_cruft_below_size,\n-\t\t\t\t\t  existing);\n-\t} else {\n-\t\tfor_each_string_list_item(item, &existing->non_kept_packs)\n-\t\t\tfprintf(in, \"-%s.pack\\n\", item->string);\n-\t\tfor_each_string_list_item(item, &existing->cruft_packs)\n-\t\t\tfprintf(in, \"-%s.pack\\n\", item->string);\n-\t}\n-\tfor_each_string_list_item(item, &existing->kept_packs)\n-\t\tfprintf(in, \"%s.pack\\n\", item->string);\n-\tfclose(in);\n-\n-\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n-\t\t\t\t       names);\n-}\n-\n int cmd_repack(int argc,\n \t       const char **argv,\n \t       const char *prefix,\ndiff --git a/meson.build b/meson.build\nindex f5325b5941..81a3d5b96a 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -463,6 +463,7 @@ libgit_sources = [\n   'reftable/writer.c',\n   'remote.c',\n   'repack.c',\n+  'repack-cruft.c',\n   'repack-filtered.c',\n   'repack-geometry.c',\n   'repack-midx.c',\ndiff --git a/repack-cruft.c b/repack-cruft.c\nnew file mode 100644\nindex 0000000000..c51df36722\n--- /dev/null\n+++ b/repack-cruft.c\n@@ -0,0 +1,99 @@\n+#include \"git-compat-util.h\"\n+#include \"repack.h\"\n+#include \"packfile.h\"\n+#include \"repository.h\"\n+#include \"run-command.h\"\n+\n+static void combine_small_cruft_packs(FILE *in, off_t combine_cruft_below_size,\n+\t\t\t\t      struct existing_packs *existing)\n+{\n+\tstruct packfile_store *packs = existing->repo->objects->packfiles;\n+\tstruct packed_git *p;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tsize_t i;\n+\n+\tfor (p = packfile_store_get_all_packs(packs); p; p = p->next) {\n+\t\tif (!(p->is_cruft && p->pack_local))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, pack_basename(p));\n+\t\tstrbuf_strip_suffix(&buf, \".pack\");\n+\n+\t\tif (!string_list_has_string(&existing->cruft_packs, buf.buf))\n+\t\t\tcontinue;\n+\n+\t\tif (p->pack_size < combine_cruft_below_size) {\n+\t\t\tfprintf(in, \"-%s\\n\", pack_basename(p));\n+\t\t} else {\n+\t\t\texisting_packs_retain_cruft(existing, p);\n+\t\t\tfprintf(in, \"%s\\n\", pack_basename(p));\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < existing->non_kept_packs.nr; i++)\n+\t\tfprintf(in, \"-%s.pack\\n\",\n+\t\t\texisting->non_kept_packs.items[i].string);\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+int write_cruft_pack(const struct write_pack_opts *opts,\n+\t\t     const char *cruft_expiration,\n+\t\t     unsigned long combine_cruft_below_size,\n+\t\t     struct string_list *names,\n+\t\t     struct existing_packs *existing)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstruct string_list_item *item;\n+\tFILE *in;\n+\tint ret;\n+\tconst char *pack_prefix = write_pack_opts_pack_prefix(opts);\n+\n+\tprepare_pack_objects(&cmd, opts->po_args, opts->destination);\n+\n+\tstrvec_push(&cmd.args, \"--cruft\");\n+\tif (cruft_expiration)\n+\t\tstrvec_pushf(&cmd.args, \"--cruft-expiration=%s\",\n+\t\t\t     cruft_expiration);\n+\n+\tstrvec_push(&cmd.args, \"--non-empty\");\n+\n+\tcmd.in = -1;\n+\n+\tret = start_command(&cmd);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * names has a confusing double use: it both provides the list\n+\t * of just-written new packs, and accepts the name of the cruft\n+\t * pack we are writing.\n+\t *\n+\t * By the time it is read here, it contains only the pack(s)\n+\t * that were just written, which is exactly the set of packs we\n+\t * want to consider kept.\n+\t *\n+\t * If `--expire-to` is given, the double-use served by `names`\n+\t * ensures that the pack written to `--expire-to` excludes any\n+\t * objects contained in the cruft pack.\n+\t */\n+\tin = xfdopen(cmd.in, \"w\");\n+\tfor_each_string_list_item(item, names)\n+\t\tfprintf(in, \"%s-%s.pack\\n\", pack_prefix, item->string);\n+\tif (combine_cruft_below_size && !cruft_expiration) {\n+\t\tcombine_small_cruft_packs(in, combine_cruft_below_size,\n+\t\t\t\t\t  existing);\n+\t} else {\n+\t\tfor_each_string_list_item(item, &existing->non_kept_packs)\n+\t\t\tfprintf(in, \"-%s.pack\\n\", item->string);\n+\t\tfor_each_string_list_item(item, &existing->cruft_packs)\n+\t\t\tfprintf(in, \"-%s.pack\\n\", item->string);\n+\t}\n+\tfor_each_string_list_item(item, &existing->kept_packs)\n+\t\tfprintf(in, \"%s.pack\\n\", item->string);\n+\tfclose(in);\n+\n+\treturn finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,\n+\t\t\t\t       names);\n+}\ndiff --git a/repack.h b/repack.h\nindex c790c90ef8..3a688a12ee 100644\n--- a/repack.h\n+++ b/repack.h\n@@ -137,4 +137,10 @@ int write_filtered_pack(const struct write_pack_opts *opts,\n \t\t\tstruct existing_packs *existing,\n \t\t\tstruct string_list *names);\n \n+int write_cruft_pack(const struct write_pack_opts *opts,\n+\t\t     const char *cruft_expiration,\n+\t\t     unsigned long combine_cruft_below_size,\n+\t\t     struct string_list *names,\n+\t\t     struct existing_packs *existing);\n+\n #endif /* REPACK_H */\n-- \n2.51.0.540.ga7423965ad8\n\n"},{"id":"528920","messageId":"b7178565ac6d7dcbaff8c1eb825dddef13128a02.1760567210.git.me@ttaylorr.com","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"[PATCH v2 49/49] builtin/repack.c: clean up unused `#include`s","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-15T22:29:41Z","receivedAt":"2025-10-15T22:29:43Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Over the past several dozen commits, we have moved a large amount of\nfunctionality out of the repack builtin and into other files like\nrepack.c, repack-cruft.c, repack-filtered.c, repack-midx.c, and\nrepack-promisor.c.\n\nThese files specify the minimal set of `#include`s that they need to\ncompile successfully, but we did not change the set of `#include`s in\nthe repack builtin itself.\n\nNow that the code movement is complete, let's clean up that set of\n`#include`s and trim down the builtin to include the minimal amount of\nexternal headers necessary to compile.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/repack.c | 9 ---------\n 1 file changed, 9 deletions(-)\n\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex a68c22f605..cfdb4c0920 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -4,26 +4,17 @@\n #include \"builtin.h\"\n #include \"config.h\"\n #include \"environment.h\"\n-#include \"gettext.h\"\n-#include \"hex.h\"\n #include \"parse-options.h\"\n #include \"path.h\"\n #include \"run-command.h\"\n #include \"server-info.h\"\n-#include \"strbuf.h\"\n #include \"string-list.h\"\n-#include \"strvec.h\"\n #include \"midx.h\"\n #include \"packfile.h\"\n #include \"prune-packed.h\"\n-#include \"odb.h\"\n #include \"promisor-remote.h\"\n #include \"repack.h\"\n #include \"shallow.h\"\n-#include \"pack.h\"\n-#include \"pack-bitmap.h\"\n-#include \"refs.h\"\n-#include \"list-objects-filter-options.h\"\n \n #define ALL_INTO_ONE 1\n #define LOOSEN_UNREACHABLE 2\n-- \n2.51.0.540.ga7423965ad8\n"},{"id":"528945","messageId":"aPDJj5e9GKLSeVfS@pks.im","threadId":"64222","inReplyTo":"cover.1760567210.git.me@ttaylorr.com","subject":"Re: [PATCH v2 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-16T10:31:43Z","receivedAt":"2025-10-16T10:32:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 15, 2025 at 06:26:57PM -0400, Taylor Blau wrote:\n> Note to the maintainer:\n> \n>  * This series has been rebased on to 'master' (which is 4b71b29477\n>    (The seventeenth batch, 2025-10-10) at the time of writing) to\n>    avoid semantic conflicts now that ps/packfile-store has been\n>    merged.\n> \n> This is a relatively small reroll of my series to clean up the repack\n> builtin by introducing a repack.h API, which is the first of ~three\n> series that will implement incremental MIDX/bitmap-based repacking.\n> \n> A range-diff is included below for convenience, but the changes since\n> v1 are generally limited to the following:\n> \n>  * Wording tweaks and a couple of minor typo fixes.\n> \n>  * Dropping explicit casts out of 'void *'.\n> \n>  * Clarification in commit \"builtin/repack.c: introduce `struct\n>    write_pack_opts`\" that additional cleanup follows in the upcoming\n>    patches.\n> \n>  * Marking parts of the new API as const where possible.\n> \n>  * Using 'bool' as the return type and simplifying the implementation\n>    of `write_pack_opts_is_local()`.\n> \n>  * Avoid shadowing \"struct write_pack_opts opts\" in `cmd_repack()`.\n> \n> Outside of that, the series is unchanged, and I am hopeful that this\n> round looks good to reviewers so that we can move on to the more\n> interesting parts of incremental MIDX/bitmap repacking ;-).\n\nI didn't quite feel like reviewing all of these patches again, so I only\nhad a look at the range-diff. The changes in there all look good to me\nand address my feedback.\n\nSo I feel like all of this is in a reasonably good shape and a\ndefinitive improvement for our code base. Thanks!\n\nPatrick\n"},{"id":"528975","messageId":"xmqqtszy4vrc.fsf@gitster.g","threadId":"64222","inReplyTo":"aPDJj5e9GKLSeVfS@pks.im","subject":"Re: [PATCH v2 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-16T17:11:19Z","receivedAt":"2025-10-16T17:11:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> A range-diff is included below for convenience, but the changes since\n>> v1 are generally limited to the following:\n>> \n>>  * Wording tweaks and a couple of minor typo fixes.\n>> \n>>  * Dropping explicit casts out of 'void *'.\n>> \n>>  * Clarification in commit \"builtin/repack.c: introduce `struct\n>>    write_pack_opts`\" that additional cleanup follows in the upcoming\n>>    patches.\n>> \n>>  * Marking parts of the new API as const where possible.\n>> \n>>  * Using 'bool' as the return type and simplifying the implementation\n>>    of `write_pack_opts_is_local()`.\n>> \n>>  * Avoid shadowing \"struct write_pack_opts opts\" in `cmd_repack()`.\n>> \n>> Outside of that, the series is unchanged, and I am hopeful that this\n>> round looks good to reviewers so that we can move on to the more\n>> interesting parts of incremental MIDX/bitmap repacking ;-).\n>\n> I didn't quite feel like reviewing all of these patches again, so I only\n> had a look at the range-diff. The changes in there all look good to me\n> and address my feedback.\n>\n> So I feel like all of this is in a reasonably good shape and a\n> definitive improvement for our code base. Thanks!\n\n;-)\n\nThanks for a vote of confidence.  With what I saw in v1 round, I\nagree these are in good shape.\n\nThanks.\n"},{"id":"528994","messageId":"aPFN2cURAlknAypj@nand.local","threadId":"64222","inReplyTo":"xmqqtszy4vrc.fsf@gitster.g","subject":"Re: [PATCH v2 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-16T19:56:09Z","receivedAt":"2025-10-16T19:56:21Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Oct 16, 2025 at 10:11:19AM -0700, Junio C Hamano wrote:\n> > I didn't quite feel like reviewing all of these patches again, so I only\n> > had a look at the range-diff. The changes in there all look good to me\n> > and address my feedback.\n> >\n> > So I feel like all of this is in a reasonably good shape and a\n> > definitive improvement for our code base. Thanks!\n>\n> ;-)\n>\n> Thanks for a vote of confidence.  With what I saw in v1 round, I\n> agree these are in good shape.\n\nThanks, both :-).\n\nThanks,\nTaylor\n"},{"id":"529048","messageId":"20251017084018.GA4096665@coredump.intra.peff.net","threadId":"64222","inReplyTo":"xmqqtszy4vrc.fsf@gitster.g","subject":"Re: [PATCH v2 00/49] repack: prepare for incremental MIDX-based repacking","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-17T08:40:18Z","receivedAt":"2025-10-17T08:40:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Oct 16, 2025 at 10:11:19AM -0700, Junio C Hamano wrote:\n\n> > I didn't quite feel like reviewing all of these patches again, so I only\n> > had a look at the range-diff. The changes in there all look good to me\n> > and address my feedback.\n> >\n> > So I feel like all of this is in a reasonably good shape and a\n> > definitive improvement for our code base. Thanks!\n> \n> ;-)\n> \n> Thanks for a vote of confidence.  With what I saw in v1 round, I\n> agree these are in good shape.\n\nI likewise looked over the range-diff, and it covered all of my\nactionable comments from the v1 review.\n\n-Peff\n"}]}