mirror of
https://github.com/git/git.git
synced 2026-08-08 17:11:48 +00:00
Merge branch 'ss/repack-drop-filtered' into seen
'git repack' has been taught '--drop-filtered' to delete local promisor blobs exceeding a limit (currently 'blob:limit=') in partial clones, reclaiming space. Guards prevent running during other operations or if referenced by the index. * ss/repack-drop-filtered: SQUASH??? Documentation/git-repack: document --drop-filtered and --dry-run builtin/repack: add guards for --drop-filtered builtin/repack: actually drop filtered promisor blobs builtin/repack: enumerate promisor blobs for --drop-filtered repack-promisor: allow excluding objects from the rebuilt promisor pack list-objects-filter: add list_objects_filter__filter_oidset() builtin/repack.c: add --drop-filtered and --dry-run options
This commit is contained in:
@@ -12,6 +12,7 @@ SYNOPSIS
|
||||
'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m]
|
||||
[--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>]
|
||||
[--write-midx[=<mode>]] [--name-hash-version=<n>] [--path-walk]
|
||||
[--filter=<filter-spec>] [--drop-filtered [--dry-run]]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@@ -187,6 +188,42 @@ depth is 4095.
|
||||
`objects` and `objects/info/alternates` sections of
|
||||
linkgit:gitrepository-layout[5].
|
||||
|
||||
--drop-filtered::
|
||||
Delete the local objects that match the `--filter` specification
|
||||
instead of keeping them in a separate packfile, reclaiming the
|
||||
disk space they occupy. This is intended for partial clones,
|
||||
where the filtered objects are promisor objects that remain
|
||||
recoverable from the promisor remote and are lazily re-fetched
|
||||
on demand when they are next needed.
|
||||
+
|
||||
Only large blobs are supported for now, so `--filter=blob:limit=<n>`
|
||||
is currently the only accepted filter. Because dropped objects must be
|
||||
recoverable, this option requires a promisor remote to be configured
|
||||
and refuses to run otherwise.
|
||||
+
|
||||
This option requires `-a`, and implies `-d`: the objects are dropped by
|
||||
rebuilding the promisor pack without them and then removing the now
|
||||
redundant old packs, so the redundant packs must be deleted for the
|
||||
space to actually be reclaimed. It is incompatible with `--filter-to`
|
||||
and with bitmap writing (`-b`/`--write-bitmap-index`), since filtering
|
||||
breaks the single-pack closure that bitmaps require. A bitmap setting
|
||||
coming from configuration is silently disabled for the duration of the
|
||||
command.
|
||||
+
|
||||
As a convenience since dropped objects remain recoverable by lazy fetch,
|
||||
`--drop-filtered` refuses to run while another operation
|
||||
(merge, rebase, am, cherry-pick, revert, or bisect) is in progress, to
|
||||
avoid a surprising network fetch mid-operation, and refuses to drop any
|
||||
blob that the current index references, since such a blob would only be
|
||||
lazily re-fetched by the next command that inspects the working tree.
|
||||
These checks are skipped in bare repositories, which have neither a
|
||||
working tree nor an index.
|
||||
|
||||
--dry-run::
|
||||
Only meaningful with `--drop-filtered`. List the objects that
|
||||
would be dropped, one object ID per line, without rebuilding any
|
||||
pack or deleting anything.
|
||||
|
||||
-b::
|
||||
--write-bitmap-index::
|
||||
Write a reachability bitmap index as part of the repack. This
|
||||
|
||||
151
builtin/repack.c
151
builtin/repack.c
@@ -14,6 +14,11 @@
|
||||
#include "promisor-remote.h"
|
||||
#include "repack.h"
|
||||
#include "shallow.h"
|
||||
#include "list-objects-filter-options.h"
|
||||
#include "oidset.h"
|
||||
#include "hex.h"
|
||||
#include "wt-status.h"
|
||||
#include "read-cache-ll.h"
|
||||
|
||||
#define ALL_INTO_ONE 1
|
||||
#define LOOSEN_UNREACHABLE 2
|
||||
@@ -28,11 +33,15 @@ static int use_delta_islands;
|
||||
static int run_update_server_info = 1;
|
||||
static char *packdir, *packtmp_name, *packtmp;
|
||||
static int midx_must_contain_cruft = 1;
|
||||
static int drop_filtered;
|
||||
static int dry_run;
|
||||
static int write_bitmaps_given;
|
||||
|
||||
static const char *const git_repack_usage[] = {
|
||||
N_("git repack [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [-m]\n"
|
||||
"[--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>]\n"
|
||||
"[--write-midx[=<mode>]] [--name-hash-version=<n>] [--path-walk]"),
|
||||
"[--write-midx[=<mode>]] [--name-hash-version=<n>] [--path-walk]\n"
|
||||
"[--filter=<filter-spec>] [--drop-filtered [--dry-run]]"),
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -111,6 +120,21 @@ static int repack_config(const char *var, const char *value,
|
||||
return git_default_config(var, value, ctx, cb);
|
||||
}
|
||||
|
||||
static int option_parse_write_bitmaps(const struct option *opt, const char *arg,
|
||||
int unset)
|
||||
{
|
||||
int *value = opt->value;
|
||||
|
||||
BUG_ON_OPT_ARG(arg);
|
||||
if (unset)
|
||||
*value = 0;
|
||||
else
|
||||
*value = 1;
|
||||
|
||||
write_bitmaps_given = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int option_parse_write_midx(const struct option *opt, const char *arg,
|
||||
int unset)
|
||||
{
|
||||
@@ -140,6 +164,7 @@ int cmd_repack(int argc,
|
||||
struct string_list_item *item;
|
||||
struct string_list names = STRING_LIST_INIT_DUP;
|
||||
struct existing_packs existing = EXISTING_PACKS_INIT;
|
||||
struct oidset drop_oids = OIDSET_INIT;
|
||||
struct pack_geometry geometry = { 0 };
|
||||
struct tempfile *refs_snapshot = NULL;
|
||||
int i, ret;
|
||||
@@ -194,8 +219,9 @@ int cmd_repack(int argc,
|
||||
OPT__QUIET(&po_args.quiet, N_("be quiet")),
|
||||
OPT_BOOL('l', "local", &po_args.local,
|
||||
N_("pass --local to git-pack-objects")),
|
||||
OPT_BOOL('b', "write-bitmap-index", &write_bitmaps,
|
||||
N_("write bitmap index")),
|
||||
OPT_CALLBACK_F('b', "write-bitmap-index", &write_bitmaps, NULL,
|
||||
N_("write bitmap index"),
|
||||
PARSE_OPT_NOARG, option_parse_write_bitmaps),
|
||||
OPT_BOOL('i', "delta-islands", &use_delta_islands,
|
||||
N_("pass --delta-islands to git-pack-objects")),
|
||||
OPT_STRING(0, "unpack-unreachable", &unpack_unreachable, N_("approxidate"),
|
||||
@@ -231,6 +257,10 @@ int cmd_repack(int argc,
|
||||
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_BOOL(0, "drop-filtered", &drop_filtered,
|
||||
N_("delete filtered out objects (requires --filter)")),
|
||||
OPT_BOOL(0, "dry-run", &dry_run,
|
||||
N_("only show which objects would be dropped")),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
@@ -252,6 +282,115 @@ int cmd_repack(int argc,
|
||||
po_args.depth = xstrdup_or_null(opt_depth);
|
||||
po_args.threads = xstrdup_or_null(opt_threads);
|
||||
|
||||
die_for_incompatible_opt2(drop_filtered, "--drop-filtered",
|
||||
!!filter_to, "--filter-to");
|
||||
|
||||
if (dry_run && !drop_filtered)
|
||||
die(_("--dry-run only takes effect with --drop-filtered"));
|
||||
|
||||
if (drop_filtered) {
|
||||
if (!po_args.filter_options.choice)
|
||||
die(_("--drop-filtered requires --filter"));
|
||||
|
||||
if (!(pack_everything & ALL_INTO_ONE))
|
||||
die(_("--drop-filtered requires -a"));
|
||||
|
||||
/*
|
||||
* Only blob:limit=<n> is supported for now. Reject other
|
||||
* filter choices early, before walking the object database.
|
||||
*/
|
||||
if (po_args.filter_options.choice != LOFC_BLOB_LIMIT)
|
||||
die(_("--drop-filtered only supports --filter=blob:limit=<n> for now"));
|
||||
|
||||
/*
|
||||
* an explicit -b on the command line is a conflict we have to
|
||||
* report, a bitmap setting from config is silently overridden
|
||||
* for the duration of the command
|
||||
*/
|
||||
if (write_bitmaps_given && write_bitmaps > 0)
|
||||
die(_("options '%s' and '%s' cannot be used together"),
|
||||
"--drop-filtered", "--write-bitmap-index");
|
||||
|
||||
/*
|
||||
* Without a promisor remote there is nowhere to re-fetch the
|
||||
* dropped objects from, so dropping them would be permanent
|
||||
* data loss.
|
||||
*/
|
||||
if (!repo_has_promisor_remote(repo))
|
||||
die(_("--drop-filtered requires a promisor remote"));
|
||||
|
||||
/*
|
||||
* refuse to run while another operation is in progress. A
|
||||
* dropped object would just be lazily re-fetched when the
|
||||
* operation resumes, but triggering a network fetch in the
|
||||
* middle of a half-finished
|
||||
* merge/rebase/cherry-pick/revert/bisect is a poor
|
||||
* experience, so this is a UX convenience rather than a
|
||||
* safety measure. Bare repositories have no such state, so
|
||||
* the check is skipped there.
|
||||
*/
|
||||
if (!is_bare_repository(repo)) {
|
||||
struct wt_status_state state = { 0 };
|
||||
|
||||
wt_status_get_state(repo, &state, 0);
|
||||
if (state.merge_in_progress || state.revert_in_progress ||
|
||||
state.rebase_in_progress ||state.bisect_in_progress ||
|
||||
state.cherry_pick_in_progress ||state.am_in_progress||
|
||||
state.rebase_interactive_in_progress) {
|
||||
wt_status_state_free_buffers(&state);
|
||||
die(_("--drop-filtered cannot be used while another operation is in progress"));
|
||||
}
|
||||
wt_status_state_free_buffers(&state);
|
||||
}
|
||||
|
||||
write_bitmaps = 0;
|
||||
|
||||
/*
|
||||
* Dropping objects means rebuilding the promisor packs
|
||||
* without them and then removing the old packs, so the
|
||||
* redundant packs must be deleted. Imply -d on a real run.
|
||||
*/
|
||||
if (!dry_run)
|
||||
delete_redundant = 1;
|
||||
|
||||
ret = enumerate_promisor_blobs(repo, &po_args.filter_options, &drop_oids);
|
||||
|
||||
if (ret)
|
||||
goto cleanup;
|
||||
|
||||
/*
|
||||
* refuse to drop blobs that the current index references.
|
||||
* such a blob would only be lazily re-fetched by the next
|
||||
* command that touches the worktree, so dropping it reclaims
|
||||
* nothing. This guard just avoids that churn. bare
|
||||
* repositories have no index, so the check is skipped there.
|
||||
*/
|
||||
if (!is_bare_repository(repo) && oidset_size(&drop_oids)) {
|
||||
struct index_state *istate = repo->index;
|
||||
unsigned int i;
|
||||
|
||||
if (repo_read_index(repo) < 0)
|
||||
die(_("could not read the index"));
|
||||
|
||||
for (i = 0; i < istate->cache_nr; i++) {
|
||||
const struct cache_entry *ce = istate->cache[i];
|
||||
|
||||
if (oidset_contains(&drop_oids, &ce->oid))
|
||||
die(_("cannot drop '%s' (%s): it is referenced by the current index"),
|
||||
ce->name, oid_to_hex(&ce->oid));
|
||||
}
|
||||
}
|
||||
|
||||
if (dry_run) {
|
||||
struct oidset_iter iter;
|
||||
const struct object_id *oid;
|
||||
|
||||
oidset_iter_init(&drop_oids, &iter);
|
||||
while ((oid = oidset_iter_next(&iter)))
|
||||
printf("%s\n", oid_to_hex(oid));
|
||||
}
|
||||
}
|
||||
|
||||
if (delete_redundant && repo->repository_format_precious_objects)
|
||||
die(_("cannot delete packs in a precious-objects repo"));
|
||||
|
||||
@@ -365,7 +504,8 @@ int cmd_repack(int argc,
|
||||
strvec_push(&cmd.args, "--delta-islands");
|
||||
|
||||
if (pack_everything & ALL_INTO_ONE) {
|
||||
repack_promisor_objects(repo, &po_args, &names, packtmp);
|
||||
repack_promisor_objects(repo, &po_args, &names, packtmp,
|
||||
(drop_filtered && !dry_run) ? &drop_oids : NULL);
|
||||
|
||||
if (existing_packs_has_non_kept(&existing) &&
|
||||
delete_redundant &&
|
||||
@@ -556,7 +696,7 @@ int cmd_repack(int argc,
|
||||
}
|
||||
}
|
||||
|
||||
if (po_args.filter_options.choice) {
|
||||
if (po_args.filter_options.choice && !drop_filtered) {
|
||||
struct write_pack_opts opts = {
|
||||
.po_args = &po_args,
|
||||
.destination = filter_to,
|
||||
@@ -648,6 +788,7 @@ int cmd_repack(int argc,
|
||||
cleanup:
|
||||
string_list_clear(&keep_pack_list, 0);
|
||||
string_list_clear(&names, 1);
|
||||
oidset_clear(&drop_oids);
|
||||
existing_packs_release(&existing);
|
||||
pack_geometry_release(&geometry);
|
||||
pack_objects_args_release(&po_args);
|
||||
|
||||
@@ -828,3 +828,48 @@ void list_objects_filter__free(struct filter *filter)
|
||||
filter->free_fn(filter->filter_data);
|
||||
free(filter);
|
||||
}
|
||||
|
||||
/*
|
||||
* NEEDSWORK: this reimplements the blob:limit size check rather than
|
||||
* reusing the existing filter machinery in
|
||||
* list_objects_filter__filter_object(). That machinery is currently
|
||||
* tied to the object-walk path and cannot easily be driven from a
|
||||
* plain oidset. It would be nice to refactor the filter code so this
|
||||
* helper can reuse it instead of duplicating the size check.
|
||||
*/
|
||||
int list_objects_filter__filter_oidset(struct repository *r,
|
||||
struct list_objects_filter_options *opts,
|
||||
const struct oidset *in,
|
||||
struct oidset *omitted)
|
||||
{
|
||||
struct oidset_iter iter;
|
||||
const struct object_id *oid;
|
||||
|
||||
if (opts->choice != LOFC_BLOB_LIMIT)
|
||||
return error(_("filter_oidset: only blob:limit filters are supported"));
|
||||
|
||||
oidset_iter_init(in, &iter);
|
||||
while ((oid = oidset_iter_next(&iter))) {
|
||||
struct object_info info = OBJECT_INFO_INIT;
|
||||
enum object_type type;
|
||||
unsigned long size;
|
||||
|
||||
info.typep = &type;
|
||||
info.sizep = &size;
|
||||
|
||||
/*
|
||||
* Use OBJECT_INFO_SKIP_FETCH_OBJECT to avoid triggering
|
||||
* a lazy fetch while inspecting candidates for removal.
|
||||
*/
|
||||
if (odb_read_object_info_extended(r->objects, oid, &info,
|
||||
OBJECT_INFO_SKIP_FETCH_OBJECT) < 0)
|
||||
continue;
|
||||
|
||||
if (type != OBJ_BLOB)
|
||||
continue;
|
||||
|
||||
if (size >= opts->blob_limit_value)
|
||||
oidset_insert(omitted, oid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -94,4 +94,20 @@ enum list_objects_filter_result list_objects_filter__filter_object(
|
||||
*/
|
||||
void list_objects_filter__free(struct filter *filter);
|
||||
|
||||
/*
|
||||
* Given a set of OIDs in 'in', populate 'omitted' with those that
|
||||
* would be filtered by 'opts'. Currently only blob:limit=N is
|
||||
* supported. Objects that cannot be read are silently skipped.
|
||||
*
|
||||
* NEEDSWORK: this reimplements the blob:limit size check rather than
|
||||
* reusing the existing filter machinery. See the matching comment in
|
||||
* list-objects-filter.c.
|
||||
*
|
||||
* Return 0 on success, -1 if the filter is not supported.
|
||||
*/
|
||||
int list_objects_filter__filter_oidset(struct repository *r,
|
||||
struct list_objects_filter_options *opts,
|
||||
const struct oidset *in,
|
||||
struct oidset *omitted);
|
||||
|
||||
#endif /* LIST_OBJECTS_FILTER_H */
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
#include "repository.h"
|
||||
#include "run-command.h"
|
||||
#include "string-list.h"
|
||||
#include "hex.h"
|
||||
#include "packfile.h"
|
||||
#include "list-objects-filter-options.h"
|
||||
#include "list-objects-filter.h"
|
||||
#include "odb.h"
|
||||
#include "promisor-remote.h"
|
||||
|
||||
int write_filtered_pack(const struct write_pack_opts *opts,
|
||||
struct existing_packs *existing,
|
||||
@@ -49,3 +55,79 @@ int write_filtered_pack(const struct write_pack_opts *opts,
|
||||
return finish_pack_objects_cmd(existing->repo->hash_algo, opts, &cmd,
|
||||
names);
|
||||
}
|
||||
|
||||
struct collect_cb_data {
|
||||
struct repository *repo;
|
||||
struct oidset *set;
|
||||
};
|
||||
|
||||
static int collect_promisor_blob(const struct object_id *oid,
|
||||
struct object_info *oi UNUSED,
|
||||
void *cb_data)
|
||||
{
|
||||
struct collect_cb_data *data = cb_data;
|
||||
struct object_info info = OBJECT_INFO_INIT;
|
||||
enum object_type type;
|
||||
|
||||
info.typep = &type;
|
||||
|
||||
/*
|
||||
* Use OBJECT_INFO_SKIP_FETCH_OBJECT to avoid triggering a
|
||||
* lazy fetch while collecting promisor blobs.
|
||||
*/
|
||||
if (odb_read_object_info_extended(data->repo->objects, oid, &info,
|
||||
OBJECT_INFO_SKIP_FETCH_OBJECT) < 0)
|
||||
return 0;
|
||||
|
||||
if (type == OBJ_BLOB)
|
||||
oidset_insert(data->set, oid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int enumerate_promisor_blobs(struct repository *repo,
|
||||
const struct list_objects_filter_options *filter,
|
||||
struct oidset *to_drop)
|
||||
{
|
||||
struct oidset all_promisor_blobs = OIDSET_INIT;
|
||||
struct collect_cb_data cb = {
|
||||
.repo = repo,
|
||||
.set = &all_promisor_blobs
|
||||
};
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* The caller (cmd_repack) is responsible for validating that a
|
||||
* blob:limit filter and a promisor remote are present before
|
||||
* calling this function.
|
||||
*
|
||||
* Walk only promisor objects. every object visited here is a
|
||||
* promisor object, so it is recoverable from the promisor remote
|
||||
* as long as the remote still has it, the same assumption the rest
|
||||
* of partial clone relies on
|
||||
|
||||
* We do not use write_filtered_pack() here because git repack
|
||||
* routes promisor objects through repack_promisor_objects()
|
||||
* before the filter machinery runs, so the filtered pack never
|
||||
* contains promisor blobs. Direct enumeration via
|
||||
* ODB_FOR_EACH_OBJECT_PROMISOR_ONLY is the correct approach.
|
||||
*/
|
||||
ret = odb_for_each_object(repo->objects, NULL,
|
||||
collect_promisor_blob, &cb,
|
||||
ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);
|
||||
if (ret)
|
||||
goto cleanup;
|
||||
|
||||
/*
|
||||
* Apply the filter to find which blobs exceed the threshold.
|
||||
* The caller has to_drop and is responsible for clearing it.
|
||||
*/
|
||||
ret = list_objects_filter__filter_oidset(repo,
|
||||
(struct list_objects_filter_options *)filter,
|
||||
&all_promisor_blobs,
|
||||
to_drop);
|
||||
|
||||
cleanup:
|
||||
oidset_clear(&all_promisor_blobs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include "path.h"
|
||||
#include "repository.h"
|
||||
#include "run-command.h"
|
||||
#include "oidset.h"
|
||||
|
||||
struct write_oid_context {
|
||||
struct child_process *cmd;
|
||||
const struct git_hash_algo *algop;
|
||||
const struct oidset *to_drop;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -23,6 +25,15 @@ static int write_oid(const struct object_id *oid,
|
||||
struct write_oid_context *ctx = data;
|
||||
struct child_process *cmd = ctx->cmd;
|
||||
|
||||
/*
|
||||
* Objects in to_drop are being removed from the repository, so
|
||||
* omit them from the rebuilt promisor pack. Each such object is a
|
||||
* promisor object and therefore remains recoverable from the
|
||||
* promisor remote.
|
||||
*/
|
||||
if (ctx->to_drop && oidset_contains(ctx->to_drop, oid))
|
||||
return 0;
|
||||
|
||||
if (cmd->in == -1) {
|
||||
if (start_command(cmd))
|
||||
die(_("could not start pack-objects to repack promisor objects"));
|
||||
@@ -81,7 +92,8 @@ static void finish_repacking_promisor_objects(struct repository *repo,
|
||||
|
||||
void repack_promisor_objects(struct repository *repo,
|
||||
const struct pack_objects_args *args,
|
||||
struct string_list *names, const char *packtmp)
|
||||
struct string_list *names, const char *packtmp,
|
||||
const struct oidset *to_drop)
|
||||
{
|
||||
struct write_oid_context ctx;
|
||||
struct child_process cmd = CHILD_PROCESS_INIT;
|
||||
@@ -98,6 +110,7 @@ void repack_promisor_objects(struct repository *repo,
|
||||
*/
|
||||
ctx.cmd = &cmd;
|
||||
ctx.algop = repo->hash_algo;
|
||||
ctx.to_drop = to_drop;
|
||||
odb_for_each_object(repo->objects, NULL, write_oid, &ctx,
|
||||
ODB_FOR_EACH_OBJECT_PROMISOR_ONLY);
|
||||
|
||||
|
||||
8
repack.h
8
repack.h
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "list-objects-filter-options.h"
|
||||
#include "string-list.h"
|
||||
#include "oidset.h"
|
||||
|
||||
struct pack_objects_args {
|
||||
char *window;
|
||||
@@ -105,7 +106,8 @@ void generated_pack_install(struct generated_pack *pack, const char *name,
|
||||
|
||||
void repack_promisor_objects(struct repository *repo,
|
||||
const struct pack_objects_args *args,
|
||||
struct string_list *names, const char *packtmp);
|
||||
struct string_list *names, const char *packtmp,
|
||||
const struct oidset *to_drop);
|
||||
|
||||
struct pack_geometry {
|
||||
struct packed_git **pack;
|
||||
@@ -165,6 +167,10 @@ int write_filtered_pack(const struct write_pack_opts *opts,
|
||||
struct existing_packs *existing,
|
||||
struct string_list *names);
|
||||
|
||||
int enumerate_promisor_blobs(struct repository *repo,
|
||||
const struct list_objects_filter_options *filter,
|
||||
struct oidset *to_drop);
|
||||
|
||||
int write_cruft_pack(const struct write_pack_opts *opts,
|
||||
const char *cruft_expiration,
|
||||
unsigned long combine_cruft_below_size,
|
||||
|
||||
@@ -970,6 +970,7 @@ integration_tests = [
|
||||
't7703-repack-geometric.sh',
|
||||
't7704-repack-cruft.sh',
|
||||
't7705-repack-incremental-midx.sh',
|
||||
't7706-repack-drop-filtered.sh',
|
||||
't7800-difftool.sh',
|
||||
't7810-grep.sh',
|
||||
't7811-grep-open.sh',
|
||||
|
||||
185
t/t7706-repack-drop-filtered.sh
Executable file
185
t/t7706-repack-drop-filtered.sh
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='git repack --drop-filtered enumerates filtered promisor blobs'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
delete_object () {
|
||||
local repo="$1" &&
|
||||
local obj="$2" &&
|
||||
local path="$repo/.git/objects/$(test_oid_to_path "$obj")" &&
|
||||
rm "$path"
|
||||
}
|
||||
|
||||
# pack the objects into a promisor pack inside "repo". it is a pack
|
||||
# accompanied by an empty ".promisor" marker file. objects
|
||||
# in such a pack are treated as recoverable from the promisor remote.
|
||||
pack_as_from_promisor () {
|
||||
HASH=$(git -C repo pack-objects .git/objects/pack/pack) &&
|
||||
>repo/.git/objects/pack/pack-$HASH.promisor &&
|
||||
echo $HASH
|
||||
}
|
||||
|
||||
# write a blob of $1 bytes into "repo", record it as coming from the
|
||||
# promisor remote, and remove the loose copy so the object is only
|
||||
# present in the promisor pack
|
||||
promisor_blob () {
|
||||
test-tool genrandom "$1" "$2" >blob_content &&
|
||||
OID=$(git -C repo hash-object -w --stdin <blob_content) &&
|
||||
printf "%s\n" "$OID" | pack_as_from_promisor >/dev/null &&
|
||||
delete_object repo "$OID" &&
|
||||
echo "$OID"
|
||||
}
|
||||
|
||||
# checks for options validations before any promisor walk
|
||||
test_expect_success 'setup plain repo for validation' '
|
||||
git init plain &&
|
||||
test_commit -C plain initial &&
|
||||
git clone --bare plain plain.git &&
|
||||
git -C plain.git repack -a -d
|
||||
'
|
||||
|
||||
test_expect_success '--drop-filtered requires --filter' '
|
||||
test_must_fail git -C plain.git repack --drop-filtered --dry-run -a 2>err &&
|
||||
test_grep "drop-filtered requires --filter" err
|
||||
'
|
||||
|
||||
test_expect_success '--drop-filtered cannot be used with --filter-to' '
|
||||
test_must_fail git -C plain.git repack --drop-filtered \
|
||||
--filter=blob:limit=1k --filter-to=./filter-out 2>err &&
|
||||
test_grep "options .--drop-filtered. and .--filter-to. cannot be used together" err
|
||||
'
|
||||
|
||||
test_expect_success '--dry-run only takes effect with --drop-filtered' '
|
||||
test_must_fail git -C plain.git repack --dry-run 2>err &&
|
||||
test_grep "dry-run only takes effect with --drop-filtered" err
|
||||
'
|
||||
|
||||
test_expect_success '--drop-filtered requires -a' '
|
||||
test_must_fail git -C plain.git repack --drop-filtered \
|
||||
--filter=blob:limit=1k --dry-run 2>err &&
|
||||
test_grep "drop-filtered requires -a" err
|
||||
'
|
||||
|
||||
test_expect_success '--drop-filtered fails with --write-bitmap-index' '
|
||||
test_must_fail git -C plain.git repack --drop-filtered \
|
||||
--filter=blob:limit=1k --dry-run -a -b 2>err &&
|
||||
test_grep "options .--drop-filtered. and .--write-bitmap-index. cannot be used together" err
|
||||
'
|
||||
|
||||
test_expect_success '--drop-filtered rejects explicit -b even when repack.writeBitmaps=true' '
|
||||
test_must_fail git -C plain.git -c repack.writeBitmaps=true \
|
||||
repack --drop-filtered --filter=blob:limit=1k --dry-run -a -b 2>err &&
|
||||
test_grep "options .--drop-filtered. and .--write-bitmap-index. cannot be used together" err
|
||||
'
|
||||
|
||||
test_expect_success '--drop-filtered fails without a promisor remote' '
|
||||
test_must_fail git -C plain.git repack --drop-filtered \
|
||||
--filter=blob:limit=1k --dry-run -a 2>err &&
|
||||
test_grep "drop-filtered requires a promisor remote" err
|
||||
'
|
||||
|
||||
# enumeration tests using promisor pack
|
||||
test_expect_success 'setup repo with a promisor remote' '
|
||||
rm -rf repo &&
|
||||
test_create_repo repo &&
|
||||
test_commit -C repo base &&
|
||||
|
||||
# mark the repo as a partial clone with a promisor remote so the
|
||||
# promisor walk and the safety guard are satisfied
|
||||
git -C repo config core.repositoryformatversion 1 &&
|
||||
git -C repo config extensions.partialclone origin &&
|
||||
git -C repo config remote.origin.promisor true &&
|
||||
git -C repo config remote.origin.url "." &&
|
||||
|
||||
BIG=$(promisor_blob big 3072) &&
|
||||
SMALL=$(promisor_blob small 512) &&
|
||||
echo "$BIG" >big_oid &&
|
||||
echo "$SMALL" >small_oid
|
||||
'
|
||||
|
||||
test_expect_success 'promisor blob over the threshold is listed' '
|
||||
BIG=$(cat big_oid) &&
|
||||
SMALL=$(cat small_oid) &&
|
||||
|
||||
git -C repo -c repack.writeBitmaps=false \
|
||||
repack --drop-filtered --filter=blob:limit=1k --dry-run -a >out &&
|
||||
|
||||
test_grep "$BIG" out &&
|
||||
test_grep ! "$SMALL" out
|
||||
'
|
||||
|
||||
test_expect_success 'locally created blob is never listed' '
|
||||
BIG=$(cat big_oid) &&
|
||||
|
||||
# large blob that exists only locally must never be a drop candidate.
|
||||
# dropping it would be unrecoverable
|
||||
test-tool genrandom local 4096 >local_content &&
|
||||
LOCAL=$(git -C repo hash-object -w --stdin <local_content) &&
|
||||
|
||||
git -C repo -c repack.writeBitmaps=false \
|
||||
repack --drop-filtered --filter=blob:limit=1k --dry-run -a >out &&
|
||||
|
||||
test_grep "$BIG" out &&
|
||||
test_grep ! "$LOCAL" out
|
||||
'
|
||||
|
||||
test_expect_success '--dry-run does not remove the filtered objects' '
|
||||
BIG=$(cat big_oid) &&
|
||||
|
||||
git -C repo -c repack.writeBitmaps=false \
|
||||
repack --drop-filtered --filter=blob:limit=1k --dry-run -a >out &&
|
||||
|
||||
# candidate blob must still be present after a dry run
|
||||
git -C repo cat-file -e "$BIG"
|
||||
'
|
||||
|
||||
test_expect_success '--drop-filtered removes the promisor blob locally' '
|
||||
BIG=$(cat big_oid) &&
|
||||
SMALL=$(cat small_oid) &&
|
||||
|
||||
git -C repo -c repack.writeBitmaps=false \
|
||||
repack --drop-filtered --filter=blob:limit=1k -a &&
|
||||
|
||||
git -C repo cat-file --batch-all-objects --batch-check="%(objectname)" >present &&
|
||||
test_grep ! "$BIG" present &&
|
||||
test_grep "$SMALL" present
|
||||
'
|
||||
|
||||
test_expect_success '--drop-filtered refuses when a merge is in progress' '
|
||||
test_when_finished "git -C repo merge --abort || :" &&
|
||||
|
||||
# creat a conflicting merge so wt_status reports it
|
||||
git -C repo checkout -B mergebase base &&
|
||||
echo one >repo/conflict.txt &&
|
||||
git -C repo add conflict.txt &&
|
||||
git -C repo commit -m one &&
|
||||
|
||||
git -C repo checkout -B mergeother base &&
|
||||
echo two >repo/conflict.txt &&
|
||||
git -C repo add conflict.txt &&
|
||||
git -C repo commit -m two &&
|
||||
|
||||
test_must_fail git -C repo merge mergebase &&
|
||||
|
||||
test_must_fail git -C repo -c repack.writeBitmaps=false \
|
||||
repack --drop-filtered --filter=blob:limit=1k --dry-run -a 2>err &&
|
||||
test_grep "in progress" err
|
||||
'
|
||||
|
||||
|
||||
test_expect_success '--drop-filtered refuses to drop an index-referenced blob' '
|
||||
# create a large blob, add it to the index and make it a promisor object
|
||||
# so the index references it and enumeration picks it up
|
||||
test-tool genrandom idx 4096 >repo/tracked-big.bin &&
|
||||
git -C repo add tracked-big.bin &&
|
||||
OID=$(git -C repo rev-parse :tracked-big.bin) &&
|
||||
printf "%s\n" "$OID" | pack_as_from_promisor >/dev/null &&
|
||||
delete_object repo "$OID" &&
|
||||
|
||||
test_must_fail git -C repo -c repack.writeBitmaps=false \
|
||||
repack --drop-filtered --filter=blob:limit=1k --dry-run -a 2>err &&
|
||||
test_grep "referenced by the current index" err
|
||||
'
|
||||
|
||||
test_done
|
||||
Reference in New Issue
Block a user