{"thread":{"id":"65074","subject":"[PATCH] gc: add git maintenance list command","startedAt":"2026-02-25T17:32:41Z","lastAt":"2026-03-13T11:59:37Z","messageCount":2,"participants":["Rémy Léone via GitGitGadget","Pablo Sabater"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"537107","messageId":"pull.2201.git.git.1772040758787.gitgitgadget@gmail.com","threadId":"65074","inReplyTo":null,"subject":"[PATCH] gc: add git maintenance list command","fromName":"Rémy Léone via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-25T17:32:38Z","receivedAt":"2026-02-25T17:32:41Z","isPatch":true,"sender":{"key":"name:Rémy Léone","avatar":null},"body":"From: =?UTF-8?q?R=C3=A9my=20L=C3=A9one?= <rleone@scaleway.com>\n\nList all repositories registered for background maintenance.\n\nThis displays the paths of all repositories that are configured in the\nmaintenance.repo config variable. By default, it reads from the global\nconfig, but you can specify a different config file with the\n--config-file option.\n\nSigned-off-by: Rémy Léone <rleone@scaleway.com>\n---\n    gc: add git maintenance list command\n    \n    List all repositories registered for background maintenance. This\n    displays the paths of all repositories that are configured in the\n    maintenance.repo config variable. By default, it reads from the global\n    config, but you can specify a different config file with the\n    --config-file option.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2201%2Fremyleone%2Fmaintenance_list-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2201/remyleone/maintenance_list-v1\nPull-Request: https://github.com/git/git/pull/2201\n\n Documentation/git-maintenance.adoc |  9 ++++-\n builtin/gc.c                       | 59 ++++++++++++++++++++++++++++++\n t/t7900-maintenance.sh             | 33 +++++++++++++++++\n 3 files changed, 100 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-maintenance.adoc b/Documentation/git-maintenance.adoc\nindex bda616f14c..17cb52ea2d 100644\n--- a/Documentation/git-maintenance.adoc\n+++ b/Documentation/git-maintenance.adoc\n@@ -11,7 +11,7 @@ SYNOPSIS\n [verse]\n 'git maintenance' run [<options>]\n 'git maintenance' start [--scheduler=<scheduler>]\n-'git maintenance' (stop|register|unregister) [<options>]\n+'git maintenance' (stop|register|unregister|list) [<options>]\n 'git maintenance' is-needed [<options>]\n \n \n@@ -85,6 +85,13 @@ The `unregister` subcommand will report an error if the current repository\n is not already registered. Use the `--force` option to return success even\n when the current repository is not registered.\n \n+list::\n+\tList all repositories registered for background maintenance. This\n+\tdisplays the paths of all repositories that are configured in the\n+\t`maintenance.repo` config variable. By default, it reads from the\n+\tglobal config, but you can specify a different config file with\n+\tthe `--config-file` option.\n+\n is-needed::\n     Check whether maintenance needs to be run without actually running it.\n     Exits with a 0 status code if maintenance needs to be run, 1 otherwise.\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 4390eee6ec..9b15e236b4 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -2239,6 +2239,64 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n \treturn 0;\n }\n \n+static const char * const builtin_maintenance_list_usage[] = {\n+\t\"git maintenance list [--config-file <path>]\",\n+\tNULL\n+};\n+\n+static int maintenance_list(int argc, const char **argv, const char *prefix,\n+\t\t    struct repository *repo UNUSED)\n+{\n+\tchar *config_file = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"config-file\", &config_file, N_(\"file\"), N_(\"use given config file\")),\n+\t\tOPT_END(),\n+\t};\n+\tconst char *key = \"maintenance.repo\";\n+\tconst struct string_list *list;\n+\tstruct config_set cs = { { 0 } };\n+\tchar *global_config_file = NULL;\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     builtin_maintenance_list_usage, 0);\n+\tif (argc)\n+\t\tusage_with_options(builtin_maintenance_list_usage,\n+\t\t\t\t   options);\n+\n+\tif (config_file) {\n+\t\tgit_configset_init(&cs);\n+\t\tgit_configset_add_file(&cs, config_file);\n+\t\tif (git_configset_get_string_multi(&cs, key, &list)) {\n+\t\t\t/* No repositories registered in custom config */\n+\t\t\tgit_configset_clear(&cs);\n+\t\t\treturn 0;\n+\t\t}\n+\t} else {\n+\t\tglobal_config_file = git_global_config();\n+\t\tif (!global_config_file)\n+\t\t\tdie(_(\"$HOME not set\"));\n+\t\tgit_configset_init(&cs);\n+\t\tgit_configset_add_file(&cs, global_config_file);\n+\t\tif (git_configset_get_string_multi(&cs, key, &list)) {\n+\t\t\t/* No repositories registered in global config */\n+\t\t\tfree(global_config_file);\n+\t\t\tgit_configset_clear(&cs);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\t{\n+\t\tstruct string_list_item *item;\n+\t\tfor_each_string_list_item(item, list) {\n+\t\t\tprintf(\"%s\\n\", item->string);\n+\t\t}\n+\t}\n+\n+\tfree(global_config_file);\n+\tgit_configset_clear(&cs);\n+\treturn 0;\n+}\n+\n static const char *get_frequency(enum schedule_priority schedule)\n {\n \tswitch (schedule) {\n@@ -3535,6 +3593,7 @@ int cmd_maintenance(int argc,\n \t\tOPT_SUBCOMMAND(\"stop\", &fn, maintenance_stop),\n \t\tOPT_SUBCOMMAND(\"register\", &fn, maintenance_register),\n \t\tOPT_SUBCOMMAND(\"unregister\", &fn, maintenance_unregister),\n+\t\tOPT_SUBCOMMAND(\"list\", &fn, maintenance_list),\n \t\tOPT_SUBCOMMAND(\"is-needed\", &fn, maintenance_is_needed),\n \t\tOPT_END(),\n \t};\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 7cc0ce57f8..9dc16e154e 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -1482,4 +1482,37 @@ test_expect_success 'maintenance aborts with existing lock file' '\n \ttest_grep \"Another scheduled git-maintenance(1) process seems to be running\" err\n '\n \n+test_expect_success 'maintenance list shows registered repositories' '\n+\ttest_when_finished \"rm -rf repo1 repo2\" &&\n+\ttest_when_finished git config --global --unset-all maintenance.repo &&\n+\tgit init repo1 &&\n+\tgit init repo2 &&\n+\t(\n+\t\tcd repo1 &&\n+\t\tgit maintenance register &&\n+\t\tcd ../repo2 &&\n+\t\tgit maintenance register\n+\t) &&\n+\tgit config --global --get-all maintenance.repo >expect &&\n+\tgit maintenance list >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'maintenance list with --config-file' '\n+\tCUSTOM_CONFIG=\"./custom-maintenance-config\" &&\n+\ttest_when_finished \"rm -rf repo3 repo4\" &&\n+\ttest_when_finished rm -f \"$CUSTOM_CONFIG\" &&\n+\tgit init repo3 &&\n+\tgit init repo4 &&\n+\t(\n+\t\tcd repo3 &&\n+\t\tgit maintenance register --config-file \"../$CUSTOM_CONFIG\" &&\n+\t\tcd ../repo4 &&\n+\t\tgit maintenance register --config-file \"../$CUSTOM_CONFIG\"\n+\t) &&\n+\tgit config --file=\"$CUSTOM_CONFIG\" --get-all maintenance.repo >expect &&\n+\tgit maintenance list --config-file \"$CUSTOM_CONFIG\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n\nbase-commit: 852829b3dd2fe4e7c7fc4d8badde644cf1b66c74\n-- \ngitgitgadget\n"},{"id":"538879","messageId":"20260313115932.15259-1-pabloosabaterr@gmail.com","threadId":"65074","inReplyTo":"pull.2201.git.git.1772040758787.gitgitgadget@gmail.com","subject":"Re: [PATCH] gc: add git maintenance list command","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-03-13T11:59:31Z","receivedAt":"2026-03-13T11:59:37Z","isPatch":true,"sender":{"key":"pabloosabaterr@gmail.com","avatar":"https://avatars.githubusercontent.com/u/63864637?v=4"},"body":"> char *config_file = NULL;\n\n>\tif (config_file) {\n>\t\tgit_configset_init(&cs);\n>\t\tgit_configset_add_file(&cs, config_file);\n>\t\tif (git_configset_get_string_multi(&cs, key, &list)) {\n>\t\t\t/* No repositories registered in custom config */\n>\t\t\tgit_configset_clear(&cs);\n>\t\t\treturn 0;\n>\t\t}\n>\t} else {\n>\t\tglobal_config_file = git_global_config();\n>\t\tif (!global_config_file)\n>\t\t\tdie(_(\"$HOME not set\"));\n>\t\tgit_configset_init(&cs);\n>\t\tgit_configset_add_file(&cs, global_config_file);\n>\t\tif (git_configset_get_string_multi(&cs, key, &list)) {\n>\t\t\t/* No repositories registered in global config */\n>\t\t\tfree(global_config_file);\n>\t\t\tgit_configset_clear(&cs);\n>\t\t\treturn 0;\n>\t\t}\n>\t}\n\nHere the branches look too similar, after the third line at the else it becomes\nexactly the same. If you notice, global_config_file is initialized as NULL\nthen in case of !config_file, its value comes from git_global_config().\nAnyways, there's no need to separate the free logic because both NULL and\ngit_global_config() can be freed.\n\nyou can avoid this by extracting the diferent logic from the else\n\n  if (!config_file) {\n      config_file = git_global_config();\n      if (!config_file)\n          die(_(\"$HOME not set\"));\n      global_config_file = config_file;\n  }\n\nand then the rest of the code is common for both cases\n\n  git_configset_init(&cs);\n  git_configset_add_file(&cs, config_file);\n  if (git_configset_get_string_multi(&cs, key, &list)) {\n      free(global_config_file);\n      git_configset_clear(&cs);\n      return 0;\n  }\n\n"}]}