Skip to content

Commit 27f9425

Browse files
ttaylorrgitster
authored andcommitted
pack-objects: introduce '--stdin-packs=follow'
When invoked with '--stdin-packs', pack-objects will generate a pack which contains the objects found in the "included" packs, less any objects from "excluded" packs. Packs that exist in the repository but weren't specified as either included or excluded are in practice treated like the latter, at least in the sense that pack-objects won't include objects from those packs. This behavior forces us to include any cruft pack(s) in a repository's multi-pack index for the reasons described in ddee370 (builtin/repack.c: add cruft packs to MIDX during geometric repack, 2022-05-20). The full details are in ddee370, but the gist is if you have a once-unreachable object in a cruft pack which later becomes reachable via one or more commits in a pack generated with '--stdin-packs', you *have* to include that object in the MIDX via the copy in the cruft pack, otherwise we cannot generate reachability bitmaps for any commits which reach that object. This prepares us for new repacking behavior which will "resurrect" objects found in cruft or otherwise unspecified packs when generating new packs. In the context of geometric repacking, this may be used to maintain a sequence of geometrically-repacked packs, the union of which is closed under reachability, even in the case described earlier. Signed-off-by: Taylor Blau <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9801319 commit 27f9425

File tree

3 files changed

+152
-23
lines changed

3 files changed

+152
-23
lines changed

Documentation/git-pack-objects.adoc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,21 @@ base-name::
8787
reference was included in the resulting packfile. This
8888
can be useful to send new tags to native Git clients.
8989

90-
--stdin-packs::
90+
--stdin-packs[=<mode>]::
9191
Read the basenames of packfiles (e.g., `pack-1234abcd.pack`)
9292
from the standard input, instead of object names or revision
9393
arguments. The resulting pack contains all objects listed in the
9494
included packs (those not beginning with `^`), excluding any
9595
objects listed in the excluded packs (beginning with `^`).
9696
+
97+
When `mode` is "follow", objects from packs not listed on stdin receive
98+
special treatment. Objects within unlisted packs will be included if
99+
those objects are (1) reachable from the included packs, and (2) not
100+
found in any excluded packs. This mode is useful, for example, to
101+
resurrect once-unreachable objects found in cruft packs to generate
102+
packs which are closed under reachability up to the boundary set by the
103+
excluded packs.
104+
+
97105
Incompatible with `--revs`, or options that imply `--revs` (such as
98106
`--all`), with the exception of `--unpacked`, which is compatible.
99107

builtin/pack-objects.c

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,12 @@ static struct oidmap configured_exclusions;
272272
static struct oidset excluded_by_config;
273273
static int name_hash_version = -1;
274274

275+
enum stdin_packs_mode {
276+
STDIN_PACKS_MODE_NONE,
277+
STDIN_PACKS_MODE_STANDARD,
278+
STDIN_PACKS_MODE_FOLLOW,
279+
};
280+
275281
/**
276282
* Check whether the name_hash_version chosen by user input is appropriate,
277283
* and also validate whether it is compatible with other features.
@@ -3517,31 +3523,44 @@ static int add_object_entry_from_pack(const struct object_id *oid,
35173523
}
35183524

35193525
static void show_object_pack_hint(struct object *object, const char *name,
3520-
void *data UNUSED)
3526+
void *data)
35213527
{
3522-
struct object_entry *oe = packlist_find(&to_pack, &object->oid);
3523-
if (!oe)
3524-
return;
3528+
enum stdin_packs_mode mode = *(enum stdin_packs_mode *)data;
3529+
if (mode == STDIN_PACKS_MODE_FOLLOW) {
3530+
add_object_entry(&object->oid, object->type, name, 0);
3531+
} else {
3532+
struct object_entry *oe = packlist_find(&to_pack, &object->oid);
3533+
if (!oe)
3534+
return;
35253535

3526-
/*
3527-
* Our 'to_pack' list was constructed by iterating all objects packed in
3528-
* included packs, and so doesn't have a non-zero hash field that you
3529-
* would typically pick up during a reachability traversal.
3530-
*
3531-
* Make a best-effort attempt to fill in the ->hash and ->no_try_delta
3532-
* fields here in order to perhaps improve the delta selection
3533-
* process.
3534-
*/
3535-
oe->hash = pack_name_hash_fn(name);
3536-
oe->no_try_delta = name && no_try_delta(name);
3536+
/*
3537+
* Our 'to_pack' list was constructed by iterating all
3538+
* objects packed in included packs, and so doesn't have
3539+
* a non-zero hash field that you would typically pick
3540+
* up during a reachability traversal.
3541+
*
3542+
* Make a best-effort attempt to fill in the ->hash and
3543+
* ->no_try_delta fields here in order to perhaps
3544+
* improve the delta selection process.
3545+
*/
3546+
oe->hash = pack_name_hash_fn(name);
3547+
oe->no_try_delta = name && no_try_delta(name);
35373548

3538-
stdin_packs_hints_nr++;
3549+
stdin_packs_hints_nr++;
3550+
}
35393551
}
35403552

3541-
static void show_commit_pack_hint(struct commit *commit UNUSED,
3542-
void *data UNUSED)
3553+
static void show_commit_pack_hint(struct commit *commit, void *data)
35433554
{
3555+
enum stdin_packs_mode mode = *(enum stdin_packs_mode *)data;
3556+
3557+
if (mode == STDIN_PACKS_MODE_FOLLOW) {
3558+
show_object_pack_hint((struct object *)commit, "", data);
3559+
return;
3560+
}
3561+
35443562
/* nothing to do; commits don't have a namehash */
3563+
35453564
}
35463565

35473566
static int pack_mtime_cmp(const void *_a, const void *_b)
@@ -3649,7 +3668,7 @@ static void read_packs_list_from_stdin(struct rev_info *revs)
36493668

36503669
static void add_unreachable_loose_objects(struct rev_info *revs);
36513670

3652-
static void read_stdin_packs(int rev_list_unpacked)
3671+
static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)
36533672
{
36543673
struct rev_info revs;
36553674

@@ -3681,7 +3700,7 @@ static void read_stdin_packs(int rev_list_unpacked)
36813700
traverse_commit_list(&revs,
36823701
show_commit_pack_hint,
36833702
show_object_pack_hint,
3684-
NULL);
3703+
&mode);
36853704

36863705
trace2_data_intmax("pack-objects", the_repository, "stdin_packs_found",
36873706
stdin_packs_found_nr);
@@ -4472,6 +4491,23 @@ static int is_not_in_promisor_pack(struct commit *commit, void *data) {
44724491
return is_not_in_promisor_pack_obj((struct object *) commit, data);
44734492
}
44744493

