Merge branch 'cc/repack-sift-filtered-objects-to-separate-pack'

"git repack" machinery learns to pay attention to the "--filter="
option.

* cc/repack-sift-filtered-objects-to-separate-pack:
  gc: add `gc.repackFilterTo` config option
  repack: implement `--filter-to` for storing filtered out objects
  gc: add `gc.repackFilter` config option
  repack: add `--filter=<filter-spec>` option
  pack-bitmap-write: rebuild using new bitmap when remapping
  repack: refactor finding pack prefix
  repack: refactor finishing pack-objects command
  t/helper: add 'find-pack' test-tool
  pack-objects: allow `--filter` without `--stdout`
This commit is contained in:
Junio C Hamano 2023-10-10 11:39:14 -07:00
commit 1fdedb7c7d
15 changed files with 544 additions and 51 deletions

View file

@ -145,6 +145,22 @@ Multiple hooks are supported, but all must exit successfully, else the
operation (either generating a cruft pack or unpacking unreachable
objects) will be halted.
gc.repackFilter::
When repacking, use the specified filter to move certain
objects into a separate packfile. See the
`--filter=<filter-spec>` option of linkgit:git-repack[1].
gc.repackFilterTo::
When repacking and using a filter, see `gc.repackFilter`, the
specified location will be used to create the packfile
containing the filtered out objects. **WARNING:** The
specified location should be accessible, using for example the
Git alternates mechanism, otherwise the repo could be
considered corrupt by Git as it migh not be able to access the
objects in that packfile. See the `--filter-to=<dir>` option
of linkgit:git-repack[1] and the `objects/info/alternates`
section of linkgit:gitrepository-layout[5].
gc.rerereResolved::
Records of conflicted merge you resolved earlier are
kept for this many days when 'git rerere gc' is run.

View file

@ -296,8 +296,8 @@ So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle.
nevertheless.
--filter=<filter-spec>::
Requires `--stdout`. Omits certain objects (usually blobs) from
the resulting packfile. See linkgit:git-rev-list[1] for valid
Omits certain objects (usually blobs) from the resulting
packfile. See linkgit:git-rev-list[1] for valid
`<filter-spec>` forms.
--no-filter::

View file

@ -143,6 +143,29 @@ depth is 4095.
a larger and slower repository; see the discussion in
`pack.packSizeLimit`.
--filter=<filter-spec>::
Remove objects matching the filter specification from the
resulting packfile and put them into a separate packfile. Note
that objects used in the working directory are not filtered
out. So for the split to fully work, it's best to perform it
in a bare repo and to use the `-a` and `-d` options along with
this option. Also `--no-write-bitmap-index` (or the
`repack.writebitmaps` config option set to `false`) should be
used otherwise writing bitmap index will fail, as it supposes
a single packfile containing all the objects. See
linkgit:git-rev-list[1] for valid `<filter-spec>` forms.
--filter-to=<dir>::
Write the pack containing filtered out objects to the
directory `<dir>`. Only useful with `--filter`. This can be
used for putting the pack on a separate object directory that
is accessed through the Git alternates mechanism. **WARNING:**
If the packfile containing the filtered out objects is not
accessible, the repo can become corrupt as it might not be
possible to access the objects in that packfile. See the
`objects` and `objects/info/alternates` sections of
linkgit:gitrepository-layout[5].
-b::
--write-bitmap-index::
Write a reachability bitmap index as part of the repack. This

View file

@ -800,6 +800,7 @@ TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
TEST_BUILTINS_OBJS += test-env-helper.o
TEST_BUILTINS_OBJS += test-example-decorate.o
TEST_BUILTINS_OBJS += test-fast-rebase.o
TEST_BUILTINS_OBJS += test-find-pack.o
TEST_BUILTINS_OBJS += test-fsmonitor-client.o
TEST_BUILTINS_OBJS += test-genrandom.o
TEST_BUILTINS_OBJS += test-genzeros.o

View file

