Subject: Re: [PATCH 01/11] maintenance: create basic maintenance runner

From: Jonathan Nieder <[email-masked]>
Date: Wed, 12 Aug 2020 14:03:26 -0700

Derrick Stolee wrote:

> Helped-by: Jonathan Nieder <[email-masked]>
> Signed-off-by: Derrick Stolee <[email-masked]>
> ---
>  .gitignore                        |  1 +
>  Documentation/git-maintenance.txt | 57 +++++++++++++++++++++++++++++++
>  builtin.h                         |  1 +
>  builtin/gc.c                      | 57 +++++++++++++++++++++++++++++++
>  git.c                             |  1 +
>  t/t7900-maintenance.sh            | 19 +++++++++++
>  t/test-lib-functions.sh           | 33 ++++++++++++++++++
>  7 files changed, 169 insertions(+)
>  create mode 100644 Documentation/git-maintenance.txt
>  create mode 100755 t/t7900-maintenance.sh

Looks reasonable.

[...]
> --- /dev/null
> +++ b/Documentation/git-maintenance.txt
> @@ -0,0 +1,57 @@
> +git-maintenance(1)
> +==================
> +
> +NAME
> +----
> +git-maintenance - Run tasks to optimize Git repository data
> +
> +
> +SYNOPSIS
> +--------
> +[verse]
> +'git maintenance' run [<options>]
> +
> +
> +DESCRIPTION
> +-----------
> +Run tasks to optimize Git repository data, speeding up other Git commands
> +and reducing storage requirements for the repository.
> ++
> +Git commands that add repository data, such as `git add` or `git fetch`,
> +are optimized for a responsive user experience. These commands do not take
> +time to optimize the Git data, since such optimizations scale with the full
> +size of the repository while these user commands each perform a relatively
> +small action.
> ++
> +The `git maintenance` command provides flexibility for how to optimize the
> +Git repository.
> +
> +SUBCOMMANDS
> +-----------
> +
> +run::
> +	Run one or more maintenance tasks.

This is still confusing --- shouldn't it say something like "Run the
`gc` maintenance task (see below)"?

[...]
> +TASKS
> +-----
> +
> +gc::
> +	Cleanup unnecessary files and optimize the local repository. "GC"

nit: cleanup is the noun, "clean up" is the verb

> +	stands for "garbage collection," but this task performs many
> +	smaller tasks. This task can be rather expensive for large

nit: the word "rather" is not doing much work here, so we could leave
it out

> +	repositories, as it repacks all Git objects into a single pack-file.
> +	It can also be disruptive in some situations, as it deletes stale
> +	data.

What stale data is this referring to?  Where can I read more about
what disruption it causes (or in other words, as a user, how would I
decide when *not* to run this command)?

[...]
> +OPTIONS
> +-------
> +--auto::
> +	When combined with the `run` subcommand, run maintenance tasks
> +	only if certain thresholds are met. For example, the `gc` task
> +	runs when the number of loose objects exceeds the number stored
> +	in the `gc.auto` config setting, or when the number of pack-files
> +	exceeds the `gc.autoPackLimit` config setting.

Hm, today I learned.  I had thought that --auto doesn't only affect
thresholds but also would affect how aggressive the gc is when
triggered, but the git-gc(1) manpage agrees with what's said above.

Is there a way we can share information between the two to avoid one
falling out of date?  For example, should git-gc.txt point to this
page for the authoritative description?

