Hi, this small patch series implements the last couple of remaining tasks that are missing compared to the functionality git-gc(1) provides. Right now, git-maintenance(1) still executes git-gc(1). With these last gaps plugged though we can in theory fully replace git-gc(1) with finer grained tasks without losing any functionality. The benefit is that it becomes possible for users to have finer-grained control over what exactly the maintenance does. This patch series doesn't do that yet, but only implements whatever is needed to get there. Changes in v2: - Introduce "maintenance.worktree-prune.auto", which controls how many stale worktrees need to exist before executing `git worktree prune`. - Introduce "maintenance.rerere-gc.auto", which controls how many stale rerere entries need to exist before executing `git rerere gc`. - Add tests to verify that "gc.worktreePruneExpire" works. - Remove some fragile test logic by introducing functions that check for a given maintenance subprocess. - Link to v1: https://lore.kernel.org/r/20250425-pks-maintenance-missing-tasks-v1-0-972ed6ab2c0d@xxxxxx Changes in v3: - Simplify the heuristic for "rerere-gc" so that we only count the number of directory entries in ".git/rr-cache", without considering staleness. - Link to v2: https://lore.kernel.org/r/20250430-pks-maintenance-missing-tasks-v2-0-2580b7b8ca3a@xxxxxx Thanks! Patrick --- Patrick Steinhardt (7): builtin/gc: fix indentation of `cmd_gc()` parameters builtin/gc: remove global variables where it trivial to do builtin/gc: move pruning of worktrees into a separate function worktree: expose function to retrieve worktree names builtin/maintenance: introduce "worktree-prune" task builtin/gc: move rerere garbage collection into separate function builtin/maintenance: introduce "rerere-gc" task Documentation/config/maintenance.adoc | 16 ++++ Documentation/git-maintenance.adoc | 8 ++ builtin/gc.c | 157 +++++++++++++++++++++++++++------- builtin/worktree.c | 25 +++--- t/t7900-maintenance.sh | 114 ++++++++++++++++++++++++ worktree.c | 30 +++++++ worktree.h | 8 ++ 7 files changed, 314 insertions(+), 44 deletions(-) Range-diff versus v2: 1: fe67cebba69 = 1: ac3cd73438b builtin/gc: fix indentation of `cmd_gc()` parameters 2: 623c53de24c = 2: 662dee455b6 builtin/gc: remove global variables where it trivial to do 3: 7c6b33075ca = 3: 522d40d8da7 builtin/gc: move pruning of worktrees into a separate function 4: 423963c589e = 4: 737df0a7dcc worktree: expose function to retrieve worktree names 5: a031d9aa9fc = 5: 8a24fb6547b builtin/maintenance: introduce "worktree-prune" task 6: 6610b72a0f5 < -: ----------- rerere: provide function to collect stale entries 7: 4e6c461e0b6 = 6: 3ce62afb54f builtin/gc: move rerere garbage collection into separate function 8: 4763c2aa09e ! 7: 90e86a52fcc builtin/maintenance: introduce "rerere-gc" task @@ Documentation/config/maintenance.adoc: maintenance.reflog-expire.auto:: + 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. ++ value implies the command should run when there are at least this many ++ directory entries in the "rr-cache" directory. The default value is 1. + maintenance.worktree-prune.auto:: This integer config option controls how often the `worktree-prune` task @@ builtin/gc.c: static int maintenance_task_rerere_gc(struct maintenance_run_opts +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; ++ int should_gc = 0, limit = 1; ++ DIR *dir = NULL; + + git_config_get_int("maintenance.rerere-gc.auto", &limit); + if (limit <= 0) { @@ builtin/gc.c: static int maintenance_task_rerere_gc(struct maintenance_run_opts + goto out; + } + -+ /* Skip garbage collecting the rerere cache in case rerere is disabled. */ ++ /* ++ * We skip garbage collection in case we either have no "rr-cache" ++ * directory or when it doesn't contain at least as many directories as ++ * indicated by "maintenance.rerere-gc.auto". ++ */ + repo_git_path_replace(the_repository, &path, "rr-cache"); -+ if (!is_directory(path.buf)) ++ dir = opendir(path.buf); ++ if (!dir) + goto out; + -+ if (rerere_collect_stale_entries(the_repository, &prunable_dirs, -+ &prunable_entries, &prunable_entries_nr) < 0) -+ goto out; ++ while (readdir_skip_dot_and_dotdot(dir)) { ++ if (--limit) ++ continue; + -+ should_gc = prunable_entries_nr >= limit; ++ should_gc = 1; ++ goto out; ++ } + +out: -+ string_list_clear(&prunable_dirs, 0); -+ free(prunable_entries); + strbuf_release(&path); ++ if (dir) ++ closedir(dir); + return should_gc; +} + @@ t/t7900-maintenance.sh: test_expect_success 'worktree-prune task honors gc.workt 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" = "!" @@ t/t7900-maintenance.sh: test_expect_success 'worktree-prune task honors gc.workt +' + +test_expect_success 'rerere-gc task with --auto only prunes with prunable entries' ' ++ test_when_finished "rm -rf .git/rr-cache" && + 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 && ++ mkdir .git/rr-cache && + test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc && -+ setup_stale_rerere_entry 20 && ++ : >.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 && + -+ for i in $(test_seq 20) -+ do -+ setup_stale_rerere_entry $i || return 1 -+ done && ++ mkdir .git/rr-cache && ++ : >.git/rr-cache/entry-1 && ++ : >.git/rr-cache/entry-2 && + + # 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_rerere_gc ! git -c maintenance.rerere-gc.auto=3 maintenance run --auto --task=rerere-gc && ++ test_expect_rerere_gc git -c maintenance.rerere-gc.auto=2 maintenance run --auto --task=rerere-gc +' + test_expect_success '--auto and --schedule incompatible' ' --- base-commit: a2955b34f48265d240ab8c7deb0a929ec2d65fd0 change-id: 20250424-pks-maintenance-missing-tasks-8ffcdd596b73