repack: remove pack_geometry API from the builtin

Now that the pack_geometry API is fully factored and isolated from the rest of the builtin, declare it within repack.h and move its implementation to "repack-geometry.c" as a separate component. Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed Oct 15, 2025 at 18:28 UTC 62d3fa09b3890631af7c572cb6132088a14d2653
5 files changed +256 -235
Makefile
+1
@@ -1137,6 +1137,7 @@ LIB_OBJS += refs/ref-cache.o
1137 LIB_OBJS += refspec.o
1138 LIB_OBJS += remote.o
1139 LIB_OBJS += repack.o
1140 +LIB_OBJS += repack-geometry.o
1141 LIB_OBJS += repack-promisor.o
1142 LIB_OBJS += replace-object.o
1143 LIB_OBJS += repo-settings.o
builtin/repack.c
-235
@@ -107,241 +107,6 @@ static int repack_config(const char *var, const char *value,
107 return git_default_config(var, value, ctx, cb);
108 }
109
110 -struct pack_geometry {
111 - struct packed_git **pack;
112 - uint32_t pack_nr, pack_alloc;
113 - uint32_t split;
114 -
115 - int split_factor;
116 -};
117 -
118 -static uint32_t pack_geometry_weight(struct packed_git *p)
119 -{
120 - if (open_pack_index(p))
121 - die(_("cannot open index for %s"), p->pack_name);
122 - return p->num_objects;
123 -}
124 -
125 -static int pack_geometry_cmp(const void *va, const void *vb)
126 -{
127 - uint32_t aw = pack_geometry_weight(*(struct packed_git **)va),
128 - bw = pack_geometry_weight(*(struct packed_git **)vb);
129 -
130 - if (aw < bw)
131 - return -1;
132 - if (aw > bw)
133 - return 1;
134 - return 0;
135 -}
136 -
137 -static void pack_geometry_init(struct pack_geometry *geometry,
138 - struct existing_packs *existing,
139 - const struct pack_objects_args *args,
140 - int pack_kept_objects)
141 -{
142 - struct packfile_store *packs = existing->repo->objects->packfiles;
143 - struct packed_git *p;
144 - struct strbuf buf = STRBUF_INIT;
145 -
146 - for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
147 - if (args->local && !p->pack_local)
148 - /*
149 - * When asked to only repack local packfiles we skip
150 - * over any packfiles that are borrowed from alternate
151 - * object directories.
152 - */
153 - continue;
154 -
155 - if (!pack_kept_objects) {
156 - /*
157 - * Any pack that has its pack_keep bit set will
158 - * appear in existing->kept_packs below, but
159 - * this saves us from doing a more expensive
160 - * check.
161 - */
162 - if (p->pack_keep)
163 - continue;
164 -
165 - /*
166 - * The pack may be kept via the --keep-pack
167 - * option; check 'existing->kept_packs' to
168 - * determine whether to ignore it.
169 - */
170 - strbuf_reset(&buf);
171 - strbuf_addstr(&buf, pack_basename(p));
172 - strbuf_strip_suffix(&buf, ".pack");
173 -
174 - if (string_list_has_string(&existing->kept_packs, buf.buf))
175 - continue;
176 - }
177 - if (p->is_cruft)
178 - continue;
179 -
180 - ALLOC_GROW(geometry->pack,
181 - geometry->pack_nr + 1,
182 - geometry->pack_alloc);
183 -
184 - geometry->pack[geometry->pack_nr] = p;
185 - geometry->pack_nr++;
186 - }
187 -
188 - QSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);
189 - strbuf_release(&buf);
190 -}
191 -
192 -static void pack_geometry_split(struct pack_geometry *geometry)
193 -{
194 - uint32_t i;
195 - uint32_t split;
196 - off_t total_size = 0;
197 -
198 - if (!geometry->pack_nr) {
199 - geometry->split = geometry->pack_nr;
200 - return;
201 - }
202 -
203 - /*
204 - * First, count the number of packs (in descending order of size) which
205 - * already form a geometric progression.
206 - */
207 - for (i = geometry->pack_nr - 1; i > 0; i--) {
208 - struct packed_git *ours = geometry->pack[i];
209 - struct packed_git *prev = geometry->pack[i - 1];
210 -
211 - if (unsigned_mult_overflows(geometry->split_factor,
212 - pack_geometry_weight(prev)))
213 - die(_("pack %s too large to consider in geometric "
214 - "progression"),
215 - prev->pack_name);
216 -
217 - if (pack_geometry_weight(ours) <
218 - geometry->split_factor * pack_geometry_weight(prev))
219 - break;
220 - }
221 -
222 - split = i;
223 -
224 - if (split) {
225 - /*
226 - * Move the split one to the right, since the top element in the
227 - * last-compared pair can't be in the progression. Only do this
228 - * when we split in the middle of the array (otherwise if we got
229 - * to the end, then the split is in the right place).
230 - */
231 - split++;
232 - }
233 -
234 - /*
235 - * Then, anything to the left of 'split' must be in a new pack. But,
236 - * creating that new pack may cause packs in the heavy half to no longer
237 - * form a geometric progression.
238 - *
239 - * Compute an expected size of the new pack, and then determine how many
240 - * packs in the heavy half need to be joined into it (if any) to restore
241 - * the geometric progression.
242 - */
243 - for (i = 0; i < split; i++) {
244 - struct packed_git *p = geometry->pack[i];
245 -
246 - if (unsigned_add_overflows(total_size, pack_geometry_weight(p)))
247 - die(_("pack %s too large to roll up"), p->pack_name);
248 - total_size += pack_geometry_weight(p);
249 - }
250 - for (i = split; i < geometry->pack_nr; i++) {
251 - struct packed_git *ours = geometry->pack[i];
252 -
253 - if (unsigned_mult_overflows(geometry->split_factor,
254 - total_size))
255 - die(_("pack %s too large to roll up"), ours->pack_name);
256 -
257 - if (pack_geometry_weight(ours) <
258 - geometry->split_factor * total_size) {
259 - if (unsigned_add_overflows(total_size,
260 - pack_geometry_weight(ours)))
261 - die(_("pack %s too large to roll up"),
262 - ours->pack_name);
263 -
264 - split++;
265 - total_size += pack_geometry_weight(ours);
266 - } else
267 - break;
268 - }
269 -
270 - geometry->split = split;
271 -}
272 -
273 -static struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)
274 -{
275 - uint32_t i;
276 -
277 - if (!geometry) {
278 - /*
279 - * No geometry means either an all-into-one repack (in which
280 - * case there is only one pack left and it is the largest) or an
281 - * incremental one.
282 - *
283 - * If repacking incrementally, then we could check the size of
284 - * all packs to determine which should be preferred, but leave
285 - * this for later.
286 - */
287 - return NULL;
288 - }
289 - if (geometry->split == geometry->pack_nr)
290 - return NULL;
291 -
292 - /*
293 - * The preferred pack is the largest pack above the split line. In
294 - * other words, it is the largest pack that does not get rolled up in
295 - * the geometric repack.
296 - */
297 - for (i = geometry->pack_nr; i > geometry->split; i--)
298 - /*
299 - * A pack that is not local would never be included in a
300 - * multi-pack index. We thus skip over any non-local packs.
301 - */
302 - if (geometry->pack[i - 1]->pack_local)
303 - return geometry->pack[i - 1];
304 -
305 - return NULL;
306 -}
307 -
308 -static void pack_geometry_remove_redundant(struct pack_geometry *geometry,
309 - struct string_list *names,
310 - struct existing_packs *existing,
311 - const char *packdir)
312 -{
313 - const struct git_hash_algo *algop = existing->repo->hash_algo;
314 - struct strbuf buf = STRBUF_INIT;
315 - uint32_t i;
316 -
317 - for (i = 0; i < geometry->split; i++) {
318 - struct packed_git *p = geometry->pack[i];
319 - if (string_list_has_string(names, hash_to_hex_algop(p->hash,
320 - algop)))
321 - continue;
322 -
323 - strbuf_reset(&buf);
324 - strbuf_addstr(&buf, pack_basename(p));
325 - strbuf_strip_suffix(&buf, ".pack");
326 -
327 - if ((p->pack_keep) ||
328 - (string_list_has_string(&existing->kept_packs, buf.buf)))
329 - continue;
330 -
331 - repack_remove_redundant_pack(existing->repo, packdir, buf.buf);
332 - }
333 -
334 - strbuf_release(&buf);
335 -}
336 -
337 -static void pack_geometry_release(struct pack_geometry *geometry)
338 -{
339 - if (!geometry)
340 - return;
341 -
342 - free(geometry->pack);
343 -}
344 -
110 static int midx_has_unknown_packs(char **midx_pack_names,
111 size_t midx_pack_names_nr,
112 struct string_list *include,
meson.build
+1
@@ -463,6 +463,7 @@ libgit_sources = [
463 'reftable/writer.c',
464 'remote.c',
465 'repack.c',
466 + 'repack-geometry.c',
467 'repack-promisor.c',
468 'replace-object.c',
469 'repo-settings.c',
repack-geometry.c new
+234
@@ -0,0 +1,234 @@
1 +#define DISABLE_SIGN_COMPARE_WARNINGS
2 +
3 +#include "git-compat-util.h"
4 +#include "repack.h"
5 +#include "repository.h"
6 +#include "hex.h"
7 +#include "packfile.h"
8 +
9 +static uint32_t pack_geometry_weight(struct packed_git *p)
10 +{
11 + if (open_pack_index(p))
12 + die(_("cannot open index for %s"), p->pack_name);
13 + return p->num_objects;
14 +}
15 +
16 +static int pack_geometry_cmp(const void *va, const void *vb)
17 +{
18 + uint32_t aw = pack_geometry_weight(*(struct packed_git **)va),
19 + bw = pack_geometry_weight(*(struct packed_git **)vb);
20 +
21 + if (aw < bw)
22 + return -1;
23 + if (aw > bw)
24 + return 1;
25 + return 0;
26 +}
27 +
28 +void pack_geometry_init(struct pack_geometry *geometry,
29 + struct existing_packs *existing,
30 + const struct pack_objects_args *args,
31 + int pack_kept_objects)
32 +{
33 + struct packfile_store *packs = existing->repo->objects->packfiles;
34 + struct packed_git *p;
35 + struct strbuf buf = STRBUF_INIT;
36 +
37 + for (p = packfile_store_get_all_packs(packs); p; p = p->next) {
38 + if (args->local && !p->pack_local)
39 + /*
40 + * When asked to only repack local packfiles we skip
41 + * over any packfiles that are borrowed from alternate
42 + * object directories.
43 + */
44 + continue;
45 +
46 + if (!pack_kept_objects) {
47 + /*
48 + * Any pack that has its pack_keep bit set will
49 + * appear in existing->kept_packs below, but
50 + * this saves us from doing a more expensive
51 + * check.
52 + */
53 + if (p->pack_keep)
54 + continue;
55 +
56 + /*
57 + * The pack may be kept via the --keep-pack
58 + * option; check 'existing->kept_packs' to
59 + * determine whether to ignore it.
60 + */
61 + strbuf_reset(&buf);
62 + strbuf_addstr(&buf, pack_basename(p));
63 + strbuf_strip_suffix(&buf, ".pack");
64 +
65 + if (string_list_has_string(&existing->kept_packs, buf.buf))
66 + continue;
67 + }
68 + if (p->is_cruft)
69 + continue;
70 +
71 + ALLOC_GROW(geometry->pack,
72 + geometry->pack_nr + 1,
73 + geometry->pack_alloc);
74 +
75 + geometry->pack[geometry->pack_nr] = p;
76 + geometry->pack_nr++;
77 + }
78 +
79 + QSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);
80 + strbuf_release(&buf);
81 +}
82 +
83 +void pack_geometry_split(struct pack_geometry *geometry)
84 +{
85 + uint32_t i;
86 + uint32_t split;
87 + off_t total_size = 0;
88 +
89 + if (!geometry->pack_nr) {
90 + geometry->split = geometry->pack_nr;
91 + return;
92 + }
93 +
94 + /*
95 + * First, count the number of packs (in descending order of size) which
96 + * already form a geometric progression.
97 + */
98 + for (i = geometry->pack_nr - 1; i > 0; i--) {
99 + struct packed_git *ours = geometry->pack[i];
100 + struct packed_git *prev = geometry->pack[i - 1];
101 +
102 + if (unsigned_mult_overflows(geometry->split_factor,
103 + pack_geometry_weight(prev)))
104 + die(_("pack %s too large to consider in geometric "
105 + "progression"),
106 + prev->pack_name);
107 +
108 + if (pack_geometry_weight(ours) <
109 + geometry->split_factor * pack_geometry_weight(prev))
110 + break;
111 + }
112 +
113 + split = i;
114 +
115 + if (split) {
116 + /*
117 + * Move the split one to the right, since the top element in the
118 + * last-compared pair can't be in the progression. Only do this
119 + * when we split in the middle of the array (otherwise if we got
120 + * to the end, then the split is in the right place).
121 + */
122 + split++;
123 + }
124 +
125 + /*
126 + * Then, anything to the left of 'split' must be in a new pack. But,
127 + * creating that new pack may cause packs in the heavy half to no longer
128 + * form a geometric progression.
129 + *
130 + * Compute an expected size of the new pack, and then determine how many
131 + * packs in the heavy half need to be joined into it (if any) to restore
132 + * the geometric progression.
133 + */
134 + for (i = 0; i < split; i++) {
135 + struct packed_git *p = geometry->pack[i];
136 +
137 + if (unsigned_add_overflows(total_size, pack_geometry_weight(p)))
138 + die(_("pack %s too large to roll up"), p->pack_name);
139 + total_size += pack_geometry_weight(p);
140 + }
141 + for (i = split; i < geometry->pack_nr; i++) {
142 + struct packed_git *ours = geometry->pack[i];
143 +
144 + if (unsigned_mult_overflows(geometry->split_factor,
145 + total_size))
146 + die(_("pack %s too large to roll up"), ours->pack_name);
147 +
148 + if (pack_geometry_weight(ours) <
149 + geometry->split_factor * total_size) {
150 + if (unsigned_add_overflows(total_size,
151 + pack_geometry_weight(ours)))
152 + die(_("pack %s too large to roll up"),
153 + ours->pack_name);
154 +
155 + split++;
156 + total_size += pack_geometry_weight(ours);
157 + } else
158 + break;
159 + }
160 +
161 + geometry->split = split;
162 +}
163 +
164 +struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)
165 +{
166 + uint32_t i;
167 +
168 + if (!geometry) {
169 + /*
170 + * No geometry means either an all-into-one repack (in which
171 + * case there is only one pack left and it is the largest) or an
172 + * incremental one.
173 + *
174 + * If repacking incrementally, then we could check the size of
175 + * all packs to determine which should be preferred, but leave
176 + * this for later.
177 + */
178 + return NULL;
179 + }
180 + if (geometry->split == geometry->pack_nr)
181 + return NULL;
182 +
183 + /*
184 + * The preferred pack is the largest pack above the split line. In
185 + * other words, it is the largest pack that does not get rolled up in
186 + * the geometric repack.
187 + */
188 + for (i = geometry->pack_nr; i > geometry->split; i--)
189 + /*
190 + * A pack that is not local would never be included in a
191 + * multi-pack index. We thus skip over any non-local packs.
192 + */
193 + if (geometry->pack[i - 1]->pack_local)
194 + return geometry->pack[i - 1];
195 +
196 + return NULL;
197 +}
198 +
199 +void pack_geometry_remove_redundant(struct pack_geometry *geometry,
200 + struct string_list *names,
201 + struct existing_packs *existing,
202 + const char *packdir)
203 +{
204 + const struct git_hash_algo *algop = existing->repo->hash_algo;
205 + struct strbuf buf = STRBUF_INIT;
206 + uint32_t i;
207 +
208 + for (i = 0; i < geometry->split; i++) {
209 + struct packed_git *p = geometry->pack[i];
210 + if (string_list_has_string(names, hash_to_hex_algop(p->hash,
211 + algop)))
212 + continue;
213 +
214 + strbuf_reset(&buf);
215 + strbuf_addstr(&buf, pack_basename(p));
216 + strbuf_strip_suffix(&buf, ".pack");
217 +
218 + if ((p->pack_keep) ||
219 + (string_list_has_string(&existing->kept_packs, buf.buf)))
220 + continue;
221 +
222 + repack_remove_redundant_pack(existing->repo, packdir, buf.buf);
223 + }
224 +
225 + strbuf_release(&buf);
226 +}
227 +
228 +void pack_geometry_release(struct pack_geometry *geometry)
229 +{
230 + if (!geometry)
231 + return;
232 +
233 + free(geometry->pack);
234 +}
repack.h
+20
@@ -78,4 +78,24 @@ void repack_promisor_objects(struct repository *repo,
78 const struct pack_objects_args *args,
79 struct string_list *names, const char *packtmp);
80
81 +struct pack_geometry {
82 + struct packed_git **pack;
83 + uint32_t pack_nr, pack_alloc;
84 + uint32_t split;
85 +
86 + int split_factor;
87 +};
88 +
89 +void pack_geometry_init(struct pack_geometry *geometry,
90 + struct existing_packs *existing,
91 + const struct pack_objects_args *args,
92 + int pack_kept_objects);
93 +void pack_geometry_split(struct pack_geometry *geometry);
94 +struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry);
95 +void pack_geometry_remove_redundant(struct pack_geometry *geometry,
96 + struct string_list *names,
97 + struct existing_packs *existing,
98 + const char *packdir);
99 +void pack_geometry_release(struct pack_geometry *geometry);
100 +
101 #endif /* REPACK_H */