@ -61,6 +61,8 @@ static timestamp_t gc_log_expire_time;
static const char *gc_log_expire = "1.day.ago";
static const char *prune_expire = "2.weeks.ago";
static const char *prune_worktrees_expire = "3.months.ago";
static char *repack_filter;
static char *repack_filter_to;
static unsigned long big_pack_threshold;
static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE;
@ -170,6 +172,9 @@ static void gc_config(void)
git_config_get_ulong("gc.bigpackthreshold", &big_pack_threshold);
git_config_get_ulong("pack.deltacachesize", &max_delta_cache_size);
git_config_get_string("gc.repackfilter", &repack_filter);
git_config_get_string("gc.repackfilterto", &repack_filter_to);
git_config(git_default_config, NULL);
}
@ -355,6 +360,11 @@ static void add_repack_all_option(struct string_list *keep_pack)
if (keep_pack)
for_each_string_list(keep_pack, keep_one_pack, NULL);
if (repack_filter && *repack_filter)
strvec_pushf(&repack, "--filter=%s", repack_filter);
if (repack_filter_to && *repack_filter_to)
strvec_pushf(&repack, "--filter-to=%s", repack_filter_to);
}
static void add_repack_incremental_option(void)

View file

@ -4402,12 +4402,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (!rev_list_all || !rev_list_reflog || !rev_list_index)
unpack_unreachable_expiration = 0;
if (filter_options.choice) {
if (!pack_to_stdout)
die(_("cannot use --filter without --stdout"));
if (stdin_packs)
die(_("cannot use --filter with --stdin-packs"));
}
if (stdin_packs && filter_options.choice)
die(_("cannot use --filter with --stdin-packs"));
if (stdin_packs && use_internal_rev_list)
die(_("cannot use internal rev list with --stdin-packs"));

View file

@ -21,6 +21,7 @@
#include "pack.h"
#include "pack-bitmap.h"
#include "refs.h"
#include "list-objects-filter-options.h"
#define ALL_INTO_ONE 1
#define LOOSEN_UNREACHABLE 2
@ -56,6 +57,7 @@ struct pack_objects_args {
int no_reuse_object;
int quiet;
int local;
struct list_objects_filter_options filter_options;
};
static int repack_config(const char *var, const char *value,
@ -806,6 +808,86 @@ static void remove_redundant_bitmaps(struct string_list *include,
strbuf_release(&path);
}
static int finish_pack_objects_cmd(struct child_process *cmd,
struct string_list *names,
int local)
{
FILE *out;
struct strbuf line = STRBUF_INIT;
out = xfdopen(cmd->out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
struct string_list_item *item;
if (line.len != the_hash_algo->hexsz)
die(_("repack: Expecting full hex object ID lines only "
"from pack-objects."));
/*
* Avoid putting packs written outside of the repository in the
* list of names.
*/
if (local) {
item = string_list_append(names, line.buf);
item->util = populate_pack_exts(line.buf);
}
}
fclose(out);
strbuf_release(&line);
return finish_command(cmd);
}
static int write_filtered_pack(const struct pack_objects_args *args,
const char *destination,
const char *pack_prefix,
struct existing_packs *existing,
struct string_list *names)
{
struct child_process cmd = CHILD_PROCESS_INIT;
struct string_list_item *item;
FILE *in;
int ret;
const char *caret;
const char *scratch;
int local = skip_prefix(destination, packdir, &scratch);
prepare_pack_objects(&cmd, args, destination);
strvec_push(&cmd.args, "--stdin-packs");
if (!pack_kept_objects)
strvec_push(&cmd.args, "--honor-pack-keep");
for_each_string_list_item(item, &existing->kept_packs)
strvec_pushf(&cmd.args, "--keep-pack=%s", item->string);
cmd.in = -1;
ret = start_command(&cmd);
if (ret)
return ret;
/*
* Here 'names' contains only the pack(s) that were just
* written, which is exactly the packs we want to keep. Also
* 'existing_kept_packs' already contains the packs in
* 'keep_pack_list'.
*/
in = xfdopen(cmd.in, "w");
for_each_string_list_item(item, names)
fprintf(in, "^%s-%s.pack\n", pack_prefix, item->string);
for_each_string_list_item(item, &existing->non_kept_packs)
fprintf(in, "%s.pack\n", item->string);
for_each_string_list_item(item, &existing->cruft_packs)
fprintf(in, "%s.pack\n", item->string);
caret = pack_kept_objects ? "" : "^";
for_each_string_list_item(item, &existing->kept_packs)
fprintf(in, "%s%s.pack\n", caret, item->string);
fclose(in);
return finish_pack_objects_cmd(&cmd, names, local);
}
static int write_cruft_pack(const struct pack_objects_args *args,
const char *destination,
const char *pack_prefix,
@ -814,9 +896,8 @@ static int write_cruft_pack(const struct pack_objects_args *args,
struct existing_packs *existing)
{
struct child_process cmd = CHILD_PROCESS_INIT;
struct strbuf line = STRBUF_INIT;
struct string_list_item *item;
FILE *in, *out;
FILE *in;
int ret;
const char *scratch;
int local = skip_prefix(destination, packdir, &scratch);
@ -861,27 +942,18 @@ static int write_cruft_pack(const struct pack_objects_args *args,
fprintf(in, "%s.pack\n", item->string);
fclose(in);
out = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
struct string_list_item *item;
return finish_pack_objects_cmd(&cmd, names, local);
}
if (line.len != the_hash_algo->hexsz)
die(_("repack: Expecting full hex object ID lines only "
"from pack-objects."));
/*
* avoid putting packs written outside of the repository in the
* list of names
*/
if (local) {
item = string_list_append(names, line.buf);
item->util = populate_pack_exts(line.buf);
}
}
fclose(out);
strbuf_release(&line);
return finish_command(&cmd);
static const char *find_pack_prefix(const char *packdir, const char *packtmp)
{
const char *pack_prefix;
if (!skip_prefix(packtmp, packdir, &pack_prefix))
die(_("pack prefix %s does not begin with objdir %s"),
packtmp, packdir);
if (*pack_prefix == '/')
pack_prefix++;
return pack_prefix;
}
int cmd_repack(int argc, const char **argv, const char *prefix)
@ -891,10 +963,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
struct string_list names = STRING_LIST_INIT_DUP;
struct existing_packs existing = EXISTING_PACKS_INIT;
struct pack_geometry geometry = { 0 };
struct strbuf line = STRBUF_INIT;
struct tempfile *refs_snapshot = NULL;
int i, ext, ret;
FILE *out;
int show_progress;
/* variables to be filled by option parsing */
@ -907,6 +977,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
int write_midx = 0;
const char *cruft_expiration = NULL;
const char *expire_to = NULL;
const char *filter_to = NULL;
struct option builtin_repack_options[] = {
OPT_BIT('a', NULL, &pack_everything,
@ -948,6 +1019,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
N_("limits the maximum number of threads")),
OPT_STRING(0, "max-pack-size", &po_args.max_pack_size, N_("bytes"),
N_("maximum size of each packfile")),
OPT_PARSE_LIST_OBJECTS_FILTER(&po_args.filter_options),
OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects,
N_("repack objects in packs marked with .keep")),
OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"),
@ -958,9 +1030,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
N_("write a multi-pack index of the resulting packs")),
OPT_STRING(0, "expire-to", &expire_to, N_("dir"),
N_("pack prefix to store a pack containing pruned objects")),
OPT_STRING(0, "filter-to", &filter_to, N_("dir"),
N_("pack prefix to store a pack containing filtered out objects")),
OPT_END()
};
list_objects_filter_init(&po_args.filter_options);
git_config(repack_config, &cruft_po_args);
argc = parse_options(argc, argv, prefix, builtin_repack_options,
@ -1101,6 +1177,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
strvec_push(&cmd.args, "--incremental");
}
if (po_args.filter_options.choice)
strvec_pushf(&cmd.args, "--filter=%s",
expand_list_objects_filter_spec(&po_args.filter_options));
else if (filter_to)
die(_("option '%s' can only be used along with '%s'"), "--filter-to", "--filter");
if (geometry.split_factor)
cmd.in = -1;
else
@ -1124,18 +1206,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
fclose(in);
}
out = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
struct string_list_item *item;
if (line.len != the_hash_algo->hexsz)
die(_("repack: Expecting full hex object ID lines only from pack-objects."));
item = string_list_append(&names, line.buf);
item->util = populate_pack_exts(item->string);
}
strbuf_release(&line);
fclose(out);
ret = finish_command(&cmd);
ret = finish_pack_objects_cmd(&cmd, &names, 1);
if (ret)
goto cleanup;
@ -1143,12 +1214,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
printf_ln(_("Nothing new to pack."));
if (pack_everything & PACK_CRUFT) {
const char *pack_prefix;
if (!skip_prefix(packtmp, packdir, &pack_prefix))
die(_("pack prefix %s does not begin with objdir %s"),
packtmp, packdir);
if (*pack_prefix == '/')
pack_prefix++;
const char *pack_prefix = find_pack_prefix(packdir, packtmp);
if (!cruft_po_args.window)
cruft_po_args.window = po_args.window;
@ -1203,6 +1269,19 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
}
}
if (po_args.filter_options.choice) {
if (!filter_to)
filter_to = packtmp;
ret = write_filtered_pack(&po_args,
filter_to,
find_pack_prefix(packdir, packtmp),
&existing,
&names);
if (ret)
goto cleanup;
}
string_list_sort(&names);
close_object_store(the_repository->objects);
@ -1295,6 +1374,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
string_list_clear(&names, 1);
existing_packs_release(&existing);
free_pack_geometry(&geometry);
list_objects_filter_release(&po_args.filter_options);
return ret;
}

View file

@ -413,15 +413,19 @@ static int fill_bitmap_commit(struct bb_commit *ent,
if (old_bitmap && mapping) {
struct ewah_bitmap *old = bitmap_for_commit(old_bitmap, c);
struct bitmap *remapped = bitmap_new();
/*
* If this commit has an old bitmap, then translate that
* bitmap and add its bits to this one. No need to walk
* parents or the tree for this commit.
*/
if (old && !rebuild_bitmap(mapping, old, ent->bitmap)) {
if (old && !rebuild_bitmap(mapping, old, remapped)) {
bitmap_or(ent->bitmap, remapped);
bitmap_free(remapped);
reused_bitmaps_nr++;
continue;
}
bitmap_free(remapped);
}
/*

50
t/helper/test-find-pack.c Normal file
View file

@ -0,0 +1,50 @@
#include "test-tool.h"
#include "object-name.h"
#include "object-store.h"
#include "packfile.h"
#include "parse-options.h"
#include "setup.h"
/*
* Display the path(s), one per line, of the packfile(s) containing
* the given object.
*
* If '--check-count <n>' is passed, then error out if the number of
* packfiles containing the object is not <n>.
*/
static const char *find_pack_usage[] = {
"test-tool find-pack [--check-count <n>] <object>",
NULL
};
int cmd__find_pack(int argc, const char **argv)
{
struct object_id oid;
struct packed_git *p;
int count = -1, actual_count = 0;
const char *prefix = setup_git_directory();
struct option options[] = {
OPT_INTEGER('c', "check-count", &count, "expected number of packs"),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options, find_pack_usage, 0);
if (argc != 1)
usage(find_pack_usage[0]);
if (repo_get_oid(the_repository, argv[0], &oid))
die("cannot parse %s as an object name", argv[0]);
for (p = get_all_packs(the_repository); p; p = p->next)
if (find_pack_entry_one(oid.hash, p)) {
printf("%s\n", p->pack_name);
actual_count++;
}
if (count > -1 && count != actual_count)
die("bad packfile count %d instead of %d", actual_count, count);
return 0;
}

View file

@ -31,6 +31,7 @@ static struct test_cmd cmds[] = {
{ "env-helper", cmd__env_helper },
{ "example-decorate", cmd__example_decorate },
{ "fast-rebase", cmd__fast_rebase },
{ "find-pack", cmd__find_pack },
{ "fsmonitor-client", cmd__fsmonitor_client },
{ "genrandom", cmd__genrandom },
{ "genzeros", cmd__genzeros },

View file

@ -25,6 +25,7 @@ int cmd__dump_reftable(int argc, const char **argv);
int cmd__env_helper(int argc, const char **argv);
int cmd__example_decorate(int argc, const char **argv);
int cmd__fast_rebase(int argc, const char **argv);
int cmd__find_pack(int argc, const char **argv);
int cmd__fsmonitor_client(int argc, const char **argv);
int cmd__genrandom(int argc, const char **argv);
int cmd__genzeros(int argc, const char **argv);

82
t/t0081-find-pack.sh Executable file
View file

@ -0,0 +1,82 @@
#!/bin/sh
test_description='test `test-tool find-pack`'
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_expect_success 'setup' '
test_commit one &&
test_commit two &&
test_commit three &&
test_commit four &&
test_commit five
'
test_expect_success 'repack everything into a single packfile' '
git repack -a -d --no-write-bitmap-index &&
head_commit_pack=$(test-tool find-pack HEAD) &&
head_tree_pack=$(test-tool find-pack HEAD^{tree}) &&
one_pack=$(test-tool find-pack HEAD:one.t) &&
three_pack=$(test-tool find-pack HEAD:three.t) &&
old_commit_pack=$(test-tool find-pack HEAD~4) &&
test-tool find-pack --check-count 1 HEAD &&
test-tool find-pack --check-count=1 HEAD^{tree} &&
! test-tool find-pack --check-count=0 HEAD:one.t &&
! test-tool find-pack -c 2 HEAD:one.t &&
test-tool find-pack -c 1 HEAD:three.t &&
# Packfile exists at the right path
case "$head_commit_pack" in
".git/objects/pack/pack-"*".pack") true ;;
*) false ;;
esac &&
test -f "$head_commit_pack" &&
# Everything is in the same pack
test "$head_commit_pack" = "$head_tree_pack" &&
test "$head_commit_pack" = "$one_pack" &&
test "$head_commit_pack" = "$three_pack" &&
test "$head_commit_pack" = "$old_commit_pack"
'
test_expect_success 'add more packfiles' '
git rev-parse HEAD^{tree} HEAD:two.t HEAD:four.t >objects &&
git pack-objects .git/objects/pack/mypackname1 >packhash1 <objects &&
git rev-parse HEAD~ HEAD~^{tree} HEAD:five.t >objects &&
git pack-objects .git/objects/pack/mypackname2 >packhash2 <objects &&
head_commit_pack=$(test-tool find-pack HEAD) &&
# HEAD^{tree} is in 2 packfiles
test-tool find-pack HEAD^{tree} >head_tree_packs &&
grep "$head_commit_pack" head_tree_packs &&
grep mypackname1 head_tree_packs &&
! grep mypackname2 head_tree_packs &&
test-tool find-pack --check-count 2 HEAD^{tree} &&
! test-tool find-pack --check-count 1 HEAD^{tree} &&
# HEAD:five.t is also in 2 packfiles
test-tool find-pack HEAD:five.t >five_packs &&
grep "$head_commit_pack" five_packs &&
! grep mypackname1 five_packs &&
grep mypackname2 five_packs &&
test-tool find-pack -c 2 HEAD:five.t &&
! test-tool find-pack --check-count=0 HEAD:five.t
'
test_expect_success 'add more commits (as loose objects)' '
test_commit six &&
test_commit seven &&
test -z "$(test-tool find-pack HEAD)" &&
test -z "$(test-tool find-pack HEAD:six.t)" &&
test-tool find-pack --check-count 0 HEAD &&
test-tool find-pack -c 0 HEAD:six.t &&
! test-tool find-pack -c 1 HEAD:seven.t
'
test_done

View file

@ -53,6 +53,14 @@ test_expect_success 'verify blob:none packfile has no blobs' '
! grep blob verify_result
'
test_expect_success 'verify blob:none packfile without --stdout' '
git -C r1 pack-objects --revs --filter=blob:none mypackname >packhash <<-EOF &&
HEAD
EOF
git -C r1 verify-pack -v "mypackname-$(cat packhash).pack" >verify_result &&
! grep blob verify_result
'
test_expect_success 'verify normal and blob:none packfiles have same commits/trees' '
git -C r1 verify-pack -v ../all.pack >verify_result &&
grep -E "commit|tree" verify_result |

View file

@ -202,6 +202,30 @@ test_expect_success 'one of gc.reflogExpire{Unreachable,}=never does not skip "e
grep -E "^trace: (built-in|exec|run_command): git reflog expire --" trace.out
'
test_expect_success 'gc.repackFilter launches repack with a filter' '
git clone --no-local --bare . bare.git &&
git -C bare.git -c gc.cruftPacks=false gc &&
test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack &&
GIT_TRACE=$(pwd)/trace.out git -C bare.git -c gc.repackFilter=blob:none \
-c repack.writeBitmaps=false -c gc.cruftPacks=false gc &&
test_stdout_line_count = 2 ls bare.git/objects/pack/*.pack &&
grep -E "^trace: (built-in|exec|run_command): git repack .* --filter=blob:none ?.*" trace.out
'
test_expect_success 'gc.repackFilterTo store filtered out objects' '
test_when_finished "rm -rf bare.git filtered.git" &&
git init --bare filtered.git &&
git -C bare.git -c gc.repackFilter=blob:none \
-c gc.repackFilterTo=../filtered.git/objects/pack/pack \
-c repack.writeBitmaps=false -c gc.cruftPacks=false gc &&
test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack &&
test_stdout_line_count = 1 ls filtered.git/objects/pack/*.pack
'
prepare_cruft_history () {
test_commit base &&

View file

@ -327,6 +327,203 @@ test_expect_success 'auto-bitmaps do not complain if unavailable' '
test_must_be_empty actual
'
test_expect_success 'repacking with a filter works' '
git -C bare.git repack -a -d &&
test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack &&
git -C bare.git -c repack.writebitmaps=false repack -a -d --filter=blob:none &&
test_stdout_line_count = 2 ls bare.git/objects/pack/*.pack &&
commit_pack=$(test-tool -C bare.git find-pack -c 1 HEAD) &&
blob_pack=$(test-tool -C bare.git find-pack -c 1 HEAD:file1) &&
test "$commit_pack" != "$blob_pack" &&
tree_pack=$(test-tool -C bare.git find-pack -c 1 HEAD^{tree}) &&
test "$tree_pack" = "$commit_pack" &&
blob_pack2=$(test-tool -C bare.git find-pack -c 1 HEAD:file2) &&
test "$blob_pack2" = "$blob_pack"
'
test_expect_success '--filter fails with --write-bitmap-index' '
test_must_fail \
env GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 \
git -C bare.git repack -a -d --write-bitmap-index --filter=blob:none
'
test_expect_success 'repacking with two filters works' '
git init two-filters &&
(
cd two-filters &&
mkdir subdir &&
test_commit foo &&
test_commit subdir_bar subdir/bar &&
test_commit subdir_baz subdir/baz
) &&
git clone --no-local --bare two-filters two-filters.git &&
(
cd two-filters.git &&
test_stdout_line_count = 1 ls objects/pack/*.pack &&
git -c repack.writebitmaps=false repack -a -d \
--filter=blob:none --filter=tree:1 &&
test_stdout_line_count = 2 ls objects/pack/*.pack &&
commit_pack=$(test-tool find-pack -c 1 HEAD) &&
blob_pack=$(test-tool find-pack -c 1 HEAD:foo.t) &&
root_tree_pack=$(test-tool find-pack -c 1 HEAD^{tree}) &&
subdir_tree_hash=$(git ls-tree --object-only HEAD -- subdir) &&
subdir_tree_pack=$(test-tool find-pack -c 1 "$subdir_tree_hash") &&
# Root tree and subdir tree are not in the same packfiles
test "$commit_pack" != "$blob_pack" &&
test "$commit_pack" = "$root_tree_pack" &&
test "$blob_pack" = "$subdir_tree_pack"
)
'
prepare_for_keep_packs () {
git init keep-packs &&
(
cd keep-packs &&
test_commit foo &&
test_commit bar
) &&
git clone --no-local --bare keep-packs keep-packs.git &&
(
cd keep-packs.git &&
# Create two packs
# The first pack will contain all of the objects except one blob
git rev-list --objects --all >objs &&
grep -v "bar.t" objs | git pack-objects pack &&
# The second pack will contain the excluded object and be kept
packid=$(grep "bar.t" objs | git pack-objects pack) &&
>pack-$packid.keep &&
# Replace the existing pack with the 2 new ones
rm -f objects/pack/pack* &&
mv pack-* objects/pack/
)
}
test_expect_success '--filter works with .keep packs' '
prepare_for_keep_packs &&
(
cd keep-packs.git &&
foo_pack=$(test-tool find-pack -c 1 HEAD:foo.t) &&
bar_pack=$(test-tool find-pack -c 1 HEAD:bar.t) &&
head_pack=$(test-tool find-pack -c 1 HEAD) &&
test "$foo_pack" != "$bar_pack" &&
test "$foo_pack" = "$head_pack" &&
git -c repack.writebitmaps=false repack -a -d --filter=blob:none &&
foo_pack_1=$(test-tool find-pack -c 1 HEAD:foo.t) &&
bar_pack_1=$(test-tool find-pack -c 1 HEAD:bar.t) &&
head_pack_1=$(test-tool find-pack -c 1 HEAD) &&
# Object bar is still only in the old .keep pack
test "$foo_pack_1" != "$foo_pack" &&
test "$bar_pack_1" = "$bar_pack" &&
test "$head_pack_1" != "$head_pack" &&
test "$foo_pack_1" != "$bar_pack_1" &&
test "$foo_pack_1" != "$head_pack_1" &&
test "$bar_pack_1" != "$head_pack_1"
)
'
test_expect_success '--filter works with --pack-kept-objects and .keep packs' '
rm -rf keep-packs keep-packs.git &&
prepare_for_keep_packs &&
(
cd keep-packs.git &&
foo_pack=$(test-tool find-pack -c 1 HEAD:foo.t) &&
bar_pack=$(test-tool find-pack -c 1 HEAD:bar.t) &&
head_pack=$(test-tool find-pack -c 1 HEAD) &&
test "$foo_pack" != "$bar_pack" &&
test "$foo_pack" = "$head_pack" &&
git -c repack.writebitmaps=false repack -a -d --filter=blob:none \
--pack-kept-objects &&
foo_pack_1=$(test-tool find-pack -c 1 HEAD:foo.t) &&
test-tool find-pack -c 2 HEAD:bar.t >bar_pack_1 &&
head_pack_1=$(test-tool find-pack -c 1 HEAD) &&
test "$foo_pack_1" != "$foo_pack" &&
test "$foo_pack_1" != "$bar_pack" &&
test "$head_pack_1" != "$head_pack" &&
# Object bar is in both the old .keep pack and the new
# pack that contained the filtered out objects
grep "$bar_pack" bar_pack_1 &&
grep "$foo_pack_1" bar_pack_1 &&
test "$foo_pack_1" != "$head_pack_1"
)
'
test_expect_success '--filter-to stores filtered out objects' '
git -C bare.git repack -a -d &&
test_stdout_line_count = 1 ls bare.git/objects/pack/*.pack &&
git init --bare filtered.git &&
git -C bare.git -c repack.writebitmaps=false repack -a -d \
--filter=blob:none \
--filter-to=../filtered.git/objects/pack/pack &&
test_stdout_line_count = 1 ls bare.git/objects/pack/pack-*.pack &&
test_stdout_line_count = 1 ls filtered.git/objects/pack/pack-*.pack &&
commit_pack=$(test-tool -C bare.git find-pack -c 1 HEAD) &&
blob_pack=$(test-tool -C bare.git find-pack -c 0 HEAD:file1) &&
blob_hash=$(git -C bare.git rev-parse HEAD:file1) &&
test -n "$blob_hash" &&
blob_pack=$(test-tool -C filtered.git find-pack -c 1 $blob_hash) &&
echo $(pwd)/filtered.git/objects >bare.git/objects/info/alternates &&
blob_pack=$(test-tool -C bare.git find-pack -c 1 HEAD:file1) &&
blob_content=$(git -C bare.git show $blob_hash) &&
test "$blob_content" = "content1"
'
test_expect_success '--filter works with --max-pack-size' '
rm -rf filtered.git &&
git init --bare filtered.git &&
git init max-pack-size &&
(
cd max-pack-size &&
test_commit base &&
# two blobs which exceed the maximum pack size
test-tool genrandom foo 1048576 >foo &&
git hash-object -w foo &&
test-tool genrandom bar 1048576 >bar &&
git hash-object -w bar &&
git add foo bar &&
git commit -m "adding foo and bar"
) &&
git clone --no-local --bare max-pack-size max-pack-size.git &&
(
cd max-pack-size.git &&
git -c repack.writebitmaps=false repack -a -d --filter=blob:none \
--max-pack-size=1M \
--filter-to=../filtered.git/objects/pack/pack &&
echo $(cd .. && pwd)/filtered.git/objects >objects/info/alternates &&
# Check that the 3 blobs are in different packfiles in filtered.git
test_stdout_line_count = 3 ls ../filtered.git/objects/pack/pack-*.pack &&
test_stdout_line_count = 1 ls objects/pack/pack-*.pack &&
foo_pack=$(test-tool find-pack -c 1 HEAD:foo) &&
bar_pack=$(test-tool find-pack -c 1 HEAD:bar) &&
base_pack=$(test-tool find-pack -c 1 HEAD:base.t) &&
test "$foo_pack" != "$bar_pack" &&
test "$foo_pack" != "$base_pack" &&
test "$bar_pack" != "$base_pack" &&
for pack in "$foo_pack" "$bar_pack" "$base_pack"
do
case "$foo_pack" in */filtered.git/objects/pack/*) true ;; *) return 1 ;; esac
done
)
'
objdir=.git/objects
midx=$objdir/pack/multi-pack-index