Merge branch 'ps/maintenance-geometric'
"git maintenance" command learns the "geometric" strategy where it avoids doing maintenance tasks that rebuilds everything from scratch. * ps/maintenance-geometric: t7900: fix a flaky test due to git-repack always regenerating MIDX builtin/maintenance: introduce "geometric" strategy builtin/maintenance: make "gc" strategy accessible builtin/maintenance: extend "maintenance.strategy" to manual maintenance builtin/maintenance: run maintenance tasks depending on type builtin/maintenance: improve readability of strategies builtin/maintenance: don't silently ignore invalid strategy builtin/maintenance: make the geometric factor configurable builtin/maintenance: introduce "geometric-repack" task builtin/gc: make `too_many_loose_objects()` reusable without GC config builtin/gc: remove global `repack` variablemain
commit
3cf3369e81
|
|
@ -16,19 +16,36 @@ detach.
|
|||
|
||||
maintenance.strategy::
|
||||
This string config option provides a way to specify one of a few
|
||||
recommended schedules for background maintenance. This only affects
|
||||
which tasks are run during `git maintenance run --schedule=X`
|
||||
commands, provided no `--task=<task>` arguments are provided.
|
||||
Further, if a `maintenance.<task>.schedule` config value is set,
|
||||
then that value is used instead of the one provided by
|
||||
`maintenance.strategy`. The possible strategy strings are:
|
||||
recommended strategies for repository maintenance. This affects
|
||||
which tasks are run during `git maintenance run`, provided no
|
||||
`--task=<task>` arguments are provided. This setting impacts manual
|
||||
maintenance, auto-maintenance as well as scheduled maintenance. The
|
||||
tasks that run may be different depending on the maintenance type.
|
||||
+
|
||||
* `none`: This default setting implies no tasks are run at any schedule.
|
||||
The maintenance strategy can be further tweaked by setting
|
||||
`maintenance.<task>.enabled` and `maintenance.<task>.schedule`. If set, these
|
||||
values are used instead of the defaults provided by `maintenance.strategy`.
|
||||
+
|
||||
The possible strategies are:
|
||||
+
|
||||
* `none`: This strategy implies no tasks are run at all. This is the default
|
||||
strategy for scheduled maintenance.
|
||||
* `gc`: This strategy runs the `gc` task. This is the default strategy for
|
||||
manual maintenance.
|
||||
* `geometric`: This strategy performs geometric repacking of packfiles and
|
||||
keeps auxiliary data structures up-to-date. The strategy expires data in the
|
||||
reflog and removes worktrees that cannot be located anymore. When the
|
||||
geometric repacking strategy would decide to do an all-into-one repack, then
|
||||
the strategy generates a cruft pack for all unreachable objects. Objects that
|
||||
are already part of a cruft pack will be expired.
|
||||
+
|
||||
This repacking strategy is a full replacement for the `gc` strategy and is
|
||||
recommended for large repositories.
|
||||
* `incremental`: This setting optimizes for performing small maintenance
|
||||
activities that do not delete any data. This does not schedule the `gc`
|
||||
task, but runs the `prefetch` and `commit-graph` tasks hourly, the
|
||||
`loose-objects` and `incremental-repack` tasks daily, and the `pack-refs`
|
||||
task weekly.
|
||||
task weekly. Manual repository maintenance uses the `gc` task.
|
||||
|
||||
maintenance.<task>.enabled::
|
||||
This boolean config option controls whether the maintenance task
|
||||
|
|
@ -75,6 +92,22 @@ maintenance.incremental-repack.auto::
|
|||
number of pack-files not in the multi-pack-index is at least the value
|
||||
of `maintenance.incremental-repack.auto`. The default value is 10.
|
||||
|
||||
maintenance.geometric-repack.auto::
|
||||
This integer config option controls how often the `geometric-repack`
|
||||
task should be run as part of `git maintenance run --auto`. If zero,
|
||||
then the `geometric-repack` task will not run with the `--auto`
|
||||
option. A negative value will force the task to run every time.
|
||||
Otherwise, a positive value implies the command should run either when
|
||||
there are packfiles that need to be merged together to retain the
|
||||
geometric progression, or when there are at least this many loose
|
||||
objects that would be written into a new packfile. The default value is
|
||||
100.
|
||||
|
||||
maintenance.geometric-repack.splitFactor::
|
||||
This integer config option controls the factor used for the geometric
|
||||
sequence. See the `--geometric=` option in linkgit:git-repack[1] for
|
||||
more details. Defaults to `2`.
|
||||
|
||||
maintenance.reflog-expire.auto::
|
||||
This integer config option controls how often the `reflog-expire` task
|
||||
should be run as part of `git maintenance run --auto`. If zero, then
|
||||
|
|
|
|||
313
builtin/gc.c
313
builtin/gc.c
|
|
@ -34,6 +34,7 @@
|
|||
#include "pack-objects.h"
|
||||
#include "path.h"
|
||||
#include "reflog.h"
|
||||
#include "repack.h"
|
||||
#include "rerere.h"
|
||||
#include "blob.h"
|
||||
#include "tree.h"
|
||||
|
|
@ -55,7 +56,6 @@ static const char * const builtin_gc_usage[] = {
|
|||
};
|
||||
|
||||
static timestamp_t gc_log_expire_time;
|
||||
static struct strvec repack = STRVEC_INIT;
|
||||
static struct tempfile *pidfile;
|
||||
static struct lock_file log_lock;
|
||||
static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
|
||||
|
|
@ -255,6 +255,7 @@ enum maintenance_task_label {
|
|||
TASK_PREFETCH,
|
||||
TASK_LOOSE_OBJECTS,
|
||||
TASK_INCREMENTAL_REPACK,
|
||||
TASK_GEOMETRIC_REPACK,
|
||||
TASK_GC,
|
||||
TASK_COMMIT_GRAPH,
|
||||
TASK_PACK_REFS,
|
||||
|
|
@ -448,7 +449,7 @@ out:
|
|||
return should_gc;
|
||||
}
|
||||
|
||||
static int too_many_loose_objects(struct gc_config *cfg)
|
||||
static int too_many_loose_objects(int limit)
|
||||
{
|
||||
/*
|
||||
* Quickly check if a "gc" is needed, by estimating how
|
||||
|
|
@ -470,7 +471,7 @@ static int too_many_loose_objects(struct gc_config *cfg)
|
|||
if (!dir)
|
||||
return 0;
|
||||
|
||||
auto_threshold = DIV_ROUND_UP(cfg->gc_auto_threshold, 256);
|
||||
auto_threshold = DIV_ROUND_UP(limit, 256);
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
if (strspn(ent->d_name, "0123456789abcdef") != hexsz_loose ||
|
||||
ent->d_name[hexsz_loose] != '\0')
|
||||
|
|
@ -616,48 +617,50 @@ static uint64_t estimate_repack_memory(struct gc_config *cfg,
|
|||
return os_cache + heap;
|
||||
}
|
||||
|
||||
static int keep_one_pack(struct string_list_item *item, void *data UNUSED)
|
||||
static int keep_one_pack(struct string_list_item *item, void *data)
|
||||
{
|
||||
strvec_pushf(&repack, "--keep-pack=%s", basename(item->string));
|
||||
struct strvec *args = data;
|
||||
strvec_pushf(args, "--keep-pack=%s", basename(item->string));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_repack_all_option(struct gc_config *cfg,
|
||||
struct string_list *keep_pack)
|
||||
struct string_list *keep_pack,
|
||||
struct strvec *args)
|
||||
{
|
||||
if (cfg->prune_expire && !strcmp(cfg->prune_expire, "now")
|
||||
&& !(cfg->cruft_packs && cfg->repack_expire_to))
|
||||
strvec_push(&repack, "-a");
|
||||
strvec_push(args, "-a");
|
||||
else if (cfg->cruft_packs) {
|
||||
strvec_push(&repack, "--cruft");
|
||||
strvec_push(args, "--cruft");
|
||||
if (cfg->prune_expire)
|
||||
strvec_pushf(&repack, "--cruft-expiration=%s", cfg->prune_expire);
|
||||
strvec_pushf(args, "--cruft-expiration=%s", cfg->prune_expire);
|
||||
if (cfg->max_cruft_size)
|
||||
strvec_pushf(&repack, "--max-cruft-size=%lu",
|
||||
strvec_pushf(args, "--max-cruft-size=%lu",
|
||||
cfg->max_cruft_size);
|
||||
if (cfg->repack_expire_to)
|
||||
strvec_pushf(&repack, "--expire-to=%s", cfg->repack_expire_to);
|
||||
strvec_pushf(args, "--expire-to=%s", cfg->repack_expire_to);
|
||||
} else {
|
||||
strvec_push(&repack, "-A");
|
||||
strvec_push(args, "-A");
|
||||
if (cfg->prune_expire)
|
||||
strvec_pushf(&repack, "--unpack-unreachable=%s", cfg->prune_expire);
|
||||
strvec_pushf(args, "--unpack-unreachable=%s", cfg->prune_expire);
|
||||
}
|
||||
|
||||
if (keep_pack)
|
||||
for_each_string_list(keep_pack, keep_one_pack, NULL);
|
||||
for_each_string_list(keep_pack, keep_one_pack, args);
|
||||
|
||||
if (cfg->repack_filter && *cfg->repack_filter)
|
||||
strvec_pushf(&repack, "--filter=%s", cfg->repack_filter);
|
||||
strvec_pushf(args, "--filter=%s", cfg->repack_filter);
|
||||
if (cfg->repack_filter_to && *cfg->repack_filter_to)
|
||||
strvec_pushf(&repack, "--filter-to=%s", cfg->repack_filter_to);
|
||||
strvec_pushf(args, "--filter-to=%s", cfg->repack_filter_to);
|
||||
}
|
||||
|
||||
static void add_repack_incremental_option(void)
|
||||
static void add_repack_incremental_option(struct strvec *args)
|
||||
{
|
||||
strvec_push(&repack, "--no-write-bitmap-index");
|
||||
strvec_push(args, "--no-write-bitmap-index");
|
||||
}
|
||||
|
||||
static int need_to_gc(struct gc_config *cfg)
|
||||
static int need_to_gc(struct gc_config *cfg, struct strvec *repack_args)
|
||||
{
|
||||
/*
|
||||
* Setting gc.auto to 0 or negative can disable the
|
||||
|
|
@ -698,10 +701,10 @@ static int need_to_gc(struct gc_config *cfg)
|
|||
string_list_clear(&keep_pack, 0);
|
||||
}
|
||||
|
||||
add_repack_all_option(cfg, &keep_pack);
|
||||
add_repack_all_option(cfg, &keep_pack, repack_args);
|
||||
string_list_clear(&keep_pack, 0);
|
||||
} else if (too_many_loose_objects(cfg))
|
||||
add_repack_incremental_option();
|
||||
} else if (too_many_loose_objects(cfg->gc_auto_threshold))
|
||||
add_repack_incremental_option(repack_args);
|
||||
else
|
||||
return 0;
|
||||
|
||||
|
|
@ -850,6 +853,7 @@ int cmd_gc(int argc,
|
|||
int keep_largest_pack = -1;
|
||||
int skip_foreground_tasks = 0;
|
||||
timestamp_t dummy;
|
||||
struct strvec repack_args = STRVEC_INIT;
|
||||
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
|
||||
struct gc_config cfg = GC_CONFIG_INIT;
|
||||
const char *prune_expire_sentinel = "sentinel";
|
||||
|
|
@ -889,7 +893,7 @@ int cmd_gc(int argc,
|
|||
show_usage_with_options_if_asked(argc, argv,
|
||||
builtin_gc_usage, builtin_gc_options);
|
||||
|
||||
strvec_pushl(&repack, "repack", "-d", "-l", NULL);
|
||||
strvec_pushl(&repack_args, "repack", "-d", "-l", NULL);
|
||||
|
||||
gc_config(&cfg);
|
||||
|
||||
|
|
@ -912,14 +916,14 @@ int cmd_gc(int argc,
|
|||
die(_("failed to parse prune expiry value %s"), cfg.prune_expire);
|
||||
|
||||
if (aggressive) {
|
||||
strvec_push(&repack, "-f");
|
||||
strvec_push(&repack_args, "-f");
|
||||
if (cfg.aggressive_depth > 0)
|
||||
strvec_pushf(&repack, "--depth=%d", cfg.aggressive_depth);
|
||||
strvec_pushf(&repack_args, "--depth=%d", cfg.aggressive_depth);
|
||||
if (cfg.aggressive_window > 0)
|
||||
strvec_pushf(&repack, "--window=%d", cfg.aggressive_window);
|
||||
strvec_pushf(&repack_args, "--window=%d", cfg.aggressive_window);
|
||||
}
|
||||
if (opts.quiet)
|
||||
strvec_push(&repack, "-q");
|
||||
strvec_push(&repack_args, "-q");
|
||||
|
||||
if (opts.auto_flag) {
|
||||
if (cfg.detach_auto && opts.detach < 0)
|
||||
|
|
@ -928,7 +932,7 @@ int cmd_gc(int argc,
|
|||
/*
|
||||
* Auto-gc should be least intrusive as possible.
|
||||
*/
|
||||
if (!need_to_gc(&cfg)) {
|
||||
if (!need_to_gc(&cfg, &repack_args)) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -950,7 +954,7 @@ int cmd_gc(int argc,
|
|||
find_base_packs(&keep_pack, cfg.big_pack_threshold);
|
||||
}
|
||||
|
||||
add_repack_all_option(&cfg, &keep_pack);
|
||||
add_repack_all_option(&cfg, &keep_pack, &repack_args);
|
||||
string_list_clear(&keep_pack, 0);
|
||||
}
|
||||
|
||||
|
|
@ -1012,9 +1016,9 @@ int cmd_gc(int argc,
|
|||
|
||||
repack_cmd.git_cmd = 1;
|
||||
repack_cmd.close_object_store = 1;
|
||||
strvec_pushv(&repack_cmd.args, repack.v);
|
||||
strvec_pushv(&repack_cmd.args, repack_args.v);
|
||||
if (run_command(&repack_cmd))
|
||||
die(FAILED_RUN, repack.v[0]);
|
||||
die(FAILED_RUN, repack_args.v[0]);
|
||||
|
||||
if (cfg.prune_expire) {
|
||||
struct child_process prune_cmd = CHILD_PROCESS_INIT;
|
||||
|
|
@ -1053,7 +1057,7 @@ int cmd_gc(int argc,
|
|||
!opts.quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
|
||||
NULL);
|
||||
|
||||
if (opts.auto_flag && too_many_loose_objects(&cfg))
|
||||
if (opts.auto_flag && too_many_loose_objects(cfg.gc_auto_threshold))
|
||||
warning(_("There are too many unreachable loose objects; "
|
||||
"run 'git prune' to remove them."));
|
||||
|
||||
|
|
@ -1065,6 +1069,7 @@ int cmd_gc(int argc,
|
|||
|
||||
out:
|
||||
maintenance_run_opts_release(&opts);
|
||||
strvec_clear(&repack_args);
|
||||
gc_config_release(&cfg);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1267,6 +1272,19 @@ static int maintenance_task_gc_background(struct maintenance_run_opts *opts,
|
|||
return run_command(&child);
|
||||
}
|
||||
|
||||
static int gc_condition(struct gc_config *cfg)
|
||||
{
|
||||
/*
|
||||
* Note that it's fine to drop the repack arguments here, as we execute
|
||||
* git-gc(1) as a separate child process anyway. So it knows to compute
|
||||
* these arguments again.
|
||||
*/
|
||||
struct strvec repack_args = STRVEC_INIT;
|
||||
int ret = need_to_gc(cfg, &repack_args);
|
||||
strvec_clear(&repack_args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int prune_packed(struct maintenance_run_opts *opts)
|
||||
{
|
||||
struct child_process child = CHILD_PROCESS_INIT;
|
||||
|
|
@ -1548,6 +1566,108 @@ static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int maintenance_task_geometric_repack(struct maintenance_run_opts *opts,
|
||||
struct gc_config *cfg)
|
||||
{
|
||||
struct pack_geometry geometry = {
|
||||
.split_factor = 2,
|
||||
};
|
||||
struct pack_objects_args po_args = {
|
||||
.local = 1,
|
||||
};
|
||||
struct existing_packs existing_packs = EXISTING_PACKS_INIT;
|
||||
struct string_list kept_packs = STRING_LIST_INIT_DUP;
|
||||
struct child_process child = CHILD_PROCESS_INIT;
|
||||
int ret;
|
||||
|
||||
repo_config_get_int(the_repository, "maintenance.geometric-repack.splitFactor",
|
||||
&geometry.split_factor);
|
||||
|
||||
existing_packs.repo = the_repository;
|
||||
existing_packs_collect(&existing_packs, &kept_packs);
|
||||
pack_geometry_init(&geometry, &existing_packs, &po_args);
|
||||
pack_geometry_split(&geometry);
|
||||
|
||||
child.git_cmd = 1;
|
||||
|
||||
strvec_pushl(&child.args, "repack", "-d", "-l", NULL);
|
||||
if (geometry.split < geometry.pack_nr)
|
||||
strvec_pushf(&child.args, "--geometric=%d",
|
||||
geometry.split_factor);
|
||||
else
|
||||
add_repack_all_option(cfg, NULL, &child.args);
|
||||
if (opts->quiet)
|
||||
strvec_push(&child.args, "--quiet");
|
||||
if (the_repository->settings.core_multi_pack_index)
|
||||
strvec_push(&child.args, "--write-midx");
|
||||
|
||||
if (run_command(&child)) {
|
||||
ret = error(_("failed to perform geometric repack"));
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
existing_packs_release(&existing_packs);
|
||||
pack_geometry_release(&geometry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int geometric_repack_auto_condition(struct gc_config *cfg UNUSED)
|
||||
{
|
||||
struct pack_geometry geometry = {
|
||||
.split_factor = 2,
|
||||
};
|
||||
struct pack_objects_args po_args = {
|
||||
.local = 1,
|
||||
};
|
||||
struct existing_packs existing_packs = EXISTING_PACKS_INIT;
|
||||
struct string_list kept_packs = STRING_LIST_INIT_DUP;
|
||||
int auto_value = 100;
|
||||
int ret;
|
||||
|
||||
repo_config_get_int(the_repository, "maintenance.geometric-repack.auto",
|
||||
&auto_value);
|
||||
if (!auto_value)
|
||||
return 0;
|
||||
if (auto_value < 0)
|
||||
return 1;
|
||||
|
||||
repo_config_get_int(the_repository, "maintenance.geometric-repack.splitFactor",
|
||||
&geometry.split_factor);
|
||||
|
||||
existing_packs.repo = the_repository;
|
||||
existing_packs_collect(&existing_packs, &kept_packs);
|
||||
pack_geometry_init(&geometry, &existing_packs, &po_args);
|
||||
pack_geometry_split(&geometry);
|
||||
|
||||
/*
|
||||
* When we'd merge at least two packs with one another we always
|
||||
* perform the repack.
|
||||
*/
|
||||
if (geometry.split) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, we estimate the number of loose objects to determine
|
||||
* whether we want to create a new packfile or not.
|
||||
*/
|
||||
if (too_many_loose_objects(auto_value)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
existing_packs_release(&existing_packs);
|
||||
pack_geometry_release(&geometry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef int (*maintenance_task_fn)(struct maintenance_run_opts *opts,
|
||||
struct gc_config *cfg);
|
||||
typedef int (*maintenance_auto_fn)(struct gc_config *cfg);
|
||||
|
|
@ -1590,11 +1710,16 @@ static const struct maintenance_task tasks[] = {
|
|||
.background = maintenance_task_incremental_repack,
|
||||
.auto_condition = incremental_repack_auto_condition,
|
||||
},
|
||||
[TASK_GEOMETRIC_REPACK] = {
|
||||
.name = "geometric-repack",
|
||||
.background = maintenance_task_geometric_repack,
|
||||
.auto_condition = geometric_repack_auto_condition,
|
||||
},
|
||||
[TASK_GC] = {
|
||||
.name = "gc",
|
||||
.foreground = maintenance_task_gc_foreground,
|
||||
.background = maintenance_task_gc_background,
|
||||
.auto_condition = need_to_gc,
|
||||
.auto_condition = gc_condition,
|
||||
},
|
||||
[TASK_COMMIT_GRAPH] = {
|
||||
.name = "commit-graph",
|
||||
|
|
@ -1700,39 +1825,116 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
|
|||
return result;
|
||||
}
|
||||
|
||||
enum maintenance_type {
|
||||
/* As invoked via `git maintenance run --schedule=`. */
|
||||
MAINTENANCE_TYPE_SCHEDULED = (1 << 0),
|
||||
/* As invoked via `git maintenance run` and with `--auto`. */
|
||||
MAINTENANCE_TYPE_MANUAL = (1 << 1),
|
||||
};
|
||||
|
||||
struct maintenance_strategy {
|
||||
struct {
|
||||
int enabled;
|
||||
unsigned type;
|
||||
enum schedule_priority schedule;
|
||||
} tasks[TASK__COUNT];
|
||||
};
|
||||
|
||||
static const struct maintenance_strategy none_strategy = { 0 };
|
||||
static const struct maintenance_strategy default_strategy = {
|
||||
|
||||
static const struct maintenance_strategy gc_strategy = {
|
||||
.tasks = {
|
||||
[TASK_GC].enabled = 1,
|
||||
[TASK_GC] = {
|
||||
.type = MAINTENANCE_TYPE_MANUAL | MAINTENANCE_TYPE_SCHEDULED,
|
||||
.schedule = SCHEDULE_DAILY,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const struct maintenance_strategy incremental_strategy = {
|
||||
.tasks = {
|
||||
[TASK_COMMIT_GRAPH].enabled = 1,
|
||||
[TASK_COMMIT_GRAPH].schedule = SCHEDULE_HOURLY,
|
||||
[TASK_PREFETCH].enabled = 1,
|
||||
[TASK_PREFETCH].schedule = SCHEDULE_HOURLY,
|
||||
[TASK_INCREMENTAL_REPACK].enabled = 1,
|
||||
[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY,
|
||||
[TASK_LOOSE_OBJECTS].enabled = 1,
|
||||
[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY,
|
||||
[TASK_PACK_REFS].enabled = 1,
|
||||
[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY,
|
||||
[TASK_COMMIT_GRAPH] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED,
|
||||
.schedule = SCHEDULE_HOURLY,
|
||||
},
|
||||
[TASK_PREFETCH] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED,
|
||||
.schedule = SCHEDULE_HOURLY,
|
||||
},
|
||||
[TASK_INCREMENTAL_REPACK] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED,
|
||||
.schedule = SCHEDULE_DAILY,
|
||||
},
|
||||
[TASK_LOOSE_OBJECTS] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED,
|
||||
.schedule = SCHEDULE_DAILY,
|
||||
},
|
||||
[TASK_PACK_REFS] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED,
|
||||
.schedule = SCHEDULE_WEEKLY,
|
||||
},
|
||||
/*
|
||||
* Historically, the "incremental" strategy was only available
|
||||
* in the context of scheduled maintenance when set up via
|
||||
* "maintenance.strategy". We have later expanded that config
|
||||
* to also cover manual maintenance.
|
||||
*
|
||||
* To retain backwards compatibility with the previous status
|
||||
* quo we thus run git-gc(1) in case manual maintenance was
|
||||
* requested. This is the same as the default strategy, which
|
||||
* would have been in use beforehand.
|
||||
*/
|
||||
[TASK_GC] = {
|
||||
.type = MAINTENANCE_TYPE_MANUAL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const struct maintenance_strategy geometric_strategy = {
|
||||
.tasks = {
|
||||
[TASK_COMMIT_GRAPH] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
|
||||
.schedule = SCHEDULE_HOURLY,
|
||||
},
|
||||
[TASK_GEOMETRIC_REPACK] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
|
||||
.schedule = SCHEDULE_DAILY,
|
||||
},
|
||||
[TASK_PACK_REFS] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
|
||||
.schedule = SCHEDULE_DAILY,
|
||||
},
|
||||
[TASK_RERERE_GC] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
|
||||
.schedule = SCHEDULE_WEEKLY,
|
||||
},
|
||||
[TASK_REFLOG_EXPIRE] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
|
||||
.schedule = SCHEDULE_WEEKLY,
|
||||
},
|
||||
[TASK_WORKTREE_PRUNE] = {
|
||||
.type = MAINTENANCE_TYPE_SCHEDULED | MAINTENANCE_TYPE_MANUAL,
|
||||
.schedule = SCHEDULE_WEEKLY,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static struct maintenance_strategy parse_maintenance_strategy(const char *name)
|
||||
{
|
||||
if (!strcasecmp(name, "incremental"))
|
||||
return incremental_strategy;
|
||||
if (!strcasecmp(name, "gc"))
|
||||
return gc_strategy;
|
||||
if (!strcasecmp(name, "geometric"))
|
||||
return geometric_strategy;
|
||||
die(_("unknown maintenance strategy: '%s'"), name);
|
||||
}
|
||||
|
||||
static void initialize_task_config(struct maintenance_run_opts *opts,
|
||||
const struct string_list *selected_tasks)
|
||||
{
|
||||
struct strbuf config_name = STRBUF_INIT;
|
||||
struct maintenance_strategy strategy;
|
||||
enum maintenance_type type;
|
||||
const char *config_str;
|
||||
|
||||
/*
|
||||
|
|
@ -1760,19 +1962,20 @@ static void initialize_task_config(struct maintenance_run_opts *opts,
|
|||
* - Unscheduled maintenance uses our default strategy.
|
||||
*
|
||||
* Both of these are affected by the gitconfig though, which may
|
||||
* override specific aspects of our strategy.
|
||||
* override specific aspects of our strategy. Furthermore, both
|
||||
* strategies can be overridden by setting "maintenance.strategy".
|
||||
*/
|
||||
if (opts->schedule) {
|
||||
strategy = none_strategy;
|
||||
|
||||
if (!repo_config_get_string_tmp(the_repository, "maintenance.strategy", &config_str)) {
|
||||
if (!strcasecmp(config_str, "incremental"))
|
||||
strategy = incremental_strategy;
|
||||
}
|
||||
type = MAINTENANCE_TYPE_SCHEDULED;
|
||||
} else {
|
||||
strategy = default_strategy;
|
||||
strategy = gc_strategy;
|
||||
type = MAINTENANCE_TYPE_MANUAL;
|
||||
}
|
||||
|
||||
if (!repo_config_get_string_tmp(the_repository, "maintenance.strategy", &config_str))
|
||||
strategy = parse_maintenance_strategy(config_str);
|
||||
|
||||
for (size_t i = 0; i < TASK__COUNT; i++) {
|
||||
int config_value;
|
||||
|
||||
|
|
@ -1780,8 +1983,8 @@ static void initialize_task_config(struct maintenance_run_opts *opts,
|
|||
strbuf_addf(&config_name, "maintenance.%s.enabled",
|
||||
tasks[i].name);
|
||||
if (!repo_config_get_bool(the_repository, config_name.buf, &config_value))
|
||||
strategy.tasks[i].enabled = config_value;
|
||||
if (!strategy.tasks[i].enabled)
|
||||
strategy.tasks[i].type = config_value ? type : 0;
|
||||
if (!(strategy.tasks[i].type & type))
|
||||
continue;
|
||||
|
||||
if (opts->schedule) {
|
||||
|
|
|
|||
|
|
@ -465,6 +465,176 @@ test_expect_success 'maintenance.incremental-repack.auto (when config is unset)'
|
|||
)
|
||||
'
|
||||
|
||||
run_and_verify_geometric_pack () {
|
||||
EXPECTED_PACKS="$1" &&
|
||||
|
||||
# Verify that we perform a geometric repack.
|
||||
rm -f "trace2.txt" &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
|
||||
git maintenance run --task=geometric-repack 2>/dev/null &&
|
||||
test_subcommand git repack -d -l --geometric=2 \
|
||||
--quiet --write-midx <trace2.txt &&
|
||||
|
||||
# Verify that the number of packfiles matches our expectation.
|
||||
ls -l .git/objects/pack/*.pack >packfiles &&
|
||||
test_line_count = "$EXPECTED_PACKS" packfiles &&
|
||||
|
||||
# And verify that there are no loose objects anymore.
|
||||
git count-objects -v >count &&
|
||||
test_grep '^count: 0$' count
|
||||
}
|
||||
|
||||
test_expect_success 'geometric repacking task' '
|
||||
test_when_finished "rm -rf repo" &&
|
||||
git init repo &&
|
||||
(
|
||||
cd repo &&
|
||||
git config set maintenance.auto false &&
|
||||
test_commit initial &&
|
||||
|
||||
# The initial repack causes an all-into-one repack.
|
||||
GIT_TRACE2_EVENT="$(pwd)/initial-repack.txt" \
|
||||
git maintenance run --task=geometric-repack 2>/dev/null &&
|
||||
test_subcommand git repack -d -l --cruft --cruft-expiration=2.weeks.ago \
|
||||
--quiet --write-midx <initial-repack.txt &&
|
||||
|
||||
# Repacking should now cause a no-op geometric repack because
|
||||
# no packfiles need to be combined.
|
||||
ls -l .git/objects/pack/*.pack >before &&
|
||||
run_and_verify_geometric_pack 1 &&
|
||||
ls -l .git/objects/pack/*.pack >after &&
|
||||
test_cmp before after &&
|
||||
|
||||
# This incremental change creates a new packfile that only
|
||||
# soaks up loose objects. The packfiles are not getting merged
|
||||
# at this point.
|
||||
test_commit loose &&
|
||||
run_and_verify_geometric_pack 2 &&
|
||||
|
||||
# Both packfiles have 3 objects, so the next run would cause us
|
||||
# to merge all packfiles together. This should be turned into
|
||||
# an all-into-one-repack.
|
||||
GIT_TRACE2_EVENT="$(pwd)/all-into-one-repack.txt" \
|
||||
git maintenance run --task=geometric-repack 2>/dev/null &&
|
||||
test_subcommand git repack -d -l --cruft --cruft-expiration=2.weeks.ago \
|
||||
--quiet --write-midx <all-into-one-repack.txt &&
|
||||
|
||||
# The geometric repack soaks up unreachable objects.
|
||||
echo blob-1 | git hash-object -w --stdin -t blob &&
|
||||
run_and_verify_geometric_pack 2 &&
|
||||
|
||||
# A second unreachable object should be written into another packfile.
|
||||
echo blob-2 | git hash-object -w --stdin -t blob &&
|
||||
run_and_verify_geometric_pack 3 &&
|
||||
|
||||
# And these two small packs should now be merged via the
|
||||
# geometric repack. The large packfile should remain intact.
|
||||
run_and_verify_geometric_pack 2 &&
|
||||
|
||||
# If we now add two more objects and repack twice we should
|
||||
# then see another all-into-one repack. This time around
|
||||
# though, as we have unreachable objects, we should also see a
|
||||
# cruft pack.
|
||||
echo blob-3 | git hash-object -w --stdin -t blob &&
|
||||
echo blob-4 | git hash-object -w --stdin -t blob &&
|
||||
run_and_verify_geometric_pack 3 &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/cruft-repack.txt" \
|
||||
git maintenance run --task=geometric-repack 2>/dev/null &&
|
||||
test_subcommand git repack -d -l --cruft --cruft-expiration=2.weeks.ago \
|
||||
--quiet --write-midx <cruft-repack.txt &&
|
||||
ls .git/objects/pack/*.pack >packs &&
|
||||
test_line_count = 2 packs &&
|
||||
ls .git/objects/pack/*.mtimes >cruft &&
|
||||
test_line_count = 1 cruft
|
||||
)
|
||||
'
|
||||
|
||||
test_geometric_repack_needed () {
|
||||
NEEDED="$1"
|
||||
GEOMETRIC_CONFIG="$2" &&
|
||||
rm -f trace2.txt &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
|
||||
git ${GEOMETRIC_CONFIG:+-c maintenance.geometric-repack.$GEOMETRIC_CONFIG} \
|
||||
maintenance run --auto --task=geometric-repack 2>/dev/null &&
|
||||
case "$NEEDED" in
|
||||
true)
|
||||
test_grep "\[\"git\",\"repack\"," trace2.txt;;
|
||||
false)
|
||||
! test_grep "\[\"git\",\"repack\"," trace2.txt;;
|
||||
*)
|
||||
BUG "invalid parameter: $NEEDED";;
|
||||
esac
|
||||
}
|
||||
|
||||
test_expect_success 'geometric repacking with --auto' '
|
||||
test_when_finished "rm -rf repo" &&
|
||||
git init repo &&
|
||||
(
|
||||
cd repo &&
|
||||
|
||||
# An empty repository does not need repacking, except when
|
||||
# explicitly told to do it.
|
||||
test_geometric_repack_needed false &&
|
||||
test_geometric_repack_needed false auto=0 &&
|
||||
test_geometric_repack_needed false auto=1 &&
|
||||
test_geometric_repack_needed true auto=-1 &&
|
||||
|
||||
test_oid_init &&
|
||||
|
||||
# Loose objects cause a repack when crossing the limit. Note
|
||||
# that the number of objects gets extrapolated by having a look
|
||||
# at the "objects/17/" shard.
|
||||
test_commit "$(test_oid blob17_1)" &&
|
||||
test_geometric_repack_needed false &&
|
||||
test_commit "$(test_oid blob17_2)" &&
|
||||
test_geometric_repack_needed false auto=257 &&
|
||||
test_geometric_repack_needed true auto=256 &&
|
||||
|
||||
# Force another repack.
|
||||
test_commit first &&
|
||||
test_commit second &&
|
||||
test_geometric_repack_needed true auto=-1 &&
|
||||
|
||||
# We now have two packfiles that would be merged together. As
|
||||
# such, the repack should always happen unless the user has
|
||||
# disabled the auto task.
|
||||
test_geometric_repack_needed false auto=0 &&
|
||||
test_geometric_repack_needed true auto=9000
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'geometric repacking honors configured split factor' '
|
||||
test_when_finished "rm -rf repo" &&
|
||||
git init repo &&
|
||||
(
|
||||
cd repo &&
|
||||
git config set maintenance.auto false &&
|
||||
|
||||
# Create three different packs with 9, 2 and 1 object, respectively.
|
||||
# This is done so that only a subset of packs would be merged
|
||||
# together so that we can verify that `git repack` receives the
|
||||
# correct geometric factor.
|
||||
for i in $(test_seq 9)
|
||||
do
|
||||
echo first-$i | git hash-object -w --stdin -t blob || return 1
|
||||
done &&
|
||||
git repack --geometric=2 -d &&
|
||||
|
||||
for i in $(test_seq 2)
|
||||
do
|
||||
echo second-$i | git hash-object -w --stdin -t blob || return 1
|
||||
done &&
|
||||
git repack --geometric=2 -d &&
|
||||
|
||||
echo third | git hash-object -w --stdin -t blob &&
|
||||
git repack --geometric=2 -d &&
|
||||
|
||||
test_geometric_repack_needed false splitFactor=2 &&
|
||||
test_geometric_repack_needed true splitFactor=3 &&
|
||||
test_subcommand git repack -d -l --geometric=3 --quiet --write-midx <trace2.txt
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'pack-refs task' '
|
||||
for n in $(test_seq 1 5)
|
||||
do
|
||||
|
|
@ -716,6 +886,76 @@ test_expect_success 'maintenance.strategy inheritance' '
|
|||
<modified-daily.txt
|
||||
'
|
||||
|
||||
test_strategy () {
|
||||
STRATEGY="$1"
|
||||
shift
|
||||
|
||||
cat >expect &&
|
||||
rm -f trace2.txt &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
|
||||
git -c maintenance.strategy=$STRATEGY maintenance run --quiet "$@" &&
|
||||
sed -n 's/{"event":"child_start","sid":"[^/"]*",.*,"argv":\["\(.*\)\"]}/\1/p' <trace2.txt |
|
||||
sed 's/","/ /g' >actual
|
||||
test_cmp expect actual
|
||||
}
|
||||
|
||||
test_expect_success 'maintenance.strategy is respected' '
|
||||
test_when_finished "rm -rf repo" &&
|
||||
git init repo &&
|
||||
(
|
||||
cd repo &&
|
||||
test_commit initial &&
|
||||
|
||||
test_must_fail git -c maintenance.strategy=unknown maintenance run 2>err &&
|
||||
test_grep "unknown maintenance strategy: .unknown." err &&
|
||||
|
||||
test_strategy incremental <<-\EOF &&
|
||||
git pack-refs --all --prune
|
||||
git reflog expire --all
|
||||
git gc --quiet --no-detach --skip-foreground-tasks
|
||||
EOF
|
||||
|
||||
test_strategy incremental --schedule=weekly <<-\EOF &&
|
||||
git pack-refs --all --prune
|
||||
git prune-packed --quiet
|
||||
git multi-pack-index write --no-progress
|
||||
git multi-pack-index expire --no-progress
|
||||
git multi-pack-index repack --no-progress --batch-size=1
|
||||
git commit-graph write --split --reachable --no-progress
|
||||
EOF
|
||||
|
||||
test_strategy gc <<-\EOF &&
|
||||
git pack-refs --all --prune
|
||||
git reflog expire --all
|
||||
git gc --quiet --no-detach --skip-foreground-tasks
|
||||
EOF
|
||||
|
||||
test_strategy gc --schedule=weekly <<-\EOF &&
|
||||
git pack-refs --all --prune
|
||||
git reflog expire --all
|
||||
git gc --quiet --no-detach --skip-foreground-tasks
|
||||
EOF
|
||||
|
||||
test_strategy geometric <<-\EOF &&
|
||||
git pack-refs --all --prune
|
||||
git reflog expire --all
|
||||
git repack -d -l --geometric=2 --quiet --write-midx
|
||||
git commit-graph write --split --reachable --no-progress
|
||||
git worktree prune --expire 3.months.ago
|
||||
git rerere gc
|
||||
EOF
|
||||
|
||||
test_strategy geometric --schedule=weekly <<-\EOF
|
||||
git pack-refs --all --prune
|
||||
git reflog expire --all
|
||||
git repack -d -l --geometric=2 --quiet --write-midx
|
||||
git commit-graph write --split --reachable --no-progress
|
||||
git worktree prune --expire 3.months.ago
|
||||
git rerere gc
|
||||
EOF
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'register and unregister' '
|
||||
test_when_finished git config --global --unset-all maintenance.repo &&
|
||||
|
||||
|
|
@ -1093,6 +1333,11 @@ test_expect_success 'fails when running outside of a repository' '
|
|||
nongit test_must_fail git maintenance unregister
|
||||
'
|
||||
|
||||
test_expect_success 'fails when configured to use an invalid strategy' '
|
||||
test_must_fail git -c maintenance.strategy=invalid maintenance run --schedule=hourly 2>err &&
|
||||
test_grep "unknown maintenance strategy: .invalid." err
|
||||
'
|
||||
|
||||
test_expect_success 'register and unregister bare repo' '
|
||||
test_when_finished "git config --global --unset-all maintenance.repo || :" &&
|
||||
test_might_fail git config --global --unset-all maintenance.repo &&
|
||||
|
|
|
|||
Loading…
Reference in New Issue