4494+
static int parse_stdin_packs_mode(const struct option *opt, const char *arg,
4495+
int unset)
4496+
{
4497+
enum stdin_packs_mode *mode = opt->value;
4498+
4499+
if (unset)
4500+
*mode = STDIN_PACKS_MODE_NONE;
4501+
else if (!arg || !*arg)
4502+
*mode = STDIN_PACKS_MODE_STANDARD;
4503+
else if (!strcmp(arg, "follow"))
4504+
*mode = STDIN_PACKS_MODE_FOLLOW;
4505+
else
4506+
die(_("invalid value for '%s': '%s'"), opt->long_name, arg);
4507+
4508+
return 0;
4509+
}
4510+
44754511
int cmd_pack_objects(int argc,
44764512
const char **argv,
44774513
const char *prefix,
@@ -4483,7 +4519,7 @@ int cmd_pack_objects(int argc,
44834519
struct strvec rp = STRVEC_INIT;
44844520
int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
44854521
int rev_list_index = 0;
4486-
int stdin_packs = 0;
4522+
enum stdin_packs_mode stdin_packs = STDIN_PACKS_MODE_NONE;
44874523
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
44884524
struct list_objects_filter_options filter_options =
44894525
LIST_OBJECTS_FILTER_INIT;
@@ -4538,6 +4574,9 @@ int cmd_pack_objects(int argc,
45384574
OPT_SET_INT_F(0, "indexed-objects", &rev_list_index,
45394575
N_("include objects referred to by the index"),
45404576
1, PARSE_OPT_NONEG),
4577+
OPT_CALLBACK_F(0, "stdin-packs", &stdin_packs, N_("mode"),
4578+
N_("read packs from stdin"),
4579+
PARSE_OPT_OPTARG, parse_stdin_packs_mode),
45414580
OPT_BOOL(0, "stdin-packs", &stdin_packs,
45424581
N_("read packs from stdin")),
45434582
OPT_BOOL(0, "stdout", &pack_to_stdout,
@@ -4794,7 +4833,7 @@ int cmd_pack_objects(int argc,
47944833
progress_state = start_progress(the_repository,
47954834
_("Enumerating objects"), 0);
47964835
if (stdin_packs) {
4797-
read_stdin_packs(rev_list_unpacked);
4836+
read_stdin_packs(stdin_packs, rev_list_unpacked);
47984837
} else if (cruft) {
47994838
read_cruft_objects();
48004839
} else if (!use_internal_rev_list) {

t/t5331-pack-objects-stdin.sh

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,86 @@ test_expect_success 'pack-objects --stdin with packfiles from main and alternate
236236
test_cmp expected-objects actual-objects
237237
'
238238

239+
packdir=.git/objects/pack
240+
241+
objects_in_packs () {
242+
for p in "$@"
243+
do
244+
git show-index <"$packdir/pack-$p.idx" || return 1
245+
done >objects.raw &&
246+
247+
cut -d' ' -f2 objects.raw | sort &&
248+
rm -f objects.raw
249+
}
250+
251+
test_expect_success '--stdin-packs=follow walks into unknown packs' '
252+
test_when_finished "rm -fr repo" &&
253+
254+
git init repo &&
255+
(
256+
cd repo &&
257+
258+
for c in A B C D
259+
do
260+
test_commit "$c" || return 1
261+
done &&
262+
263+
A="$(echo A | git pack-objects --revs $packdir/pack)" &&
264+
B="$(echo A..B | git pack-objects --revs $packdir/pack)" &&
265+
C="$(echo B..C | git pack-objects --revs $packdir/pack)" &&
266+
D="$(echo C..D | git pack-objects --revs $packdir/pack)" &&
267+
test_commit E &&
268+
269+
git prune-packed &&
270+
271+
cat >in <<-EOF &&
272+
pack-$B.pack
273+
^pack-$C.pack
274+
pack-$D.pack
275+
EOF
276+
277+
# With just --stdin-packs, pack "A" is unknown to us, so
278+
# only objects from packs "B" and "D" are included in
279+
# the output pack.
280+
P=$(git pack-objects --stdin-packs $packdir/pack <in) &&
281+
objects_in_packs $B $D >expect &&
282+
objects_in_packs $P >actual &&
283+
test_cmp expect actual &&
284+
285+
# But with --stdin-packs=follow, objects from both
286+
# included packs reach objects from the unknown pack, so
287+
# objects from pack "A" is included in the output pack
288+
# in addition to the above.
289+
P=$(git pack-objects --stdin-packs=follow $packdir/pack <in) &&
290+
objects_in_packs $A $B $D >expect &&
291+
objects_in_packs $P >actual &&
292+
test_cmp expect actual &&
293+
294+
# And with --unpacked, we will pick up objects from unknown
295+
# packs that are reachable from loose objects. Loose object E
296+
# reaches objects in pack A, but there are three excluded packs
297+
# in between.
298+
#
299+
# The resulting pack should include objects reachable from E
300+
# that are not present in packs B, C, or D, along with those
301+
# present in pack A.
302+
cat >in <<-EOF &&
303+
^pack-$B.pack
304+
^pack-$C.pack
305+
^pack-$D.pack
306+
EOF
307+
308+
P=$(git pack-objects --stdin-packs=follow --unpacked \
309+
$packdir/pack <in) &&
310+
311+
{
312+
objects_in_packs $A &&
313+
git rev-list --objects --no-object-names D..E
314+
}>expect.raw &&
315+
sort expect.raw >expect &&
316+
objects_in_packs $P >actual &&
317+
test_cmp expect actual
318+
)
319+
'
320+
239321
test_done

0 commit comments

Comments
 (0)