[...]
> --- a/builtin/gc.c
> +++ b/builtin/gc.c
> @@ -699,3 +699,60 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
>  
>  	return 0;
>  }
> +
> +static const char * const builtin_maintenance_usage[] = {
> +	N_("git maintenance run [<options>]"),

not about this patch: I wish we could automatically generate a usage
string in this simple kind of case, to decrease the burden on
translators.

[...]
> +static int maintenance_task_gc(struct maintenance_opts *opts)
> +{
> +	struct child_process child = CHILD_PROCESS_INIT;
> +
> +	child.git_cmd = 1;
> +	strvec_push(&child.args, "gc");
> +
> +	if (opts->auto_flag)
> +		strvec_push(&child.args, "--auto");
> +
> +	close_object_store(the_repository->objects);

Interesting --- what does this function call do?

> +	return run_command(&child);
> +}
> +
> +static int maintenance_run(struct maintenance_opts *opts)
> +{
> +	return maintenance_task_gc(opts);
> +}
> +
> +int cmd_maintenance(int argc, const char **argv, const char *prefix)
> +{
> +	static struct maintenance_opts opts;
> +	static struct option builtin_maintenance_options[] = {
> +		OPT_BOOL(0, "auto", &opts.auto_flag,
> +			 N_("run tasks based on the state of the repository")),
> +		OPT_END()
> +	};

optional: these could be local instead of static

> +
> +	memset(&opts, 0, sizeof(opts));

not needed if it's static.  If it's not static, could use `opts = {0}`
where it's declared to do the same thing in a simpler way.

> +
> +	if (argc == 2 && !strcmp(argv[1], "-h"))
> +		usage_with_options(builtin_maintenance_usage,
> +				   builtin_maintenance_options);
> +
> +	argc = parse_options(argc, argv, prefix,
> +			     builtin_maintenance_options,
> +			     builtin_maintenance_usage,
> +			     PARSE_OPT_KEEP_UNKNOWN);
> +
> +	if (argc == 1) {
> +		if (!strcmp(argv[0], "run"))
> +			return maintenance_run(&opts);
> +	}
> +
> +	usage_with_options(builtin_maintenance_usage,
> +			   builtin_maintenance_options);

optional: could reverse this test to get the uninteresting case out of
the way first:

	if (argc != 1)
		usage ...

	...

That would also allow making the usage string when argv[0] is not
"run" more specific.

[...]
> --- /dev/null
> +++ b/t/t7900-maintenance.sh
> @@ -0,0 +1,19 @@
> +#!/bin/sh
> +
> +test_description='git maintenance builtin'
> +
> +. ./test-lib.sh
> +
> +test_expect_success 'help text' '
> +	test_expect_code 129 git maintenance -h 2>err &&
> +	test_i18ngrep "usage: git maintenance run" err
> +'
> +
> +test_expect_success 'run [--auto]' '
> +	GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" git maintenance run &&
> +	GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" git maintenance run --auto &&
> +	test_subcommand git gc <run-no-auto.txt &&
> +	test_subcommand git gc --auto <run-auto.txt

Nice.

[...]
> --- a/t/test-lib-functions.sh
> +++ b/t/test-lib-functions.sh
> @@ -1561,3 +1561,36 @@ test_path_is_hidden () {
>  	case "$("$SYSTEMROOT"/system32/attrib "$1")" in *H*?:*) return 0;; esac
>  	return 1
>  }
> +
> +# Check that the given command was invoked as part of the
> +# trace2-format trace on stdin.
> +#
> +#	test_subcommand [!] <command> <args>... < <trace>
> +#
> +# For example, to look for an invocation of "git upload-pack
> +# /path/to/repo"
> +#
> +#	GIT_TRACE2_EVENT=event.log git fetch ... &&
> +#	test_subcommand git upload-pack "$PATH" <event.log
> +#
> +# If the first parameter passed is !, this instead checks that
> +# the given command was not called.
> +#
> +test_subcommand () {
> +	local negate=
> +	if test "$1" = "!"
> +	then
> +		negate=t
> +		shift
> +	fi
> +
> +	local expr=$(printf '"%s",' "$@")
> +	expr="${expr%,}"
> +
> +	if test -n "$negate"
> +	then
> +		! grep "\[$expr\]"
> +	else
> +		grep "\[$expr\]"
> +	fi
> +}

With whatever subset of the changes described above makes sense,

Reviewed-by: Jonathan Nieder <[email-masked]>

Thanks for your patient work.