From aa31120f7c131cbe79f0f95b9bd5e745792ae47d Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 6 Feb 2022 07:22:59 -0600 Subject: [PATCH 01/10] config: introduce GIT_CONFIG_LEVEL_WORKTREE Introduce the logical concept of a worktree-level config. The new value sits between _LOCAL and _APP to allow `git_config_get_*` to 'just work'. The assumption of how `git_config_get_*` works was tested experimentally by setting _WORKTREE to some nonsense value (like -3) and watching the new test fail. --- include/git2/config.h | 6 +++++- tests/libgit2/config/configlevel.c | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/git2/config.h b/include/git2/config.h index 332e62036d0..63293dbdaec 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -48,9 +48,13 @@ typedef enum { */ GIT_CONFIG_LEVEL_LOCAL = 5, + /** Worktree specific configuration file; $GIT_DIR/config.worktree + */ + GIT_CONFIG_LEVEL_WORKTREE = 6, + /** Application specific configuration file; freely defined by applications */ - GIT_CONFIG_LEVEL_APP = 6, + GIT_CONFIG_LEVEL_APP = 7, /** Represents the highest level available config file (i.e. the most * specific config file available that actually is loaded) diff --git a/tests/libgit2/config/configlevel.c b/tests/libgit2/config/configlevel.c index 8422d32c944..3534fbc2c84 100644 --- a/tests/libgit2/config/configlevel.c +++ b/tests/libgit2/config/configlevel.c @@ -71,3 +71,21 @@ void test_config_configlevel__fetching_a_level_from_an_empty_compound_config_ret git_config_free(cfg); } + +void test_config_configlevel__can_override_local_with_worktree(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_WORKTREE, NULL, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.stringglobal")); + cl_assert_equal_s("don't find me!", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(cfg); +} From c4df10285edc3bab45cd779293800e7ec19d9bb0 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 6 Feb 2022 09:26:41 -0600 Subject: [PATCH 02/10] config: load worktree config from disk Now that GIT_CONFIG_LEVEL_WORKTREE exists logically, define and load $GIT_DIR/config.worktree into that level. --- include/git2/repository.h | 1 + src/libgit2/repository.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index 0ff0856510f..9ad6176f940 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -499,6 +499,7 @@ typedef enum { GIT_REPOSITORY_ITEM_PACKED_REFS, GIT_REPOSITORY_ITEM_REMOTES, GIT_REPOSITORY_ITEM_CONFIG, + GIT_REPOSITORY_ITEM_WORKTREE_CONFIG, GIT_REPOSITORY_ITEM_INFO, GIT_REPOSITORY_ITEM_HOOKS, GIT_REPOSITORY_ITEM_LOGS, diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 4eb3449133b..2859bd4800b 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -58,6 +58,7 @@ static const struct { { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "packed-refs", false }, { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "remotes", true }, { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "config", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "config.worktree", false }, { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "info", true }, { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "hooks", true }, { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "logs", true }, @@ -1291,6 +1292,12 @@ static int load_config( return error; if (repo) { + if ((error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_WORKTREE_CONFIG)) == 0) + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_WORKTREE, repo, 0); + + if (error && error != GIT_ENOTFOUND) + goto on_error; + if ((error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0) error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0); From 5a86dfc17298bb80ff0dabe95f8e9fbdd6ad6e7c Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 6 Feb 2022 10:00:40 -0600 Subject: [PATCH 03/10] repository: support the 'worktreeConfig' extension Now that worktree-level configuration can be read from disk and manipulated in memory, we should be able to say we support 'extensions.worktreeConfig'. --- src/libgit2/repository.c | 3 ++- tests/libgit2/core/opts.c | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 2859bd4800b..be938b5b9d0 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -1852,7 +1852,8 @@ static int check_repositoryformatversion(int *version, git_config *config) static const char *builtin_extensions[] = { "noop", - "objectformat" + "objectformat", + "worktreeconfig", }; static git_vector user_extensions = { 0, git__strcmp_cb }; diff --git a/tests/libgit2/core/opts.c b/tests/libgit2/core/opts.c index 1aa095adf4c..cbef29f991d 100644 --- a/tests/libgit2/core/opts.c +++ b/tests/libgit2/core/opts.c @@ -34,9 +34,10 @@ void test_core_opts__extensions_query(void) cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 2); + cl_assert_equal_sz(out.count, 3); cl_assert_equal_s("noop", out.strings[0]); cl_assert_equal_s("objectformat", out.strings[1]); + cl_assert_equal_s("worktreeconfig", out.strings[2]); git_strarray_dispose(&out); } @@ -49,10 +50,11 @@ void test_core_opts__extensions_add(void) cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 3); + cl_assert_equal_sz(out.count, 4); cl_assert_equal_s("foo", out.strings[0]); cl_assert_equal_s("noop", out.strings[1]); cl_assert_equal_s("objectformat", out.strings[2]); + cl_assert_equal_s("worktreeconfig", out.strings[3]); git_strarray_dispose(&out); } @@ -65,10 +67,11 @@ void test_core_opts__extensions_remove(void) cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 3); + cl_assert_equal_sz(out.count, 4); cl_assert_equal_s("bar", out.strings[0]); cl_assert_equal_s("baz", out.strings[1]); cl_assert_equal_s("objectformat", out.strings[2]); + cl_assert_equal_s("worktreeconfig", out.strings[3]); git_strarray_dispose(&out); } @@ -81,11 +84,12 @@ void test_core_opts__extensions_uniq(void) cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - cl_assert_equal_sz(out.count, 4); + cl_assert_equal_sz(out.count, 5); cl_assert_equal_s("bar", out.strings[0]); cl_assert_equal_s("foo", out.strings[1]); cl_assert_equal_s("noop", out.strings[2]); cl_assert_equal_s("objectformat", out.strings[3]); + cl_assert_equal_s("worktreeconfig", out.strings[4]); git_strarray_dispose(&out); } From 42e7e681de2dbd37b0b1018fc78a0e3a7ff1cf68 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:38:52 -0600 Subject: [PATCH 04/10] config: refactor get_backend_for_use This structure provides for cleaner diffs in upcoming commits. --- src/libgit2/config.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 04f3ec2fee2..e67839c3622 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -594,6 +594,7 @@ static int get_backend_for_use(git_config_backend **out, { size_t i; backend_internal *backend; + int error = 0; *out = NULL; @@ -605,16 +606,20 @@ static int get_backend_for_use(git_config_backend **out, } git_vector_foreach(&cfg->backends, i, backend) { - if (!backend->backend->readonly) { - *out = backend->backend; - return 0; - } + if (backend->backend->readonly) + continue; + + *out = backend->backend; + goto cleanup; } + error = GIT_ENOTFOUND; git_error_set(GIT_ERROR_CONFIG, "cannot %s value for '%s' when all config backends are readonly", uses[use], name); - return GIT_ENOTFOUND; + + cleanup: + return error; } int git_config_delete_entry(git_config *cfg, const char *name) From ea66ab07738642a3d7dae6d249a92f5fd1ff3e89 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:39:11 -0600 Subject: [PATCH 05/10] config: skip worktree config in get_backend_for_use It would seem that `get_backend_for_use` is primarily used when writing config data -- either to set keys or delete them (based on the possible values of `backend_use`). When git-config(1) is used for side-effects, it will modify only the local (repository-level) configuration unless explicitly overridden. From git-config(1): --local For writing options: write to the repository .git/config file. This is the default behavior. `get_backend_for_use` does not have the ability to specify a config level and typically is expected (it seems) to 'do the right thing'. Taking its cue from git-config(1), don't update worktree-specific config unless it's the only option. If that functionality is needed by consumers, I assume they would find the appropriate backend with `git_config_open_level` and feed that `git_config` object through to the `git_config_set_*` functions (as demonstrated in the provided test). --- src/libgit2/config.c | 13 ++++++++++++- tests/libgit2/worktree/config.c | 30 +++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index e67839c3622..732d10830ec 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -593,12 +593,14 @@ static int get_backend_for_use(git_config_backend **out, git_config *cfg, const char *name, backend_use use) { size_t i; + size_t len; backend_internal *backend; int error = 0; *out = NULL; - if (git_vector_length(&cfg->backends) == 0) { + len = git_vector_length(&cfg->backends); + if (len == 0) { git_error_set(GIT_ERROR_CONFIG, "cannot %s value for '%s' when no config backends exist", uses[use], name); @@ -609,6 +611,15 @@ static int get_backend_for_use(git_config_backend **out, if (backend->backend->readonly) continue; + /* git-config doesn't update worktree-level config + unless specifically requested; follow suit. If you + specifically want to update that level, open the + single config level with git_config_open_level and + provide that as the config. In this case, there + will only be one backend in the config. */ + if (len > 1 && backend->level == GIT_CONFIG_LEVEL_WORKTREE) + continue; + *out = backend->backend; goto cleanup; } diff --git a/tests/libgit2/worktree/config.c b/tests/libgit2/worktree/config.c index 81dcfe1fa51..c23cd044fba 100644 --- a/tests/libgit2/worktree/config.c +++ b/tests/libgit2/worktree/config.c @@ -27,7 +27,7 @@ void test_worktree_config__open(void) git_config_free(cfg); } -void test_worktree_config__set(void) +void test_worktree_config__set_level_local(void) { git_config *cfg; int32_t val; @@ -45,3 +45,31 @@ void test_worktree_config__set(void) cl_assert_equal_i(val, 5); git_config_free(cfg); } + +void test_worktree_config__set_level_worktree(void) +{ + git_config *cfg; + git_config *wtcfg; + int32_t val; + + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_open_level(&wtcfg, cfg, GIT_CONFIG_LEVEL_WORKTREE)); + cl_git_pass(git_config_set_int32(wtcfg, "worktree.specific", 42)); + + cl_git_pass(git_config_get_int32(&val, cfg, "worktree.specific")); + cl_assert_equal_i(val, 42); + + /* reopen to verify config has been set */ + git_config_free(cfg); + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_get_int32(&val, cfg, "worktree.specific")); + cl_assert_equal_i(val, 42); + + cl_assert(git_config_delete_entry(cfg, "worktree.specific") == GIT_ENOTFOUND); + + cl_git_pass(git_config_delete_entry(wtcfg, "worktree.specific")); + cl_assert(git_config_get_int32(&val, cfg, "worktree.specific") == GIT_ENOTFOUND); + + git_config_free(cfg); + git_config_free(wtcfg); +} From 9b21c428cfebdb5cd047d7fd6505d8c32e47f101 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:46:07 -0600 Subject: [PATCH 06/10] config: return only backends that contain the key When deleting a key from a repository with multiple config backends (like a --local config and a --worktree config), it's important to return the correct backend to modify. This patch ensures that we don't return a backend that is incapable of deleting a given piece of configuration when that is the required use. --- src/libgit2/config.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 732d10830ec..6d3cd9836d3 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -596,6 +596,7 @@ static int get_backend_for_use(git_config_backend **out, size_t len; backend_internal *backend; int error = 0; + git_config_entry *entry = NULL; *out = NULL; @@ -620,6 +621,15 @@ static int get_backend_for_use(git_config_backend **out, if (len > 1 && backend->level == GIT_CONFIG_LEVEL_WORKTREE) continue; + /* If we're trying to delete a piece of config, make + sure the backend we return actually defines it in + the first place. */ + if (use == BACKEND_USE_DELETE) { + if (backend->backend->get(backend->backend, name, &entry) < 0) + continue; + git_config_entry_free(entry); + } + *out = backend->backend; goto cleanup; } From 498f443379f3fb248bb20c9ae6dce0f956dea327 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:46:16 -0600 Subject: [PATCH 07/10] config: normalize key name in get_backend_for_use Before passing the config key name to backend->get(), it needs to be normalized first. Not all current callers are performing this normalization, so perform it centrally instead. Co-authored-by: Brian Lyles --- src/libgit2/config.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 6d3cd9836d3..6994a8e2736 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -597,6 +597,7 @@ static int get_backend_for_use(git_config_backend **out, backend_internal *backend; int error = 0; git_config_entry *entry = NULL; + char *key = NULL; *out = NULL; @@ -625,7 +626,9 @@ static int get_backend_for_use(git_config_backend **out, sure the backend we return actually defines it in the first place. */ if (use == BACKEND_USE_DELETE) { - if (backend->backend->get(backend->backend, name, &entry) < 0) + if (key == NULL && (error = git_config__normalize_name(name, &key)) < 0) + goto cleanup; + if (backend->backend->get(backend->backend, key, &entry) < 0) continue; git_config_entry_free(entry); } @@ -640,6 +643,7 @@ static int get_backend_for_use(git_config_backend **out, uses[use], name); cleanup: + git__free(key); return error; } From 47f3953111a94bcd11b9798989bc702eda2cdb44 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Wed, 21 Feb 2024 13:51:36 -0600 Subject: [PATCH 08/10] config: use appropriate backend_use value This looks like a simple copy/paste error. --- src/libgit2/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 6994a8e2736..908bf8d390a 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -1126,7 +1126,7 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex { git_config_backend *backend; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) return GIT_ENOTFOUND; return backend->set_multivar(backend, name, regexp, value); From 4cd2d1798ce2af4bff7ce8a76b6e97d702183567 Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Tue, 20 Feb 2024 21:34:29 -0600 Subject: [PATCH 09/10] config: propagate errors from get_backend_for_use Now that get_backend_for_use can return other error codes (by virtue of key-name normalization), make sure to propagate the appropriate error code when used. --- src/libgit2/config.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 908bf8d390a..d3193c95e26 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -650,9 +650,10 @@ static int get_backend_for_use(git_config_backend **out, int git_config_delete_entry(git_config *cfg, const char *name) { git_config_backend *backend; + int error = 0; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; + if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE)) < 0) + return error; return backend->del(backend, name); } @@ -684,8 +685,8 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return -1; } - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) - return GIT_ENOTFOUND; + if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET)) < 0) + return error; error = backend->set(backend, name, value); @@ -1125,9 +1126,10 @@ int git_config_multivar_iterator_new(git_config_iterator **out, const git_config int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) { git_config_backend *backend; + int error = 0; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) - return GIT_ENOTFOUND; + if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET)) < 0) + return error; return backend->set_multivar(backend, name, regexp, value); } @@ -1135,9 +1137,10 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) { git_config_backend *backend; + int error = 0; - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; + if ((error = get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE)) < 0) + return error; return backend->del_multivar(backend, name, regexp); } From c82bef33a090217e731944e91385e1f3095c1fee Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Sun, 3 Mar 2024 18:19:04 -0600 Subject: [PATCH 10/10] WIP proposal In this version: 1. If there is exactly one config backend, use it. 2. If `--local` is available, use it. 3. Signal an error (as `git` does). This closely simulates the behavior of git-config. A test does fail, however, introduced in [1]: test_config_readonly__writing_to_cfg_with_ro_precedence_succeeds Reading the message, I think the spirit of the readonly functionality is still satisfied and the test can be adjusted. Another test also fails: test_remote_httpproxy__env This one eludes me -- because it only fails *sometimes*. It does not fail if I just run that suite or surrounding suites. When it does fail, it fails on the very first assertion: /* HTTP proxy is ignored for HTTPS */ cl_setenv("HTTP_PROXY", "http://localhost:9/"); assert_proxy_is(NULL); I'm not sure what kind of test interaction could be going on here, but I suspect (given my changes) that the problem is somewhere within `http_proxy_config`. [1]: 95f29fb35b9fbc7bd98f20bd91886522ddc03f4b --- src/libgit2/config.c | 53 ++++++++++++-------------------- tests/libgit2/config/readonly.c | 6 +++- tests/libgit2/remote/httpproxy.c | 5 ++- 3 files changed, 29 insertions(+), 35 deletions(-) diff --git a/src/libgit2/config.c b/src/libgit2/config.c index d3193c95e26..986d4ae7071 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -592,59 +592,46 @@ static const char *uses[] = { static int get_backend_for_use(git_config_backend **out, git_config *cfg, const char *name, backend_use use) { - size_t i; size_t len; - backend_internal *backend; int error = 0; - git_config_entry *entry = NULL; - char *key = NULL; + backend_internal *backend; + git_config *cfg_single; *out = NULL; len = git_vector_length(&cfg->backends); - if (len == 0) { + if (len <= 0) { + /* todo: tweak message? */ git_error_set(GIT_ERROR_CONFIG, "cannot %s value for '%s' when no config backends exist", uses[use], name); return GIT_ENOTFOUND; } - git_vector_foreach(&cfg->backends, i, backend) { - if (backend->backend->readonly) - continue; + if (len == 1) { + backend = git_vector_get(&cfg->backends, 0); - /* git-config doesn't update worktree-level config - unless specifically requested; follow suit. If you - specifically want to update that level, open the - single config level with git_config_open_level and - provide that as the config. In this case, there - will only be one backend in the config. */ - if (len > 1 && backend->level == GIT_CONFIG_LEVEL_WORKTREE) - continue; + if (backend == NULL) { + /* todo: tweak message? */ + git_error_set(GIT_ERROR_CONFIG, "failed to retrieve config backend"); + return GIT_ENOTFOUND; + } - /* If we're trying to delete a piece of config, make - sure the backend we return actually defines it in - the first place. */ - if (use == BACKEND_USE_DELETE) { - if (key == NULL && (error = git_config__normalize_name(name, &key)) < 0) - goto cleanup; - if (backend->backend->get(backend->backend, key, &entry) < 0) - continue; - git_config_entry_free(entry); + if (backend->backend->readonly) { + /* todo: tweak message? */ + git_error_set(GIT_ERROR_CONFIG, "cannot %s value for '%s' when config backend is readonly", uses[use], name); + return GIT_ENOTFOUND; } *out = backend->backend; - goto cleanup; + return 0; } - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_CONFIG, - "cannot %s value for '%s' when all config backends are readonly", - uses[use], name); + if ((error = git_config_open_level(&cfg_single, cfg, GIT_CONFIG_LEVEL_LOCAL)) < 0) { + return error; + } - cleanup: - git__free(key); - return error; + return get_backend_for_use(out, cfg_single, name, use); } int git_config_delete_entry(git_config *cfg, const char *name) diff --git a/tests/libgit2/config/readonly.c b/tests/libgit2/config/readonly.c index a8901e394c0..0140bcb414d 100644 --- a/tests/libgit2/config/readonly.c +++ b/tests/libgit2/config/readonly.c @@ -49,6 +49,7 @@ void test_config_readonly__writing_to_cfg_with_rw_precedence_succeeds(void) void test_config_readonly__writing_to_cfg_with_ro_precedence_succeeds(void) { git_config_backend *backend; + git_config *cfg_global; cl_git_pass(git_config_backend_from_file(&backend, "local")); backend->readonly = 1; @@ -57,7 +58,10 @@ void test_config_readonly__writing_to_cfg_with_ro_precedence_succeeds(void) cl_git_pass(git_config_backend_from_file(&backend, "global")); cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - cl_git_pass(git_config_set_string(cfg, "foo.bar", "baz")); + cl_git_fail_with(GIT_ENOTFOUND, git_config_set_string(cfg, "foo.bar", "baz")); + + git_config_open_global(&cfg_global, cfg); + cl_git_pass(git_config_set_string(cfg_global, "foo.bar", "baz")); cl_assert(!git_fs_path_exists("local")); cl_assert(git_fs_path_exists("global")); diff --git a/tests/libgit2/remote/httpproxy.c b/tests/libgit2/remote/httpproxy.c index 60fc67dec31..2ea5bf099b2 100644 --- a/tests/libgit2/remote/httpproxy.c +++ b/tests/libgit2/remote/httpproxy.c @@ -115,11 +115,14 @@ static void assert_global_config_match(const char *config, const char *expected) git_remote *remote; char *proxy; git_config* cfg; + git_config* cfg_global; if (config) { cl_git_pass(git_config_open_default(&cfg)); - git_config_set_string(cfg, config, expected); + cl_git_pass(git_config_open_global(&cfg_global, cfg)); + git_config_set_string(cfg_global, config, expected); git_config_free(cfg); + git_config_free(cfg_global); } cl_git_pass(git_remote_create_detached(&remote, "https://github.com/libgit2/libgit2"));