* [PATCH] maintenance: make unregister idempotent @ 2022-09-20 1:02 Derrick Stolee via GitGitGadget 2022-09-21 17:19 ` Junio C Hamano 2022-09-22 13:37 ` [PATCH v2 0/2] scalar: " Derrick Stolee via GitGitGadget 0 siblings, 2 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-20 1:02 UTC (permalink / raw) To: git; +Cc: gitster, vdye, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The 'git maintenance unregister' subcommand has a step that removes the current repository from the multi-valued maitenance.repo config key. This fails if the repository is not listed in that key. This makes running 'git maintenance unregister' twice result in a failure in the second instance. Make this task idempotent, since the end result is the same in both cases: maintenance will no longer run on this repository. Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- maintenance: make unregister idempotent I noticed this while we were updating the microsoft/git fork to include v2.38.0-rc0. I don't think 'git maintenance unregister' was idempotent before, but instead some change in 'scalar unregister' led to it relying on the return code of 'git maintenance unregister'. Our functional tests expect 'scalar unregister' to be idempotent, and I think that's a good pattern for 'git maintenance unregister', so I'm fixing it at that layer. Despite finding this during the 2.38.0-rc0 integration, this isn't critical to the release. Perhaps an argument could be made that "failure means it wasn't registered before", but I think that isn't terribly helpful. Our functional tests are running the unregister subcommand to disable maintenance in order to run tests on the object store (such as running maintenance commands in the foreground and checking the object store afterwards). This is a form of automation using 'unregister' as a check that maintenance will not run at the same time, and it doesn't care if maintenance was already disabled. I can imagine other scripting scenarios wanting that kind of guarantee. Thanks, -Stolee Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1358%2Fderrickstolee%2Fmaintenance-unregister-v1 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1358/derrickstolee/maintenance-unregister-v1 Pull-Request: https://github.com/gitgitgadget/git/pull/1358 builtin/gc.c | 24 +++++++++++++++++++----- t/t7900-maintenance.sh | 5 ++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/builtin/gc.c b/builtin/gc.c index 0accc024067..787e9c702b2 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1528,9 +1528,13 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi struct option options[] = { OPT_END(), }; - int rc; + const char *key = "maintenance.repo"; + int rc = 0; struct child_process config_unset = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); + int found = 0; + struct string_list_item *item; + const struct string_list *list = git_config_get_value_multi(key); argc = parse_options(argc, argv, prefix, options, builtin_maintenance_unregister_usage, 0); @@ -1538,11 +1542,21 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi usage_with_options(builtin_maintenance_unregister_usage, options); - config_unset.git_cmd = 1; - strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", "maintenance.repo", maintpath, NULL); + for_each_string_list_item(item, list) { + if (!strcmp(maintpath, item->string)) { + found = 1; + break; + } + } + + if (found) { + config_unset.git_cmd = 1; + strvec_pushl(&config_unset.args, "config", "--global", "--unset", + "--fixed-value", key, maintpath, NULL); + + rc = run_command(&config_unset); + } - rc = run_command(&config_unset); free(maintpath); return rc; } diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 2724a44fe3e..3747e4a14f8 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -493,7 +493,10 @@ test_expect_success 'register and unregister' ' git maintenance unregister && git config --global --get-all maintenance.repo >actual && - test_cmp before actual + test_cmp before actual && + + # Expect unregister to be idempotent. + git maintenance unregister ' test_expect_success !MINGW 'register and unregister with regex metacharacters' ' base-commit: d3fa443f97e3a8d75b51341e2d5bac380b7422df -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH] maintenance: make unregister idempotent 2022-09-20 1:02 [PATCH] maintenance: make unregister idempotent Derrick Stolee via GitGitGadget @ 2022-09-21 17:19 ` Junio C Hamano 2022-09-22 12:37 ` Derrick Stolee 2022-09-22 13:37 ` [PATCH v2 0/2] scalar: " Derrick Stolee via GitGitGadget 1 sibling, 1 reply; 34+ messages in thread From: Junio C Hamano @ 2022-09-21 17:19 UTC (permalink / raw) To: Derrick Stolee via GitGitGadget; +Cc: git, vdye, Derrick Stolee "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > From: Derrick Stolee <derrickstolee@github.com> > > The 'git maintenance unregister' subcommand has a step that removes the > current repository from the multi-valued maitenance.repo config key. > This fails if the repository is not listed in that key. This makes > running 'git maintenance unregister' twice result in a failure in the > second instance. > > Make this task idempotent, since the end result is the same in both > cases: maintenance will no longer run on this repository. I am not sure if this is a good idea. What is the ultimate reason why we want to allow running it blindly without knowing if it is necessary? Is it because there is no easy way to tell if unregister is needed in the first place? If this were "unregister X" that takes a parameter that tells what to unregister, I would be vehemently opposed to the idea, because I'd expect people make typos and want to be reminded of them when they happen, but ... > git maintenance unregister && > git config --global --get-all maintenance.repo >actual && > - test_cmp before actual > + test_cmp before actual && > + > + # Expect unregister to be idempotent. > + git maintenance unregister > ' ... given that the user does not have any control over what to unregister from the command line (it is decided implicitly by where the user is), I am halfway OK with it. A user with two repositories may still be upset after running "unregister" in the one they did not mean to, and not getting told about the mistake, though, e.g. $ ls *.git a.git b.git $ cd a.git $ git maintenance unregister $ cd b.git ... this would give you an error message ... $ git maintenance unregister ... this is silent with the change ... Surely, the second "cd" should be to ../b.git instead of b.git and surely the user should have noticed that "cd" gave them an error, but the unregister that complains would be an extra chance for them to notice the mistake when they wanted to unregister the maintenance tasks from all their repositories. I wonder if something like the "--force" option, i.e. $ >cruft $ rm curft; echo $? rm: cannot remove 'curft': No such file or directory 1 $ rm cruft; echo $? 0 $ rm cruft; echo $? rm: cannot remove 'cruft': No such file or directory 1 $ rm -f cruft; echo $? 0 which gives us both typo detection while supporting blind removal, is a usable workaround? I dunno. But as I said, I am halfway OK with the change. I just think it is a bit unfriendly to the users. Thanks. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH] maintenance: make unregister idempotent 2022-09-21 17:19 ` Junio C Hamano @ 2022-09-22 12:37 ` Derrick Stolee 2022-09-22 19:31 ` Junio C Hamano 0 siblings, 1 reply; 34+ messages in thread From: Derrick Stolee @ 2022-09-22 12:37 UTC (permalink / raw) To: Junio C Hamano, Derrick Stolee via GitGitGadget; +Cc: git, vdye On 9/21/2022 1:19 PM, Junio C Hamano wrote: > "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > >> From: Derrick Stolee <derrickstolee@github.com> >> >> The 'git maintenance unregister' subcommand has a step that removes the >> current repository from the multi-valued maitenance.repo config key. >> This fails if the repository is not listed in that key. This makes >> running 'git maintenance unregister' twice result in a failure in the >> second instance. >> >> Make this task idempotent, since the end result is the same in both >> cases: maintenance will no longer run on this repository. > > I am not sure if this is a good idea. What is the ultimate reason > why we want to allow running it blindly without knowing if it is > necessary? Is it because there is no easy way to tell if unregister > is needed in the first place? We want to leave the internal details of what it means to be registered as hidden to the user. They could look for the repo in the global config, but that seems like a hassle when they just want to make sure they are not currently registered. >> + # Expect unregister to be idempotent. >> + git maintenance unregister >> ' > > ... given that the user does not have any control over what to > unregister from the command line (it is decided implicitly by where > the user is), I am halfway OK with it. > > A user with two repositories may still be upset after running > "unregister" in the one they did not mean to, and not getting told > about the mistake, though, e.g. ... > I wonder if something like the "--force" option, I like the --force option. I'll add it in v2 and then update Scalar to use that option. Thanks, -Stolee ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH] maintenance: make unregister idempotent 2022-09-22 12:37 ` Derrick Stolee @ 2022-09-22 19:31 ` Junio C Hamano 2022-09-22 19:46 ` Derrick Stolee 0 siblings, 1 reply; 34+ messages in thread From: Junio C Hamano @ 2022-09-22 19:31 UTC (permalink / raw) To: Derrick Stolee; +Cc: Derrick Stolee via GitGitGadget, git, vdye Derrick Stolee <derrickstolee@github.com> writes: >> I am not sure if this is a good idea. What is the ultimate reason >> why we want to allow running it blindly without knowing if it is >> necessary? Is it because there is no easy way to tell if unregister >> is needed in the first place? > > We want to leave the internal details of what it means to be > registered as hidden to the user. They could look for the repo in > the global config, but that seems like a hassle when they just > want to make sure they are not currently registered. OK, so there is no published officially sanctioned way to ask "is this repository under maintenance's control and cron jobs run in it?" or "give me the list of such repositories". Then I can see why you want to allow users to blindly run "unregister", with or without "--force". But doesn't it point at a more fundamental problem? Is there a reason why we want to hide the list of repositories (enlistments?) from the users? ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH] maintenance: make unregister idempotent 2022-09-22 19:31 ` Junio C Hamano @ 2022-09-22 19:46 ` Derrick Stolee 2022-09-22 20:44 ` Junio C Hamano 0 siblings, 1 reply; 34+ messages in thread From: Derrick Stolee @ 2022-09-22 19:46 UTC (permalink / raw) To: Junio C Hamano; +Cc: Derrick Stolee via GitGitGadget, git, vdye On 9/22/2022 3:31 PM, Junio C Hamano wrote: > Derrick Stolee <derrickstolee@github.com> writes: > >>> I am not sure if this is a good idea. What is the ultimate reason >>> why we want to allow running it blindly without knowing if it is >>> necessary? Is it because there is no easy way to tell if unregister >>> is needed in the first place? >> >> We want to leave the internal details of what it means to be >> registered as hidden to the user. They could look for the repo in >> the global config, but that seems like a hassle when they just >> want to make sure they are not currently registered. > > OK, so there is no published officially sanctioned way to ask "is > this repository under maintenance's control and cron jobs run in > it?" or "give me the list of such repositories". > > Then I can see why you want to allow users to blindly run > "unregister", with or without "--force". > > But doesn't it point at a more fundamental problem? > > Is there a reason why we want to hide the list of repositories > (enlistments?) from the users? I don't think we want to hide it, but we've never needed to present the list in a canonical way before. It's been sufficient to let users run 'git config --global --get-all maintenance.repo', assuming they know that config key is the important one. Adding a 'git maintenance list-registered' or something would solve that problem, but I'm not sure it is a super high priority. Thanks, -Stolee ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH] maintenance: make unregister idempotent 2022-09-22 19:46 ` Derrick Stolee @ 2022-09-22 20:44 ` Junio C Hamano 0 siblings, 0 replies; 34+ messages in thread From: Junio C Hamano @ 2022-09-22 20:44 UTC (permalink / raw) To: Derrick Stolee; +Cc: Derrick Stolee via GitGitGadget, git, vdye Derrick Stolee <derrickstolee@github.com> writes: > Adding a 'git maintenance list-registered' or something would solve > that problem, but I'm not sure it is a super high priority. Is that "git maintenance" or "scalar" that list-registered would hang below? In any case, I tend to think the word "idempotent" is used as a rough synonym to "sloppy", but unregistering from the automatic maintenance (but still known as part of enlistment?) would probably be a rare event that would not be a huge deal if the user failed to do so without getting reminded, so I would not oppose to the step [2/2] of the updated series. Thanks, queued. ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v2 0/2] scalar: make unregister idempotent 2022-09-20 1:02 [PATCH] maintenance: make unregister idempotent Derrick Stolee via GitGitGadget 2022-09-21 17:19 ` Junio C Hamano @ 2022-09-22 13:37 ` Derrick Stolee via GitGitGadget 2022-09-22 13:37 ` [PATCH v2 1/2] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget ` (2 more replies) 1 sibling, 3 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-22 13:37 UTC (permalink / raw) To: git; +Cc: gitster, vdye, Derrick Stolee I noticed this while we were updating the microsoft/git fork to include v2.38.0-rc0. I don't think 'git maintenance unregister' was idempotent before, but instead some change in 'scalar unregister' led to it relying on the return code of 'git maintenance unregister'. Our functional tests expect 'scalar unregister' to be idempotent, and I think that's a good pattern for 'git maintenance unregister', so I'm fixing it at that layer. Despite finding this during the 2.38.0-rc0 integration, this isn't critical to the release. Perhaps an argument could be made that "failure means it wasn't registered before", but I think that isn't terribly helpful. Our functional tests are running the unregister subcommand to disable maintenance in order to run tests on the object store (such as running maintenance commands in the foreground and checking the object store afterwards). This is a form of automation using 'unregister' as a check that maintenance will not run at the same time, and it doesn't care if maintenance was already disabled. I can imagine other scripting scenarios wanting that kind of guarantee. Updates in v2 ============= * This is now a two-patch series. * I rebased onto v2.38.0-rc1 for two reasons: Scalar is now merged, and the usage for 'git maintenance unregister' removed its translation markers. * Instead of making git maintenance unregister idempotent, add a --force option for those who do not want to require that the repository is already registered. * Make scalar unregister idempotent, with reasons argued in patch 2. Thanks, -Stolee Derrick Stolee (2): maintenance: add 'unregister --force' scalar: make 'unregister' idempotent Documentation/git-maintenance.txt | 6 +++++- builtin/gc.c | 31 +++++++++++++++++++++++++------ scalar.c | 5 ++++- t/t7900-maintenance.sh | 6 +++++- t/t9210-scalar.sh | 5 ++++- 5 files changed, 43 insertions(+), 10 deletions(-) base-commit: 1b3d6e17fe83eb6f79ffbac2f2c61bbf1eaef5f8 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1358%2Fderrickstolee%2Fmaintenance-unregister-v2 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1358/derrickstolee/maintenance-unregister-v2 Pull-Request: https://github.com/gitgitgadget/git/pull/1358 Range-diff vs v1: 1: f028208a3ab ! 1: 69c74f52eef maintenance: make unregister idempotent @@ Metadata Author: Derrick Stolee <derrickstolee@github.com> ## Commit message ## - maintenance: make unregister idempotent + maintenance: add 'unregister --force' The 'git maintenance unregister' subcommand has a step that removes the current repository from the multi-valued maitenance.repo config key. @@ Commit message running 'git maintenance unregister' twice result in a failure in the second instance. - Make this task idempotent, since the end result is the same in both - cases: maintenance will no longer run on this repository. + This failure exit code is helpful, but its message is not. Add a new + die() message that explicitly calls out the failure due to the + repository not being registered. + + In some cases, users may want to run 'git maintenance unregister' just + to make sure that background jobs will not start on this repository, but + they do not want to check to see if it is registered first. Add a new + '--force' option that will siltently succeed if the repository is not + already registered. Signed-off-by: Derrick Stolee <derrickstolee@github.com> + ## Documentation/git-maintenance.txt ## +@@ Documentation/git-maintenance.txt: SYNOPSIS + [verse] + 'git maintenance' run [<options>] + 'git maintenance' start [--scheduler=<scheduler>] +-'git maintenance' (stop|register|unregister) ++'git maintenance' (stop|register|unregister) [<options>] + + + DESCRIPTION +@@ Documentation/git-maintenance.txt: unregister:: + Remove the current repository from background maintenance. This + only removes the repository from the configured list. It does not + stop the background maintenance processes from running. +++ ++The `unregister` subcommand will report an error if the current repository ++is not already registered. Use the `--force` option to return success even ++when the current repository is not registered. + + TASKS + ----- + ## builtin/gc.c ## -@@ builtin/gc.c: static int maintenance_unregister(int argc, const char **argv, const char *prefi +@@ builtin/gc.c: done: + } + + static char const * const builtin_maintenance_unregister_usage[] = { +- "git maintenance unregister", ++ "git maintenance unregister [--force]", + NULL + }; + + static int maintenance_unregister(int argc, const char **argv, const char *prefix) + { ++ int force = 0; struct option options[] = { ++ OPT_BOOL(0, "force", &force, ++ N_("return success even if repository was not registered")), OPT_END(), }; - int rc; @@ builtin/gc.c: static int maintenance_unregister(int argc, const char **argv, con + "--fixed-value", key, maintpath, NULL); + + rc = run_command(&config_unset); ++ } else if (!force) { ++ die(_("repository '%s' is not registered"), maintpath); + } - rc = run_command(&config_unset); @@ t/t7900-maintenance.sh: test_expect_success 'register and unregister' ' - test_cmp before actual + test_cmp before actual && + -+ # Expect unregister to be idempotent. -+ git maintenance unregister ++ test_must_fail git maintenance unregister 2>err && ++ grep "is not registered" err && ++ git maintenance unregister --force ' test_expect_success !MINGW 'register and unregister with regex metacharacters' ' -: ----------- > 2: f5d8d6e4901 scalar: make 'unregister' idempotent -- gitgitgadget ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v2 1/2] maintenance: add 'unregister --force' 2022-09-22 13:37 ` [PATCH v2 0/2] scalar: " Derrick Stolee via GitGitGadget @ 2022-09-22 13:37 ` Derrick Stolee via GitGitGadget 2022-09-23 13:08 ` SZEDER Gábor 2022-09-22 13:37 ` [PATCH v2 2/2] scalar: make 'unregister' idempotent Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 0/3] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 2 siblings, 1 reply; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-22 13:37 UTC (permalink / raw) To: git; +Cc: gitster, vdye, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The 'git maintenance unregister' subcommand has a step that removes the current repository from the multi-valued maitenance.repo config key. This fails if the repository is not listed in that key. This makes running 'git maintenance unregister' twice result in a failure in the second instance. This failure exit code is helpful, but its message is not. Add a new die() message that explicitly calls out the failure due to the repository not being registered. In some cases, users may want to run 'git maintenance unregister' just to make sure that background jobs will not start on this repository, but they do not want to check to see if it is registered first. Add a new '--force' option that will siltently succeed if the repository is not already registered. Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- Documentation/git-maintenance.txt | 6 +++++- builtin/gc.c | 31 +++++++++++++++++++++++++------ t/t7900-maintenance.sh | 6 +++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt index 9c630efe19c..bb888690e4d 100644 --- a/Documentation/git-maintenance.txt +++ b/Documentation/git-maintenance.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git maintenance' run [<options>] 'git maintenance' start [--scheduler=<scheduler>] -'git maintenance' (stop|register|unregister) +'git maintenance' (stop|register|unregister) [<options>] DESCRIPTION @@ -79,6 +79,10 @@ unregister:: Remove the current repository from background maintenance. This only removes the repository from the configured list. It does not stop the background maintenance processes from running. ++ +The `unregister` subcommand will report an error if the current repository +is not already registered. Use the `--force` option to return success even +when the current repository is not registered. TASKS ----- diff --git a/builtin/gc.c b/builtin/gc.c index 2753bd15a5e..3189b4ba2b1 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1519,18 +1519,25 @@ done: } static char const * const builtin_maintenance_unregister_usage[] = { - "git maintenance unregister", + "git maintenance unregister [--force]", NULL }; static int maintenance_unregister(int argc, const char **argv, const char *prefix) { + int force = 0; struct option options[] = { + OPT_BOOL(0, "force", &force, + N_("return success even if repository was not registered")), OPT_END(), }; - int rc; + const char *key = "maintenance.repo"; + int rc = 0; struct child_process config_unset = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); + int found = 0; + struct string_list_item *item; + const struct string_list *list = git_config_get_value_multi(key); argc = parse_options(argc, argv, prefix, options, builtin_maintenance_unregister_usage, 0); @@ -1538,11 +1545,23 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi usage_with_options(builtin_maintenance_unregister_usage, options); - config_unset.git_cmd = 1; - strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", "maintenance.repo", maintpath, NULL); + for_each_string_list_item(item, list) { + if (!strcmp(maintpath, item->string)) { + found = 1; + break; + } + } + + if (found) { + config_unset.git_cmd = 1; + strvec_pushl(&config_unset.args, "config", "--global", "--unset", + "--fixed-value", key, maintpath, NULL); + + rc = run_command(&config_unset); + } else if (!force) { + die(_("repository '%s' is not registered"), maintpath); + } - rc = run_command(&config_unset); free(maintpath); return rc; } diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 2724a44fe3e..cfe3236567a 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -493,7 +493,11 @@ test_expect_success 'register and unregister' ' git maintenance unregister && git config --global --get-all maintenance.repo >actual && - test_cmp before actual + test_cmp before actual && + + test_must_fail git maintenance unregister 2>err && + grep "is not registered" err && + git maintenance unregister --force ' test_expect_success !MINGW 'register and unregister with regex metacharacters' ' -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v2 1/2] maintenance: add 'unregister --force' 2022-09-22 13:37 ` [PATCH v2 1/2] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget @ 2022-09-23 13:08 ` SZEDER Gábor 2022-09-26 13:32 ` Derrick Stolee 0 siblings, 1 reply; 34+ messages in thread From: SZEDER Gábor @ 2022-09-23 13:08 UTC (permalink / raw) To: Derrick Stolee via GitGitGadget; +Cc: git, gitster, vdye, Derrick Stolee On Thu, Sep 22, 2022 at 01:37:16PM +0000, Derrick Stolee via GitGitGadget wrote: > static int maintenance_unregister(int argc, const char **argv, const char *prefix) > { > + int force = 0; > struct option options[] = { > + OPT_BOOL(0, "force", &force, > + N_("return success even if repository was not registered")), This could be shortened a bit by using OPT__FORCE() instead of OPT_BOOL(). OTOH, please make it a bit longer, and declare the option with the PARSE_OPT_NOCOMPLETE flag to hide it from completion: '--force' is usually used to enable something potentially dangerous, and therefore should be used with care, so our completion script in general doesn't offer it, giving the users more opportunity to think things through while typing it out. Though, arguably in this particular case it seems there is not much danger to be afraid of. > @@ -1538,11 +1545,23 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi > usage_with_options(builtin_maintenance_unregister_usage, > options); > > - config_unset.git_cmd = 1; > - strvec_pushl(&config_unset.args, "config", "--global", "--unset", > - "--fixed-value", "maintenance.repo", maintpath, NULL); > + for_each_string_list_item(item, list) { > + if (!strcmp(maintpath, item->string)) { > + found = 1; > + break; > + } > + } > + > + if (found) { > + config_unset.git_cmd = 1; > + strvec_pushl(&config_unset.args, "config", "--global", "--unset", > + "--fixed-value", key, maintpath, NULL); > + > + rc = run_command(&config_unset); It seems a bit heavy-handed to fork() an extra git process just to unset a config variable. Wouldn't a properly parametrized git_config_set_multivar_in_file_gently() call be sufficient? Though TBH I don't offhand know what those parameters should be, in particular whether there is a convenient way to find out the path of the global config file in order to pass it to this function. (Not an issue with this patch, as the context shows that this has been done with the extra process before; it just caught my eye.) ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 1/2] maintenance: add 'unregister --force' 2022-09-23 13:08 ` SZEDER Gábor @ 2022-09-26 13:32 ` Derrick Stolee 2022-09-26 15:39 ` Ævar Arnfjörð Bjarmason 0 siblings, 1 reply; 34+ messages in thread From: Derrick Stolee @ 2022-09-26 13:32 UTC (permalink / raw) To: SZEDER Gábor, Derrick Stolee via GitGitGadget; +Cc: git, gitster, vdye On 9/23/2022 9:08 AM, SZEDER Gábor wrote: > On Thu, Sep 22, 2022 at 01:37:16PM +0000, Derrick Stolee via GitGitGadget wrote: >> static int maintenance_unregister(int argc, const char **argv, const char *prefix) >> { >> + int force = 0; >> struct option options[] = { >> + OPT_BOOL(0, "force", &force, >> + N_("return success even if repository was not registered")), > > This could be shortened a bit by using OPT__FORCE() instead of > OPT_BOOL(). OTOH, please make it a bit longer, and declare the option > with the PARSE_OPT_NOCOMPLETE flag to hide it from completion: Looks like I can do both like this: OPT__FORCE(&force, N_("return success even if repository was not registered"), PARSE_OPT_NOCOMPLETE), >> @@ -1538,11 +1545,23 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi >> usage_with_options(builtin_maintenance_unregister_usage, >> options); >> >> - config_unset.git_cmd = 1; >> - strvec_pushl(&config_unset.args, "config", "--global", "--unset", >> - "--fixed-value", "maintenance.repo", maintpath, NULL); >> + for_each_string_list_item(item, list) { >> + if (!strcmp(maintpath, item->string)) { >> + found = 1; >> + break; >> + } >> + } >> + >> + if (found) { >> + config_unset.git_cmd = 1; >> + strvec_pushl(&config_unset.args, "config", "--global", "--unset", >> + "--fixed-value", key, maintpath, NULL); >> + >> + rc = run_command(&config_unset); > > It seems a bit heavy-handed to fork() an extra git process just to > unset a config variable. Wouldn't a properly parametrized > git_config_set_multivar_in_file_gently() call be sufficient? Though > TBH I don't offhand know what those parameters should be, in > particular whether there is a convenient way to find out the path of > the global config file in order to pass it to this function. > > (Not an issue with this patch, as the context shows that this has been > done with the extra process before; it just caught my eye.) Thanks. I'll look into it. -Stolee ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 1/2] maintenance: add 'unregister --force' 2022-09-26 13:32 ` Derrick Stolee @ 2022-09-26 15:39 ` Ævar Arnfjörð Bjarmason 2022-09-26 17:25 ` Derrick Stolee 0 siblings, 1 reply; 34+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-09-26 15:39 UTC (permalink / raw) To: Derrick Stolee Cc: SZEDER Gábor, Derrick Stolee via GitGitGadget, git, gitster, vdye On Mon, Sep 26 2022, Derrick Stolee wrote: > On 9/23/2022 9:08 AM, SZEDER Gábor wrote: >> On Thu, Sep 22, 2022 at 01:37:16PM +0000, Derrick Stolee via GitGitGadget wrote: >>> static int maintenance_unregister(int argc, const char **argv, const char *prefix) >>> { >>> + int force = 0; >>> struct option options[] = { >>> + OPT_BOOL(0, "force", &force, >>> + N_("return success even if repository was not registered")), >> >> This could be shortened a bit by using OPT__FORCE() instead of >> OPT_BOOL(). OTOH, please make it a bit longer, and declare the option >> with the PARSE_OPT_NOCOMPLETE flag to hide it from completion: > > Looks like I can do both like this: > > OPT__FORCE(&force, > N_("return success even if repository was not registered"), > PARSE_OPT_NOCOMPLETE), I don't think PARSE_OPT_NOCOMPLETE is appropriate here. Yes we use it for most of --force, but in some non-destructive cases (e.g. "add") we don't. This seems to be such a case, we'll destroy no data or do anything irrecoverable. It's really just a --do-not-be-so-anal-about-your-exit-code option. I'm guessing that you wanted to be able to error check this more strictly in some cases. For "git remote" I post-hoc filled in this use-case by exiting with a code of 2 on missing remotes on e.g. "git remote remove", see: 9144ba4cf52 (remote: add meaningful exit code on missing/existing, 2020-10-27). In this case we would return exit code 5 for this in most case before, as we wouldn't be able to find the key, wouldn't we? I.e. the return value from "git config". Seems so: $ GIT_TRACE=1 git maintenance unregister; echo $? 17:48:59.984529 exec-cmd.c:90 trace: resolved executable path from procfs: /home/avar/local/bin/git 17:48:59.984566 exec-cmd.c:237 trace: resolved executable dir: /home/avar/local/bin 17:48:59.986795 git.c:509 trace: built-in: git maintenance unregister 17:48:59.986817 run-command.c:654 trace: run_command: git config --global --unset --fixed-value maintenance.repo /home/avar/g/git 17:48:59.987532 exec-cmd.c:90 trace: resolved executable path from procfs: /home/avar/local/bin/git 17:48:59.987561 exec-cmd.c:237 trace: resolved executable dir: /home/avar/local/bin 17:48:59.988733 git.c:509 trace: built-in: git config --global --unset --fixed-value maintenance.repo /home/avar/g/git 5 Maybe we want to just define an exit code here too? I think doing so is a better interface, the user can then pipe the STDERR to /dev/null themselves (or equivalent). Aside from anything else, I think this would be much clearer if it were split up so that: * We first test what we do now without --force, which is clearly untested and undocumented (you are adding tests for it here while-at-it) * This commit, which adds a --force. Also: > @@ -1538,11 +1545,23 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi > usage_with_options(builtin_maintenance_unregister_usage, > options); > > - config_unset.git_cmd = 1; > - strvec_pushl(&config_unset.args, "config", "--global", "--unset", > - "--fixed-value", "maintenance.repo", maintpath, NULL); > + for_each_string_list_item(item, list) { > + if (!strcmp(maintpath, item->string)) { > + found = 1; > + break; > + } > + } This code now has a race condition it didn't before. Before we just did a "git config --unset" which would have locked the config file, so if we didn't have a key we'd return 5. > + if (found) { But here we looked for the key *earlier*, so in that window we could have raced and had the key again, so .... > + config_unset.git_cmd = 1; > + strvec_pushl(&config_unset.args, "config", "--global", "--unset", > + "--fixed-value", key, maintpath, NULL); > + > + rc = run_command(&config_unset); > + } else if (!force) { ...found would not be true, and if you you didn't have --force... > + die(_("repository '%s' is not registered"), maintpath); > + } > > - rc = run_command(&config_unset); ...this removal would cause us to still have the key in the end, no? I.e.: 1. We check if the key is there 2. Another process LOCKS config 3. Another process SETS the key 4. Another process UNLOCKS config 5. We act with the assumption that the key isn't set Maybe it's not racy, or it doesn't matter. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 1/2] maintenance: add 'unregister --force' 2022-09-26 15:39 ` Ævar Arnfjörð Bjarmason @ 2022-09-26 17:25 ` Derrick Stolee 2022-09-26 19:17 ` Junio C Hamano 0 siblings, 1 reply; 34+ messages in thread From: Derrick Stolee @ 2022-09-26 17:25 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: SZEDER Gábor, Derrick Stolee via GitGitGadget, git, gitster, vdye On 9/26/2022 11:39 AM, Ævar Arnfjörð Bjarmason wrote: > > On Mon, Sep 26 2022, Derrick Stolee wrote: > >> On 9/23/2022 9:08 AM, SZEDER Gábor wrote: >>> On Thu, Sep 22, 2022 at 01:37:16PM +0000, Derrick Stolee via GitGitGadget wrote: >>>> static int maintenance_unregister(int argc, const char **argv, const char *prefix) >>>> { >>>> + int force = 0; >>>> struct option options[] = { >>>> + OPT_BOOL(0, "force", &force, >>>> + N_("return success even if repository was not registered")), >>> >>> This could be shortened a bit by using OPT__FORCE() instead of >>> OPT_BOOL(). OTOH, please make it a bit longer, and declare the option >>> with the PARSE_OPT_NOCOMPLETE flag to hide it from completion: >> >> Looks like I can do both like this: >> >> OPT__FORCE(&force, >> N_("return success even if repository was not registered"), >> PARSE_OPT_NOCOMPLETE), > > I don't think PARSE_OPT_NOCOMPLETE is appropriate here. Yes we use it > for most of --force, but in some non-destructive cases (e.g. "add") we > don't. > > This seems to be such a case, we'll destroy no data or do anything > irrecoverable. It's really just a > --do-not-be-so-anal-about-your-exit-code option. I agree, so I wasn't completely sold on PARSE_OPT_NOCOMPLETE. I'll use your vote to not add that option. > I'm guessing that you wanted to be able to error check this more > strictly in some cases. For "git remote" I post-hoc filled in this > use-case by exiting with a code of 2 on missing remotes on e.g. "git > remote remove", see: 9144ba4cf52 (remote: add meaningful exit code on > missing/existing, 2020-10-27). Generally, I'm not terribly interested in the exit code value other than 0, 128, and <otherwise non-zero> being three categories. I definitely don't want to create a strict list of exit code modes here. > In this case we would return exit code 5 for this in most case before, > as we wouldn't be able to find the key, wouldn't we? I.e. the return > value from "git config". We definitely inherited an exit code from 'git config' here, but I should probably convert it into a die() message to make it clearer. > This code now has a race condition it didn't before. Before we just did > a "git config --unset" which would have locked the config file, so if we > didn't have a key we'd return 5. > >> + if (found) { > > But here we looked for the key *earlier*, so in that window we could > have raced and had the key again, so .... > >> + config_unset.git_cmd = 1; >> + strvec_pushl(&config_unset.args, "config", "--global", "--unset", >> + "--fixed-value", key, maintpath, NULL); >> + >> + rc = run_command(&config_unset); >> + } else if (!force) { > > ...found would not be true, and if you you didn't have --force... > >> + die(_("repository '%s' is not registered"), maintpath); >> + } >> >> - rc = run_command(&config_unset); > > ...this removal would cause us to still have the key in the end, no? I.e.: > > 1. We check if the key is there > 2. Another process LOCKS config > 3. Another process SETS the key > 4. Another process UNLOCKS config> 5. We act with the assumption that the key isn't set I think this list of events is fine, since we check the value and then do not try to modify state if it isn't there. Instead if we had this scenario: 1. We check and see that it _is_there. 2. Another process modifies config to remove the value. 3. We try to remove the value and fail. In this case, the --force option would still fail even though the end result is the same. We could ignore a "not found" case during a --force option to avoid failing due to such concurrency. > Maybe it's not racy, or it doesn't matter. Generally, I think in this case it doesn't matter, but we can be a bit more careful about handling races. Thanks, -Stolee ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v2 1/2] maintenance: add 'unregister --force' 2022-09-26 17:25 ` Derrick Stolee @ 2022-09-26 19:17 ` Junio C Hamano 0 siblings, 0 replies; 34+ messages in thread From: Junio C Hamano @ 2022-09-26 19:17 UTC (permalink / raw) To: Derrick Stolee Cc: Ævar Arnfjörð Bjarmason, SZEDER Gábor, Derrick Stolee via GitGitGadget, git, vdye Derrick Stolee <derrickstolee@github.com> writes: > On 9/26/2022 11:39 AM, Ævar Arnfjörð Bjarmason wrote: >> >> On Mon, Sep 26 2022, Derrick Stolee wrote: >> >>> On 9/23/2022 9:08 AM, SZEDER Gábor wrote: >>>> On Thu, Sep 22, 2022 at 01:37:16PM +0000, Derrick Stolee via GitGitGadget wrote: >>>>> static int maintenance_unregister(int argc, const char **argv, const char *prefix) >>>>> { >>>>> + int force = 0; >>>>> struct option options[] = { >>>>> + OPT_BOOL(0, "force", &force, >>>>> + N_("return success even if repository was not registered")), >>>> >>>> This could be shortened a bit by using OPT__FORCE() instead of >>>> OPT_BOOL(). OTOH, please make it a bit longer, and declare the option >>>> with the PARSE_OPT_NOCOMPLETE flag to hide it from completion: >>> >>> Looks like I can do both like this: >>> >>> OPT__FORCE(&force, >>> N_("return success even if repository was not registered"), >>> PARSE_OPT_NOCOMPLETE), >> >> I don't think PARSE_OPT_NOCOMPLETE is appropriate here. Yes we use it >> for most of --force, but in some non-destructive cases (e.g. "add") we >> don't. >> >> This seems to be such a case, we'll destroy no data or do anything >> irrecoverable. It's really just a >> --do-not-be-so-anal-about-your-exit-code option. > > I agree, so I wasn't completely sold on PARSE_OPT_NOCOMPLETE. I'll use > your vote to not add that option. I am perfectly OK with that. Given that "git reset --hard" is not given nocomplete option, I do not see much point in "destructive ones are not completed" guideline in practice anyway. After all, "add --force" would be destructively removing the object name recorded for the path previously. Thanks for carefully thinking the UI remifications through. ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v2 2/2] scalar: make 'unregister' idempotent 2022-09-22 13:37 ` [PATCH v2 0/2] scalar: " Derrick Stolee via GitGitGadget 2022-09-22 13:37 ` [PATCH v2 1/2] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget @ 2022-09-22 13:37 ` Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 0/3] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 2 siblings, 0 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-22 13:37 UTC (permalink / raw) To: git; +Cc: gitster, vdye, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The 'scalar unregister' command removes a repository from the list of registered Scalar repositories and removes it from the list of repositories registered for background maintenance. If the repository was not already registered for background maintenance, then the command fails, even if the repository was still registered as a Scalar repository. After using 'scalar clone' or 'scalar register', the repository would be enrolled in background maintenance since those commands run 'git maintenance start'. If the user runs 'git maintenance unregister' on that repository, then it is still in the list of repositories which get new config updates from 'scalar reconfigure'. The 'scalar unregister' command would fail since 'git maintenance unregister' would fail. Further, the add_or_remove_enlistment() method in scalar.c already has this idempotent nature built in as an expectation since it returns zero when the scalar.repo list already has the proper containment of the repository. The previous change added the 'git maintenance unregister --force' option, so use it within 'scalar unregister' to make it idempotent. Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- scalar.c | 5 ++++- t/t9210-scalar.sh | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scalar.c b/scalar.c index c5c1ce68919..6de9c0ee523 100644 --- a/scalar.c +++ b/scalar.c @@ -207,7 +207,10 @@ static int set_recommended_config(int reconfigure) static int toggle_maintenance(int enable) { - return run_git("maintenance", enable ? "start" : "unregister", NULL); + return run_git("maintenance", + enable ? "start" : "unregister", + enable ? NULL : "--force", + NULL); } static int add_or_remove_enlistment(int add) diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh index 14ca575a214..be51a8bb7a4 100755 --- a/t/t9210-scalar.sh +++ b/t/t9210-scalar.sh @@ -116,7 +116,10 @@ test_expect_success 'scalar unregister' ' test_must_fail git config --get --global --fixed-value \ maintenance.repo "$(pwd)/vanish/src" && scalar list >scalar.repos && - ! grep -F "$(pwd)/vanish/src" scalar.repos + ! grep -F "$(pwd)/vanish/src" scalar.repos && + + # scalar unregister should be idempotent + scalar unregister vanish ' test_expect_success 'set up repository to clone' ' -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* [PATCH v3 0/3] scalar: make unregister idempotent 2022-09-22 13:37 ` [PATCH v2 0/2] scalar: " Derrick Stolee via GitGitGadget 2022-09-22 13:37 ` [PATCH v2 1/2] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget 2022-09-22 13:37 ` [PATCH v2 2/2] scalar: make 'unregister' idempotent Derrick Stolee via GitGitGadget @ 2022-09-26 18:48 ` Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 1/3] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget ` (3 more replies) 2 siblings, 4 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-26 18:48 UTC (permalink / raw) To: git Cc: gitster, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee I noticed this while we were updating the microsoft/git fork to include v2.38.0-rc0. I don't think 'git maintenance unregister' was idempotent before, but instead some change in 'scalar unregister' led to it relying on the return code of 'git maintenance unregister'. Our functional tests expect 'scalar unregister' to be idempotent, and I think that's a good pattern for 'git maintenance unregister', so I'm fixing it at that layer. Despite finding this during the 2.38.0-rc0 integration, this isn't critical to the release. Perhaps an argument could be made that "failure means it wasn't registered before", but I think that isn't terribly helpful. Our functional tests are running the unregister subcommand to disable maintenance in order to run tests on the object store (such as running maintenance commands in the foreground and checking the object store afterwards). This is a form of automation using 'unregister' as a check that maintenance will not run at the same time, and it doesn't care if maintenance was already disabled. I can imagine other scripting scenarios wanting that kind of guarantee. Updates in v3 ============= * The --force option now uses OPT_FORCE and is hidden from autocomplete. * A new commit is added that removes the use of Git subprocesses in favor of git_config_set_multivar_in_file_gently(). Updates in v2 ============= * This is now a two-patch series. * I rebased onto v2.38.0-rc1 for two reasons: Scalar is now merged, and the usage for 'git maintenance unregister' removed its translation markers. * Instead of making git maintenance unregister idempotent, add a --force option for those who do not want to require that the repository is already registered. * Make scalar unregister idempotent, with reasons argued in patch 2. Thanks, -Stolee Derrick Stolee (3): maintenance: add 'unregister --force' scalar: make 'unregister' idempotent gc: replace config subprocesses with API calls Documentation/git-maintenance.txt | 6 +- builtin/gc.c | 95 +++++++++++++++++++++---------- scalar.c | 5 +- t/t7900-maintenance.sh | 6 +- t/t9210-scalar.sh | 5 +- 5 files changed, 82 insertions(+), 35 deletions(-) base-commit: 1b3d6e17fe83eb6f79ffbac2f2c61bbf1eaef5f8 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1358%2Fderrickstolee%2Fmaintenance-unregister-v3 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1358/derrickstolee/maintenance-unregister-v3 Pull-Request: https://github.com/gitgitgadget/git/pull/1358 Range-diff vs v2: 1: 69c74f52eef ! 1: 8a8bffaec89 maintenance: add 'unregister --force' @@ builtin/gc.c: done: { + int force = 0; struct option options[] = { -+ OPT_BOOL(0, "force", &force, -+ N_("return success even if repository was not registered")), ++ OPT__FORCE(&force, ++ N_("return success even if repository was not registered"), ++ PARSE_OPT_NOCOMPLETE), OPT_END(), }; - int rc; 2: f5d8d6e4901 = 2: 06d5ef3fc57 scalar: make 'unregister' idempotent -: ----------- > 3: 260d7bee36e gc: replace config subprocesses with API calls -- gitgitgadget ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v3 1/3] maintenance: add 'unregister --force' 2022-09-26 18:48 ` [PATCH v3 0/3] scalar: make unregister idempotent Derrick Stolee via GitGitGadget @ 2022-09-26 18:48 ` Derrick Stolee via GitGitGadget 2022-09-26 19:23 ` Junio C Hamano 2022-09-26 21:55 ` Junio C Hamano 2022-09-26 18:48 ` [PATCH v3 2/3] scalar: make 'unregister' idempotent Derrick Stolee via GitGitGadget ` (2 subsequent siblings) 3 siblings, 2 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-26 18:48 UTC (permalink / raw) To: git Cc: gitster, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The 'git maintenance unregister' subcommand has a step that removes the current repository from the multi-valued maitenance.repo config key. This fails if the repository is not listed in that key. This makes running 'git maintenance unregister' twice result in a failure in the second instance. This failure exit code is helpful, but its message is not. Add a new die() message that explicitly calls out the failure due to the repository not being registered. In some cases, users may want to run 'git maintenance unregister' just to make sure that background jobs will not start on this repository, but they do not want to check to see if it is registered first. Add a new '--force' option that will siltently succeed if the repository is not already registered. Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- Documentation/git-maintenance.txt | 6 +++++- builtin/gc.c | 32 +++++++++++++++++++++++++------ t/t7900-maintenance.sh | 6 +++++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt index 9c630efe19c..bb888690e4d 100644 --- a/Documentation/git-maintenance.txt +++ b/Documentation/git-maintenance.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git maintenance' run [<options>] 'git maintenance' start [--scheduler=<scheduler>] -'git maintenance' (stop|register|unregister) +'git maintenance' (stop|register|unregister) [<options>] DESCRIPTION @@ -79,6 +79,10 @@ unregister:: Remove the current repository from background maintenance. This only removes the repository from the configured list. It does not stop the background maintenance processes from running. ++ +The `unregister` subcommand will report an error if the current repository +is not already registered. Use the `--force` option to return success even +when the current repository is not registered. TASKS ----- diff --git a/builtin/gc.c b/builtin/gc.c index 2753bd15a5e..8d154586272 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1519,18 +1519,26 @@ done: } static char const * const builtin_maintenance_unregister_usage[] = { - "git maintenance unregister", + "git maintenance unregister [--force]", NULL }; static int maintenance_unregister(int argc, const char **argv, const char *prefix) { + int force = 0; struct option options[] = { + OPT__FORCE(&force, + N_("return success even if repository was not registered"), + PARSE_OPT_NOCOMPLETE), OPT_END(), }; - int rc; + const char *key = "maintenance.repo"; + int rc = 0; struct child_process config_unset = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); + int found = 0; + struct string_list_item *item; + const struct string_list *list = git_config_get_value_multi(key); argc = parse_options(argc, argv, prefix, options, builtin_maintenance_unregister_usage, 0); @@ -1538,11 +1546,23 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi usage_with_options(builtin_maintenance_unregister_usage, options); - config_unset.git_cmd = 1; - strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", "maintenance.repo", maintpath, NULL); + for_each_string_list_item(item, list) { + if (!strcmp(maintpath, item->string)) { + found = 1; + break; + } + } + + if (found) { + config_unset.git_cmd = 1; + strvec_pushl(&config_unset.args, "config", "--global", "--unset", + "--fixed-value", key, maintpath, NULL); + + rc = run_command(&config_unset); + } else if (!force) { + die(_("repository '%s' is not registered"), maintpath); + } - rc = run_command(&config_unset); free(maintpath); return rc; } diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 2724a44fe3e..cfe3236567a 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -493,7 +493,11 @@ test_expect_success 'register and unregister' ' git maintenance unregister && git config --global --get-all maintenance.repo >actual && - test_cmp before actual + test_cmp before actual && + + test_must_fail git maintenance unregister 2>err && + grep "is not registered" err && + git maintenance unregister --force ' test_expect_success !MINGW 'register and unregister with regex metacharacters' ' -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v3 1/3] maintenance: add 'unregister --force' 2022-09-26 18:48 ` [PATCH v3 1/3] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget @ 2022-09-26 19:23 ` Junio C Hamano 2022-09-26 20:49 ` Derrick Stolee 2022-09-26 21:55 ` Junio C Hamano 1 sibling, 1 reply; 34+ messages in thread From: Junio C Hamano @ 2022-09-26 19:23 UTC (permalink / raw) To: Derrick Stolee via GitGitGadget Cc: git, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > @@ -11,7 +11,7 @@ SYNOPSIS > [verse] > 'git maintenance' run [<options>] > 'git maintenance' start [--scheduler=<scheduler>] > -'git maintenance' (stop|register|unregister) > +'git maintenance' (stop|register|unregister) [<options>] An unrelated tangent, but should register complain when given in a repository that is already registered as well? Just being curious. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v3 1/3] maintenance: add 'unregister --force' 2022-09-26 19:23 ` Junio C Hamano @ 2022-09-26 20:49 ` Derrick Stolee 0 siblings, 0 replies; 34+ messages in thread From: Derrick Stolee @ 2022-09-26 20:49 UTC (permalink / raw) To: Junio C Hamano, Derrick Stolee via GitGitGadget Cc: git, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason On 9/26/2022 3:23 PM, Junio C Hamano wrote: > "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > >> @@ -11,7 +11,7 @@ SYNOPSIS >> [verse] >> 'git maintenance' run [<options>] >> 'git maintenance' start [--scheduler=<scheduler>] >> -'git maintenance' (stop|register|unregister) >> +'git maintenance' (stop|register|unregister) [<options>] > > An unrelated tangent, but should register complain when given in a > repository that is already registered as well? Just being curious. Let's leave that as a #leftoverbits and perhaps as something to consider next to something like 'git maintenance list' to list the currently-registered repositories. Thanks, -Stolee ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v3 1/3] maintenance: add 'unregister --force' 2022-09-26 18:48 ` [PATCH v3 1/3] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget 2022-09-26 19:23 ` Junio C Hamano @ 2022-09-26 21:55 ` Junio C Hamano 2022-09-27 11:38 ` Derrick Stolee 1 sibling, 1 reply; 34+ messages in thread From: Junio C Hamano @ 2022-09-26 21:55 UTC (permalink / raw) To: Derrick Stolee via GitGitGadget Cc: git, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > @@ -1538,11 +1546,23 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi > usage_with_options(builtin_maintenance_unregister_usage, > options); > > - config_unset.git_cmd = 1; > - strvec_pushl(&config_unset.args, "config", "--global", "--unset", > - "--fixed-value", "maintenance.repo", maintpath, NULL); > + for_each_string_list_item(item, list) { > + if (!strcmp(maintpath, item->string)) { > + found = 1; > + break; > + } > + } Just out of curiosity, I ran this in a fresh repository and got a segfault. An attached patch obviously fixes it, but I am wondering if a better "fix" is to teach for_each_string_list_item() that it is perfectly reasonable to see a NULL passed to it as the list, which is a mere special case that the caller has a string list with 0 items on it. Thoughts? Thanks. diff --git a/builtin/gc.c b/builtin/gc.c index 4c59235950..67f41fad4b 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1549,6 +1549,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi usage_with_options(builtin_maintenance_unregister_usage, options); + if (list) for_each_string_list_item(item, list) { if (!strcmp(maintpath, item->string)) { found = 1; -- 2.38.0-rc1-123-g02867222b8 ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v3 1/3] maintenance: add 'unregister --force' 2022-09-26 21:55 ` Junio C Hamano @ 2022-09-27 11:38 ` Derrick Stolee 2022-09-27 11:54 ` Derrick Stolee 0 siblings, 1 reply; 34+ messages in thread From: Derrick Stolee @ 2022-09-27 11:38 UTC (permalink / raw) To: Junio C Hamano, Derrick Stolee via GitGitGadget Cc: git, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason On 9/26/2022 5:55 PM, Junio C Hamano wrote: > "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > >> @@ -1538,11 +1546,23 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi >> usage_with_options(builtin_maintenance_unregister_usage, >> options); >> >> - config_unset.git_cmd = 1; >> - strvec_pushl(&config_unset.args, "config", "--global", "--unset", >> - "--fixed-value", "maintenance.repo", maintpath, NULL); >> + for_each_string_list_item(item, list) { >> + if (!strcmp(maintpath, item->string)) { >> + found = 1; >> + break; >> + } >> + } > > Just out of curiosity, I ran this in a fresh repository and got a > segfault. Yikes! Thanks for catching. I think there was another instance in the 'register' code that I caught by tests, but I appreciate you catching this one. > An attached patch obviously fixes it, but I am wondering > if a better "fix" is to teach for_each_string_list_item() that it is > perfectly reasonable to see a NULL passed to it as the list, which > is a mere special case that the caller has a string list with 0 > items on it. > > Thoughts? I agree that for_each_string_list_item() could handle NULL lists better, especially because it looks like a method and hides some details. Plus, wrapping the for-ish loop with an if statement is particularly ugly. I think this might be more confusing that git_configset_get_value_multi() can return a NULL list instead of an empty list. It boils down to this diff: diff --git a/config.c b/config.c index cbb5a3bab74..39cc0534170 100644 --- a/config.c +++ b/config.c @@ -2416,8 +2416,9 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char * const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key) { + static struct string_list empty_list = STRING_LIST_INIT_NODUP; struct config_set_element *e = configset_find_element(cs, key); - return e ? &e->value_list : NULL; + return e ? &e->value_list : &empty_list; } int git_configset_get_string(struct config_set *cs, const char *key, char **dest) However, this change causes a lot of cascading failures that are probably not worth tracking down. I'll get a patch put together that changes the behavior of for_each_string_list_item() and adds the missing 'unregister' test so we can avoid this problem. Thanks, -Stolee ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v3 1/3] maintenance: add 'unregister --force' 2022-09-27 11:38 ` Derrick Stolee @ 2022-09-27 11:54 ` Derrick Stolee 2022-09-27 13:36 ` Ævar Arnfjörð Bjarmason 0 siblings, 1 reply; 34+ messages in thread From: Derrick Stolee @ 2022-09-27 11:54 UTC (permalink / raw) To: Junio C Hamano, Derrick Stolee via GitGitGadget Cc: git, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason On 9/27/2022 7:38 AM, Derrick Stolee wrote: > On 9/26/2022 5:55 PM, Junio C Hamano wrote: >> "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: >> >>> @@ -1538,11 +1546,23 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi >>> usage_with_options(builtin_maintenance_unregister_usage, >>> options); >>> >>> - config_unset.git_cmd = 1; >>> - strvec_pushl(&config_unset.args, "config", "--global", "--unset", >>> - "--fixed-value", "maintenance.repo", maintpath, NULL); >>> + for_each_string_list_item(item, list) { >>> + if (!strcmp(maintpath, item->string)) { >>> + found = 1; >>> + break; >>> + } >>> + } >> >> Just out of curiosity, I ran this in a fresh repository and got a >> segfault. > > Yikes! Thanks for catching. I think there was another instance in > the 'register' code that I caught by tests, but I appreciate you > catching this one. > >> An attached patch obviously fixes it, but I am wondering >> if a better "fix" is to teach for_each_string_list_item() that it is >> perfectly reasonable to see a NULL passed to it as the list, which >> is a mere special case that the caller has a string list with 0 >> items on it. >> >> Thoughts? > > I agree that for_each_string_list_item() could handle NULL lists > better, especially because it looks like a method and hides some > details. Plus, wrapping the for-ish loop with an if statement is > particularly ugly. ... > I'll get a patch put together that changes the behavior of > for_each_string_list_item() and adds the missing 'unregister' test > so we can avoid this problem. Of course, there is a reason why we don't check for NULL here, and it's because -Werror=address complains when we use a non-pointer value in the macro: string-list.h:146:28: error: the address of ‘friendly_ref_names’ will always evaluate as ‘true’ [-Werror=address] 146 | for (item = (list) ? (list)->items : NULL; \ | I tried searching for a way to suppress this error in a particular case like this (perhaps using something like an attribute?), but I couldn't find anything. Thanks, -Stolee ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v3 1/3] maintenance: add 'unregister --force' 2022-09-27 11:54 ` Derrick Stolee @ 2022-09-27 13:36 ` Ævar Arnfjörð Bjarmason 2022-09-27 13:55 ` Derrick Stolee 0 siblings, 1 reply; 34+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2022-09-27 13:36 UTC (permalink / raw) To: Derrick Stolee Cc: Junio C Hamano, Derrick Stolee via GitGitGadget, git, vdye, SZEDER Gábor On Tue, Sep 27 2022, Derrick Stolee wrote: > On 9/27/2022 7:38 AM, Derrick Stolee wrote: >> On 9/26/2022 5:55 PM, Junio C Hamano wrote: >>> "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: >>> >>>> @@ -1538,11 +1546,23 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi >>>> usage_with_options(builtin_maintenance_unregister_usage, >>>> options); >>>> >>>> - config_unset.git_cmd = 1; >>>> - strvec_pushl(&config_unset.args, "config", "--global", "--unset", >>>> - "--fixed-value", "maintenance.repo", maintpath, NULL); >>>> + for_each_string_list_item(item, list) { >>>> + if (!strcmp(maintpath, item->string)) { >>>> + found = 1; >>>> + break; >>>> + } >>>> + } >>> >>> Just out of curiosity, I ran this in a fresh repository and got a >>> segfault. >> >> Yikes! Thanks for catching. I think there was another instance in >> the 'register' code that I caught by tests, but I appreciate you >> catching this one. >> >>> An attached patch obviously fixes it, but I am wondering >>> if a better "fix" is to teach for_each_string_list_item() that it is >>> perfectly reasonable to see a NULL passed to it as the list, which >>> is a mere special case that the caller has a string list with 0 >>> items on it. >>> >>> Thoughts? >> >> I agree that for_each_string_list_item() could handle NULL lists >> better, especially because it looks like a method and hides some >> details. Plus, wrapping the for-ish loop with an if statement is >> particularly ugly. > ... >> I'll get a patch put together that changes the behavior of >> for_each_string_list_item() and adds the missing 'unregister' test >> so we can avoid this problem. > > Of course, there is a reason why we don't check for NULL here, > and it's because -Werror=address complains when we use a non-pointer > value in the macro: > > string-list.h:146:28: error: the address of ‘friendly_ref_names’ will always evaluate as ‘true’ [-Werror=address] > 146 | for (item = (list) ? (list)->items : NULL; \ > | > > I tried searching for a way to suppress this error in a particular > case like this (perhaps using something like an attribute?), but I > couldn't find anything. We discussed this exact issue just a few months ago, see: https://lore.kernel.org/git/220614.86czfcytlz.gmgdl@evledraar.gmail.com/ In general I don't think we should be teaching for_each_string_list_item() to handle NULL. Instead most callers that need to deal with a "NULL" list should probably just use a list that's never NULL. See: https://lore.kernel.org/git/220616.86bkuswuh5.gmgdl@evledraar.gmail.com/ In this case however it seems perfectly reasonable to return a valid pointer or NULL, and the function documents as much: /** * Finds and returns the value list, sorted in order of increasing priority * for the configuration variable `key`. When the configuration variable * `key` is not found, returns NULL. The caller should not free or modify * the returned pointer, as it is owned by the cache. */ const struct string_list *git_config_get_value_multi(const char *key); You also have code in 3/3 that uses that API in the correct way, I think just adjusting this callsite in 1/3 would be the right move here. This also gives the reader & compiler more information to e.g. eliminate dead code. You're calling maintpath() unconditionally, but if you have no config & the user provided --force we'll never end up using it, so we can avoid allocating it in the first place. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v3 1/3] maintenance: add 'unregister --force' 2022-09-27 13:36 ` Ævar Arnfjörð Bjarmason @ 2022-09-27 13:55 ` Derrick Stolee 0 siblings, 0 replies; 34+ messages in thread From: Derrick Stolee @ 2022-09-27 13:55 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: Junio C Hamano, Derrick Stolee via GitGitGadget, git, vdye, SZEDER Gábor On 9/27/2022 9:36 AM, Ævar Arnfjörð Bjarmason wrote: > > On Tue, Sep 27 2022, Derrick Stolee wrote: >> Of course, there is a reason why we don't check for NULL here, >> and it's because -Werror=address complains when we use a non-pointer >> value in the macro: >> >> string-list.h:146:28: error: the address of ‘friendly_ref_names’ will always evaluate as ‘true’ [-Werror=address] >> 146 | for (item = (list) ? (list)->items : NULL; \ >> | >> >> I tried searching for a way to suppress this error in a particular >> case like this (perhaps using something like an attribute?), but I >> couldn't find anything. > > We discussed this exact issue just a few months ago, see: > https://lore.kernel.org/git/220614.86czfcytlz.gmgdl@evledraar.gmail.com/ Thanks for finding this thread. I knew it was vaguely familiar. > In general I don't think we should be teaching > for_each_string_list_item() to handle NULL. > > Instead most callers that need to deal with a "NULL" list should > probably just use a list that's never NULL. See: > https://lore.kernel.org/git/220616.86bkuswuh5.gmgdl@evledraar.gmail.com/ > > In this case however it seems perfectly reasonable to return a valid > pointer or NULL, and the function documents as much: > > /** > * Finds and returns the value list, sorted in order of increasing priority > * for the configuration variable `key`. When the configuration variable > * `key` is not found, returns NULL. The caller should not free or modify > * the returned pointer, as it is owned by the cache. > */ > const struct string_list *git_config_get_value_multi(const char *key); It documents that it will never return an empty list, and instead will return NULL. There are several places that check that condition explicitly. Converting them is not terribly hard, though, and I'll send an RFC soon that performs that conversion. > This also gives the reader & compiler more information to e.g. eliminate > dead code. You're calling maintpath() unconditionally, but if you have > no config & the user provided --force we'll never end up using it, so we > can avoid allocating it in the first place. While you're correct that we could avoid that allocation, it makes the code look terrible and hard to reason about, so I won't make that change. Thanks, -Stolee ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v3 2/3] scalar: make 'unregister' idempotent 2022-09-26 18:48 ` [PATCH v3 0/3] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 1/3] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget @ 2022-09-26 18:48 ` Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 3/3] gc: replace config subprocesses with API calls Derrick Stolee via GitGitGadget 2022-09-27 13:56 ` [PATCH v4 0/4] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 3 siblings, 0 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-26 18:48 UTC (permalink / raw) To: git Cc: gitster, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The 'scalar unregister' command removes a repository from the list of registered Scalar repositories and removes it from the list of repositories registered for background maintenance. If the repository was not already registered for background maintenance, then the command fails, even if the repository was still registered as a Scalar repository. After using 'scalar clone' or 'scalar register', the repository would be enrolled in background maintenance since those commands run 'git maintenance start'. If the user runs 'git maintenance unregister' on that repository, then it is still in the list of repositories which get new config updates from 'scalar reconfigure'. The 'scalar unregister' command would fail since 'git maintenance unregister' would fail. Further, the add_or_remove_enlistment() method in scalar.c already has this idempotent nature built in as an expectation since it returns zero when the scalar.repo list already has the proper containment of the repository. The previous change added the 'git maintenance unregister --force' option, so use it within 'scalar unregister' to make it idempotent. Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- scalar.c | 5 ++++- t/t9210-scalar.sh | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scalar.c b/scalar.c index c5c1ce68919..6de9c0ee523 100644 --- a/scalar.c +++ b/scalar.c @@ -207,7 +207,10 @@ static int set_recommended_config(int reconfigure) static int toggle_maintenance(int enable) { - return run_git("maintenance", enable ? "start" : "unregister", NULL); + return run_git("maintenance", + enable ? "start" : "unregister", + enable ? NULL : "--force", + NULL); } static int add_or_remove_enlistment(int add) diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh index 14ca575a214..be51a8bb7a4 100755 --- a/t/t9210-scalar.sh +++ b/t/t9210-scalar.sh @@ -116,7 +116,10 @@ test_expect_success 'scalar unregister' ' test_must_fail git config --get --global --fixed-value \ maintenance.repo "$(pwd)/vanish/src" && scalar list >scalar.repos && - ! grep -F "$(pwd)/vanish/src" scalar.repos + ! grep -F "$(pwd)/vanish/src" scalar.repos && + + # scalar unregister should be idempotent + scalar unregister vanish ' test_expect_success 'set up repository to clone' ' -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* [PATCH v3 3/3] gc: replace config subprocesses with API calls 2022-09-26 18:48 ` [PATCH v3 0/3] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 1/3] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 2/3] scalar: make 'unregister' idempotent Derrick Stolee via GitGitGadget @ 2022-09-26 18:48 ` Derrick Stolee via GitGitGadget 2022-09-26 19:27 ` Junio C Hamano 2022-09-27 13:56 ` [PATCH v4 0/4] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 3 siblings, 1 reply; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-26 18:48 UTC (permalink / raw) To: git Cc: gitster, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The 'git maintenance [un]register' commands set or unset the multi- valued maintenance.repo config key with the absolute path of the current repository. These are set in the global config file. Instead of calling a subcommand and creating a new process, create the proper API calls to git_config_set_multivar_in_file_gently(). It requires loading the filename for the global config file (and erroring out if now $HOME value is set). We also need to be careful about using CONFIG_REGEX_NONE when adding the value and using CONFIG_FLAGS_FIXED_VALUE when removing the value. In both cases, we check that the value already exists (this check already existed for 'unregister'). Also, remove the transparent translation of the error code from the config API to the exit code of 'git maintenance'. Instead, use die() to recover from failures at that level. In the case of 'unregister --force', allow the CONFIG_NOTHING_SET error code to be a success. This allows a possible race where another process removes the config value. The end result is that the config value is not set anymore, so we can treat this as a success. Reported-by: SZEDER Gábor <szeder.dev@gmail.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- builtin/gc.c | 75 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/builtin/gc.c b/builtin/gc.c index 8d154586272..4c59235950d 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1470,11 +1470,12 @@ static int maintenance_register(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_END(), }; - int rc; + int found = 0; + const char *key = "maintenance.repo"; char *config_value; - struct child_process config_set = CHILD_PROCESS_INIT; - struct child_process config_get = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); + struct string_list_item *item; + const struct string_list *list; argc = parse_options(argc, argv, prefix, options, builtin_maintenance_register_usage, 0); @@ -1491,31 +1492,35 @@ static int maintenance_register(int argc, const char **argv, const char *prefix) else git_config_set("maintenance.strategy", "incremental"); - config_get.git_cmd = 1; - strvec_pushl(&config_get.args, "config", "--global", "--get", - "--fixed-value", "maintenance.repo", maintpath, NULL); - config_get.out = -1; - - if (start_command(&config_get)) { - rc = error(_("failed to run 'git config'")); - goto done; + list = git_config_get_value_multi(key); + if (list) { + for_each_string_list_item(item, list) { + if (!strcmp(maintpath, item->string)) { + found = 1; + break; + } + } } - /* We already have this value in our config! */ - if (!finish_command(&config_get)) { - rc = 0; - goto done; + if (!found) { + int rc; + char *user_config, *xdg_config; + git_global_config(&user_config, &xdg_config); + if (!user_config) + die(_("$HOME not set")); + rc = git_config_set_multivar_in_file_gently( + user_config, "maintenance.repo", maintpath, + CONFIG_REGEX_NONE, 0); + free(user_config); + free(xdg_config); + + if (rc) + die(_("unable to add '%s' value of '%s'"), + key, maintpath); } - config_set.git_cmd = 1; - strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo", - maintpath, NULL); - - rc = run_command(&config_set); - -done: free(maintpath); - return rc; + return 0; } static char const * const builtin_maintenance_unregister_usage[] = { @@ -1533,8 +1538,6 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi OPT_END(), }; const char *key = "maintenance.repo"; - int rc = 0; - struct child_process config_unset = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); int found = 0; struct string_list_item *item; @@ -1554,17 +1557,27 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi } if (found) { - config_unset.git_cmd = 1; - strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", key, maintpath, NULL); - - rc = run_command(&config_unset); + int rc; + char *user_config, *xdg_config; + git_global_config(&user_config, &xdg_config); + if (!user_config) + die(_("$HOME not set")); + rc = git_config_set_multivar_in_file_gently( + user_config, key, NULL, maintpath, + CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE); + free(user_config); + free(xdg_config); + + if (rc && + (!force || rc == CONFIG_NOTHING_SET)) + die(_("unable to unset '%s' value of '%s'"), + key, maintpath); } else if (!force) { die(_("repository '%s' is not registered"), maintpath); } free(maintpath); - return rc; + return 0; } static const char *get_frequency(enum schedule_priority schedule) -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v3 3/3] gc: replace config subprocesses with API calls 2022-09-26 18:48 ` [PATCH v3 3/3] gc: replace config subprocesses with API calls Derrick Stolee via GitGitGadget @ 2022-09-26 19:27 ` Junio C Hamano 0 siblings, 0 replies; 34+ messages in thread From: Junio C Hamano @ 2022-09-26 19:27 UTC (permalink / raw) To: Derrick Stolee via GitGitGadget Cc: git, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > From: Derrick Stolee <derrickstolee@github.com> > > The 'git maintenance [un]register' commands set or unset the multi- > valued maintenance.repo config key with the absolute path of the current > repository. These are set in the global config file. OK. This step is new but it looks reasonable. Embarrassingly sadly, we open, rewrite, and close the configuration file for each of these "proper API calls", so the IO load is not reduced, even though we do not have to spawn extra processes ;-) All three patches queued. Thanks. ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 0/4] scalar: make unregister idempotent 2022-09-26 18:48 ` [PATCH v3 0/3] scalar: make unregister idempotent Derrick Stolee via GitGitGadget ` (2 preceding siblings ...) 2022-09-26 18:48 ` [PATCH v3 3/3] gc: replace config subprocesses with API calls Derrick Stolee via GitGitGadget @ 2022-09-27 13:56 ` Derrick Stolee via GitGitGadget 2022-09-27 13:56 ` [PATCH v4 1/4] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget ` (4 more replies) 3 siblings, 5 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-27 13:56 UTC (permalink / raw) To: git Cc: gitster, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee I noticed this while we were updating the microsoft/git fork to include v2.38.0-rc0. I don't think 'git maintenance unregister' was idempotent before, but instead some change in 'scalar unregister' led to it relying on the return code of 'git maintenance unregister'. Our functional tests expect 'scalar unregister' to be idempotent, and I think that's a good pattern for 'git maintenance unregister', so I'm fixing it at that layer. Despite finding this during the 2.38.0-rc0 integration, this isn't critical to the release. Perhaps an argument could be made that "failure means it wasn't registered before", but I think that isn't terribly helpful. Our functional tests are running the unregister subcommand to disable maintenance in order to run tests on the object store (such as running maintenance commands in the foreground and checking the object store afterwards). This is a form of automation using 'unregister' as a check that maintenance will not run at the same time, and it doesn't care if maintenance was already disabled. I can imagine other scripting scenarios wanting that kind of guarantee. Updates in v4 ============= * The previous version would segfault if 'git maintenance unregister' was called with an empty 'maintenance.repo' config list. This scenario is fixed and tested. * Part of the issue is that for_each_string_list_item() cannot handle a NULL value. The macro can't be made smarter without failing -Werror=address issues, so for now I added a patch that adds a warning to its doc comment. Updates in v3 ============= * The --force option now uses OPT_FORCE and is hidden from autocomplete. * A new commit is added that removes the use of Git subprocesses in favor of git_config_set_multivar_in_file_gently(). Updates in v2 ============= * This is now a two-patch series. * I rebased onto v2.38.0-rc1 for two reasons: Scalar is now merged, and the usage for 'git maintenance unregister' removed its translation markers. * Instead of making git maintenance unregister idempotent, add a --force option for those who do not want to require that the repository is already registered. * Make scalar unregister idempotent, with reasons argued in patch 2. Thanks, -Stolee Derrick Stolee (4): maintenance: add 'unregister --force' scalar: make 'unregister' idempotent gc: replace config subprocesses with API calls string-list: document iterator behavior on NULL input Documentation/git-maintenance.txt | 6 +- builtin/gc.c | 98 +++++++++++++++++++++---------- scalar.c | 5 +- string-list.h | 7 ++- t/t7900-maintenance.sh | 11 +++- t/t9210-scalar.sh | 5 +- 6 files changed, 96 insertions(+), 36 deletions(-) base-commit: 1b3d6e17fe83eb6f79ffbac2f2c61bbf1eaef5f8 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1358%2Fderrickstolee%2Fmaintenance-unregister-v4 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1358/derrickstolee/maintenance-unregister-v4 Pull-Request: https://github.com/gitgitgadget/git/pull/1358 Range-diff vs v3: 1: 8a8bffaec89 ! 1: c3301e21109 maintenance: add 'unregister --force' @@ Commit message '--force' option that will siltently succeed if the repository is not already registered. + Also add an extra test of 'git maintenance unregister' at a point where + there are no registered repositories. This should fail without --force. + Signed-off-by: Derrick Stolee <derrickstolee@github.com> ## Documentation/git-maintenance.txt ## @@ builtin/gc.c: done: char *maintpath = get_maintpath(); + int found = 0; + struct string_list_item *item; -+ const struct string_list *list = git_config_get_value_multi(key); ++ const struct string_list *list; argc = parse_options(argc, argv, prefix, options, builtin_maintenance_unregister_usage, 0); @@ builtin/gc.c: static int maintenance_unregister(int argc, const char **argv, con - config_unset.git_cmd = 1; - strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", "maintenance.repo", maintpath, NULL); -+ for_each_string_list_item(item, list) { -+ if (!strcmp(maintpath, item->string)) { -+ found = 1; -+ break; ++ list = git_config_get_value_multi(key); ++ if (list) { ++ for_each_string_list_item(item, list) { ++ if (!strcmp(maintpath, item->string)) { ++ found = 1; ++ break; ++ } + } + } + @@ builtin/gc.c: static int maintenance_unregister(int argc, const char **argv, con } ## t/t7900-maintenance.sh ## +@@ t/t7900-maintenance.sh: test_expect_success 'maintenance.strategy inheritance' ' + + test_expect_success 'register and unregister' ' + test_when_finished git config --global --unset-all maintenance.repo && ++ ++ test_must_fail git maintenance unregister 2>err && ++ grep "is not registered" err && ++ git maintenance unregister --force && ++ + git config --global --add maintenance.repo /existing1 && + git config --global --add maintenance.repo /existing2 && + git config --global --get-all maintenance.repo >before && @@ t/t7900-maintenance.sh: test_expect_success 'register and unregister' ' git maintenance unregister && 2: 06d5ef3fc57 = 2: a768c326c0f scalar: make 'unregister' idempotent 3: 260d7bee36e = 3: 5aa9cc1d6b9 gc: replace config subprocesses with API calls -: ----------- > 4: 73a262cdca4 string-list: document iterator behavior on NULL input -- gitgitgadget ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 1/4] maintenance: add 'unregister --force' 2022-09-27 13:56 ` [PATCH v4 0/4] scalar: make unregister idempotent Derrick Stolee via GitGitGadget @ 2022-09-27 13:56 ` Derrick Stolee via GitGitGadget 2022-09-27 13:56 ` [PATCH v4 2/4] scalar: make 'unregister' idempotent Derrick Stolee via GitGitGadget ` (3 subsequent siblings) 4 siblings, 0 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-27 13:56 UTC (permalink / raw) To: git Cc: gitster, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The 'git maintenance unregister' subcommand has a step that removes the current repository from the multi-valued maitenance.repo config key. This fails if the repository is not listed in that key. This makes running 'git maintenance unregister' twice result in a failure in the second instance. This failure exit code is helpful, but its message is not. Add a new die() message that explicitly calls out the failure due to the repository not being registered. In some cases, users may want to run 'git maintenance unregister' just to make sure that background jobs will not start on this repository, but they do not want to check to see if it is registered first. Add a new '--force' option that will siltently succeed if the repository is not already registered. Also add an extra test of 'git maintenance unregister' at a point where there are no registered repositories. This should fail without --force. Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- Documentation/git-maintenance.txt | 6 +++++- builtin/gc.c | 35 +++++++++++++++++++++++++------ t/t7900-maintenance.sh | 11 +++++++++- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt index 9c630efe19c..bb888690e4d 100644 --- a/Documentation/git-maintenance.txt +++ b/Documentation/git-maintenance.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git maintenance' run [<options>] 'git maintenance' start [--scheduler=<scheduler>] -'git maintenance' (stop|register|unregister) +'git maintenance' (stop|register|unregister) [<options>] DESCRIPTION @@ -79,6 +79,10 @@ unregister:: Remove the current repository from background maintenance. This only removes the repository from the configured list. It does not stop the background maintenance processes from running. ++ +The `unregister` subcommand will report an error if the current repository +is not already registered. Use the `--force` option to return success even +when the current repository is not registered. TASKS ----- diff --git a/builtin/gc.c b/builtin/gc.c index 2753bd15a5e..dc0ba9e3648 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1519,18 +1519,26 @@ done: } static char const * const builtin_maintenance_unregister_usage[] = { - "git maintenance unregister", + "git maintenance unregister [--force]", NULL }; static int maintenance_unregister(int argc, const char **argv, const char *prefix) { + int force = 0; struct option options[] = { + OPT__FORCE(&force, + N_("return success even if repository was not registered"), + PARSE_OPT_NOCOMPLETE), OPT_END(), }; - int rc; + const char *key = "maintenance.repo"; + int rc = 0; struct child_process config_unset = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); + int found = 0; + struct string_list_item *item; + const struct string_list *list; argc = parse_options(argc, argv, prefix, options, builtin_maintenance_unregister_usage, 0); @@ -1538,11 +1546,26 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi usage_with_options(builtin_maintenance_unregister_usage, options); - config_unset.git_cmd = 1; - strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", "maintenance.repo", maintpath, NULL); + list = git_config_get_value_multi(key); + if (list) { + for_each_string_list_item(item, list) { + if (!strcmp(maintpath, item->string)) { + found = 1; + break; + } + } + } + + if (found) { + config_unset.git_cmd = 1; + strvec_pushl(&config_unset.args, "config", "--global", "--unset", + "--fixed-value", key, maintpath, NULL); + + rc = run_command(&config_unset); + } else if (!force) { + die(_("repository '%s' is not registered"), maintpath); + } - rc = run_command(&config_unset); free(maintpath); return rc; } diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 2724a44fe3e..96bdd420456 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -480,6 +480,11 @@ test_expect_success 'maintenance.strategy inheritance' ' test_expect_success 'register and unregister' ' test_when_finished git config --global --unset-all maintenance.repo && + + test_must_fail git maintenance unregister 2>err && + grep "is not registered" err && + git maintenance unregister --force && + git config --global --add maintenance.repo /existing1 && git config --global --add maintenance.repo /existing2 && git config --global --get-all maintenance.repo >before && @@ -493,7 +498,11 @@ test_expect_success 'register and unregister' ' git maintenance unregister && git config --global --get-all maintenance.repo >actual && - test_cmp before actual + test_cmp before actual && + + test_must_fail git maintenance unregister 2>err && + grep "is not registered" err && + git maintenance unregister --force ' test_expect_success !MINGW 'register and unregister with regex metacharacters' ' -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* [PATCH v4 2/4] scalar: make 'unregister' idempotent 2022-09-27 13:56 ` [PATCH v4 0/4] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 2022-09-27 13:56 ` [PATCH v4 1/4] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget @ 2022-09-27 13:56 ` Derrick Stolee via GitGitGadget 2022-09-27 13:57 ` [PATCH v4 3/4] gc: replace config subprocesses with API calls Derrick Stolee via GitGitGadget ` (2 subsequent siblings) 4 siblings, 0 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-27 13:56 UTC (permalink / raw) To: git Cc: gitster, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The 'scalar unregister' command removes a repository from the list of registered Scalar repositories and removes it from the list of repositories registered for background maintenance. If the repository was not already registered for background maintenance, then the command fails, even if the repository was still registered as a Scalar repository. After using 'scalar clone' or 'scalar register', the repository would be enrolled in background maintenance since those commands run 'git maintenance start'. If the user runs 'git maintenance unregister' on that repository, then it is still in the list of repositories which get new config updates from 'scalar reconfigure'. The 'scalar unregister' command would fail since 'git maintenance unregister' would fail. Further, the add_or_remove_enlistment() method in scalar.c already has this idempotent nature built in as an expectation since it returns zero when the scalar.repo list already has the proper containment of the repository. The previous change added the 'git maintenance unregister --force' option, so use it within 'scalar unregister' to make it idempotent. Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- scalar.c | 5 ++++- t/t9210-scalar.sh | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scalar.c b/scalar.c index c5c1ce68919..6de9c0ee523 100644 --- a/scalar.c +++ b/scalar.c @@ -207,7 +207,10 @@ static int set_recommended_config(int reconfigure) static int toggle_maintenance(int enable) { - return run_git("maintenance", enable ? "start" : "unregister", NULL); + return run_git("maintenance", + enable ? "start" : "unregister", + enable ? NULL : "--force", + NULL); } static int add_or_remove_enlistment(int add) diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh index 14ca575a214..be51a8bb7a4 100755 --- a/t/t9210-scalar.sh +++ b/t/t9210-scalar.sh @@ -116,7 +116,10 @@ test_expect_success 'scalar unregister' ' test_must_fail git config --get --global --fixed-value \ maintenance.repo "$(pwd)/vanish/src" && scalar list >scalar.repos && - ! grep -F "$(pwd)/vanish/src" scalar.repos + ! grep -F "$(pwd)/vanish/src" scalar.repos && + + # scalar unregister should be idempotent + scalar unregister vanish ' test_expect_success 'set up repository to clone' ' -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* [PATCH v4 3/4] gc: replace config subprocesses with API calls 2022-09-27 13:56 ` [PATCH v4 0/4] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 2022-09-27 13:56 ` [PATCH v4 1/4] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget 2022-09-27 13:56 ` [PATCH v4 2/4] scalar: make 'unregister' idempotent Derrick Stolee via GitGitGadget @ 2022-09-27 13:57 ` Derrick Stolee via GitGitGadget 2022-09-27 13:57 ` [PATCH v4 4/4] string-list: document iterator behavior on NULL input Derrick Stolee via GitGitGadget 2022-09-27 16:31 ` [PATCH v4 0/4] scalar: make unregister idempotent Junio C Hamano 4 siblings, 0 replies; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-27 13:57 UTC (permalink / raw) To: git Cc: gitster, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The 'git maintenance [un]register' commands set or unset the multi- valued maintenance.repo config key with the absolute path of the current repository. These are set in the global config file. Instead of calling a subcommand and creating a new process, create the proper API calls to git_config_set_multivar_in_file_gently(). It requires loading the filename for the global config file (and erroring out if now $HOME value is set). We also need to be careful about using CONFIG_REGEX_NONE when adding the value and using CONFIG_FLAGS_FIXED_VALUE when removing the value. In both cases, we check that the value already exists (this check already existed for 'unregister'). Also, remove the transparent translation of the error code from the config API to the exit code of 'git maintenance'. Instead, use die() to recover from failures at that level. In the case of 'unregister --force', allow the CONFIG_NOTHING_SET error code to be a success. This allows a possible race where another process removes the config value. The end result is that the config value is not set anymore, so we can treat this as a success. Reported-by: SZEDER Gábor <szeder.dev@gmail.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- builtin/gc.c | 75 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/builtin/gc.c b/builtin/gc.c index dc0ba9e3648..7a585f0b71d 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1470,11 +1470,12 @@ static int maintenance_register(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_END(), }; - int rc; + int found = 0; + const char *key = "maintenance.repo"; char *config_value; - struct child_process config_set = CHILD_PROCESS_INIT; - struct child_process config_get = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); + struct string_list_item *item; + const struct string_list *list; argc = parse_options(argc, argv, prefix, options, builtin_maintenance_register_usage, 0); @@ -1491,31 +1492,35 @@ static int maintenance_register(int argc, const char **argv, const char *prefix) else git_config_set("maintenance.strategy", "incremental"); - config_get.git_cmd = 1; - strvec_pushl(&config_get.args, "config", "--global", "--get", - "--fixed-value", "maintenance.repo", maintpath, NULL); - config_get.out = -1; - - if (start_command(&config_get)) { - rc = error(_("failed to run 'git config'")); - goto done; + list = git_config_get_value_multi(key); + if (list) { + for_each_string_list_item(item, list) { + if (!strcmp(maintpath, item->string)) { + found = 1; + break; + } + } } - /* We already have this value in our config! */ - if (!finish_command(&config_get)) { - rc = 0; - goto done; + if (!found) { + int rc; + char *user_config, *xdg_config; + git_global_config(&user_config, &xdg_config); + if (!user_config) + die(_("$HOME not set")); + rc = git_config_set_multivar_in_file_gently( + user_config, "maintenance.repo", maintpath, + CONFIG_REGEX_NONE, 0); + free(user_config); + free(xdg_config); + + if (rc) + die(_("unable to add '%s' value of '%s'"), + key, maintpath); } - config_set.git_cmd = 1; - strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo", - maintpath, NULL); - - rc = run_command(&config_set); - -done: free(maintpath); - return rc; + return 0; } static char const * const builtin_maintenance_unregister_usage[] = { @@ -1533,8 +1538,6 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi OPT_END(), }; const char *key = "maintenance.repo"; - int rc = 0; - struct child_process config_unset = CHILD_PROCESS_INIT; char *maintpath = get_maintpath(); int found = 0; struct string_list_item *item; @@ -1557,17 +1560,27 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi } if (found) { - config_unset.git_cmd = 1; - strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", key, maintpath, NULL); - - rc = run_command(&config_unset); + int rc; + char *user_config, *xdg_config; + git_global_config(&user_config, &xdg_config); + if (!user_config) + die(_("$HOME not set")); + rc = git_config_set_multivar_in_file_gently( + user_config, key, NULL, maintpath, + CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE); + free(user_config); + free(xdg_config); + + if (rc && + (!force || rc == CONFIG_NOTHING_SET)) + die(_("unable to unset '%s' value of '%s'"), + key, maintpath); } else if (!force) { die(_("repository '%s' is not registered"), maintpath); } free(maintpath); - return rc; + return 0; } static const char *get_frequency(enum schedule_priority schedule) -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* [PATCH v4 4/4] string-list: document iterator behavior on NULL input 2022-09-27 13:56 ` [PATCH v4 0/4] scalar: make unregister idempotent Derrick Stolee via GitGitGadget ` (2 preceding siblings ...) 2022-09-27 13:57 ` [PATCH v4 3/4] gc: replace config subprocesses with API calls Derrick Stolee via GitGitGadget @ 2022-09-27 13:57 ` Derrick Stolee via GitGitGadget 2022-09-27 16:39 ` Junio C Hamano 2022-09-27 16:31 ` [PATCH v4 0/4] scalar: make unregister idempotent Junio C Hamano 4 siblings, 1 reply; 34+ messages in thread From: Derrick Stolee via GitGitGadget @ 2022-09-27 13:57 UTC (permalink / raw) To: git Cc: gitster, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee, Derrick Stolee From: Derrick Stolee <derrickstolee@github.com> The for_each_string_list_item() macro takes a string_list and automatically constructs a for loop to iterate over its contents. This macro will segfault if the list is non-NULL. We cannot change the macro to be careful around NULL values because there are many callers that use the address of a local variable, which will never be NULL and will cause compile errors with -Werror=address. For now, leave a documentation comment to try to avoid mistakes in the future where a caller does not check for a NULL list. Signed-off-by: Derrick Stolee <derrickstolee@github.com> --- string-list.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/string-list.h b/string-list.h index d5a744e1438..c7b0d5d0008 100644 --- a/string-list.h +++ b/string-list.h @@ -141,7 +141,12 @@ void string_list_clear_func(struct string_list *list, string_list_clear_func_t c int for_each_string_list(struct string_list *list, string_list_each_func_t func, void *cb_data); -/** Iterate over each item, as a macro. */ +/** + * Iterate over each item, as a macro. + * + * Be sure that 'list' is non-NULL. The macro cannot perform NULL + * checks due to -Werror=address errors. + */ #define for_each_string_list_item(item,list) \ for (item = (list)->items; \ item && item < (list)->items + (list)->nr; \ -- gitgitgadget ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v4 4/4] string-list: document iterator behavior on NULL input 2022-09-27 13:57 ` [PATCH v4 4/4] string-list: document iterator behavior on NULL input Derrick Stolee via GitGitGadget @ 2022-09-27 16:39 ` Junio C Hamano 0 siblings, 0 replies; 34+ messages in thread From: Junio C Hamano @ 2022-09-27 16:39 UTC (permalink / raw) To: Derrick Stolee via GitGitGadget Cc: git, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > From: Derrick Stolee <derrickstolee@github.com> > > The for_each_string_list_item() macro takes a string_list and > automatically constructs a for loop to iterate over its contents. This > macro will segfault if the list is non-NULL. > > We cannot change the macro to be careful around NULL values because > there are many callers that use the address of a local variable, which > will never be NULL and will cause compile errors with -Werror=address. > > For now, leave a documentation comment to try to avoid mistakes in the > future where a caller does not check for a NULL list. > > Signed-off-by: Derrick Stolee <derrickstolee@github.com> > --- > string-list.h | 7 ++++++- > 1 file changed, 6 insertions(+), 1 deletion(-) For exactly the -Werror=address reason, any other way that checks list for NULL-ness cannot be done here (other than with Peff's inline helper that returns the first item or NULL), which is a bit of shame but this is totally outside the topic, and an additional comment is a good thing to have here. Thanks. All four patches look reasonable. Will queue (but after I make sure I can tag and push out -rc2 today). > diff --git a/string-list.h b/string-list.h > index d5a744e1438..c7b0d5d0008 100644 > --- a/string-list.h > +++ b/string-list.h > @@ -141,7 +141,12 @@ void string_list_clear_func(struct string_list *list, string_list_clear_func_t c > int for_each_string_list(struct string_list *list, > string_list_each_func_t func, void *cb_data); > > -/** Iterate over each item, as a macro. */ > +/** > + * Iterate over each item, as a macro. > + * > + * Be sure that 'list' is non-NULL. The macro cannot perform NULL > + * checks due to -Werror=address errors. > + */ > #define for_each_string_list_item(item,list) \ > for (item = (list)->items; \ > item && item < (list)->items + (list)->nr; \ ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 0/4] scalar: make unregister idempotent 2022-09-27 13:56 ` [PATCH v4 0/4] scalar: make unregister idempotent Derrick Stolee via GitGitGadget ` (3 preceding siblings ...) 2022-09-27 13:57 ` [PATCH v4 4/4] string-list: document iterator behavior on NULL input Derrick Stolee via GitGitGadget @ 2022-09-27 16:31 ` Junio C Hamano 2022-09-27 16:54 ` Derrick Stolee 4 siblings, 1 reply; 34+ messages in thread From: Junio C Hamano @ 2022-09-27 16:31 UTC (permalink / raw) To: Derrick Stolee via GitGitGadget Cc: git, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Derrick Stolee "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > I noticed this while we were updating the microsoft/git fork to include > v2.38.0-rc0. I don't think 'git maintenance unregister' was idempotent > before, but instead some change in 'scalar unregister' led to it relying on > the return code of 'git maintenance unregister'. Our functional tests expect > 'scalar unregister' to be idempotent, and I think that's a good pattern for > 'git maintenance unregister', so I'm fixing it at that layer. > > Despite finding this during the 2.38.0-rc0 integration, this isn't critical > to the release. > > Perhaps an argument could be made that "failure means it wasn't registered > before", but I think that isn't terribly helpful. I happen _not_ to share the idea that "unregister is expected to be idempotent" is a good pattern at all, and it is a better pattern to make failure mean that the object specified to be acted upon did not even exist (cf. "rm no-such-file"). But that aside, does what the above paragraphs mention still true for this round, namely, you are "fixing" it at that (i.e. "maintenance unregister") layer? The cover letter does not become part of the permanent history, so it is not a huge deal as long as the reviewers know what they are looking at ;-). Just leaving a note in case somebody who missed the iterations up to v3 is now interested in the topic. Thanks for a quick iteration. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 0/4] scalar: make unregister idempotent 2022-09-27 16:31 ` [PATCH v4 0/4] scalar: make unregister idempotent Junio C Hamano @ 2022-09-27 16:54 ` Derrick Stolee 0 siblings, 0 replies; 34+ messages in thread From: Derrick Stolee @ 2022-09-27 16:54 UTC (permalink / raw) To: Junio C Hamano, Derrick Stolee via GitGitGadget Cc: git, vdye, SZEDER Gábor, Ævar Arnfjörð Bjarmason On 9/27/2022 12:31 PM, Junio C Hamano wrote: > "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes: > >> I noticed this while we were updating the microsoft/git fork to include >> v2.38.0-rc0. I don't think 'git maintenance unregister' was idempotent >> before, but instead some change in 'scalar unregister' led to it relying on >> the return code of 'git maintenance unregister'. Our functional tests expect >> 'scalar unregister' to be idempotent, and I think that's a good pattern for >> 'git maintenance unregister', so I'm fixing it at that layer. >> >> Despite finding this during the 2.38.0-rc0 integration, this isn't critical >> to the release. >> >> Perhaps an argument could be made that "failure means it wasn't registered >> before", but I think that isn't terribly helpful. > > I happen _not_ to share the idea that "unregister is expected to be > idempotent" is a good pattern at all, and it is a better pattern to > make failure mean that the object specified to be acted upon did not > even exist (cf. "rm no-such-file"). But that aside, does what the > above paragraphs mention still true for this round, namely, you are > "fixing" it at that (i.e. "maintenance unregister") layer? Sorry, I forgot to update the cover letter when updating the title. "git maintenance unregister" will fail if the repository is not already registered (unless --force is given). Now, "scalar unregister" _is_ idempotent (it uses "git maintenance unregister --force"). > The cover letter does not become part of the permanent history, so > it is not a huge deal as long as the reviewers know what they are > looking at ;-). Just leaving a note in case somebody who missed the > iterations up to v3 is now interested in the topic. > > Thanks for a quick iteration. Thank you for your very careful review! -Stolee ^ permalink raw reply [flat|nested] 34+ messages in thread
end of thread, other threads:[~2022-09-27 16:55 UTC | newest] Thread overview: 34+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2022-09-20 1:02 [PATCH] maintenance: make unregister idempotent Derrick Stolee via GitGitGadget 2022-09-21 17:19 ` Junio C Hamano 2022-09-22 12:37 ` Derrick Stolee 2022-09-22 19:31 ` Junio C Hamano 2022-09-22 19:46 ` Derrick Stolee 2022-09-22 20:44 ` Junio C Hamano 2022-09-22 13:37 ` [PATCH v2 0/2] scalar: " Derrick Stolee via GitGitGadget 2022-09-22 13:37 ` [PATCH v2 1/2] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget 2022-09-23 13:08 ` SZEDER Gábor 2022-09-26 13:32 ` Derrick Stolee 2022-09-26 15:39 ` Ævar Arnfjörð Bjarmason 2022-09-26 17:25 ` Derrick Stolee 2022-09-26 19:17 ` Junio C Hamano 2022-09-22 13:37 ` [PATCH v2 2/2] scalar: make 'unregister' idempotent Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 0/3] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 1/3] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget 2022-09-26 19:23 ` Junio C Hamano 2022-09-26 20:49 ` Derrick Stolee 2022-09-26 21:55 ` Junio C Hamano 2022-09-27 11:38 ` Derrick Stolee 2022-09-27 11:54 ` Derrick Stolee 2022-09-27 13:36 ` Ævar Arnfjörð Bjarmason 2022-09-27 13:55 ` Derrick Stolee 2022-09-26 18:48 ` [PATCH v3 2/3] scalar: make 'unregister' idempotent Derrick Stolee via GitGitGadget 2022-09-26 18:48 ` [PATCH v3 3/3] gc: replace config subprocesses with API calls Derrick Stolee via GitGitGadget 2022-09-26 19:27 ` Junio C Hamano 2022-09-27 13:56 ` [PATCH v4 0/4] scalar: make unregister idempotent Derrick Stolee via GitGitGadget 2022-09-27 13:56 ` [PATCH v4 1/4] maintenance: add 'unregister --force' Derrick Stolee via GitGitGadget 2022-09-27 13:56 ` [PATCH v4 2/4] scalar: make 'unregister' idempotent Derrick Stolee via GitGitGadget 2022-09-27 13:57 ` [PATCH v4 3/4] gc: replace config subprocesses with API calls Derrick Stolee via GitGitGadget 2022-09-27 13:57 ` [PATCH v4 4/4] string-list: document iterator behavior on NULL input Derrick Stolee via GitGitGadget 2022-09-27 16:39 ` Junio C Hamano 2022-09-27 16:31 ` [PATCH v4 0/4] scalar: make unregister idempotent Junio C Hamano 2022-09-27 16:54 ` Derrick Stolee
Code repositories for project(s) associated with this public inbox https://80x24.org/mirrors/git.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).