From f47bcc3413a946b2735fce84e66efd47cb7be2d2 Mon Sep 17 00:00:00 2001 From: Collin Funk Date: Mon, 5 May 2025 18:08:59 -0700 Subject: [PATCH 01/11] wrapper: NetBSD gives EFTYPE and FreeBSD gives EMFILE where POSIX uses ELOOP As documented on NetBSD's man page, open with the O_NOFOLLOW flag and a symlink returns -1 and sets errno to EFTYPE which differs from POSIX. This patch fixes the following test failure: $ sh t0602-reffiles-fsck.sh --verbose --- expect 2025-05-02 23:05:23.920890147 +0000 +++ err 2025-05-02 23:05:23.916794959 +0000 @@ -1 +1 @@ -error: packed-refs: badRefFiletype: not a regular file but a symlink +error: unable to open '.git/packed-refs': Inappropriate file type or format not ok 12 - the filetype of packed-refs should be checked FreeBSD has the same issue for EMLINK instead of EFTYPE. This portability issue was introduced in cfea2f2da8 (packed-backend: check whether the "packed-refs" is regular file, 2025-02-28) Signed-off-by: Collin Funk Acked-by: brian m. carlson Acked-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- wrapper.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/wrapper.c b/wrapper.c index 8b985931490d62..38fce5327a4d97 100644 --- a/wrapper.c +++ b/wrapper.c @@ -737,7 +737,26 @@ int is_empty_or_missing_file(const char *filename) int open_nofollow(const char *path, int flags) { #ifdef O_NOFOLLOW - return open(path, flags | O_NOFOLLOW); + int ret = open(path, flags | O_NOFOLLOW); + /* + * NetBSD sets errno to EFTYPE when path is a symlink. The only other + * time this errno occurs when O_REGULAR is used. Since we don't use + * it anywhere we can avoid an lstat here. FreeBSD does the same with + * EMLINK. + */ +# ifdef __NetBSD__ +# define SYMLINK_ERRNO EFTYPE +# elif defined(__FreeBSD__) +# define SYMLINK_ERRNO EMLINK +# endif +# if SYMLINK_ERRNO + if (ret < 0 && errno == SYMLINK_ERRNO) { + errno = ELOOP; + return -1; + } +# undef SYMLINK_ERRNO +# endif + return ret; #else struct stat st; if (lstat(path, &st) < 0) From 41429cb4e4ef452e843c126a6ff185998da43431 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 6 May 2025 15:48:55 -0700 Subject: [PATCH 02/11] t6011: fix misconversion from perl to sed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No, this is not about a quiz on regexp compatibility between Perl and sed. Back when cdbdc6bf (t: refactor tests depending on Perl substitution operator, 2025-04-03) rewrote many uses of perl with sed, the general pattern of the original scripts were chmod +w some_read_only_file && perl -p -e "regexp to munge" some_read_only_file >some_tmp && mv some_tmp some_read_only_file persumably because the author knew that replacing some_read_only_file with "mv" at the last step would not work without "mv -f" in some environments (GNU seems to succeed without giving any prompt when not running interactively, which is what happens when running t/ scripts). Replacing perl with sed would be fine as long as sed with updated regexp does the equivalent munging. But one place used to use a different construct in the original: perl -i.bak -p -e "regexp to munge" some_read_only_file With _no_ temporary file or "mv", "perl -i" allows you to replace a read-only file in place. When we replaced the use of "perl" with "sed" in the said commit, however, because "sed -i" is not portable, we rewrote that in-place replacement to sed "regexp to munge" some_read_only_file >some_tmp && mv some_tmp some_read_only_file Again, unfortunately that does not work in some environment, without "mv -f". We could run "mv -f" here, but we would then need to remove "chmod +w" and have them use "mv -f" instead at all places that were touched cdbdc6bf (t: refactor tests depending on Perl substitution operator, 2025-04-03) to be consistent (and more concise). For now, let's make it consistent in the other direction by mimick the other places that made the target read-write before moving. Speaking of portability, the outcome of using "sed" on non-text files is unspecified, so the entire exercise of cdbdc6bf may have needed to be reverted if people still used ancient version of "standard compliant" sed that barfs on non-text files, but these days we may be able to get away with "BSDs and GNU seem OK with it" ;-) But one fix at a time. Reported-by: Torsten Bögershausen Signed-off-by: Junio C Hamano --- t/t6011-rev-list-with-bad-commit.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/t/t6011-rev-list-with-bad-commit.sh b/t/t6011-rev-list-with-bad-commit.sh index b6f3344dbfb865..1dd1e50d2104d7 100755 --- a/t/t6011-rev-list-with-bad-commit.sh +++ b/t/t6011-rev-list-with-bad-commit.sh @@ -38,6 +38,7 @@ test_expect_success 'verify number of revisions' \ test_expect_success 'corrupt second commit object' ' for p in .git/objects/pack/*.pack do + chmod +w "$p" && sed "s/second commit/socond commit/" "$p" >"$p.munged" && mv "$p.munged" "$p" || return 1 From bebc728d7457023699667f2e9fde2e5d740b67e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20B=C3=B6gershausen?= Date: Tue, 6 May 2025 14:06:44 +0200 Subject: [PATCH 03/11] intialize false_but_the_compiler_does_not_know_it_ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compiling/linking 82e79c63642c on an older MacOs machine (like Xcode 14.3.1, the last version of 14.x series) leads to this: Undefined symbols for architecture x86_64: "_false_but_the_compiler_does_not_know_it_", referenced from: _start_command in libgit.a(run-command.o) The linker fails to pick up compiler-tricks/not-constant.o that defines the needed false_but_the_compiler_does_not_know_it_ symbol, which is the only thing defined in that object file, from the libgit.a archive. Initializing the variable explicitly to 0 works around the linker bug; the symbol type changes from 'C' to 'S' and is picked up by the linker. Xcode 15 introduces a new linker, which seems to fix the bug, making the workaround here unnecessary, and Apple requires to build with Xcode 16 or later in order to upload to their App Store Connect since April 24, 2025, but not everybody is expected to upgrade their toolchain immediately. Helped-by: Koji Nakamaru Signed-off-by: Torsten Bögershausen Signed-off-by: Junio C Hamano --- compiler-tricks/not-constant.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler-tricks/not-constant.c b/compiler-tricks/not-constant.c index 1da3ffc2f593d2..9fb4f275b1c428 100644 --- a/compiler-tricks/not-constant.c +++ b/compiler-tricks/not-constant.c @@ -1,2 +1,2 @@ #include -int false_but_the_compiler_does_not_know_it_; +int false_but_the_compiler_does_not_know_it_ = 0; From 58f62837fb42fe602ceaea50f4666d98e278acbe Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 7 May 2025 09:21:37 +0200 Subject: [PATCH 04/11] builtin/gc: fix indentation of `cmd_gc()` parameters The parameters of `cmd_gc()` aren't indented properly. Fix this. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/gc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/gc.c b/builtin/gc.c index a5b86bbf168a70..d24cc7105b074d 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -728,9 +728,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; From e3a69d72b1e48b85f9dad8139797a6fe50d4059d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 7 May 2025 09:21:38 +0200 Subject: [PATCH 05/11] builtin/gc: remove global variables where it is trivial to do We use a couple of global variables to assemble command line arguments for subprocesses we execute in git-gc(1). All of these variables except the one for git-repack(1) are only used in a single place though, so they don't really add anything but confusion. Remove those variables. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/gc.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/builtin/gc.c b/builtin/gc.c index d24cc7105b074d..ba4b30c24bcb3b 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -52,15 +52,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) @@ -779,9 +773,6 @@ int cmd_gc(int argc, 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 +898,36 @@ int cmd_gc(int argc, 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; - strvec_push(&prune_worktrees, cfg.prune_worktrees_expire); prune_worktrees_cmd.git_cmd = 1; - strvec_pushv(&prune_worktrees_cmd.args, prune_worktrees.v); + strvec_pushl(&prune_worktrees_cmd.args, "worktree", "prune", "--expire", NULL); + strvec_push(&prune_worktrees_cmd.args, cfg.prune_worktrees_expire); + if (run_command(&prune_worktrees_cmd)) - die(FAILED_RUN, prune_worktrees.v[0]); + die(FAILED_RUN, prune_worktrees_cmd.args.v[0]); } rerere_cmd.git_cmd = 1; - strvec_pushv(&rerere_cmd.args, rerere.v); + strvec_pushl(&rerere_cmd.args, "rerere", "gc", NULL); if (run_command(&rerere_cmd)) - die(FAILED_RUN, rerere.v[0]); + die(FAILED_RUN, rerere_cmd.args.v[0]); report_garbage = report_pack_garbage; reprepare_packed_git(the_repository); From ae76c1c99089903ce2b786a42a7e5598a7be5c5c Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 7 May 2025 09:21:39 +0200 Subject: [PATCH 06/11] builtin/gc: move pruning of worktrees into a separate function In a subsequent commit we will introduce a new "worktree-prune" task for git-maintenance(1). To prepare for this, refactor the code that spawns `git worktree prune` into a separate function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/gc.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/builtin/gc.c b/builtin/gc.c index ba4b30c24bcb3b..d91b6b7b8cb8a7 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -333,6 +333,18 @@ 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 too_many_loose_objects(struct gc_config *cfg) { /* @@ -913,16 +925,9 @@ int cmd_gc(int argc, } } - if (cfg.prune_worktrees_expire) { - 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); - - if (run_command(&prune_worktrees_cmd)) - die(FAILED_RUN, prune_worktrees_cmd.args.v[0]); - } + if (cfg.prune_worktrees_expire && + maintenance_task_worktree_prune(&opts, &cfg)) + die(FAILED_RUN, "worktree"); rerere_cmd.git_cmd = 1; strvec_pushl(&rerere_cmd.args, "rerere", "gc", NULL); From ec31474656de3849fb9ed31f238fabdb6a59f1b1 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 7 May 2025 09:21:40 +0200 Subject: [PATCH 07/11] builtin/maintenance: introduce "worktree-prune" task While git-gc(1) knows to prune stale worktrees, git-maintenance(1) does not yet have a task for this cleanup. Introduce a new "worktree-prune" task to plug this gap. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Documentation/config/maintenance.adoc | 8 +++ Documentation/git-maintenance.adoc | 4 ++ builtin/gc.c | 45 +++++++++++++++++ t/t7900-maintenance.sh | 71 +++++++++++++++++++++++++++ 4 files changed, 128 insertions(+) diff --git a/Documentation/config/maintenance.adoc b/Documentation/config/maintenance.adoc index 41536162a779c8..b36b62c1c47e4a 100644 --- a/Documentation/config/maintenance.adoc +++ b/Documentation/config/maintenance.adoc @@ -83,3 +83,11 @@ 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.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. diff --git a/Documentation/git-maintenance.adoc b/Documentation/git-maintenance.adoc index 3a1e2a69b6b0ea..6f085a9cf8c92a 100644 --- a/Documentation/git-maintenance.adoc +++ b/Documentation/git-maintenance.adoc @@ -166,6 +166,10 @@ 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. +worktree-prune:: + The `worktree-prune` task deletes stale or broken worktrees. See + linkit:git-worktree[1] for more information. + OPTIONS ------- --auto:: diff --git a/builtin/gc.c b/builtin/gc.c index d91b6b7b8cb8a7..d28c238a80631b 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -43,6 +43,7 @@ #include "hook.h" #include "setup.h" #include "trace2.h" +#include "worktree.h" #define FAILED_RUN "failed to run %s" @@ -345,6 +346,44 @@ static int maintenance_task_worktree_prune(struct maintenance_run_opts *opts UNU return run_command(&prune_worktrees_cmd); } +static int worktree_prune_condition(struct gc_config *cfg) +{ + struct strbuf buf = STRBUF_INIT; + int should_prune = 0, limit = 1; + timestamp_t expiry_date; + struct dirent *d; + DIR *dir = NULL; + + 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)) + goto out; + + dir = opendir(repo_git_path_replace(the_repository, &buf, "worktrees")); + if (!dir) + goto out; + + while (limit && (d = readdir_skip_dot_and_dotdot(dir))) { + char *wtpath; + strbuf_reset(&buf); + if (should_prune_worktree(d->d_name, &buf, &wtpath, expiry_date)) + limit--; + free(wtpath); + } + + should_prune = !limit; + +out: + if (dir) + closedir(dir); + strbuf_release(&buf); + return should_prune; +} + static int too_many_loose_objects(struct gc_config *cfg) { /* @@ -1465,6 +1504,7 @@ enum maintenance_task_label { TASK_COMMIT_GRAPH, TASK_PACK_REFS, TASK_REFLOG_EXPIRE, + TASK_WORKTREE_PRUNE, /* Leave as final value */ TASK__COUNT @@ -1506,6 +1546,11 @@ static struct maintenance_task tasks[] = { maintenance_task_reflog_expire, reflog_expire_condition, }, + [TASK_WORKTREE_PRUNE] = { + "worktree-prune", + maintenance_task_worktree_prune, + worktree_prune_condition, + }, }; static int compare_tasks_by_selection(const void *a_, const void *b_) diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 9b82e11c10052b..8f4120a0351e2e 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -493,6 +493,77 @@ test_expect_success 'reflog-expire task --auto only packs when exceeding limits' test_subcommand git reflog expire --all .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 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 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 err && test_grep "at most one" err From 255251cce179efffe6dd17bc26f2729f6fcfd3bd Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 7 May 2025 09:21:41 +0200 Subject: [PATCH 08/11] builtin/gc: move rerere garbage collection into separate function In a subsequent commit we are going to introduce a new "rerere-gc" task for git-maintenance(1). To prepare for this, refactor the code that spawns `git rerere gc` into a separate function. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/gc.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/builtin/gc.c b/builtin/gc.c index d28c238a80631b..03b4e32bb5e8fb 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -384,6 +384,15 @@ static int worktree_prune_condition(struct gc_config *cfg) 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 too_many_loose_objects(struct gc_config *cfg) { /* @@ -785,7 +794,6 @@ int cmd_gc(int argc, 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"; @@ -968,10 +976,8 @@ int cmd_gc(int argc, maintenance_task_worktree_prune(&opts, &cfg)) die(FAILED_RUN, "worktree"); - rerere_cmd.git_cmd = 1; - strvec_pushl(&rerere_cmd.args, "rerere", "gc", NULL); - if (run_command(&rerere_cmd)) - die(FAILED_RUN, rerere_cmd.args.v[0]); + if (maintenance_task_rerere_gc(&opts, &cfg)) + die(FAILED_RUN, "rerere"); report_garbage = report_pack_garbage; reprepare_packed_git(the_repository); From 283621a553b60b26f14b9cf7e8b8c852ddba55d9 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 7 May 2025 09:21:42 +0200 Subject: [PATCH 09/11] builtin/maintenance: introduce "rerere-gc" task While git-gc(1) knows to garbage collect the rerere cache, git-maintenance(1) does not yet have a task for this cleanup. Introduce a new "rerere-gc" task to plug this gap. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Documentation/config/maintenance.adoc | 9 ++++++ Documentation/git-maintenance.adoc | 4 +++ builtin/gc.c | 37 ++++++++++++++++++++++ t/t7900-maintenance.sh | 44 +++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) diff --git a/Documentation/config/maintenance.adoc b/Documentation/config/maintenance.adoc index b36b62c1c47e4a..2f719342183322 100644 --- a/Documentation/config/maintenance.adoc +++ b/Documentation/config/maintenance.adoc @@ -84,6 +84,15 @@ maintenance.reflog-expire.auto:: 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, any positive + value implies the command will run when the "rr-cache" directory exists + and has at least one entry, regardless of whether it is stale or not. + This heuristic may be refined in the future. The default value is 1. + 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 diff --git a/Documentation/git-maintenance.adoc b/Documentation/git-maintenance.adoc index 6f085a9cf8c92a..931f3e02e85fe4 100644 --- a/Documentation/git-maintenance.adoc +++ b/Documentation/git-maintenance.adoc @@ -166,6 +166,10 @@ 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. diff --git a/builtin/gc.c b/builtin/gc.c index 03b4e32bb5e8fb..3393d2535d47c7 100644 --- a/builtin/gc.c +++ b/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" @@ -393,6 +395,35 @@ static int maintenance_task_rerere_gc(struct maintenance_run_opts *opts UNUSED, return run_command(&rerere_cmd); } +static int rerere_gc_condition(struct gc_config *cfg UNUSED) +{ + struct strbuf path = STRBUF_INIT; + int should_gc = 0, limit = 1; + DIR *dir = NULL; + + git_config_get_int("maintenance.rerere-gc.auto", &limit); + if (limit <= 0) { + should_gc = limit < 0; + goto out; + } + + /* + * We skip garbage collection in case we either have no "rr-cache" + * directory or when it doesn't contain at least one entry. + */ + repo_git_path_replace(the_repository, &path, "rr-cache"); + dir = opendir(path.buf); + if (!dir) + goto out; + should_gc = !!readdir_skip_dot_and_dotdot(dir); + +out: + strbuf_release(&path); + if (dir) + closedir(dir); + return should_gc; +} + static int too_many_loose_objects(struct gc_config *cfg) { /* @@ -1511,6 +1542,7 @@ enum maintenance_task_label { TASK_PACK_REFS, TASK_REFLOG_EXPIRE, TASK_WORKTREE_PRUNE, + TASK_RERERE_GC, /* Leave as final value */ TASK__COUNT @@ -1557,6 +1589,11 @@ static struct maintenance_task tasks[] = { 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_) diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 8f4120a0351e2e..8cf89e285f49e5 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -564,6 +564,50 @@ test_expect_success 'worktree-prune task honors gc.worktreePruneExpire' ' test_path_is_missing .git/worktrees/worktree ' +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 .git/rr-cache/entry && + test_expect_rerere_gc git maintenance run --auto --task=rerere-gc +' + +test_expect_success 'rerere-gc task with --auto honors maintenance.rerere-gc.auto' ' + test_when_finished "rm -rf .git/rr-cache" && + + # A negative value should always prune. + test_expect_rerere_gc git -c maintenance.rerere-gc.auto=-1 maintenance run --auto --task=rerere-gc && + + # A positive value prunes when there is at least one entry. + test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc && + mkdir .git/rr-cache && + test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc && + : >.git/rr-cache/entry-1 && + test_expect_rerere_gc git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc && + + # Zero should never prune. + : >.git/rr-cache/entry-1 && + test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=0 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 From 5463c1d4f6d03e63ee79bd822de667090f015356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90o=C3=A0n=20Tr=E1=BA=A7n=20C=C3=B4ng=20Danh?= Date: Thu, 8 May 2025 15:24:40 +0700 Subject: [PATCH 10/11] meson: allow customize perl installation path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some distros, notably Fedora, want to install non-core Perl libraries into specific directory, namely /usr/share/perl5/vendor_perl. The Makefile build system allows this by overriding perllibdir variable, let's make meson works on par with our Makefile. Signed-off-by: Đoàn Trần Công Danh Signed-off-by: Junio C Hamano --- meson.build | 9 +++++++-- meson_options.txt | 4 ++++ perl/FromCPAN/Mail/meson.build | 2 +- perl/FromCPAN/meson.build | 2 +- perl/Git/LoadCPAN/Mail/meson.build | 2 +- perl/Git/LoadCPAN/meson.build | 2 +- perl/Git/SVN/Memoize/meson.build | 2 +- perl/Git/SVN/meson.build | 2 +- perl/Git/meson.build | 2 +- perl/meson.build | 2 +- 10 files changed, 19 insertions(+), 10 deletions(-) diff --git a/meson.build b/meson.build index c47cb79af0815a..568da996f1d002 100644 --- a/meson.build +++ b/meson.build @@ -1871,14 +1871,19 @@ if perl_features_enabled perl_header_template = 'perl/header_templates/runtime_prefix.template.pl' endif + perllibdir = get_option('perllibdir') + if perllibdir == '' + perllibdir = get_option('datadir') / 'perl5' + endif + perl_header = configure_file( input: perl_header_template, output: 'GIT-PERL-HEADER', configuration: { 'GITEXECDIR_REL': get_option('libexecdir') / 'git-core', - 'PERLLIBDIR_REL': get_option('datadir') / 'perl5', + 'PERLLIBDIR_REL': perllibdir, 'LOCALEDIR_REL': get_option('datadir') / 'locale', - 'INSTLIBDIR': get_option('datadir') / 'perl5', + 'INSTLIBDIR': perllibdir, 'PATHSEP': pathsep, }, ) diff --git a/meson_options.txt b/meson_options.txt index 78d172a74019a4..cc19918a7ccfa4 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,7 @@ +# Configuration for Git installation +option('perllibdir', type: 'string', value: '', + description: 'Directory to install perl lib to. Defaults to /perl5') + # Configuration for how Git behaves at runtime. option('default_pager', type: 'string', value: 'less', description: 'Fall-back pager.') diff --git a/perl/FromCPAN/Mail/meson.build b/perl/FromCPAN/Mail/meson.build index b4ff2fc0b24c95..467507c5e690ef 100644 --- a/perl/FromCPAN/Mail/meson.build +++ b/perl/FromCPAN/Mail/meson.build @@ -3,6 +3,6 @@ test_dependencies += custom_target( output: 'Address.pm', command: generate_perl_command, install: true, - install_dir: get_option('datadir') / 'perl5/FromCPAN/Mail', + install_dir: perllibdir / 'FromCPAN/Mail', depends: [git_version_file], ) diff --git a/perl/FromCPAN/meson.build b/perl/FromCPAN/meson.build index 1f9ea6ce8e8442..720c60283d89b8 100644 --- a/perl/FromCPAN/meson.build +++ b/perl/FromCPAN/meson.build @@ -3,7 +3,7 @@ test_dependencies += custom_target( output: 'Error.pm', command: generate_perl_command, install: true, - install_dir: get_option('datadir') / 'perl5/FromCPAN', + install_dir: perllibdir / 'FromCPAN', depends: [git_version_file], ) diff --git a/perl/Git/LoadCPAN/Mail/meson.build b/perl/Git/LoadCPAN/Mail/meson.build index 89cde56be84912..05a5770560d3d1 100644 --- a/perl/Git/LoadCPAN/Mail/meson.build +++ b/perl/Git/LoadCPAN/Mail/meson.build @@ -3,6 +3,6 @@ test_dependencies += custom_target( output: 'Address.pm', command: generate_perl_command, install: true, - install_dir: get_option('datadir') / 'perl5/Git/LoadCPAN/Mail', + install_dir: perllibdir / 'Git/LoadCPAN/Mail', depends: [git_version_file], ) diff --git a/perl/Git/LoadCPAN/meson.build b/perl/Git/LoadCPAN/meson.build index 1ee915c650517d..b975d4972631d4 100644 --- a/perl/Git/LoadCPAN/meson.build +++ b/perl/Git/LoadCPAN/meson.build @@ -3,7 +3,7 @@ test_dependencies += custom_target( output: 'Error.pm', command: generate_perl_command, install: true, - install_dir: get_option('datadir') / 'perl5/Git/LoadCPAN', + install_dir: perllibdir / 'Git/LoadCPAN', depends: [git_version_file], ) diff --git a/perl/Git/SVN/Memoize/meson.build b/perl/Git/SVN/Memoize/meson.build index 233ec670d7de91..4c589b30c387a7 100644 --- a/perl/Git/SVN/Memoize/meson.build +++ b/perl/Git/SVN/Memoize/meson.build @@ -3,6 +3,6 @@ test_dependencies += custom_target( output: 'YAML.pm', command: generate_perl_command, install: true, - install_dir: get_option('datadir') / 'perl5/Git/SVN', + install_dir: perllibdir / 'Git/SVN', depends: [git_version_file], ) diff --git a/perl/Git/SVN/meson.build b/perl/Git/SVN/meson.build index 44abaf42b7cea3..8858985fe8660e 100644 --- a/perl/Git/SVN/meson.build +++ b/perl/Git/SVN/meson.build @@ -13,7 +13,7 @@ foreach source : [ output: source, command: generate_perl_command, install: true, - install_dir: get_option('datadir') / 'perl5/Git/SVN', + install_dir: perllibdir / 'Git/SVN', depends: [git_version_file], ) endforeach diff --git a/perl/Git/meson.build b/perl/Git/meson.build index b21fa5591e7e79..a61b7b1f4abf25 100644 --- a/perl/Git/meson.build +++ b/perl/Git/meson.build @@ -10,7 +10,7 @@ foreach source : [ output: source, command: generate_perl_command, install: true, - install_dir: get_option('datadir') / 'perl5/Git', + install_dir: perllibdir / 'Git', depends: [git_version_file], ) endforeach diff --git a/perl/meson.build b/perl/meson.build index 2d4ab1c4a986f7..3c66b007eaad9e 100644 --- a/perl/meson.build +++ b/perl/meson.build @@ -3,7 +3,7 @@ test_dependencies += custom_target( output: 'Git.pm', command: generate_perl_command, install: true, - install_dir: get_option('datadir') / 'perl5', + install_dir: perllibdir, depends: [git_version_file], ) From cb96e1697ad6e54d11fc920c95f82977f8e438f8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 15 May 2025 17:27:23 -0700 Subject: [PATCH 11/11] The fifteenth batch Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.50.0.adoc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/RelNotes/2.50.0.adoc b/Documentation/RelNotes/2.50.0.adoc index 7ae05bdbd61249..02fa875823da8d 100644 --- a/Documentation/RelNotes/2.50.0.adoc +++ b/Documentation/RelNotes/2.50.0.adoc @@ -62,6 +62,9 @@ UI, Workflows & Features delta chains from forming in a corner case even when there is no such cycle. + * Make repository clean-up tasks "gc" can do available to "git + maintenance" front-end. + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -286,6 +289,11 @@ Fixes since v2.49 also existed on the working tree, which has been corrected. (merge ec727e189c kj/glob-path-with-special-char later to maint). + * The fallback implementation of open_nofollow() depended on + open("symlink", O_NOFOLLOW) to set errno to ELOOP, but a few BSD + derived systems use different errno, which has been worked around. + (merge f47bcc3413 cf/wrapper-bsd-eloop later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 227c4f33a0 ja/doc-block-delimiter-markup-fix later to maint). (merge 2bfd3b3685 ab/decorate-code-cleanup later to maint).