Subject: [PATCH v2 48/49] repack: move `write_cruft_pack()` out of the builtin

From: Taylor Blau <[email-masked]>
Date: Wed, 15 Oct 2025 18:29:38 -0400

In an identical fashion as the previous commit, move the function
`write_cruft_pack()` into its own compilation unit, and make the
function visible through the repack.h API.

Signed-off-by: Taylor Blau <[email-masked]>
---
 Makefile         |  1 +
 builtin/repack.c | 94 ---------------------------------------------
 meson.build      |  1 +
 repack-cruft.c   | 99 ++++++++++++++++++++++++++++++++++++++++++++++++
 repack.h         |  6 +++
 5 files changed, 107 insertions(+), 94 deletions(-)
 create mode 100644 repack-cruft.c

diff --git a/Makefile b/Makefile
index 42461ea91f..fb965e6acb 100644
--- a/Makefile
+++ b/Makefile
@@ -1250,6 +1250,7 @@ LIB_OBJS += refs/ref-cache.o
 LIB_OBJS += refspec.o
 LIB_OBJS += remote.o
 LIB_OBJS += repack.o
+LIB_OBJS += repack-cruft.o
 LIB_OBJS += repack-filtered.o
 LIB_OBJS += repack-geometry.o
 LIB_OBJS += repack-midx.o
diff --git a/builtin/repack.c b/builtin/repack.c
index f65880d8f6..a68c22f605 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -106,100 +106,6 @@ static int repack_config(const char *var, const char *value,
 	return git_default_config(var, value, ctx, cb);
 }
 
-static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size,
-				      struct existing_packs *existing)
-{
-	struct packfile_store *packs = existing->repo->objects->packfiles;
-	struct packed_git *p;
-	struct strbuf buf = STRBUF_INIT;
-	size_t i;
-
-	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
-		if (!(p->is_cruft && p->pack_local))
-			continue;
-
-		strbuf_reset(&buf);
-		strbuf_addstr(&buf, pack_basename(p));
-		strbuf_strip_suffix(&buf, ".pack");
-
-		if (!string_list_has_string(&existing->cruft_packs, buf.buf))
-			continue;
-
-		if (p->pack_size < combine_cruft_below_size) {
-			fprintf(in, "-%s\n", pack_basename(p));
-		} else {
-			existing_packs_retain_cruft(existing, p);
-			fprintf(in, "%s\n", pack_basename(p));
-		}
-	}
-
-	for (i = 0; i < existing->non_kept_packs.nr; i++)
-		fprintf(in, "-%s.pack\n",
-			existing->non_kept_packs.items[i].string);
-
-	strbuf_release(&buf);
-}
-
-static int write_cruft_pack(const struct write_pack_opts *opts,
-			    const char *cruft_expiration,
-			    unsigned long combine_cruft_below_size,
-			    struct string_list *names,
-			    struct existing_packs *existing)
-{
-	struct child_process cmd = CHILD_PROCESS_INIT;
-	struct string_list_item *item;
-	FILE *in;
-	int ret;
-	const char *pack_prefix = write_pack_opts_pack_prefix(opts);
-
-	prepare_pack_objects(&cmd, opts->po_args, opts->destination);
-
-	strvec_push(&cmd.args, "--cruft");
-	if (cruft_expiration)
-		strvec_pushf(&cmd.args, "--cruft-expiration=%s",
-			     cruft_expiration);
-
-	strvec_push(&cmd.args, "--non-empty");
-
-	cmd.in = -1;
-
-	ret = start_command(&cmd);
-	if (ret)
-		return ret;
-
-	/*
-	 * names has a confusing double use: it both provides the list
-	 * of just-written new packs, and accepts the name of the cruft
-	 * pack we are writing.
-	 *
-	 * By the time it is read here, it contains only the pack(s)
-	 * that were just written, which is exactly the set of packs we
-	 * want to consider kept.
-	 *
-	 * If `--expire-to` is given, the double-use served by `names`
-	 * ensures that the pack written to `--expire-to` excludes any
-	 * objects contained in the cruft pack.
-	 */
-	in = xfdopen(cmd.in, "w");
-	for_each_string_list_item(item, names)
-		fprintf(in, "%s-%s.pack\n", pack_prefix, item->string);
-	if (combine_cruft_below_size && !cruft_expiration) {
-		combine_small_cruft_packs(in, combine_cruft_below_size,
-					  existing);
-	} else {
-		for_each_string_list_item(item, &existing->non_kept_packs)
-			fprintf(in, "-%s.pack\n", item->string);
-		for_each_string_list_item(item, &existing->cruft_packs)
-			fprintf(in, "-%s.pack\n", item->string);
-	}
-	for_each_string_list_item(item, &existing->kept_packs)
-		fprintf(in, "%s.pack\n", item->string);
-	fclose(in);
-
-	return finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,
-				       names);
-}
-
 int cmd_repack(int argc,
 	       const char **argv,
 	       const char *prefix,
diff --git a/meson.build b/meson.build
index f5325b5941..81a3d5b96a 100644
--- a/meson.build
+++ b/meson.build
@@ -463,6 +463,7 @@ libgit_sources = [
   'reftable/writer.c',
   'remote.c',
   'repack.c',
+  'repack-cruft.c',
   'repack-filtered.c',
   'repack-geometry.c',
   'repack-midx.c',
diff --git a/repack-cruft.c b/repack-cruft.c
new file mode 100644
index 0000000000..c51df36722
--- /dev/null
+++ b/repack-cruft.c
@@ -0,0 +1,99 @@
+#include "git-compat-util.h"
+#include "repack.h"
+#include "packfile.h"
+#include "repository.h"
+#include "run-command.h"
+
+static void combine_small_cruft_packs(FILE *in, off_t combine_cruft_below_size,
+				      struct existing_packs *existing)
+{
+	struct packfile_store *packs = existing->repo->objects->packfiles;
+	struct packed_git *p;
+	struct strbuf buf = STRBUF_INIT;
+	size_t i;
+
+	for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
+		if (!(p->is_cruft && p->pack_local))
+			continue;
+
+		strbuf_reset(&buf);
+		strbuf_addstr(&buf, pack_basename(p));
+		strbuf_strip_suffix(&buf, ".pack");
+
+		if (!string_list_has_string(&existing->cruft_packs, buf.buf))
+			continue;
+
+		if (p->pack_size < combine_cruft_below_size) {
+			fprintf(in, "-%s\n", pack_basename(p));
+		} else {
+			existing_packs_retain_cruft(existing, p);
+			fprintf(in, "%s\n", pack_basename(p));
+		}
+	}
+
+	for (i = 0; i < existing->non_kept_packs.nr; i++)
+		fprintf(in, "-%s.pack\n",
+			existing->non_kept_packs.items[i].string);
+
+	strbuf_release(&buf);
+}
+
+int write_cruft_pack(const struct write_pack_opts *opts,
+		     const char *cruft_expiration,
+		     unsigned long combine_cruft_below_size,
+		     struct string_list *names,
+		     struct existing_packs *existing)
+{
+	struct child_process cmd = CHILD_PROCESS_INIT;
+	struct string_list_item *item;
+	FILE *in;
+	int ret;
+	const char *pack_prefix = write_pack_opts_pack_prefix(opts);
+
+	prepare_pack_objects(&cmd, opts->po_args, opts->destination);
+
+	strvec_push(&cmd.args, "--cruft");
+	if (cruft_expiration)
+		strvec_pushf(&cmd.args, "--cruft-expiration=%s",
+			     cruft_expiration);
+
+	strvec_push(&cmd.args, "--non-empty");
+
+	cmd.in = -1;
+
+	ret = start_command(&cmd);
+	if (ret)
+		return ret;
+
+	/*
+	 * names has a confusing double use: it both provides the list
+	 * of just-written new packs, and accepts the name of the cruft
+	 * pack we are writing.
+	 *
+	 * By the time it is read here, it contains only the pack(s)
+	 * that were just written, which is exactly the set of packs we
+	 * want to consider kept.
+	 *
+	 * If `--expire-to` is given, the double-use served by `names`
+	 * ensures that the pack written to `--expire-to` excludes any
+	 * objects contained in the cruft pack.
+	 */
+	in = xfdopen(cmd.in, "w");
+	for_each_string_list_item(item, names)
+		fprintf(in, "%s-%s.pack\n", pack_prefix, item->string);
+	if (combine_cruft_below_size && !cruft_expiration) {
+		combine_small_cruft_packs(in, combine_cruft_below_size,
+					  existing);
+	} else {
+		for_each_string_list_item(item, &existing->non_kept_packs)
+			fprintf(in, "-%s.pack\n", item->string);
+		for_each_string_list_item(item, &existing->cruft_packs)
+			fprintf(in, "-%s.pack\n", item->string);
+	}
+	for_each_string_list_item(item, &existing->kept_packs)
+		fprintf(in, "%s.pack\n", item->string);
+	fclose(in);
+
+	return finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,
+				       names);
+}
diff --git a/repack.h b/repack.h
index c790c90ef8..3a688a12ee 100644
--- a/repack.h
+++ b/repack.h
@@ -137,4 +137,10 @@ int write_filtered_pack(const struct write_pack_opts *opts,
 			struct existing_packs *existing,
 			struct string_list *names);
 
+int write_cruft_pack(const struct write_pack_opts *opts,
+		     const char *cruft_expiration,
+		     unsigned long combine_cruft_below_size,
+		     struct string_list *names,
+		     struct existing_packs *existing);
+
 #endif /* REPACK_H */
-- 
2.51.0.540.ga7423965ad8