diff --git a/include/git2/config.h b/include/git2/config.h index 36946c4a5..c46e7fc9d 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -33,7 +33,7 @@ struct git_config_file { int (*set)(struct git_config_file *, const char *key, const char *value); int (*set_multivar)(git_config_file *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_file *, const char *key); - int (*foreach)(struct git_config_file *, int (*fn)(const char *, const char *, void *), void *data); + int (*foreach)(struct git_config_file *, const char *, int (*fn)(const char *, const char *, void *), void *data); void (*free)(struct git_config_file *); }; @@ -314,6 +314,24 @@ GIT_EXTERN(int) git_config_foreach( int (*callback)(const char *var_name, const char *value, void *payload), void *payload); +/** + * Perform an operation on each config variable matching a regular expression. + * + * This behaviors like `git_config_foreach` with an additional filter of a + * regular expression that filters which config keys are passed to the + * callback. + * + * @param cfg where to get the variables from + * @param regexp regular expression to match against config names + * @param callback the function to call on each variable + * @param payload the data to pass to the callback + * @return 0 or the return value of the callback which didn't return 0 + */ +GIT_EXTERN(int) git_config_foreach_match( + git_config *cfg, + const char *regexp, + int (*callback)(const char *var_name, const char *value, void *payload), + void *payload); /** * Query the value of a config variable and return it mapped to diff --git a/src/config.c b/src/config.c index d18b85c30..98fb3b20d 100644 --- a/src/config.c +++ b/src/config.c @@ -136,17 +136,27 @@ int git_config_add_file(git_config *cfg, git_config_file *file, int priority) * Loop over all the variables */ -int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) +int git_config_foreach( + git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) +{ + return git_config_foreach_match(cfg, NULL, fn, data); +} + +int git_config_foreach_match( + git_config *cfg, + const char *regexp, + int (*fn)(const char *, const char *, void *), + void *data) { int ret = 0; unsigned int i; file_internal *internal; git_config_file *file; - for(i = 0; i < cfg->files.length && ret == 0; ++i) { + for (i = 0; i < cfg->files.length && ret == 0; ++i) { internal = git_vector_get(&cfg->files, i); file = internal->file; - ret = file->foreach(file, fn, data); + ret = file->foreach(file, regexp, fn, data); } return ret; diff --git a/src/config_file.c b/src/config_file.c index fd1aa8d08..1f3ebfca9 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -188,25 +188,46 @@ static void backend_free(git_config_file *_backend) git__free(backend); } -static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data) +static int file_foreach( + git_config_file *backend, + const char *regexp, + int (*fn)(const char *, const char *, void *), + void *data) { diskfile_backend *b = (diskfile_backend *)backend; cvar_t *var; const char *key; + regex_t regex; + int result = 0; if (!b->values) return 0; - git_strmap_foreach(b->values, key, var, - do { - if (fn(key, var->value, data) < 0) - break; + if (regexp != NULL) { + if ((result = regcomp(®ex, regexp, REG_EXTENDED)) < 0) { + giterr_set_regex(®ex, result); + regfree(®ex); + return -1; + } + } - var = CVAR_LIST_NEXT(var); - } while (var != NULL); + git_strmap_foreach(b->values, key, var, + for (; var != NULL; var = CVAR_LIST_NEXT(var)) { + /* skip non-matching keys if regexp was provided */ + if (regexp && regexec(®ex, key, 0, NULL, 0) != 0) + continue; + + /* abort iterator on non-zero return value */ + if ((result = fn(key, var->value, data)) != 0) + goto cleanup; + } ); - return 0; +cleanup: + if (regexp != NULL) + regfree(®ex); + + return result; } static int config_set(git_config_file *cfg, const char *name, const char *value) @@ -337,6 +358,7 @@ static int config_get_multivar( result = regcomp(®ex, regex_str, REG_EXTENDED); if (result < 0) { giterr_set_regex(®ex, result); + regfree(®ex); return -1; } @@ -396,6 +418,7 @@ static int config_set_multivar( if (result < 0) { git__free(key); giterr_set_regex(&preg, result); + regfree(&preg); return -1; } diff --git a/src/config_file.h b/src/config_file.h index 0080b5713..c31292881 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -19,12 +19,27 @@ GIT_INLINE(void) git_config_file_free(git_config_file *cfg) cfg->free(cfg); } +GIT_INLINE(int) git_config_file_set_string( + git_config_file *cfg, const char *name, const char *value) +{ + return cfg->set(cfg, name, value); +} + GIT_INLINE(int) git_config_file_foreach( git_config_file *cfg, int (*fn)(const char *key, const char *value, void *data), void *data) { - return cfg->foreach(cfg, fn, data); + return cfg->foreach(cfg, NULL, fn, data); +} + +GIT_INLINE(int) git_config_file_foreach_match( + git_config_file *cfg, + const char *regexp, + int (*fn)(const char *key, const char *value, void *data), + void *data) +{ + return cfg->foreach(cfg, regexp, fn, data); } #endif diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c index f33bdd89e..a8504da02 100644 --- a/tests-clar/config/read.c +++ b/tests-clar/config/read.c @@ -191,6 +191,81 @@ void test_config_read__escaping_quotes(void) git_config_free(cfg); } +static int count_cfg_entries( + const char *var_name, const char *value, void *payload) +{ + int *count = payload; + GIT_UNUSED(var_name); + GIT_UNUSED(value); + (*count)++; + return 0; +} + +static int cfg_callback_countdown( + const char *var_name, const char *value, void *payload) +{ + int *count = payload; + GIT_UNUSED(var_name); + GIT_UNUSED(value); + (*count)--; + if (*count == 0) + return -100; + return 0; +} + +void test_config_read__foreach(void) +{ + git_config *cfg; + int count, ret; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + count = 0; + cl_git_pass(git_config_foreach(cfg, count_cfg_entries, &count)); + cl_assert_equal_i(5, count); + + count = 3; + cl_git_fail(ret = git_config_foreach(cfg, cfg_callback_countdown, &count)); + cl_assert_equal_i(-100, ret); + + git_config_free(cfg); +} + +void test_config_read__foreach_match(void) +{ + git_config *cfg; + int count; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, "core.*", count_cfg_entries, &count)); + cl_assert_equal_i(3, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, "remote\\.ab.*", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*url$", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*dummy.*", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*nomatch.*", count_cfg_entries, &count)); + cl_assert_equal_i(0, count); + + git_config_free(cfg); +} + #if 0 BEGIN_TEST(config10, "a repo's config overrides the global config")