repack: extend --keep-unreachable to loose objects

If you use "repack -adk" currently, we will pack all objects that are already packed into the new pack, and then drop the old packs. However, loose unreachable objects will be left as-is. In theory these are meant to expire eventually with "git prune". But if you are using "repack -k", you probably want to keep things forever and therefore do not run "git prune" at all. Meaning those loose objects may build up over time and end up fooling any object-count heuristics (such as the one done by "gc --auto", though since git-gc does not support "repack -k", this really applies to whatever custom scripts people might have driving "repack -k"). With this patch, we instead stuff any loose unreachable objects into the pack along with the already-packed unreachable objects. This may seem wasteful, but it is really no more so than using "repack -k" in the first place. We are at a slight disadvantage, in that we have no useful ordering for the result, or names to hand to the delta code. However, this is again no worse than what "repack -k" is already doing for the packed objects. The packing of these objects doesn't matter much because they should not be accessed frequently (unless they actually _do_ become referenced, but then they would get moved to a different part of the packfile during the next repack). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Jun 13, 2016 at 00:38 UTC e26a8c4721ceaf4c59e33bbd4e60f777b7ea9b62
4 files changed +47 -1
Documentation/git-repack.txt
+2 -1
@@ -138,7 +138,8 @@ other objects in that pack they already have locally.
138 --keep-unreachable::
139 When used with `-ad`, any unreachable objects from existing
140 packs will be appended to the end of the packfile instead of
141 - being removed.
141 + being removed. In addition, any unreachable loose objects will
142 + be packed (and their loose counterparts removed).
143
144 Configuration
145 -------------
builtin/pack-objects.c
+31
@@ -44,6 +44,7 @@ static int non_empty;
44 static int reuse_delta = 1, reuse_object = 1;
45 static int keep_unreachable, unpack_unreachable, include_tag;
46 static unsigned long unpack_unreachable_expiration;
47 +static int pack_loose_unreachable;
48 static int local;
49 static int incremental;
50 static int ignore_packed_keep;
@@ -2378,6 +2379,32 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
2379 free(in_pack.array);
2380 }
2381
2382 +static int add_loose_object(const unsigned char *sha1, const char *path,
2383 + void *data)
2384 +{
2385 + enum object_type type = sha1_object_info(sha1, NULL);
2386 +
2387 + if (type < 0) {
2388 + warning("loose object at %s could not be examined", path);
2389 + return 0;
2390 + }
2391 +
2392 + add_object_entry(sha1, type, "", 0);
2393 + return 0;
2394 +}
2395 +
2396 +/*
2397 + * We actually don't even have to worry about reachability here.
2398 + * add_object_entry will weed out duplicates, so we just add every
2399 + * loose object we find.
2400 + */
2401 +static void add_unreachable_loose_objects(void)
2402 +{
2403 + for_each_loose_file_in_objdir(get_object_directory(),
2404 + add_loose_object,
2405 + NULL, NULL, NULL);
2406 +}
2407 +
2408 static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
2409 {
2410 static struct packed_git *last_found = (void *)1;
@@ -2547,6 +2574,8 @@ static void get_object_list(int ac, const char **av)
2574
2575 if (keep_unreachable)
2576 add_objects_in_unpacked_packs(&revs);
2577 + if (pack_loose_unreachable)
2578 + add_unreachable_loose_objects();
2579 if (unpack_unreachable)
2580 loosen_unused_packed_objects(&revs);
2581
@@ -2647,6 +2676,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
2676 N_("include tag objects that refer to objects to be packed")),
2677 OPT_BOOL(0, "keep-unreachable", &keep_unreachable,
2678 N_("keep unreachable objects")),
2679 + OPT_BOOL(0, "pack-loose-unreachable", &pack_loose_unreachable,
2680 + N_("pack loose unreachable objects")),
2681 { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, N_("time"),
2682 N_("unpack unreachable objects newer than <time>"),
2683 PARSE_OPT_OPTARG, option_parse_unpack_unreachable },
builtin/repack.c
+1
@@ -248,6 +248,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
248 "--unpack-unreachable");
249 } else if (keep_unreachable) {
250 argv_array_push(&cmd.args, "--keep-unreachable");
251 + argv_array_push(&cmd.args, "--pack-loose-unreachable");
252 } else {
253 argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
254 }
t/t7701-repack-unpack-unreachable.sh
+13
@@ -137,4 +137,17 @@ test_expect_success 'repack -k keeps unreachable packed objects' '
137 test_must_fail git cat-file -p $sha1
138 '
139
140 +test_expect_success 'repack -k packs unreachable loose objects' '
141 + # create loose unreachable object
142 + sha1=$(echo would-be-deleted-loose | git hash-object -w --stdin) &&
143 + objpath=.git/objects/$(echo $sha1 | sed "s,..,&/,") &&
144 + test_path_is_file $objpath &&
145 +
146 + # and confirm that the loose object goes away, but we can
147 + # still access it (ergo, it is packed)
148 + git repack -adk &&
149 + test_path_is_missing $objpath &&
150 + git cat-file -p $sha1
151 +'
152 +
153 test_done