[PATCH v2 4/5] pack-objects: add --keep-pack-from-file
From: Qin ShiCheng via GitGitGadget <hidden>
Date: 2026-09-18 03:03:43
Subsystem:
documentation, the rest · Maintainers:
Jonathan Corbet, Linus Torvalds
From: Qin ShiCheng <redacted> "--keep-pack" names one pack per occurrence, and there is only so much room on the command line: ARG_MAX is shared with the environment, and on Windows the whole line is capped at 32,767 characters, which a few hundred pack names fill. Past that the spawn fails before pack-objects has started. fetch-pack grew "--stdin" in 078b895fef (fetch-pack: new --stdin option to read refs from stdin, 2012-04-02) for the same reason. stdin is taken here: every mode repack drives pack-objects in already uses it, for the revision list under "-a", object names for the promisor pack, and pack lists for "--stdin-packs" and "--cruft". So read the names from a file instead, one per line, skipping empty lines. They go into the same list as the "--keep-pack" names and are treated exactly alike: matched against local packs, ignored when they match nothing, and kept open under "--stdin-packs=follow". A relative path is resolved against the directory the user ran from, as "--refs-snapshot" of "git multi-pack-index write" is. The list now holds strings from two sources, so let it own its copies. repack is about to use this to hand pack-objects its own snapshot of the packs that have a ".keep" file. Signed-off-by: Qin ShiCheng <redacted> --- Documentation/git-pack-objects.adoc | 8 +++++ builtin/pack-objects.c | 28 ++++++++++++++++++ t/t5331-pack-objects-stdin.sh | 46 +++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+)
diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc
index 65cd00c152..938e27f69d 100644
--- a/Documentation/git-pack-objects.adoc
+++ b/Documentation/git-pack-objects.adoc@@ -13,6 +13,7 @@ SYNOPSIS [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=<n>] [--depth=<n>] [--revs [--unpacked | --all]] [--keep-pack=<pack-name>] + [--keep-pack-from-file=<file>] [--cruft] [--cruft-expiration=<time>] [--stdout [--filter=<filter-spec>] | <base-name>] [--shallow] [--keep-true-parents] [--[no-]sparse]
@@ -193,6 +194,13 @@ depth is 4095. leading directory (e.g. `pack-123.pack`). The option could be specified multiple times to keep multiple packs. +--keep-pack-from-file=<file>:: + Read names of packs to keep from `<file>`, one per line, and + treat each of them as if it had been given with `--keep-pack`. + Empty lines are ignored. This is meant for callers such as + linkgit:git-repack[1] that may have to name more packs than fit + on a command line. + --incremental:: This flag causes an object already in a pack to be ignored even if it would have otherwise been packed.
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 1fcb4ef8a5..9f8c4b9135 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c@@ -194,6 +194,7 @@ static const char *const pack_usage[] = { " [--no-reuse-delta] [--delta-base-offset] [--non-empty]\n" " [--local] [--incremental] [--window=<n>] [--depth=<n>]\n" " [--revs [--unpacked | --all]] [--keep-pack=<pack-name>]\n" + " [--keep-pack-from-file=<file>]\n" " [--cruft] [--cruft-expiration=<time>]\n" " [--stdout [--filter=<filter-spec>] | <base-name>]\n" " [--shallow] [--keep-true-parents] [--[no-]sparse]\n"
@@ -5007,6 +5008,26 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv) oid_array_clear(&recent_objects); } +/* + * Read pack names from the file, one per line, as if each of them had + * been given with "--keep-pack". + */ +static void read_keep_pack_list(struct string_list *names, const char *path) +{ + struct strbuf buf = STRBUF_INIT; + FILE *fp = xfopen(path, "r"); + + while (strbuf_getline(&buf, fp) != EOF) { + if (!buf.len) + continue; + string_list_append(names, buf.buf); + } + if (ferror(fp)) + die_errno(_("could not read '%s'"), path); + fclose(fp); + strbuf_release(&buf); +} + static void add_extra_kept_packs(struct string_list *names, enum stdin_packs_mode stdin_packs) {
@@ -5147,8 +5168,10 @@ int cmd_pack_objects(int argc, int rev_list_index = 0; enum stdin_packs_mode stdin_packs = STDIN_PACKS_MODE_NONE; struct string_list keep_pack_list = { + .strdup_strings = 1, .cmp = fspathcmp, }; + char *keep_pack_from_file = NULL; struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; struct repo_config_values *cfg = repo_config_values(the_repository);
@@ -5233,6 +5256,8 @@ int cmd_pack_objects(int argc, N_("ignore packs that have companion .keep file")), OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"), N_("ignore this pack")), + OPT_FILENAME(0, "keep-pack-from-file", &keep_pack_from_file, + N_("ignore the packs named in <file>")), OPT_INTEGER(0, "compression", &cfg->pack_compression_level, N_("pack compression level")), OPT_BOOL(0, "keep-true-parents", &grafts_keep_true_parents,
@@ -5460,6 +5485,8 @@ int cmd_pack_objects(int argc, if (progress && all_progress_implied) progress = 2; + if (keep_pack_from_file) + read_keep_pack_list(&keep_pack_list, keep_pack_from_file); add_extra_kept_packs(&keep_pack_list, stdin_packs); if (ignore_packed_keep_on_disk) { struct packed_git *p;
@@ -5554,6 +5581,7 @@ cleanup: clear_packing_data(&to_pack); list_objects_filter_release(&filter_options); string_list_clear(&keep_pack_list, 0); + free(keep_pack_from_file); strvec_clear(&rp); return 0;
diff --git a/t/t5331-pack-objects-stdin.sh b/t/t5331-pack-objects-stdin.sh
index 4e1fde1b08..d590aa4dad 100755
--- a/t/t5331-pack-objects-stdin.sh
+++ b/t/t5331-pack-objects-stdin.sh@@ -561,4 +561,50 @@ test_expect_success '--stdin-packs with !-delimited pack without follow' ' ) ' +test_expect_success '--keep-pack-from-file names packs to keep' ' + test_when_finished "rm -fr repo" && + + git init repo && + ( + cd repo && + git config set maintenance.auto false && + + test_commit A && + test_commit B && + test_commit C && + + A="$(echo A | git pack-objects --revs $packdir/pack)" && + B="$(echo A..B | git pack-objects --revs $packdir/pack)" && + C="$(echo B..C | git pack-objects --revs $packdir/pack)" && + git prune-packed && + + # Empty lines and names that match no pack are ignored, + # as they would be with --keep-pack. + cat >keep <<-EOF && + pack-$A.pack + + pack-$B.pack + pack-does-not-exist.pack + EOF + + P=$(git pack-objects --all --keep-pack=pack-$A.pack \ + --keep-pack=pack-$B.pack from-argv </dev/null) && + packed_objects from-argv-$P.idx >expect && + + P=$(git pack-objects --all --keep-pack-from-file=keep \ + from-file </dev/null) && + packed_objects from-file-$P.idx >actual && + test_cmp expect actual && + + objects_in_packs $C >expect && + test_cmp expect actual + ) +' + +test_expect_success '--keep-pack-from-file with a missing file' ' + test_must_fail git pack-objects --stdout \ + --keep-pack-from-file=does-not-exist </dev/null 2>err && + test_grep "could not open .does-not-exist. for reading" err +' + test_done
--
gitgitgadget