Skip to content

Commit dcc9c7e

Browse files
pks-tgitster
authored andcommitted
builtin/repack: handle promisor packs with geometric repacking
When performing a fetch with an object filter, we mark the resulting packfile as a promisor pack. An object part of such a pack may miss any of its referenced objects, and Git knows to handle this case by fetching any such missing objects from the promisor remote. The "promisor" property needs to be retained going forward. So every time we pack a promisor object, the resulting pack must be marked as a promisor pack. git-repack(1) does this already: when a repository has a promisor remote, it knows to pass "--exclude-promisor-objects" to the git-pack-objects(1) child process. Promisor packs are written separately when doing an all-into-one repack via `repack_promisor_objects()`. But we don't support promisor objects when doing a geometric repack yet. Promisor packs do not get any special treatment there, as we simply merge promisor and non-promisor packs. The resulting pack is not even marked as a promisor pack, which essentially corrupts the repository. This corruption couldn't happen in the real world though: we pass both "--exclude-promisor-objects" and "--stdin-packs" to git-pack-objects(1) if a repository has a promisor remote, but as those options are mutually exclusive we always end up dying. And while we made those flags compatible with one another in a preceding commit, we still end up dying in case git-pack-objects(1) is asked to repack a promisor pack. There's multiple ways to fix this: - We can exclude promisor packs from the geometric progression altogether. This would have the consequence that we never repack promisor packs at all. But in a partial clone it is quite likely that the user generates a bunch of promisor packs over time, as every backfill fetch would create another one. So this doesn't really feel like a sensible option. - We can adapt git-pack-objects(1) to support repacking promisor packs and include them in the normal geometric progression. But this would mean that the set of promisor objects expands over time as the packs are merged with normal packs. - We can use a separate geometric progression to repack promisor packs. The first two options both have significant downsides, so they aren't really feasible. But the third option fixes both of these downsides: we make sure that promisor packs get merged, and at the same time we never expand the set of promisor objects beyond the set of objects that are already marked as promisor objects. Implement this strategy so that geometric repacking works in partial clones. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent fa7b912 commit dcc9c7e

File tree

5 files changed

+124
-6
lines changed

5 files changed

+124
-6
lines changed

builtin/repack.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ int cmd_repack(int argc,
332332
!(pack_everything & PACK_CRUFT))
333333
strvec_push(&cmd.args, "--pack-loose-unreachable");
334334
} else if (geometry.split_factor) {
335+
pack_geometry_repack_promisors(repo, &po_args, &geometry,
336+
&names, packtmp);
337+
335338
if (midx_must_contain_cruft)
336339
strvec_push(&cmd.args, "--stdin-packs");
337340
else

repack-geometry.c

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,25 @@ void pack_geometry_init(struct pack_geometry *geometry,
6666
if (p->is_cruft)
6767
continue;
6868

69-
ALLOC_GROW(geometry->pack,
70-
geometry->pack_nr + 1,
71-
geometry->pack_alloc);
72-
73-
geometry->pack[geometry->pack_nr] = p;
74-
geometry->pack_nr++;
69+
if (p->pack_promisor) {
70+
ALLOC_GROW(geometry->promisor_pack,
71+
geometry->promisor_pack_nr + 1,
72+
geometry->promisor_pack_alloc);
73+
74+
geometry->promisor_pack[geometry->promisor_pack_nr] = p;
75+
geometry->promisor_pack_nr++;
76+
} else {
77+
ALLOC_GROW(geometry->pack,
78+
geometry->pack_nr + 1,
79+
geometry->pack_alloc);
80+
81+
geometry->pack[geometry->pack_nr] = p;
82+
geometry->pack_nr++;
83+
}
7584
}
7685

7786
QSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp);
87+
QSORT(geometry->promisor_pack, geometry->promisor_pack_nr, pack_geometry_cmp);
7888
strbuf_release(&buf);
7989
}
8090

@@ -160,6 +170,9 @@ void pack_geometry_split(struct pack_geometry *geometry)
160170
{
161171
geometry->split = compute_pack_geometry_split(geometry->pack, geometry->pack_nr,
162172
geometry->split_factor);
173+
geometry->promisor_split = compute_pack_geometry_split(geometry->promisor_pack,
174+
geometry->promisor_pack_nr,
175+
geometry->split_factor);
163176
}
164177

165178
struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry)
@@ -234,6 +247,8 @@ void pack_geometry_remove_redundant(struct pack_geometry *geometry,
234247
{
235248
remove_redundant_packs(geometry->pack, geometry->split,
236249
names, existing, packdir);
250+
remove_redundant_packs(geometry->promisor_pack, geometry->promisor_split,
251+
names, existing, packdir);
237252
}
238253

239254
void pack_geometry_release(struct pack_geometry *geometry)
@@ -242,4 +257,5 @@ void pack_geometry_release(struct pack_geometry *geometry)
242257
return;
243258

244259
free(geometry->pack);
260+
free(geometry->promisor_pack);
245261
}

repack-promisor.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,31 @@ void repack_promisor_objects(struct repository *repo,
109109

110110
finish_repacking_promisor_objects(repo, &cmd, names, packtmp);
111111
}
112+
113+
void pack_geometry_repack_promisors(struct repository *repo,
114+
const struct pack_objects_args *args,
115+
const struct pack_geometry *geometry,
116+
struct string_list *names,
117+
const char *packtmp)
118+
{
119+
struct child_process cmd = CHILD_PROCESS_INIT;
120+
FILE *in;
121+
122+
if (!geometry->promisor_split)
123+
return;
124+
125+
prepare_pack_objects(&cmd, args, packtmp);
126+
strvec_push(&cmd.args, "--stdin-packs");
127+
cmd.in = -1;
128+
if (start_command(&cmd))
129+
die(_("could not start pack-objects to repack promisor packs"));
130+
131+
in = xfdopen(cmd.in, "w");
132+
for (size_t i = 0; i < geometry->promisor_split; i++)
133+
fprintf(in, "%s\n", pack_basename(geometry->promisor_pack[i]));
134+
for (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++)
135+
fprintf(in, "^%s\n", pack_basename(geometry->promisor_pack[i]));
136+
fclose(in);
137+
138+
finish_repacking_promisor_objects(repo, &cmd, names, packtmp);
139+
}

repack.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,19 @@ struct pack_geometry {
103103
uint32_t pack_nr, pack_alloc;
104104
uint32_t split;
105105

106+
struct packed_git **promisor_pack;
107+
uint32_t promisor_pack_nr, promisor_pack_alloc;
108+
uint32_t promisor_split;
109+
106110
int split_factor;
107111
};
108112

113+
void pack_geometry_repack_promisors(struct repository *repo,
114+
const struct pack_objects_args *args,
115+
const struct pack_geometry *geometry,
116+
struct string_list *names,
117+
const char *packtmp);
118+
109119
void pack_geometry_init(struct pack_geometry *geometry,
110120
struct existing_packs *existing,
111121
const struct pack_objects_args *args);

t/t7703-repack-geometric.sh

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,4 +480,65 @@ test_expect_success '--geometric -l disables writing bitmaps with non-local pack
480480
test_path_is_file member/.git/objects/pack/multi-pack-index-*.bitmap
481481
'
482482

483+
write_packfile () {
484+
NR="$1"
485+
PREFIX="$2"
486+
487+
printf "blob\ndata <<EOB\n$PREFIX %s\nEOB\n" $(test_seq $NR) |
488+
git fast-import &&
489+
git pack-objects --pack-loose-unreachable .git/objects/pack/pack &&
490+
git prune-packed
491+
}
492+
493+
write_promisor_packfile () {
494+
PACKFILE=$(write_packfile "$@") &&
495+
touch .git/objects/pack/pack-$PACKFILE.promisor &&
496+
echo "$PACKFILE"
497+
}
498+
499+
test_expect_success 'geometric repack works with promisor packs' '
500+
test_when_finished "rm -fr repo" &&
501+
git init repo &&
502+
(
503+
cd repo &&
504+
git config set maintenance.auto false &&
505+
git remote add promisor garbage &&
506+
git config set remote.promisor.promisor true &&
507+
508+
# Packs A and B need to be merged.
509+
NORMAL_A=$(write_packfile 2 normal-a) &&
510+
NORMAL_B=$(write_packfile 2 normal-b) &&
511+
NORMAL_C=$(write_packfile 14 normal-c) &&
512+
513+
# Packs A, B and C need to be merged.
514+
PROMISOR_A=$(write_promisor_packfile 1 promisor-a) &&
515+
PROMISOR_B=$(write_promisor_packfile 3 promisor-b) &&
516+
PROMISOR_C=$(write_promisor_packfile 3 promisor-c) &&
517+
PROMISOR_D=$(write_promisor_packfile 20 promisor-d) &&
518+
PROMISOR_E=$(write_promisor_packfile 40 promisor-e) &&
519+
520+
git cat-file --batch-all-objects --batch-check="%(objectname)" >objects-expect &&
521+
522+
ls .git/objects/pack/*.pack >packs-before &&
523+
test_line_count = 8 packs-before &&
524+
git repack --geometric=2 -d &&
525+
ls .git/objects/pack/*.pack >packs-after &&
526+
test_line_count = 5 packs-after &&
527+
test_grep ! "$NORMAL_A" packs-after &&
528+
test_grep ! "$NORMAL_B" packs-after &&
529+
test_grep "$NORMAL_C" packs-after &&
530+
test_grep ! "$PROMISOR_A" packs-after &&
531+
test_grep ! "$PROMISOR_B" packs-after &&
532+
test_grep ! "$PROMISOR_C" packs-after &&
533+
test_grep "$PROMISOR_D" packs-after &&
534+
test_grep "$PROMISOR_E" packs-after &&
535+
536+
ls .git/objects/pack/*.promisor >promisors &&
537+
test_line_count = 3 promisors &&
538+
539+
git cat-file --batch-all-objects --batch-check="%(objectname)" >objects-actual &&
540+
test_cmp objects-expect objects-actual
541+
)
542+
'
543+
483544
test_done

0 commit comments

Comments
 (0)