[PATCH v2 0/8] builtin/maintenance: implement missing tasks compared to git-gc(1)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

Thanks!

Patrick

---
Patrick Steinhardt (8):
      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
      rerere: provide function to collect stale entries
      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                          | 153 +++++++++++++++++++++++++++-------
 builtin/worktree.c                    |  25 +++---
 rerere.c                              |  92 +++++++++++++-------
 rerere.h                              |  14 ++++
 t/t7900-maintenance.sh                | 125 +++++++++++++++++++++++++++
 worktree.c                            |  30 +++++++
 worktree.h                            |   8 ++
 9 files changed, 399 insertions(+), 72 deletions(-)

Range-diff versus v1:

1:  0304b81df0b = 1:  9c62b493297 builtin/gc: fix indentation of `cmd_gc()` parameters
2:  22c499601ee = 2:  9ae42b139fa builtin/gc: remove global variables where it trivial to do
3:  db9622a408f = 3:  50a5305b6d2 builtin/gc: move pruning of worktrees into a separate function
4:  f42205e1b6b = 4:  b71dcb0debc worktree: expose function to retrieve worktree names
5:  eade37df904 ! 5:  47d31f41c2e builtin/maintenance: introduce "worktree-prune" task
    @@ Commit message
     
         Signed-off-by: Patrick Steinhardt <ps@xxxxxx>
     
    + ## Documentation/config/maintenance.adoc ##
    +@@ Documentation/config/maintenance.adoc: 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.
    +
      ## Documentation/git-maintenance.adoc ##
     @@ Documentation/git-maintenance.adoc: reflog-expire::
      	The `reflog-expire` task deletes any entries in the reflog older than the
    @@ builtin/gc.c: static int maintenance_task_worktree_prune(struct maintenance_run_
     +	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)
    @@ builtin/gc.c: static int maintenance_task_worktree_prune(struct maintenance_run_
     +
     +		strbuf_reset(&reason);
     +		if (should_prune_worktree(worktrees.v[i], &reason, &wtpath, expiry_date)) {
    -+			should_prune = 1;
    -+			goto out;
    ++			limit--;
    ++
    ++			if (!limit) {
    ++				should_prune = 1;
    ++				goto out;
    ++			}
     +		}
     +		free(wtpath);
     +	}
    @@ t/t7900-maintenance.sh: test_expect_success 'reflog-expire task --auto only pack
      	test_subcommand git reflog expire --all <reflog-expire-auto.txt
      '
      
    -+test_expect_success 'worktree-prune task' '
    -+	GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" \
    -+		git maintenance run --task=worktree-prune &&
    -+	test_subcommand git worktree prune --expire 3.months.ago <worktree-prune.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' '
    -+	GIT_TRACE2_EVENT="$(pwd)/worktree-prune-auto.txt" \
    -+		git maintenance run --auto --task=worktree-prune &&
    -+	test_subcommand ! git worktree prune --expire 3.months.ago <worktree-prune-auto.txt &&
    ++	test_expect_worktree_prune ! git maintenance run --auto --task=worktree-prune &&
     +	mkdir .git/worktrees &&
     +	: >.git/worktrees/abc &&
    -+	GIT_TRACE2_EVENT="$(pwd)/worktree-prune-auto.txt" \
    -+		git maintenance run --auto --task=worktree-prune &&
    -+	test_subcommand git worktree prune --expire 3.months.ago <worktree-prune-auto.txt
    ++	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
     +'
     +
      test_expect_success '--auto and --schedule incompatible' '
-:  ----------- > 6:  5550c115e84 rerere: provide function to collect stale entries
6:  66b2b033743 = 7:  f5b234c859e builtin/gc: move rerere garbage collection into separate function
7:  9604fc4fc6b ! 8:  092e57cce01 builtin/maintenance: introduce "rerere-gc" task
    @@ Commit message
     
         Signed-off-by: Patrick Steinhardt <ps@xxxxxx>
     
    + ## Documentation/config/maintenance.adoc ##
    +@@ Documentation/config/maintenance.adoc: 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, 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
    +
      ## Documentation/git-maintenance.adoc ##
     @@ Documentation/git-maintenance.adoc: reflog-expire::
      	The `reflog-expire` task deletes any entries in the reflog older than the
    @@ builtin/gc.c
      #include "environment.h"
      #include "hex.h"
      #include "config.h"
    +@@
    + #include "pack-objects.h"
    + #include "path.h"
    + #include "reflog.h"
    ++#include "rerere.h"
    + #include "blob.h"
    + #include "tree.h"
    + #include "promisor-remote.h"
     @@ builtin/gc.c: static int maintenance_task_rerere_gc(struct maintenance_run_opts *opts UNUSED,
      	return run_command(&rerere_cmd);
      }
    @@ 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;
    -+	DIR *dir;
    ++	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;
     +
    -+	dir = opendir(path.buf);
    -+	if (!dir)
    ++	if (rerere_collect_stale_entries(the_repository, &prunable_dirs,
    ++					 &prunable_entries, &prunable_entries_nr) < 0)
     +		goto out;
    -+	should_gc = !!readdir_skip_dot_and_dotdot(dir);
    ++
    ++	should_gc = prunable_entries_nr >= limit;
     +
     +out:
    ++	string_list_clear(&prunable_dirs, 0);
    ++	free(prunable_entries);
     +	strbuf_release(&path);
    -+	closedir(dir);
     +	return should_gc;
     +}
     +
    @@ builtin/gc.c: static struct maintenance_task tasks[] = {
      static int compare_tasks_by_selection(const void *a_, const void *b_)
     
      ## t/t7900-maintenance.sh ##
    -@@ t/t7900-maintenance.sh: test_expect_success 'worktree-prune task --auto only prunes with prunable worktr
    - 	test_subcommand git worktree prune --expire 3.months.ago <worktree-prune-auto.txt
    +@@ t/t7900-maintenance.sh: test_expect_success 'worktree-prune task honors gc.worktreePruneExpire' '
    + 	test_path_is_missing .git/worktrees/worktree
      '
      
    -+test_expect_success 'rerere-gc task' '
    -+	GIT_TRACE2_EVENT="$(pwd)/rerere-gc.txt" \
    -+		git maintenance run --task=rerere-gc &&
    -+	test_subcommand git rerere gc <rerere-gc.txt
    ++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 --auto only prunes with existing rr-cache dir' '
    -+	mkdir .git/rr-cache &&
    -+	GIT_TRACE2_EVENT="$(pwd)/rerere-gc-auto.txt" \
    -+		git maintenance run --auto --task=rerere-gc &&
    -+	test_subcommand ! git rerere gc <rerere-gc-auto.txt &&
    -+	: >.git/rr-cache/entry &&
    -+	GIT_TRACE2_EVENT="$(pwd)/rerere-gc-auto.txt" \
    -+		git maintenance run --auto --task=rerere-gc &&
    -+	test_subcommand git rerere gc <rerere-gc-auto.txt
    ++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' '

---
base-commit: a2955b34f48265d240ab8c7deb0a929ec2d65fd0
change-id: 20250424-pks-maintenance-missing-tasks-8ffcdd596b73





[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux