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 Changes in v4: - simplified the heuristic for "rerere-gc" even further. A positive value for "maintenance.rerere-gc.auto" now indicates that the command will run whenever there is at least one directory entry in ".rr-cache". The exact value does not matter anymore. - Link to v3: https://lore.kernel.org/r/20250502-pks-maintenance-missing-tasks-v3-0-13e130d36640@xxxxxx Changes in v5: - Drop `get_worktree_names()` in favor of an open-coded loop. - Fix a memory leak. - Simplified the logic in `worktree_prune_condition()` a bit. - Link to v4: https://lore.kernel.org/r/20250505-pks-maintenance-missing-tasks-v4-0-141f4df906a1@xxxxxx Thanks! Patrick --- Patrick Steinhardt (6): builtin/gc: fix indentation of `cmd_gc()` parameters builtin/gc: remove global variables where it is trivial to do builtin/gc: move pruning of worktrees into a separate function 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 | 17 ++++ Documentation/git-maintenance.adoc | 8 ++ builtin/gc.c | 148 +++++++++++++++++++++++++++------- t/t7900-maintenance.sh | 115 ++++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 31 deletions(-) Range-diff versus v4: 1: 59edf54e3ec = 1: 815904a68a1 builtin/gc: fix indentation of `cmd_gc()` parameters 2: 9f02c33f5b9 ! 2: 91f4c304232 builtin/gc: remove global variables where it trivial to do @@ Metadata Author: Patrick Steinhardt <ps@xxxxxx> ## Commit message ## - builtin/gc: remove global variables where it trivial to do + 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 3: b280af7bbc4 ! 3: 9232c8aac1d builtin/gc: move pruning of worktrees into a separate function @@ Metadata ## Commit message ## builtin/gc: move pruning of worktrees into a separate function - Move pruning of worktrees into a separate function. This prepares for a - subsequent commit where we introduce a new "worktree-prune" task for - git-maintenance(1). + 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 <ps@xxxxxx> 4: 58ce12459c2 < -: ----------- worktree: expose function to retrieve worktree names 5: efeec465db0 ! 4: 24ca70b35b9 builtin/maintenance: introduce "worktree-prune" task @@ builtin/gc.c: static int maintenance_task_worktree_prune(struct maintenance_run_ +static int worktree_prune_condition(struct gc_config *cfg) +{ -+ struct strvec worktrees = STRVEC_INIT; -+ struct strbuf reason = STRBUF_INIT; ++ struct strbuf buf = STRBUF_INIT; ++ int should_prune = 0, limit = 1; + timestamp_t expiry_date; -+ int should_prune = 0; -+ int limit = 1; ++ struct dirent *d; ++ DIR *dir = NULL; + + git_config_get_int("maintenance.worktree-prune.auto", &limit); + if (limit <= 0) { @@ builtin/gc.c: static int maintenance_task_worktree_prune(struct maintenance_run_ + goto out; + } + -+ if (parse_expiry_date(cfg->prune_worktrees_expire, &expiry_date) || -+ get_worktree_names(the_repository, &worktrees) < 0) ++ if (parse_expiry_date(cfg->prune_worktrees_expire, &expiry_date)) + goto out; + -+ for (size_t i = 0; i < worktrees.nr; i++) { -+ char *wtpath; ++ dir = opendir(repo_git_path_replace(the_repository, &buf, "worktrees")); ++ if (!dir) ++ goto out; + -+ strbuf_reset(&reason); -+ if (should_prune_worktree(worktrees.v[i], &reason, &wtpath, expiry_date)) { ++ 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--; -+ -+ if (!limit) { -+ should_prune = 1; -+ goto out; -+ } -+ } + free(wtpath); + } + ++ should_prune = !limit; ++ +out: -+ strvec_clear(&worktrees); -+ strbuf_release(&reason); ++ if (dir) ++ closedir(dir); ++ strbuf_release(&buf); + return should_prune; +} + @@ t/t7900-maintenance.sh: test_expect_success 'reflog-expire task --auto only pack + + # 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. ++ # 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 +' 6: 10ed12cc737 ! 5: 67e05501fbb builtin/gc: move rerere garbage collection into separate function @@ Metadata ## Commit message ## builtin/gc: move rerere garbage collection into separate function - Move garbage collection of cached rerere entries into a separate - function. This prepares us for a subsequent commit where we introduce a - new "rerere-gc" task for git-maintenance(1). + 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 <ps@xxxxxx> 7: be7fa13115c = 6: 818ed6b8999 builtin/maintenance: introduce "rerere-gc" task --- base-commit: a2955b34f48265d240ab8c7deb0a929ec2d65fd0 change-id: 20250424-pks-maintenance-missing-tasks-8ffcdd596b73