Merge branch 'ps/maintenance-missing-tasks' into seen
Make repository clean-up tasks "gc" can do available to "git maintenance" front-end. Comments? * ps/maintenance-missing-tasks: builtin/maintenance: introduce "rerere-gc" task builtin/gc: move rerere garbage collection into separate function rerere: provide function to collect stale entries builtin/maintenance: introduce "worktree-prune" task worktree: expose function to retrieve worktree names builtin/gc: move pruning of worktrees into a separate function builtin/gc: remove global variables where it trivial to do builtin/gc: fix indentation of `cmd_gc()` parameters
commit
3224af98ff
|
@ -83,3 +83,19 @@ maintenance.reflog-expire.auto::
|
|||
positive value implies the command should run when the number of
|
||||
expired reflog entries in the "HEAD" reflog is at least the value of
|
||||
`maintenance.loose-objects.auto`. The default value is 100.
|
||||
|
||||
maintenance.rerere-gc.auto::
|
||||
This integer config option controls how often the `rerere-gc` task
|
||||
should be run as part of `git maintenance run --auto`. If zero, then
|
||||
the `rerere-gc` 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 when the number of prunable rerere
|
||||
entries exceeds the value. The default value is 20.
|
||||
|
||||
maintenance.worktree-prune.auto::
|
||||
This integer config option controls how often the `worktree-prune` task
|
||||
should be run as part of `git maintenance run --auto`. If zero, then
|
||||
the `worktree-prune` 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 when the number of
|
||||
prunable worktrees exceeds the value. The default value is 1.
|
||||
|
|
|
@ -166,6 +166,14 @@ reflog-expire::
|
|||
The `reflog-expire` task deletes any entries in the reflog older than the
|
||||
expiry threshold. See linkgit:git-reflog[1] for more information.
|
||||
|
||||
rerere-gc::
|
||||
The `rerere-gc` task invokes garbage collection for stale entries in
|
||||
the rerere cache. See linkgit:git-rerere[1] for more information.
|
||||
|
||||
worktree-prune::
|
||||
The `worktree-prune` task deletes stale or broken worktrees. See
|
||||
linkit:git-worktree[1] for more information.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
--auto::
|
||||
|
|
153
builtin/gc.c
153
builtin/gc.c
|
@ -16,6 +16,7 @@
|
|||
#include "builtin.h"
|
||||
#include "abspath.h"
|
||||
#include "date.h"
|
||||
#include "dir.h"
|
||||
#include "environment.h"
|
||||
#include "hex.h"
|
||||
#include "config.h"
|
||||
|
@ -33,6 +34,7 @@
|
|||
#include "pack-objects.h"
|
||||
#include "path.h"
|
||||
#include "reflog.h"
|
||||
#include "rerere.h"
|
||||
#include "blob.h"
|
||||
#include "tree.h"
|
||||
#include "promisor-remote.h"
|
||||
|
@ -43,6 +45,7 @@
|
|||
#include "hook.h"
|
||||
#include "setup.h"
|
||||
#include "trace2.h"
|
||||
#include "worktree.h"
|
||||
|
||||
#define FAILED_RUN "failed to run %s"
|
||||
|
||||
|
@ -52,15 +55,9 @@ static const char * const builtin_gc_usage[] = {
|
|||
};
|
||||
|
||||
static timestamp_t gc_log_expire_time;
|
||||
|
||||
static struct strvec repack = STRVEC_INIT;
|
||||
static struct strvec prune = STRVEC_INIT;
|
||||
static struct strvec prune_worktrees = STRVEC_INIT;
|
||||
static struct strvec rerere = STRVEC_INIT;
|
||||
|
||||
static struct tempfile *pidfile;
|
||||
static struct lock_file log_lock;
|
||||
|
||||
static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
|
||||
|
||||
static void clean_pack_garbage(void)
|
||||
|
@ -339,6 +336,99 @@ static int maintenance_task_reflog_expire(struct maintenance_run_opts *opts UNUS
|
|||
return run_command(&cmd);
|
||||
}
|
||||
|
||||
static int maintenance_task_worktree_prune(struct maintenance_run_opts *opts UNUSED,
|
||||
struct gc_config *cfg)
|
||||
{
|
||||
struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
|
||||
|
||||
prune_worktrees_cmd.git_cmd = 1;
|
||||
strvec_pushl(&prune_worktrees_cmd.args, "worktree", "prune", "--expire", NULL);
|
||||
strvec_push(&prune_worktrees_cmd.args, cfg->prune_worktrees_expire);
|
||||
|
||||
return run_command(&prune_worktrees_cmd);
|
||||
}
|
||||
|
||||
static int worktree_prune_condition(struct gc_config *cfg)
|
||||
{
|
||||
struct strvec worktrees = STRVEC_INIT;
|
||||
struct strbuf reason = STRBUF_INIT;
|
||||
timestamp_t expiry_date;
|
||||
int should_prune = 0;
|
||||
int limit = 1;
|
||||
|
||||
git_config_get_int("maintenance.worktree-prune.auto", &limit);
|
||||
if (limit <= 0) {
|
||||
should_prune = limit < 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (parse_expiry_date(cfg->prune_worktrees_expire, &expiry_date) ||
|
||||
get_worktree_names(the_repository, &worktrees) < 0)
|
||||
goto out;
|
||||
|
||||
for (size_t i = 0; i < worktrees.nr; i++) {
|
||||
char *wtpath;
|
||||
|
||||
strbuf_reset(&reason);
|
||||
if (should_prune_worktree(worktrees.v[i], &reason, &wtpath, expiry_date)) {
|
||||
limit--;
|
||||
|
||||
if (!limit) {
|
||||
should_prune = 1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
free(wtpath);
|
||||
}
|
||||
|
||||
out:
|
||||
strvec_clear(&worktrees);
|
||||
strbuf_release(&reason);
|
||||
return should_prune;
|
||||
}
|
||||
|
||||
static int maintenance_task_rerere_gc(struct maintenance_run_opts *opts UNUSED,
|
||||
struct gc_config *cfg UNUSED)
|
||||
{
|
||||
struct child_process rerere_cmd = CHILD_PROCESS_INIT;
|
||||
rerere_cmd.git_cmd = 1;
|
||||
strvec_pushl(&rerere_cmd.args, "rerere", "gc", NULL);
|
||||
return run_command(&rerere_cmd);
|
||||
}
|
||||
|
||||
static int rerere_gc_condition(struct gc_config *cfg UNUSED)
|
||||
{
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
struct string_list prunable_dirs = STRING_LIST_INIT_DUP;
|
||||
struct rerere_id *prunable_entries = NULL;
|
||||
size_t prunable_entries_nr;
|
||||
int should_gc = 0;
|
||||
int limit = 20;
|
||||
|
||||
git_config_get_int("maintenance.rerere-gc.auto", &limit);
|
||||
if (limit <= 0) {
|
||||
should_gc = limit < 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Skip garbage collecting the rerere cache in case rerere is disabled. */
|
||||
repo_git_path_replace(the_repository, &path, "rr-cache");
|
||||
if (!is_directory(path.buf))
|
||||
goto out;
|
||||
|
||||
if (rerere_collect_stale_entries(the_repository, &prunable_dirs,
|
||||
&prunable_entries, &prunable_entries_nr) < 0)
|
||||
goto out;
|
||||
|
||||
should_gc = prunable_entries_nr >= limit;
|
||||
|
||||
out:
|
||||
string_list_clear(&prunable_dirs, 0);
|
||||
free(prunable_entries);
|
||||
strbuf_release(&path);
|
||||
return should_gc;
|
||||
}
|
||||
|
||||
static int too_many_loose_objects(struct gc_config *cfg)
|
||||
{
|
||||
/*
|
||||
|
@ -728,9 +818,9 @@ static void gc_before_repack(struct maintenance_run_opts *opts,
|
|||
}
|
||||
|
||||
int cmd_gc(int argc,
|
||||
const char **argv,
|
||||
const char *prefix,
|
||||
struct repository *repo UNUSED)
|
||||
const char **argv,
|
||||
const char *prefix,
|
||||
struct repository *repo UNUSED)
|
||||
{
|
||||
int aggressive = 0;
|
||||
int quiet = 0;
|
||||
|
@ -740,7 +830,6 @@ struct repository *repo UNUSED)
|
|||
int daemonized = 0;
|
||||
int keep_largest_pack = -1;
|
||||
timestamp_t dummy;
|
||||
struct child_process rerere_cmd = CHILD_PROCESS_INIT;
|
||||
struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT;
|
||||
struct gc_config cfg = GC_CONFIG_INIT;
|
||||
const char *prune_expire_sentinel = "sentinel";
|
||||
|
@ -779,9 +868,6 @@ struct repository *repo UNUSED)
|
|||
builtin_gc_usage, builtin_gc_options);
|
||||
|
||||
strvec_pushl(&repack, "repack", "-d", "-l", NULL);
|
||||
strvec_pushl(&prune, "prune", "--expire", NULL);
|
||||
strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
|
||||
strvec_pushl(&rerere, "rerere", "gc", NULL);
|
||||
|
||||
gc_config(&cfg);
|
||||
|
||||
|
@ -907,34 +993,27 @@ struct repository *repo UNUSED)
|
|||
if (cfg.prune_expire) {
|
||||
struct child_process prune_cmd = CHILD_PROCESS_INIT;
|
||||
|
||||
strvec_pushl(&prune_cmd.args, "prune", "--expire", NULL);
|
||||
/* run `git prune` even if using cruft packs */
|
||||
strvec_push(&prune, cfg.prune_expire);
|
||||
strvec_push(&prune_cmd.args, cfg.prune_expire);
|
||||
if (quiet)
|
||||
strvec_push(&prune, "--no-progress");
|
||||
strvec_push(&prune_cmd.args, "--no-progress");
|
||||
if (repo_has_promisor_remote(the_repository))
|
||||
strvec_push(&prune,
|
||||
strvec_push(&prune_cmd.args,
|
||||
"--exclude-promisor-objects");
|
||||
prune_cmd.git_cmd = 1;
|
||||
strvec_pushv(&prune_cmd.args, prune.v);
|
||||
|
||||
if (run_command(&prune_cmd))
|
||||
die(FAILED_RUN, prune.v[0]);
|
||||
die(FAILED_RUN, prune_cmd.args.v[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg.prune_worktrees_expire) {
|
||||
struct child_process prune_worktrees_cmd = CHILD_PROCESS_INIT;
|
||||
if (cfg.prune_worktrees_expire &&
|
||||
maintenance_task_worktree_prune(&opts, &cfg))
|
||||
die(FAILED_RUN, "worktree");
|
||||
|
||||
strvec_push(&prune_worktrees, cfg.prune_worktrees_expire);
|
||||
prune_worktrees_cmd.git_cmd = 1;
|
||||
strvec_pushv(&prune_worktrees_cmd.args, prune_worktrees.v);
|
||||
if (run_command(&prune_worktrees_cmd))
|
||||
die(FAILED_RUN, prune_worktrees.v[0]);
|
||||
}
|
||||
|
||||
rerere_cmd.git_cmd = 1;
|
||||
strvec_pushv(&rerere_cmd.args, rerere.v);
|
||||
if (run_command(&rerere_cmd))
|
||||
die(FAILED_RUN, rerere.v[0]);
|
||||
if (maintenance_task_rerere_gc(&opts, &cfg))
|
||||
die(FAILED_RUN, "rerere");
|
||||
|
||||
report_garbage = report_pack_garbage;
|
||||
reprepare_packed_git(the_repository);
|
||||
|
@ -1467,6 +1546,8 @@ enum maintenance_task_label {
|
|||
TASK_COMMIT_GRAPH,
|
||||
TASK_PACK_REFS,
|
||||
TASK_REFLOG_EXPIRE,
|
||||
TASK_WORKTREE_PRUNE,
|
||||
TASK_RERERE_GC,
|
||||
|
||||
/* Leave as final value */
|
||||
TASK__COUNT
|
||||
|
@ -1508,6 +1589,16 @@ static struct maintenance_task tasks[] = {
|
|||
maintenance_task_reflog_expire,
|
||||
reflog_expire_condition,
|
||||
},
|
||||
[TASK_WORKTREE_PRUNE] = {
|
||||
"worktree-prune",
|
||||
maintenance_task_worktree_prune,
|
||||
worktree_prune_condition,
|
||||
},
|
||||
[TASK_RERERE_GC] = {
|
||||
"rerere-gc",
|
||||
maintenance_task_rerere_gc,
|
||||
rerere_gc_condition,
|
||||
},
|
||||
};
|
||||
|
||||
static int compare_tasks_by_selection(const void *a_, const void *b_)
|
||||
|
|
|
@ -211,27 +211,24 @@ static void prune_dups(struct string_list *l)
|
|||
|
||||
static void prune_worktrees(void)
|
||||
{
|
||||
struct strbuf reason = STRBUF_INIT;
|
||||
struct strbuf main_path = STRBUF_INIT;
|
||||
struct string_list kept = STRING_LIST_INIT_DUP;
|
||||
char *path;
|
||||
DIR *dir;
|
||||
struct dirent *d;
|
||||
struct strvec worktrees = STRVEC_INIT;
|
||||
struct strbuf reason = STRBUF_INIT;
|
||||
|
||||
path = repo_git_path(the_repository, "worktrees");
|
||||
dir = opendir(path);
|
||||
free(path);
|
||||
if (!dir)
|
||||
if (get_worktree_names(the_repository, &worktrees) < 0 ||
|
||||
!worktrees.nr)
|
||||
return;
|
||||
while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
||||
|
||||
for (size_t i = 0; i < worktrees.nr; i++) {
|
||||
char *path;
|
||||
|
||||
strbuf_reset(&reason);
|
||||
if (should_prune_worktree(d->d_name, &reason, &path, expire))
|
||||
prune_worktree(d->d_name, reason.buf);
|
||||
if (should_prune_worktree(worktrees.v[i], &reason, &path, expire))
|
||||
prune_worktree(worktrees.v[i], reason.buf);
|
||||
else if (path)
|
||||
string_list_append_nodup(&kept, path)->util = xstrdup(d->d_name);
|
||||
string_list_append_nodup(&kept, path)->util = xstrdup(worktrees.v[i]);
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
strbuf_add_absolute_path(&main_path, repo_get_common_dir(the_repository));
|
||||
/* massage main worktree absolute path to match 'gitdir' content */
|
||||
|
@ -242,6 +239,8 @@ static void prune_worktrees(void)
|
|||
|
||||
if (!show_only)
|
||||
delete_worktrees_dir_if_empty();
|
||||
|
||||
strvec_clear(&worktrees);
|
||||
strbuf_release(&reason);
|
||||
}
|
||||
|
||||
|
|
92
rerere.c
92
rerere.c
|
@ -1203,8 +1203,8 @@ static void unlink_rr_item(struct rerere_id *id)
|
|||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
static void prune_one(struct rerere_id *id,
|
||||
timestamp_t cutoff_resolve, timestamp_t cutoff_noresolve)
|
||||
static int is_stale(struct rerere_id *id,
|
||||
timestamp_t cutoff_resolve, timestamp_t cutoff_noresolve)
|
||||
{
|
||||
timestamp_t then;
|
||||
timestamp_t cutoff;
|
||||
|
@ -1215,11 +1215,11 @@ static void prune_one(struct rerere_id *id,
|
|||
else {
|
||||
then = rerere_created_at(id);
|
||||
if (!then)
|
||||
return;
|
||||
return 0;
|
||||
cutoff = cutoff_noresolve;
|
||||
}
|
||||
if (then < cutoff)
|
||||
unlink_rr_item(id);
|
||||
|
||||
return then < cutoff;
|
||||
}
|
||||
|
||||
/* Does the basename in "path" look plausibly like an rr-cache entry? */
|
||||
|
@ -1230,29 +1230,35 @@ static int is_rr_cache_dirname(const char *path)
|
|||
return !parse_oid_hex(path, &oid, &end) && !*end;
|
||||
}
|
||||
|
||||
void rerere_gc(struct repository *r, struct string_list *rr)
|
||||
int rerere_collect_stale_entries(struct repository *r,
|
||||
struct string_list *prunable_dirs,
|
||||
struct rerere_id **prunable_entries,
|
||||
size_t *prunable_entries_nr)
|
||||
{
|
||||
struct string_list to_remove = STRING_LIST_INIT_DUP;
|
||||
DIR *dir;
|
||||
struct dirent *e;
|
||||
int i;
|
||||
timestamp_t now = time(NULL);
|
||||
timestamp_t cutoff_noresolve = now - 15 * 86400;
|
||||
timestamp_t cutoff_resolve = now - 60 * 86400;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
size_t prunable_entries_alloc;
|
||||
struct dirent *e;
|
||||
DIR *dir = NULL;
|
||||
int ret;
|
||||
|
||||
if (setup_rerere(r, rr, 0) < 0)
|
||||
return;
|
||||
*prunable_entries = NULL;
|
||||
*prunable_entries_nr = 0;
|
||||
prunable_entries_alloc = 0;
|
||||
|
||||
repo_config_get_expiry_in_days(the_repository, "gc.rerereresolved",
|
||||
repo_config_get_expiry_in_days(r, "gc.rerereresolved",
|
||||
&cutoff_resolve, now);
|
||||
repo_config_get_expiry_in_days(the_repository, "gc.rerereunresolved",
|
||||
repo_config_get_expiry_in_days(r, "gc.rerereunresolved",
|
||||
&cutoff_noresolve, now);
|
||||
git_config(git_default_config, NULL);
|
||||
dir = opendir(repo_git_path_replace(the_repository, &buf, "rr-cache"));
|
||||
if (!dir)
|
||||
die_errno(_("unable to open rr-cache directory"));
|
||||
/* Collect stale conflict IDs ... */
|
||||
|
||||
dir = opendir(repo_git_path_replace(r, &buf, "rr-cache"));
|
||||
if (!dir) {
|
||||
ret = error_errno(_("unable to open rr-cache directory"));
|
||||
goto out;
|
||||
}
|
||||
|
||||
while ((e = readdir_skip_dot_and_dotdot(dir))) {
|
||||
struct rerere_dir *rr_dir;
|
||||
struct rerere_id id;
|
||||
|
@ -1267,23 +1273,53 @@ void rerere_gc(struct repository *r, struct string_list *rr)
|
|||
for (id.variant = 0, id.collection = rr_dir;
|
||||
id.variant < id.collection->status_nr;
|
||||
id.variant++) {
|
||||
prune_one(&id, cutoff_resolve, cutoff_noresolve);
|
||||
if (id.collection->status[id.variant])
|
||||
if (is_stale(&id, cutoff_resolve, cutoff_noresolve)) {
|
||||
ALLOC_GROW(*prunable_entries, *prunable_entries_nr + 1,
|
||||
prunable_entries_alloc);
|
||||
(*prunable_entries)[(*prunable_entries_nr)++] = id;
|
||||
} else {
|
||||
now_empty = 0;
|
||||
}
|
||||
}
|
||||
if (now_empty)
|
||||
string_list_append(&to_remove, e->d_name);
|
||||
string_list_append(prunable_dirs, e->d_name);
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
/* ... and then remove the empty directories */
|
||||
for (i = 0; i < to_remove.nr; i++)
|
||||
rmdir(repo_git_path_replace(the_repository, &buf,
|
||||
"rr-cache/%s", to_remove.items[i].string));
|
||||
ret = 0;
|
||||
|
||||
string_list_clear(&to_remove, 0);
|
||||
out:
|
||||
strbuf_release(&buf);
|
||||
if (dir)
|
||||
closedir(dir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void rerere_gc(struct repository *r, struct string_list *rr)
|
||||
{
|
||||
struct string_list prunable_dirs = STRING_LIST_INIT_DUP;
|
||||
struct rerere_id *prunable_entries;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
size_t prunable_entries_nr;
|
||||
|
||||
if (setup_rerere(r, rr, 0) < 0)
|
||||
return;
|
||||
|
||||
git_config(git_default_config, NULL);
|
||||
|
||||
if (rerere_collect_stale_entries(r, &prunable_dirs, &prunable_entries,
|
||||
&prunable_entries_nr) < 0)
|
||||
exit(127);
|
||||
|
||||
for (size_t i = 0; i < prunable_entries_nr; i++)
|
||||
unlink_rr_item(&prunable_entries[i]);
|
||||
for (size_t i = 0; i < prunable_dirs.nr; i++)
|
||||
rmdir(repo_git_path_replace(r, &buf, "rr-cache/%s",
|
||||
prunable_dirs.items[i].string));
|
||||
|
||||
string_list_clear(&prunable_dirs, 0);
|
||||
rollback_lock_file(&write_lock);
|
||||
strbuf_release(&buf);
|
||||
free(prunable_entries);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
14
rerere.h
14
rerere.h
|
@ -37,6 +37,20 @@ const char *rerere_path(struct strbuf *buf, const struct rerere_id *,
|
|||
int rerere_forget(struct repository *, struct pathspec *);
|
||||
int rerere_remaining(struct repository *, struct string_list *);
|
||||
void rerere_clear(struct repository *, struct string_list *);
|
||||
|
||||
/*
|
||||
* Collect prunable rerere entries that would be garbage collected via
|
||||
* `rerere_gc()`. Whether or not an entry is prunable depends on both
|
||||
* "gc.rerereResolved" and "gc.rerereUnresolved".
|
||||
*
|
||||
* Returns 0 on success, a negative error code in case entries could not be
|
||||
* collected.
|
||||
*/
|
||||
int rerere_collect_stale_entries(struct repository *r,
|
||||
struct string_list *prunable_dirs,
|
||||
struct rerere_id **prunable_entries,
|
||||
size_t *prunable_entries_nr);
|
||||
|
||||
void rerere_gc(struct repository *, struct string_list *);
|
||||
|
||||
#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
|
||||
|
|
|
@ -493,6 +493,131 @@ test_expect_success 'reflog-expire task --auto only packs when exceeding limits'
|
|||
test_subcommand git reflog expire --all <reflog-expire-auto.txt
|
||||
'
|
||||
|
||||
test_expect_worktree_prune () {
|
||||
negate=
|
||||
if test "$1" = "!"
|
||||
then
|
||||
negate="!"
|
||||
shift
|
||||
fi
|
||||
|
||||
rm -f "worktree-prune.txt" &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" "$@" &&
|
||||
test_subcommand $negate git worktree prune --expire 3.months.ago <worktree-prune.txt
|
||||
}
|
||||
|
||||
test_expect_success 'worktree-prune task without --auto always prunes' '
|
||||
test_expect_worktree_prune git maintenance run --task=worktree-prune
|
||||
'
|
||||
|
||||
test_expect_success 'worktree-prune task --auto only prunes with prunable worktree' '
|
||||
test_expect_worktree_prune ! git maintenance run --auto --task=worktree-prune &&
|
||||
mkdir .git/worktrees &&
|
||||
: >.git/worktrees/abc &&
|
||||
test_expect_worktree_prune git maintenance run --auto --task=worktree-prune
|
||||
'
|
||||
|
||||
test_expect_success 'worktree-prune task with --auto honors maintenance.worktree-prune.auto' '
|
||||
# A negative value should always prune.
|
||||
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=-1 maintenance run --auto --task=worktree-prune &&
|
||||
|
||||
mkdir .git/worktrees &&
|
||||
: >.git/worktrees/first &&
|
||||
: >.git/worktrees/second &&
|
||||
: >.git/worktrees/third &&
|
||||
|
||||
# Zero should never prune.
|
||||
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune &&
|
||||
# A positive value should require at least this man prunable worktrees.
|
||||
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune &&
|
||||
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune
|
||||
'
|
||||
|
||||
test_expect_success 'worktree-prune task with --auto honors maintenance.worktree-prune.auto' '
|
||||
# A negative value should always prune.
|
||||
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=-1 maintenance run --auto --task=worktree-prune &&
|
||||
|
||||
mkdir .git/worktrees &&
|
||||
: >.git/worktrees/first &&
|
||||
: >.git/worktrees/second &&
|
||||
: >.git/worktrees/third &&
|
||||
|
||||
# Zero should never prune.
|
||||
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune &&
|
||||
# A positive value should require at least this many prunable worktrees.
|
||||
test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune &&
|
||||
test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune
|
||||
'
|
||||
|
||||
test_expect_success 'worktree-prune task honors gc.worktreePruneExpire' '
|
||||
git worktree add worktree &&
|
||||
rm -rf worktree &&
|
||||
|
||||
rm -f worktree-prune.txt &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=1.week.ago maintenance run --auto --task=worktree-prune &&
|
||||
test_subcommand ! git worktree prune --expire 1.week.ago <worktree-prune.txt &&
|
||||
test_path_is_dir .git/worktrees/worktree &&
|
||||
|
||||
rm -f worktree-prune.txt &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=now maintenance run --auto --task=worktree-prune &&
|
||||
test_subcommand git worktree prune --expire now <worktree-prune.txt &&
|
||||
test_path_is_missing .git/worktrees/worktree
|
||||
'
|
||||
|
||||
setup_stale_rerere_entry () {
|
||||
rr=.git/rr-cache/"$(printf "%0$(test_oid hexsz)d" "$1")" &&
|
||||
mkdir -p "$rr" &&
|
||||
>"$rr/preimage" &&
|
||||
>"$rr/postimage" &&
|
||||
|
||||
test-tool chmtime ="$((-61 * 86400))" "$rr/preimage" &&
|
||||
test-tool chmtime ="$((-61 * 86400))" "$rr/postimage"
|
||||
}
|
||||
|
||||
test_expect_rerere_gc () {
|
||||
negate=
|
||||
if test "$1" = "!"
|
||||
then
|
||||
negate="!"
|
||||
shift
|
||||
fi
|
||||
|
||||
rm -f "rerere-gc.txt" &&
|
||||
GIT_TRACE2_EVENT="$(pwd)/rerere-gc.txt" "$@" &&
|
||||
test_subcommand $negate git rerere gc <rerere-gc.txt
|
||||
}
|
||||
|
||||
test_expect_success 'rerere-gc task without --auto always collects garbage' '
|
||||
test_expect_rerere_gc git maintenance run --task=rerere-gc
|
||||
'
|
||||
|
||||
test_expect_success 'rerere-gc task with --auto only prunes with prunable entries' '
|
||||
test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
|
||||
for i in $(test_seq 19)
|
||||
do
|
||||
setup_stale_rerere_entry $i || return 1
|
||||
done &&
|
||||
test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc &&
|
||||
setup_stale_rerere_entry 20 &&
|
||||
test_expect_rerere_gc git maintenance run --auto --task=rerere-gc
|
||||
'
|
||||
|
||||
test_expect_success 'rerere-gc task with --auto honors maintenance.rerere-gc.auto' '
|
||||
# A negative value should always prune.
|
||||
test_expect_rerere_gc git -c maintenance.rerere-gc.auto=-1 maintenance run --auto --task=rerere-gc &&
|
||||
|
||||
for i in $(test_seq 20)
|
||||
do
|
||||
setup_stale_rerere_entry $i || return 1
|
||||
done &&
|
||||
|
||||
# Zero should never prune.
|
||||
test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=0 maintenance run --auto --task=rerere-gc &&
|
||||
# A positive value should require at least this many stale rerere entries.
|
||||
test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=21 maintenance run --auto --task=rerere-gc &&
|
||||
test_expect_rerere_gc git -c maintenance.rerere-gc.auto=10 maintenance run --auto --task=rerere-gc
|
||||
'
|
||||
|
||||
test_expect_success '--auto and --schedule incompatible' '
|
||||
test_must_fail git maintenance run --auto --schedule=daily 2>err &&
|
||||
test_grep "at most one" err
|
||||
|
|
30
worktree.c
30
worktree.c
|
@ -988,6 +988,36 @@ done:
|
|||
return rc;
|
||||
}
|
||||
|
||||
int get_worktree_names(struct repository *repo, struct strvec *out)
|
||||
{
|
||||
char *worktrees_dir;
|
||||
struct dirent *d;
|
||||
DIR *dir;
|
||||
int ret;
|
||||
|
||||
worktrees_dir = repo_git_path(repo, "worktrees");
|
||||
dir = opendir(worktrees_dir);
|
||||
if (!dir) {
|
||||
if (errno == ENOENT) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL)
|
||||
strvec_push(out, d->d_name);
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
if (dir)
|
||||
closedir(dir);
|
||||
free(worktrees_dir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int move_config_setting(const char *key, const char *value,
|
||||
const char *from_file, const char *to_file)
|
||||
{
|
||||
|
|
|
@ -38,6 +38,14 @@ struct worktree **get_worktrees(void);
|
|||
*/
|
||||
struct worktree **get_worktrees_without_reading_head(void);
|
||||
|
||||
/*
|
||||
* Retrieve all worktree names. Not all names may correspond to a fully
|
||||
* functional worktree. Returns 0 on success, a negative error code on failure.
|
||||
* Calling the function on a repository that doesn't have any worktrees is not
|
||||
* considered an error.
|
||||
*/
|
||||
int get_worktree_names(struct repository *repo, struct strvec *out);
|
||||
|
||||
/*
|
||||
* Returns 1 if linked worktrees exist, 0 otherwise.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue