From f8ede94808348ac12db1d5dd91e5f66624d8b40d Mon Sep 17 00:00:00 2001 From: yorah Date: Tue, 18 Sep 2012 14:10:40 +0200 Subject: [PATCH 1/2] Fix adding variable to config file with no trailing newline This can occur after a manual modification of a config file. --- src/config_file.c | 4 ++++ tests-clar/config/write.c | 18 ++++++++++++++++++ tests-clar/resources/config/config17 | 3 +++ 3 files changed, 25 insertions(+) create mode 100644 tests-clar/resources/config/config17 diff --git a/src/config_file.c b/src/config_file.c index 4ba83d1d9..12ae4a214 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1182,6 +1182,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p goto rewrite_fail; } + /* If we are here, there is at least a section line */ + if (*(cfg->reader.buffer.ptr + cfg->reader.buffer.size - 1) != '\n') + git_filebuf_write(&file, "\n", 1); + git_filebuf_printf(&file, "\t%s = %s\n", name, value); } } diff --git a/tests-clar/config/write.c b/tests-clar/config/write.c index 13b669cb2..eeda4d66a 100644 --- a/tests-clar/config/write.c +++ b/tests-clar/config/write.c @@ -3,11 +3,13 @@ void test_config_write__initialize(void) { cl_fixture_sandbox("config/config9"); + cl_fixture_sandbox("config/config17"); } void test_config_write__cleanup(void) { cl_fixture_cleanup("config9"); + cl_fixture_cleanup("config17"); } void test_config_write__replace_value(void) @@ -136,3 +138,19 @@ void test_config_write__escape_value(void) cl_assert_equal_s(str, "this \"has\" quotes and \t"); git_config_free(cfg); } + +void test_config_write__add_value_in_file_with_no_clrf_at_the_end(void) +{ + git_config *cfg; + int i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config17")); + cl_git_pass(git_config_set_int32(cfg, "core.newline", 7)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config17")); + cl_git_pass(git_config_get_int32(&i, cfg, "core.newline")); + cl_assert_equal_i(7, i); + + git_config_free(cfg); +} diff --git a/tests-clar/resources/config/config17 b/tests-clar/resources/config/config17 new file mode 100644 index 000000000..ca25a86af --- /dev/null +++ b/tests-clar/resources/config/config17 @@ -0,0 +1,3 @@ +[core] + dummy2 = 7 + global = 17 \ No newline at end of file From a1abe66aca3625eec1cabb2e93cf8df0be1b63f0 Mon Sep 17 00:00:00 2001 From: yorah Date: Mon, 10 Sep 2012 12:11:02 +0200 Subject: [PATCH 2/2] Add config level support in the config API Added `struct git_config_entry`: a git_config_entry contains the key, the value, and the config file level from which a config element was found. Added `git_config_open_level`: build a single-level focused config object from a multi-level one. We are now storing `git_config_entry`s in the khash of the config_file --- include/git2/config.h | 186 ++++++++-- src/config.c | 502 ++++++++++++++++++--------- src/config.h | 5 - src/config_file.c | 94 ++--- src/config_file.h | 10 +- src/remote.c | 4 +- src/repository.c | 8 +- src/submodule.c | 11 +- tests-clar/config/configlevel.c | 59 ++++ tests-clar/config/multivar.c | 10 +- tests-clar/config/new.c | 9 +- tests-clar/config/read.c | 176 ++++++++-- tests-clar/config/stress.c | 27 +- tests-clar/config/write.c | 76 +++- tests-clar/resources/config/config15 | 3 + tests-clar/resources/config/config16 | 3 + tests-clar/resources/config/config18 | 5 + tests-clar/resources/config/config19 | 5 + tests-clar/submodule/modify.c | 6 +- 19 files changed, 870 insertions(+), 329 deletions(-) create mode 100644 tests-clar/config/configlevel.c create mode 100644 tests-clar/resources/config/config15 create mode 100644 tests-clar/resources/config/config16 create mode 100644 tests-clar/resources/config/config18 create mode 100644 tests-clar/resources/config/config19 diff --git a/include/git2/config.h b/include/git2/config.h index a7d897443..67408f90f 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -19,6 +19,28 @@ */ GIT_BEGIN_DECL +/** + * Priority level of a config file. + * These priority levels correspond to the natural escalation logic + * (from higher to lower) when searching for config entries in git.git. + * + * git_config_open_default() and git_repository_config() honor those + * priority levels as well. + */ +enum { + GIT_CONFIG_LEVEL_SYSTEM = 1, /**< System-wide configuration file. */ + GIT_CONFIG_LEVEL_XDG = 2, /**< XDG compatible configuration file (.config/git/config). */ + GIT_CONFIG_LEVEL_GLOBAL = 3, /**< User-specific configuration file, also called Global configuration file. */ + GIT_CONFIG_LEVEL_LOCAL = 4, /**< Repository specific configuration file. */ + GIT_CONFIG_HIGHEST_LEVEL = -1, /**< Represents the highest level of a config file. */ +}; + +typedef struct { + const char *name; + const char *value; + unsigned int level; +} git_config_entry; + /** * Generic backend that implements the interface to * access a configuration file @@ -27,13 +49,13 @@ struct git_config_file { struct git_config *cfg; /* Open means open the file/database and parse if necessary */ - int (*open)(struct git_config_file *); - int (*get)(struct git_config_file *, const char *key, const char **value); - int (*get_multivar)(struct git_config_file *, const char *key, const char *regexp, int (*fn)(const char *, void *), void *data); + int (*open)(struct git_config_file *, unsigned int level); + int (*get)(struct git_config_file *, const char *key, const git_config_entry **entry); + int (*get_multivar)(struct git_config_file *, const char *key, const char *regexp, int (*fn)(const git_config_entry *, void *), void *data); 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 *, const char *, int (*fn)(const char *, const char *, void *), void *data); + int (*foreach)(struct git_config_file *, const char *, int (*fn)(const git_config_entry *, void *), void *data); void (*free)(struct git_config_file *); }; @@ -100,9 +122,9 @@ GIT_EXTERN(int) git_config_find_xdg(char *xdg_config_path, size_t length); GIT_EXTERN(int) git_config_find_system(char *system_config_path, size_t length); /** - * Open the global and system configuration files + * Open the global, XDG and system configuration files * - * Utility wrapper that finds the global and system configuration files + * Utility wrapper that finds the global, XDG and system configuration files * and opens them into a single prioritized config object that can be * used when accessing default config data outside a repository. * @@ -143,14 +165,21 @@ GIT_EXTERN(int) git_config_new(git_config **out); * * Further queries on this config object will access each * of the config file instances in order (instances with - * a higher priority will be accessed first). + * a higher priority level will be accessed first). * * @param cfg the configuration to add the file to * @param file the configuration file (backend) to add - * @param priority the priority the backend should have - * @return 0 or an error code + * @param level the priority level of the backend + * @param force if a config file already exists for the given + * priority level, replace it + * @return 0 on success, GIT_EEXISTS when adding more than one file + * for a given priority level (and force_replace set to 0), or error code */ -GIT_EXTERN(int) git_config_add_file(git_config *cfg, git_config_file *file, int priority); +GIT_EXTERN(int) git_config_add_file( + git_config *cfg, + git_config_file *file, + unsigned int level, + int force); /** * Add an on-disk config file instance to an existing config @@ -164,14 +193,21 @@ GIT_EXTERN(int) git_config_add_file(git_config *cfg, git_config_file *file, int * * Further queries on this config object will access each * of the config file instances in order (instances with - * a higher priority will be accessed first). + * a higher priority level will be accessed first). * * @param cfg the configuration to add the file to * @param path path to the configuration file (backend) to add - * @param priority the priority the backend should have - * @return 0 or an error code + * @param level the priority level of the backend + * @param force if a config file already exists for the given + * priority level, replace it + * @return 0 on success, GIT_EEXISTS when adding more than one file + * for a given priority level (and force_replace set to 0), or error code */ -GIT_EXTERN(int) git_config_add_file_ondisk(git_config *cfg, const char *path, int priority); +GIT_EXTERN(int) git_config_add_file_ondisk( + git_config *cfg, + const char *path, + unsigned int level, + int force); /** @@ -188,6 +224,24 @@ GIT_EXTERN(int) git_config_add_file_ondisk(git_config *cfg, const char *path, in */ GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path); +/** + * Build a single-level focused config object from a multi-level one. + * + * The returned config object can be used to perform get/set/delete operations + * on a single specific level. + * + * Getting several times the same level from the same parent multi-level config + * will return different config instances, but containing the same config_file + * instance. + * + * @return 0, GIT_ENOTFOUND if the passed level cannot be found in the + * multi-level parent config, or an error code + */ +GIT_EXTERN(int) git_config_open_level( + git_config **cfg_out, + git_config *cfg_parent, + unsigned int level); + /** * Free the configuration and its associated memory and files * @@ -195,9 +249,26 @@ GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path); */ GIT_EXTERN(void) git_config_free(git_config *cfg); +/** + * Get the git_config_entry of a config variable. + * + * The git_config_entry is owned by the config and should not be freed by the + * user. + + * @param out pointer to the variable git_config_entry + * @param cfg where to look for the variable + * @param name the variable's name + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_get_config_entry(const git_config_entry **out, git_config *cfg, const char *name); + /** * Get the value of an integer config variable. * + * All config files will be looked into, in the order of their + * defined level. A higher level means a higher priority. The + * first occurence of the variable will be returned here. + * * @param out pointer to the variable where the value should be stored * @param cfg where to look for the variable * @param name the variable's name @@ -208,6 +279,10 @@ GIT_EXTERN(int) git_config_get_int32(int32_t *out, git_config *cfg, const char * /** * Get the value of a long integer config variable. * + * All config files will be looked into, in the order of their + * defined level. A higher level means a higher priority. The + * first occurence of the variable will be returned here. + * * @param out pointer to the variable where the value should be stored * @param cfg where to look for the variable * @param name the variable's name @@ -221,6 +296,10 @@ GIT_EXTERN(int) git_config_get_int64(int64_t *out, git_config *cfg, const char * * This function uses the usual C convention of 0 being false and * anything else true. * + * All config files will be looked into, in the order of their + * defined level. A higher level means a higher priority. The + * first occurence of the variable will be returned here. + * * @param out pointer to the variable where the value should be stored * @param cfg where to look for the variable * @param name the variable's name @@ -234,6 +313,10 @@ GIT_EXTERN(int) git_config_get_bool(int *out, git_config *cfg, const char *name) * The string is owned by the variable and should not be freed by the * user. * + * All config files will be looked into, in the order of their + * defined level. A higher level means a higher priority. The + * first occurence of the variable will be returned here. + * * @param out pointer to the variable's value * @param cfg where to look for the variable * @param name the variable's name @@ -253,10 +336,11 @@ GIT_EXTERN(int) git_config_get_string(const char **out, git_config *cfg, const c * @param fn the function to be called on each value of the variable * @param data opaque pointer to pass to the callback */ -GIT_EXTERN(int) git_config_get_multivar(git_config *cfg, const char *name, const char *regexp, int (*fn)(const char *, void *), void *data); +GIT_EXTERN(int) git_config_get_multivar(git_config *cfg, const char *name, const char *regexp, int (*fn)(const git_config_entry *, void *), void *data); /** - * Set the value of an integer config variable. + * Set the value of an integer config variable in the config file + * with the highest level (usually the local one). * * @param cfg where to look for the variable * @param name the variable's name @@ -266,7 +350,8 @@ GIT_EXTERN(int) git_config_get_multivar(git_config *cfg, const char *name, const GIT_EXTERN(int) git_config_set_int32(git_config *cfg, const char *name, int32_t value); /** - * Set the value of a long integer config variable. + * Set the value of a long integer config variable in the config file + * with the highest level (usually the local one). * * @param cfg where to look for the variable * @param name the variable's name @@ -276,7 +361,8 @@ GIT_EXTERN(int) git_config_set_int32(git_config *cfg, const char *name, int32_t GIT_EXTERN(int) git_config_set_int64(git_config *cfg, const char *name, int64_t value); /** - * Set the value of a boolean config variable. + * Set the value of a boolean config variable in the config file + * with the highest level (usually the local one). * * @param cfg where to look for the variable * @param name the variable's name @@ -286,7 +372,8 @@ GIT_EXTERN(int) git_config_set_int64(git_config *cfg, const char *name, int64_t GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value); /** - * Set the value of a string config variable. + * Set the value of a string config variable in the config file + * with the highest level (usually the local one). * * A copy of the string is made and the user is free to use it * afterwards. @@ -298,9 +385,8 @@ GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value */ GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const char *value); - /** - * Set a multivar + * Set a multivar in the local config file. * * @param cfg where to look for the variable * @param name the variable's name @@ -310,7 +396,8 @@ GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const c GIT_EXTERN(int) git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); /** - * Delete a config variable + * Delete a config variable from the config file + * with the highest level (usually the local one). * * @param cfg the configuration * @param name the variable to delete @@ -332,7 +419,7 @@ GIT_EXTERN(int) git_config_delete(git_config *cfg, const char *name); */ GIT_EXTERN(int) git_config_foreach( git_config *cfg, - int (*callback)(const char *var_name, const char *value, void *payload), + int (*callback)(const git_config_entry *, void *payload), void *payload); /** @@ -351,7 +438,7 @@ GIT_EXTERN(int) git_config_foreach( GIT_EXTERN(int) git_config_foreach_match( git_config *cfg, const char *regexp, - int (*callback)(const char *var_name, const char *value, void *payload), + int (*callback)(const git_config_entry *entry, void *payload), void *payload); /** @@ -390,6 +477,57 @@ GIT_EXTERN(int) git_config_foreach_match( */ GIT_EXTERN(int) git_config_get_mapped(int *out, git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n); +/** + * Maps a string value to an integer constant + * + * @param out place to store the result of the parsing + * @param maps array of `git_cvar_map` objects specifying the possible mappings + * @param map_n number of mapping objects in `maps` + * @param value value to parse + */ +GIT_EXTERN(int) git_config_lookup_map_value( + int *out, + git_cvar_map *maps, + size_t map_n, + const char *value); + +/** + * Parse a string value as a bool. + * + * Valid values for true are: 'true', 'yes', 'on', 1 or any + * number different from 0 + * Valid values for false are: 'false', 'no', 'off', 0 + * + * @param out place to store the result of the parsing + * @param value value to parse + */ +GIT_EXTERN(int) git_config_parse_bool(int *out, const char *value); + +/** + * Parse a string value as an int64. + * + * An optional value suffix of 'k', 'm', or 'g' will + * cause the value to be multiplied by 1024, 1048576, + * or 1073741824 prior to output. + * + * @param out place to store the result of the parsing + * @param value value to parse + */ +GIT_EXTERN(int) git_config_parse_int64(int64_t *out, const char *value); + +/** + * Parse a string value as an int32. + * + * An optional value suffix of 'k', 'm', or 'g' will + * cause the value to be multiplied by 1024, 1048576, + * or 1073741824 prior to output. + * + * @param out place to store the result of the parsing + * @param value value to parse + */ +GIT_EXTERN(int) git_config_parse_int32(int32_t *out, const char *value); + + /** @} */ GIT_END_DECL #endif diff --git a/src/config.c b/src/config.c index b89c16b1c..f9bd205af 100644 --- a/src/config.c +++ b/src/config.c @@ -17,21 +17,29 @@ #include typedef struct { + git_refcount rc; + git_config_file *file; - int priority; + unsigned int level; } file_internal; +static void file_internal_free(file_internal *internal) +{ + git_config_file *file; + + file = internal->file; + file->free(file); + git__free(internal); +} + static void config_free(git_config *cfg) { unsigned int i; - git_config_file *file; file_internal *internal; for(i = 0; i < cfg->files.length; ++i){ internal = git_vector_get(&cfg->files, i); - file = internal->file; - file->free(file); - git__free(internal); + GIT_REFCOUNT_DEC(internal, file_internal_free); } git_vector_free(&cfg->files); @@ -51,7 +59,7 @@ static int config_backend_cmp(const void *a, const void *b) const file_internal *bk_a = (const file_internal *)(a); const file_internal *bk_b = (const file_internal *)(b); - return bk_b->priority - bk_a->priority; + return bk_b->level - bk_a->level; } int git_config_new(git_config **out) @@ -73,20 +81,25 @@ int git_config_new(git_config **out) return 0; } -int git_config_add_file_ondisk(git_config *cfg, const char *path, int priority) +int git_config_add_file_ondisk( + git_config *cfg, + const char *path, + unsigned int level, + int force) { git_config_file *file = NULL; + int res; if (git_config_file__ondisk(&file, path) < 0) return -1; - if (git_config_add_file(cfg, file, priority) < 0) { + if ((res = git_config_add_file(cfg, file, level, force)) < 0) { /* * free manually; the file is not owned by the config * instance yet and will not be freed on cleanup */ file->free(file); - return -1; + return res; } return 0; @@ -97,7 +110,7 @@ int git_config_open_ondisk(git_config **cfg, const char *path) if (git_config_new(cfg) < 0) return -1; - if (git_config_add_file_ondisk(*cfg, path, 1) < 0) { + if (git_config_add_file_ondisk(*cfg, path, GIT_CONFIG_LEVEL_LOCAL, 0) < 0) { git_config_free(*cfg); return -1; } @@ -105,30 +118,152 @@ int git_config_open_ondisk(git_config **cfg, const char *path) return 0; } -int git_config_add_file(git_config *cfg, git_config_file *file, int priority) +static int find_internal_file_by_level( + file_internal **internal_out, + git_config *cfg, + int level) +{ + int pos = -1; + file_internal *internal; + unsigned int i; + + assert(cfg->files.length); + + /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config file + * which has the highest level. As config files are stored in a vector + * sorted by decreasing order of level, getting the file at position 0 + * will do the job. + */ + if (level == GIT_CONFIG_HIGHEST_LEVEL) { + pos = 0; + } else { + git_vector_foreach(&cfg->files, i, internal) { + if (internal->level == (unsigned int)level) + pos = i; + } + } + + if (pos == -1) { + giterr_set(GITERR_CONFIG, + "No config file exists for the given level '%i'", level); + return GIT_ENOTFOUND; + } + + *internal_out = git_vector_get(&cfg->files, pos); + + return 0; +} + +static int duplicate_level(void **old_raw, void *new_raw) +{ + file_internal **old = (file_internal **)old_raw; + + GIT_UNUSED(new_raw); + + giterr_set(GITERR_CONFIG, "A file with the same level (%i) has already been added to the config", (*old)->level); + return GIT_EEXISTS; +} + +static void try_remove_existing_file_internal( + git_config *cfg, + unsigned int level) +{ + int pos = -1; + file_internal *internal; + unsigned int i; + + git_vector_foreach(&cfg->files, i, internal) { + if (internal->level == level) + pos = i; + } + + if (pos == -1) + return; + + internal = git_vector_get(&cfg->files, pos); + + if (git_vector_remove(&cfg->files, pos) < 0) + return; + + GIT_REFCOUNT_DEC(internal, file_internal_free); +} + +static int git_config__add_internal( + git_config *cfg, + file_internal *internal, + unsigned int level, + int force) +{ + int result; + + /* delete existing config file for level if it exists */ + if (force) + try_remove_existing_file_internal(cfg, level); + + if ((result = git_vector_insert_sorted(&cfg->files, + internal, &duplicate_level)) < 0) + return result; + + git_vector_sort(&cfg->files); + internal->file->cfg = cfg; + + GIT_REFCOUNT_INC(internal); + + return 0; +} + +int git_config_open_level( + git_config **cfg_out, + git_config *cfg_parent, + unsigned int level) +{ + git_config *cfg; + file_internal *internal; + int res; + + if ((res = find_internal_file_by_level(&internal, cfg_parent, level)) < 0) + return res; + + if ((res = git_config_new(&cfg)) < 0) + return res; + + if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) { + git_config_free(cfg); + return res; + } + + *cfg_out = cfg; + + return 0; +} + +int git_config_add_file( + git_config *cfg, + git_config_file *file, + unsigned int level, + int force) { file_internal *internal; int result; assert(cfg && file); - if ((result = file->open(file)) < 0) + if ((result = file->open(file, level)) < 0) return result; internal = git__malloc(sizeof(file_internal)); GITERR_CHECK_ALLOC(internal); + memset(internal, 0x0, sizeof(file_internal)); + internal->file = file; - internal->priority = priority; + internal->level = level; - if (git_vector_insert(&cfg->files, internal) < 0) { + if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) { git__free(internal); - return -1; + return result; } - git_vector_sort(&cfg->files); - internal->file->cfg = cfg; - return 0; } @@ -137,7 +272,7 @@ int git_config_add_file(git_config *cfg, git_config_file *file, int priority) */ int git_config_foreach( - git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) + git_config *cfg, int (*fn)(const git_config_entry *, void *), void *data) { return git_config_foreach_match(cfg, NULL, fn, data); } @@ -145,7 +280,7 @@ int git_config_foreach( int git_config_foreach_match( git_config *cfg, const char *regexp, - int (*fn)(const char *, const char *, void *), + int (*fn)(const git_config_entry *, void *), void *data) { int ret = 0; @@ -164,10 +299,8 @@ int git_config_foreach_match( int git_config_delete(git_config *cfg, const char *name) { - file_internal *internal; git_config_file *file; - - assert(cfg->files.length); + file_internal *internal; internal = git_vector_get(&cfg->files, 0); file = internal->file; @@ -198,10 +331,8 @@ int git_config_set_bool(git_config *cfg, const char *name, int value) int git_config_set_string(git_config *cfg, const char *name, const char *value) { - file_internal *internal; git_config_file *file; - - assert(cfg->files.length); + file_internal *internal; internal = git_vector_get(&cfg->files, 0); file = internal->file; @@ -209,105 +340,9 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return file->set(file, name, value); } -static int parse_int64(int64_t *out, const char *value) -{ - const char *num_end; - int64_t num; - - if (git__strtol64(&num, value, &num_end, 0) < 0) - return -1; - - switch (*num_end) { - case 'g': - case 'G': - num *= 1024; - /* fallthrough */ - - case 'm': - case 'M': - num *= 1024; - /* fallthrough */ - - case 'k': - case 'K': - num *= 1024; - - /* check that that there are no more characters after the - * given modifier suffix */ - if (num_end[1] != '\0') - return -1; - - /* fallthrough */ - - case '\0': - *out = num; - return 0; - - default: - return -1; - } -} - -static int parse_int32(int32_t *out, const char *value) -{ - int64_t tmp; - int32_t truncate; - - if (parse_int64(&tmp, value) < 0) - return -1; - - truncate = tmp & 0xFFFFFFFF; - if (truncate != tmp) - return -1; - - *out = truncate; - return 0; -} - /*********** * Getters ***********/ -int git_config_lookup_map_value( - git_cvar_map *maps, size_t map_n, const char *value, int *out) -{ - size_t i; - - if (!value) - return GIT_ENOTFOUND; - - for (i = 0; i < map_n; ++i) { - git_cvar_map *m = maps + i; - - switch (m->cvar_type) { - case GIT_CVAR_FALSE: - case GIT_CVAR_TRUE: { - int bool_val; - - if (git__parse_bool(&bool_val, value) == 0 && - bool_val == (int)m->cvar_type) { - *out = m->map_value; - return 0; - } - break; - } - - case GIT_CVAR_INT32: - if (parse_int32(out, value) == 0) - return 0; - break; - - case GIT_CVAR_STRING: - if (strcasecmp(value, m->str_match) == 0) { - *out = m->map_value; - return 0; - } - break; - } - } - - return GIT_ENOTFOUND; -} - int git_config_get_mapped( int *out, git_config *cfg, @@ -318,16 +353,10 @@ int git_config_get_mapped( const char *value; int ret; - ret = git_config_get_string(&value, cfg, name); - if (ret < 0) + if ((ret = git_config_get_string(&value, cfg, name)) < 0) return ret; - if (!git_config_lookup_map_value(maps, map_n, value, out)) - return 0; - - giterr_set(GITERR_CONFIG, - "Failed to map the '%s' config variable with a valid value", name); - return -1; + return git_config_lookup_map_value(out, maps, map_n, value); } int git_config_get_int64(int64_t *out, git_config *cfg, const char *name) @@ -335,16 +364,10 @@ int git_config_get_int64(int64_t *out, git_config *cfg, const char *name) const char *value; int ret; - ret = git_config_get_string(&value, cfg, name); - if (ret < 0) + if ((ret = git_config_get_string(&value, cfg, name)) < 0) return ret; - if (parse_int64(out, value) < 0) { - giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value); - return -1; - } - - return 0; + return git_config_parse_int64(out, value); } int git_config_get_int32(int32_t *out, git_config *cfg, const char *name) @@ -352,16 +375,10 @@ int git_config_get_int32(int32_t *out, git_config *cfg, const char *name) const char *value; int ret; - ret = git_config_get_string(&value, cfg, name); - if (ret < 0) + if ((ret = git_config_get_string(&value, cfg, name)) < 0) return ret; - if (parse_int32(out, value) < 0) { - giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value); - return -1; - } - - return 0; + return git_config_parse_int32(out, value); } int git_config_get_bool(int *out, git_config *cfg, const char *name) @@ -369,20 +386,24 @@ int git_config_get_bool(int *out, git_config *cfg, const char *name) const char *value; int ret; - ret = git_config_get_string(&value, cfg, name); - if (ret < 0) + if ((ret = git_config_get_string(&value, cfg, name)) < 0) return ret; - if (git__parse_bool(out, value) == 0) - return 0; + return git_config_parse_bool(out, value); +} - if (parse_int32(out, value) == 0) { - *out = !!(*out); - return 0; - } +static int get_string_at_file(const char **out, git_config_file *file, const char *name) +{ + const git_config_entry *entry; + int res; - giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value); - return -1; + *out = NULL; + + res = file->get(file, name, &entry); + if (res != GIT_ENOTFOUND) + *out = entry->value; + + return res; } int git_config_get_string(const char **out, git_config *cfg, const char *name) @@ -392,6 +413,23 @@ int git_config_get_string(const char **out, git_config *cfg, const char *name) assert(cfg->files.length); + git_vector_foreach(&cfg->files, i, internal) { + int res = get_string_at_file(out, internal->file, name); + + if (res != GIT_ENOTFOUND) + return res; + } + + return GIT_ENOTFOUND; +} + +int git_config_get_config_entry(const git_config_entry **out, git_config *cfg, const char *name) +{ + file_internal *internal; + unsigned int i; + + assert(cfg->files.length); + *out = NULL; git_vector_foreach(&cfg->files, i, internal) { @@ -405,7 +443,7 @@ int git_config_get_string(const char **out, git_config *cfg, const char *name) } int git_config_get_multivar(git_config *cfg, const char *name, const char *regexp, - int (*fn)(const char *value, void *data), void *data) + int (*fn)(const git_config_entry *entry, void *data), void *data) { file_internal *internal; git_config_file *file; @@ -431,20 +469,13 @@ int git_config_get_multivar(git_config *cfg, const char *name, const char *regex int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) { - file_internal *internal; git_config_file *file; - int ret = GIT_ENOTFOUND; - size_t i; + file_internal *internal; - for (i = cfg->files.length; i > 0; --i) { - internal = git_vector_get(&cfg->files, i - 1); - file = internal->file; - ret = file->set_multivar(file, name, regexp, value); - if (ret < 0 && ret != GIT_ENOTFOUND) - return ret; - } + internal = git_vector_get(&cfg->files, 0); + file = internal->file; - return 0; + return file->set_multivar(file, name, regexp, value); } int git_config_find_global_r(git_buf *path) @@ -541,13 +572,16 @@ int git_config_open_default(git_config **out) error = git_config_new(&cfg); if (!error && !git_config_find_global_r(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, 3); + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_GLOBAL, 0); if (!error && !git_config_find_xdg_r(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, 2); + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_XDG, 0); if (!error && !git_config_find_system_r(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, 1); + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_SYSTEM, 0); git_buf_free(&buf); @@ -560,3 +594,129 @@ int git_config_open_default(git_config **out) return error; } + +/*********** + * Parsers + ***********/ +int git_config_lookup_map_value( + int *out, + git_cvar_map *maps, + size_t map_n, + const char *value) +{ + size_t i; + + if (!value) + goto fail_parse; + + for (i = 0; i < map_n; ++i) { + git_cvar_map *m = maps + i; + + switch (m->cvar_type) { + case GIT_CVAR_FALSE: + case GIT_CVAR_TRUE: { + int bool_val; + + if (git__parse_bool(&bool_val, value) == 0 && + bool_val == (int)m->cvar_type) { + *out = m->map_value; + return 0; + } + break; + } + + case GIT_CVAR_INT32: + if (git_config_parse_int32(out, value) == 0) + return 0; + break; + + case GIT_CVAR_STRING: + if (strcasecmp(value, m->str_match) == 0) { + *out = m->map_value; + return 0; + } + break; + } + } + +fail_parse: + giterr_set(GITERR_CONFIG, "Failed to map '%s'", value); + return -1; +} + +int git_config_parse_bool(int *out, const char *value) +{ + if (git__parse_bool(out, value) == 0) + return 0; + + if (git_config_parse_int32(out, value) == 0) { + *out = !!(*out); + return 0; + } + + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value); + return -1; +} + +int git_config_parse_int64(int64_t *out, const char *value) +{ + const char *num_end; + int64_t num; + + if (git__strtol64(&num, value, &num_end, 0) < 0) + goto fail_parse; + + switch (*num_end) { + case 'g': + case 'G': + num *= 1024; + /* fallthrough */ + + case 'm': + case 'M': + num *= 1024; + /* fallthrough */ + + case 'k': + case 'K': + num *= 1024; + + /* check that that there are no more characters after the + * given modifier suffix */ + if (num_end[1] != '\0') + return -1; + + /* fallthrough */ + + case '\0': + *out = num; + return 0; + + default: + goto fail_parse; + } + +fail_parse: + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value); + return -1; +} + +int git_config_parse_int32(int32_t *out, const char *value) +{ + int64_t tmp; + int32_t truncate; + + if (git_config_parse_int64(&tmp, value) < 0) + goto fail_parse; + + truncate = tmp & 0xFFFFFFFF; + if (truncate != tmp) + goto fail_parse; + + *out = truncate; + return 0; + +fail_parse: + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value); + return -1; +} diff --git a/src/config.h b/src/config.h index 471b42dad..16b8413b7 100644 --- a/src/config.h +++ b/src/config.h @@ -27,9 +27,4 @@ extern int git_config_find_global_r(git_buf *global_config_path); extern int git_config_find_xdg_r(git_buf *system_config_path); extern int git_config_find_system_r(git_buf *system_config_path); -extern int git_config_parse_bool(int *out, const char *bool_string); - -extern int git_config_lookup_map_value( - git_cvar_map *maps, size_t map_n, const char *value, int *out); - #endif diff --git a/src/config_file.c b/src/config_file.c index 12ae4a214..92fe13296 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -22,15 +22,9 @@ GIT__USE_STRMAP; typedef struct cvar_t { struct cvar_t *next; - char *key; /* TODO: we might be able to get rid of this */ - char *value; + git_config_entry *entry; } cvar_t; -typedef struct { - struct cvar_t *head; - struct cvar_t *tail; -} cvar_t_list; - #define CVAR_LIST_HEAD(list) ((list)->head) #define CVAR_LIST_TAIL(list) ((list)->tail) @@ -84,7 +78,7 @@ typedef struct { char *file_path; } diskfile_backend; -static int config_parse(diskfile_backend *cfg_file); +static int config_parse(diskfile_backend *cfg_file, unsigned int level); static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value); static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value); static char *escape_value(const char *ptr); @@ -100,8 +94,9 @@ static void cvar_free(cvar_t *var) if (var == NULL) return; - git__free(var->key); - git__free(var->value); + git__free((char*)var->entry->name); + git__free((char *)var->entry->value); + git__free(var->entry); git__free(var); } @@ -150,7 +145,7 @@ static void free_vars(git_strmap *values) git_strmap_free(values); } -static int config_open(git_config_file *cfg) +static int config_open(git_config_file *cfg, unsigned int level) { int res; diskfile_backend *b = (diskfile_backend *)cfg; @@ -165,7 +160,7 @@ static int config_open(git_config_file *cfg) if (res == GIT_ENOTFOUND) return 0; - if (res < 0 || config_parse(b) < 0) { + if (res < 0 || config_parse(b, level) < 0) { free_vars(b->values); b->values = NULL; git_buf_free(&b->reader.buffer); @@ -191,7 +186,7 @@ static void backend_free(git_config_file *_backend) static int file_foreach( git_config_file *backend, const char *regexp, - int (*fn)(const char *, const char *, void *), + int (*fn)(const git_config_entry *, void *), void *data) { diskfile_backend *b = (diskfile_backend *)backend; @@ -220,7 +215,7 @@ static int file_foreach( continue; /* abort iterator on non-zero return value */ - if (fn(key, var->value, data)) { + if (fn(var->entry, data)) { giterr_clear(); result = GIT_EUSER; goto cleanup; @@ -263,8 +258,8 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) } /* don't update if old and new values already match */ - if ((!existing->value && !value) || - (existing->value && value && !strcmp(existing->value, value))) + if ((!existing->entry->value && !value) || + (existing->entry->value && value && !strcmp(existing->entry->value, value))) return 0; if (value) { @@ -274,10 +269,10 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) GITERR_CHECK_ALLOC(esc_value); } - git__free(existing->value); - existing->value = tmp; + git__free((void *)existing->entry->value); + existing->entry->value = tmp; - ret = config_write(b, existing->key, NULL, esc_value); + ret = config_write(b, existing->entry->name, NULL, esc_value); git__free(esc_value); return ret; @@ -285,15 +280,17 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) var = git__malloc(sizeof(cvar_t)); GITERR_CHECK_ALLOC(var); - memset(var, 0x0, sizeof(cvar_t)); + var->entry = git__malloc(sizeof(git_config_entry)); + GITERR_CHECK_ALLOC(var->entry); + memset(var->entry, 0x0, sizeof(git_config_entry)); - var->key = key; - var->value = NULL; + var->entry->name = key; + var->entry->value = NULL; if (value) { - var->value = git__strdup(value); - GITERR_CHECK_ALLOC(var->value); + var->entry->value = git__strdup(value); + GITERR_CHECK_ALLOC(var->entry->value); esc_value = escape_value(value); GITERR_CHECK_ALLOC(esc_value); } @@ -317,7 +314,7 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) /* * Internal function that actually gets the value in string form */ -static int config_get(git_config_file *cfg, const char *name, const char **out) +static int config_get(git_config_file *cfg, const char *name, const git_config_entry **out) { diskfile_backend *b = (diskfile_backend *)cfg; char *key; @@ -333,7 +330,7 @@ static int config_get(git_config_file *cfg, const char *name, const char **out) if (!git_strmap_valid_index(b->values, pos)) return GIT_ENOTFOUND; - *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->value; + *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->entry; return 0; } @@ -342,7 +339,7 @@ static int config_get_multivar( git_config_file *cfg, const char *name, const char *regex_str, - int (*fn)(const char *, void *), + int (*fn)(const git_config_entry *, void *), void *data) { cvar_t *var; @@ -376,10 +373,10 @@ static int config_get_multivar( /* and throw the callback only on the variables that * match the regex */ do { - if (regexec(®ex, var->value, 0, NULL, 0) == 0) { + if (regexec(®ex, var->entry->value, 0, NULL, 0) == 0) { /* early termination by the user is not an error; * just break and return successfully */ - if (fn(var->value, data) < 0) + if (fn(var->entry, data) < 0) break; } @@ -391,7 +388,7 @@ static int config_get_multivar( do { /* early termination by the user is not an error; * just break and return successfully */ - if (fn(var->value, data) < 0) + if (fn(var->entry, data) < 0) break; var = var->next; @@ -434,12 +431,12 @@ static int config_set_multivar( } for (;;) { - if (regexec(&preg, var->value, 0, NULL, 0) == 0) { + if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) { char *tmp = git__strdup(value); GITERR_CHECK_ALLOC(tmp); - git__free(var->value); - var->value = tmp; + git__free((void *)var->entry->value); + var->entry->value = tmp; replaced = 1; } @@ -453,14 +450,18 @@ static int config_set_multivar( if (!replaced) { newvar = git__malloc(sizeof(cvar_t)); GITERR_CHECK_ALLOC(newvar); - memset(newvar, 0x0, sizeof(cvar_t)); + newvar->entry = git__malloc(sizeof(git_config_entry)); + GITERR_CHECK_ALLOC(newvar->entry); + memset(newvar->entry, 0x0, sizeof(git_config_entry)); - newvar->key = git__strdup(var->key); - GITERR_CHECK_ALLOC(newvar->key); + newvar->entry->name = git__strdup(var->entry->name); + GITERR_CHECK_ALLOC(newvar->entry->name); - newvar->value = git__strdup(value); - GITERR_CHECK_ALLOC(newvar->value); + newvar->entry->value = git__strdup(value); + GITERR_CHECK_ALLOC(newvar->entry->value); + + newvar->entry->level = var->entry->level; var->next = newvar; } @@ -501,7 +502,7 @@ static int config_delete(git_config_file *cfg, const char *name) git_strmap_delete_at(b->values, pos); - result = config_write(b, var->key, NULL, NULL); + result = config_write(b, var->entry->name, NULL, NULL); cvar_free(var); return result; @@ -898,7 +899,7 @@ static int strip_comments(char *line, int in_quotes) return quote_count; } -static int config_parse(diskfile_backend *cfg_file) +static int config_parse(diskfile_backend *cfg_file, unsigned int level) { int c; char *current_section = NULL; @@ -946,8 +947,10 @@ static int config_parse(diskfile_backend *cfg_file) var = git__malloc(sizeof(cvar_t)); GITERR_CHECK_ALLOC(var); - memset(var, 0x0, sizeof(cvar_t)); + var->entry = git__malloc(sizeof(git_config_entry)); + GITERR_CHECK_ALLOC(var->entry); + memset(var->entry, 0x0, sizeof(git_config_entry)); git__strtolower(var_name); git_buf_printf(&buf, "%s.%s", current_section, var_name); @@ -956,13 +959,14 @@ static int config_parse(diskfile_backend *cfg_file) if (git_buf_oom(&buf)) return -1; - var->key = git_buf_detach(&buf); - var->value = var_value; + var->entry->name = git_buf_detach(&buf); + var->entry->value = var_value; + var->entry->level = level; /* Add or append the new config option */ - pos = git_strmap_lookup_index(cfg_file->values, var->key); + pos = git_strmap_lookup_index(cfg_file->values, var->entry->name); if (!git_strmap_valid_index(cfg_file->values, pos)) { - git_strmap_insert(cfg_file->values, var->key, var, result); + git_strmap_insert(cfg_file->values, var->entry->name, var, result); if (result < 0) break; result = 0; diff --git a/src/config_file.h b/src/config_file.h index bf687b516..b500dd64f 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -9,9 +9,9 @@ #include "git2/config.h" -GIT_INLINE(int) git_config_file_open(git_config_file *cfg) +GIT_INLINE(int) git_config_file_open(git_config_file *cfg, unsigned int level) { - return cfg->open(cfg); + return cfg->open(cfg, level); } GIT_INLINE(void) git_config_file_free(git_config_file *cfg) @@ -20,7 +20,7 @@ GIT_INLINE(void) git_config_file_free(git_config_file *cfg) } GIT_INLINE(int) git_config_file_get_string( - const char **out, git_config_file *cfg, const char *name) + const git_config_entry **out, git_config_file *cfg, const char *name) { return cfg->get(cfg, name, out); } @@ -39,7 +39,7 @@ GIT_INLINE(int) git_config_file_delete( GIT_INLINE(int) git_config_file_foreach( git_config_file *cfg, - int (*fn)(const char *key, const char *value, void *data), + int (*fn)(const git_config_entry *entry, void *data), void *data) { return cfg->foreach(cfg, NULL, fn, data); @@ -48,7 +48,7 @@ GIT_INLINE(int) git_config_file_foreach( 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), + int (*fn)(const git_config_entry *entry, void *data), void *data) { return cfg->foreach(cfg, regexp, fn, data); diff --git a/src/remote.c b/src/remote.c index 5ef791d39..e05ea059f 100644 --- a/src/remote.c +++ b/src/remote.c @@ -637,12 +637,12 @@ struct cb_data { regex_t *preg; }; -static int remote_list_cb(const char *name, const char *value, void *data_) +static int remote_list_cb(const git_config_entry *entry, void *data_) { struct cb_data *data = (struct cb_data *)data_; size_t nmatch = 2; regmatch_t pmatch[2]; - GIT_UNUSED(value); + const char *name = entry->name; if (!regexec(data->preg, name, nmatch, pmatch, 0)) { char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so); diff --git a/src/repository.c b/src/repository.c index db0888a89..43e0eda8f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -461,23 +461,23 @@ static int load_config( &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0) goto on_error; - if (git_config_add_file_ondisk(cfg, config_path.ptr, 4) < 0) + if (git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0) < 0) goto on_error; git_buf_free(&config_path); if (global_config_path != NULL) { - if (git_config_add_file_ondisk(cfg, global_config_path, 3) < 0) + if (git_config_add_file_ondisk(cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0) < 0) goto on_error; } if (xdg_config_path != NULL) { - if (git_config_add_file_ondisk(cfg, xdg_config_path, 2) < 0) + if (git_config_add_file_ondisk(cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0) < 0) goto on_error; } if (system_config_path != NULL) { - if (git_config_add_file_ondisk(cfg, system_config_path, 1) < 0) + if (git_config_add_file_ondisk(cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0) < 0) goto on_error; } diff --git a/src/submodule.c b/src/submodule.c index 180528641..e3657f9ad 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -72,7 +72,7 @@ static int submodule_get(git_submodule **, git_repository *, const char *, const static void submodule_release(git_submodule *sm, int decr); static int submodule_load_from_index(git_repository *, const git_index_entry *); static int submodule_load_from_head(git_repository*, const char*, const git_oid*); -static int submodule_load_from_config(const char *, const char *, void *); +static int submodule_load_from_config(const git_config_entry *, void *); static int submodule_load_from_wd_lite(git_submodule *, const char *, void *); static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool); static void submodule_mode_mismatch(git_repository *, const char *, unsigned int); @@ -974,11 +974,12 @@ static int submodule_config_error(const char *property, const char *value) } static int submodule_load_from_config( - const char *key, const char *value, void *data) + const git_config_entry *entry, void *data) { git_repository *repo = data; git_strmap *smcfg = repo->submodules; const char *namestart, *property, *alternate = NULL; + const char *key = entry->name, *value = entry->value; git_buf name = GIT_BUF_INIT; git_submodule *sm; bool is_path; @@ -1055,7 +1056,7 @@ static int submodule_load_from_config( else if (strcasecmp(property, "update") == 0) { int val; if (git_config_lookup_map_value( - _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) + &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) return submodule_config_error("update", value); sm->update_default = sm->update = (git_submodule_update_t)val; } @@ -1066,7 +1067,7 @@ static int submodule_load_from_config( else if (strcasecmp(property, "ignore") == 0) { int val; if (git_config_lookup_map_value( - _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) + &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) return submodule_config_error("ignore", value); sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val; } @@ -1204,7 +1205,7 @@ static git_config_file *open_gitmodules( if (git_config_file__ondisk(&mods, path.ptr) < 0) mods = NULL; /* open should only fail here if the file is malformed */ - else if (git_config_file_open(mods) < 0) { + else if (git_config_file_open(mods, GIT_CONFIG_LEVEL_LOCAL) < 0) { git_config_file_free(mods); mods = NULL; } diff --git a/tests-clar/config/configlevel.c b/tests-clar/config/configlevel.c new file mode 100644 index 000000000..d947856fa --- /dev/null +++ b/tests-clar/config/configlevel.c @@ -0,0 +1,59 @@ +#include "clar_libgit2.h" + +void test_config_configlevel__adding_the_same_level_twice_returns_EEXISTS(void) +{ + int error; + git_config *cfg; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_LOCAL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, 0)); + error = git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), + GIT_CONFIG_LEVEL_GLOBAL, 0); + + cl_git_fail(error); + cl_assert_equal_i(GIT_EEXISTS, error); + + git_config_free(cfg); +} + +void test_config_configlevel__can_replace_a_config_file_at_an_existing_level(void) +{ + git_config *cfg; + const char *s; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_LOCAL, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_LOCAL, 1)); + + cl_git_pass(git_config_get_string(&s, cfg, "core.stringglobal")); + cl_assert_equal_s("don't find me!", s); + + git_config_free(cfg); +} + +void test_config_configlevel__can_read_from_a_single_level_focused_file_after_parent_config_has_been_freed(void) +{ + git_config *cfg; + git_config *single_level_cfg; + const char *s; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_GLOBAL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_LOCAL, 0)); + + cl_git_pass(git_config_open_level(&single_level_cfg, cfg, GIT_CONFIG_LEVEL_LOCAL)); + + git_config_free(cfg); + + cl_git_pass(git_config_get_string(&s, single_level_cfg, "core.stringglobal")); + cl_assert_equal_s("don't find me!", s); + + git_config_free(single_level_cfg); +} diff --git a/tests-clar/config/multivar.c b/tests-clar/config/multivar.c index 3b40cd09a..26537e20a 100644 --- a/tests-clar/config/multivar.c +++ b/tests-clar/config/multivar.c @@ -12,13 +12,11 @@ void test_config_multivar__cleanup(void) cl_fixture_cleanup("config"); } -static int mv_read_cb(const char *name, const char *value, void *data) +static int mv_read_cb(const git_config_entry *entry, void *data) { int *n = (int *) data; - GIT_UNUSED(value); - - if (!strcmp(name, _name)) + if (!strcmp(entry->name, _name)) (*n)++; return 0; @@ -37,11 +35,11 @@ void test_config_multivar__foreach(void) git_config_free(cfg); } -static int cb(const char *val, void *data) +static int cb(const git_config_entry *entry, void *data) { int *n = (int *) data; - GIT_UNUSED(val); + GIT_UNUSED(entry); (*n)++; diff --git a/tests-clar/config/new.c b/tests-clar/config/new.c index fae3ce941..6bd719fba 100644 --- a/tests-clar/config/new.c +++ b/tests-clar/config/new.c @@ -9,21 +9,16 @@ void test_config_new__write_new_config(void) { const char *out; - struct git_config_file *file; git_config *config; - cl_git_pass(git_config_file__ondisk(&file, TEST_CONFIG)); - cl_git_pass(git_config_new(&config)); - cl_git_pass(git_config_add_file(config, file, 0)); + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); cl_git_pass(git_config_set_string(config, "color.ui", "auto")); cl_git_pass(git_config_set_string(config, "core.editor", "ed")); git_config_free(config); - cl_git_pass(git_config_file__ondisk(&file, TEST_CONFIG)); - cl_git_pass(git_config_new(&config)); - cl_git_pass(git_config_add_file(config, file, 0)); + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); cl_git_pass(git_config_get_string(&out, config, "color.ui")); cl_assert_equal_s(out, "auto"); diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c index fcd22463d..cf781e6bf 100644 --- a/tests-clar/config/read.c +++ b/tests-clar/config/read.c @@ -191,22 +191,24 @@ 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) +static int count_cfg_entries_and_compare_levels( + const git_config_entry *entry, void *payload) { int *count = payload; - GIT_UNUSED(var_name); - GIT_UNUSED(value); + + if (!strcmp(entry->value, "7") || !strcmp(entry->value, "17")) + cl_assert(entry->level == GIT_CONFIG_LEVEL_GLOBAL); + else + cl_assert(entry->level == GIT_CONFIG_LEVEL_SYSTEM); + (*count)++; return 0; } -static int cfg_callback_countdown( - const char *var_name, const char *value, void *payload) +static int cfg_callback_countdown(const git_config_entry *entry, void *payload) { int *count = payload; - GIT_UNUSED(var_name); - GIT_UNUSED(value); + GIT_UNUSED(entry); (*count)--; if (*count == 0) return -100; @@ -218,11 +220,15 @@ void test_config_read__foreach(void) git_config *cfg; int count, ret; - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, 0)); count = 0; - cl_git_pass(git_config_foreach(cfg, count_cfg_entries, &count)); - cl_assert_equal_i(5, count); + cl_git_pass(git_config_foreach(cfg, count_cfg_entries_and_compare_levels, &count)); + cl_assert_equal_i(7, count); count = 3; cl_git_fail(ret = git_config_foreach(cfg, cfg_callback_countdown, &count)); @@ -231,6 +237,14 @@ void test_config_read__foreach(void) git_config_free(cfg); } +static int count_cfg_entries(const git_config_entry *entry, void *payload) +{ + int *count = payload; + GIT_UNUSED(entry); + (*count)++; + return 0; +} + void test_config_read__foreach_match(void) { git_config *cfg; @@ -282,31 +296,129 @@ void test_config_read__whitespace_not_required_around_assignment(void) git_config_free(cfg); } -#if 0 - -BEGIN_TEST(config10, "a repo's config overrides the global config") - git_repository *repo; +void test_config_read__read_git_config_entry(void) +{ git_config *cfg; - int32_t version; + const git_config_entry *entry; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, 0)); + + cl_git_pass(git_config_get_config_entry(&entry, cfg, "core.dummy2")); + cl_assert_equal_s("core.dummy2", entry->name); + cl_assert_equal_s("42", entry->value); + cl_assert_equal_i(GIT_CONFIG_LEVEL_SYSTEM, entry->level); - cl_git_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - cl_git_pass(git_repository_config(&cfg, repo, GLOBAL_CONFIG, NULL)); - cl_git_pass(git_config_get_int32(cfg, "core.repositoryformatversion", &version)); - cl_assert(version == 0); git_config_free(cfg); - git_repository_free(repo); -END_TEST +} -BEGIN_TEST(config11, "fall back to the global config") - git_repository *repo; +/* + * At the beginning of the test: + * - config9 has: core.dummy2=42 + * - config15 has: core.dummy2=7 + * - config16 has: core.dummy2=28 + */ +void test_config_read__local_config_overrides_global_config_overrides_system_config(void) +{ git_config *cfg; - int32_t num; + int32_t i; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), + GIT_CONFIG_LEVEL_LOCAL, 0)); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); + cl_assert_equal_i(28, i); - cl_git_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - cl_git_pass(git_repository_config(&cfg, repo, GLOBAL_CONFIG, NULL)); - cl_git_pass(git_config_get_int32(cfg, "core.something", &num)); - cl_assert(num == 2); git_config_free(cfg); - git_repository_free(repo); -END_TEST -#endif + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, 0)); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); + cl_assert_equal_i(7, i); + + git_config_free(cfg); +} + +/* + * At the beginning of the test: + * - config9 has: core.global does not exist + * - config15 has: core.global=17 + * - config16 has: core.global=29 + * + * And also: + * - config9 has: core.system does not exist + * - config15 has: core.system does not exist + * - config16 has: core.system=11 + */ +void test_config_read__fallback_from_local_to_global_and_from_global_to_system(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), + GIT_CONFIG_LEVEL_LOCAL, 0)); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.global")); + cl_assert_equal_i(17, i); + cl_git_pass(git_config_get_int32(&i, cfg, "core.system")); + cl_assert_equal_i(11, i); + + git_config_free(cfg); +} + +/* + * At the beginning of the test, config18 has: + * int32global = 28 + * int64global = 9223372036854775803 + * boolglobal = true + * stringglobal = I'm a global config value! + * + * And config19 has: + * int32global = -1 + * int64global = -2 + * boolglobal = false + * stringglobal = don't find me! + * + */ +void test_config_read__simple_read_from_specific_level(void) +{ + git_config *cfg, *cfg_specific; + int i; + int64_t l, expected = +9223372036854775803; + const char *s; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_GLOBAL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_SYSTEM, 0)); + + cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); + + cl_git_pass(git_config_get_int32(&i, cfg_specific, "core.int32global")); + cl_assert_equal_i(28, i); + cl_git_pass(git_config_get_int64(&l, cfg_specific, "core.int64global")); + cl_assert(l == expected); + cl_git_pass(git_config_get_bool(&i, cfg_specific, "core.boolglobal")); + cl_assert_equal_b(true, i); + cl_git_pass(git_config_get_string(&s, cfg_specific, "core.stringglobal")); + cl_assert_equal_s("I'm a global config value!", s); + + git_config_free(cfg_specific); + git_config_free(cfg); +} diff --git a/tests-clar/config/stress.c b/tests-clar/config/stress.c index 6e7db6e8f..317e877f7 100644 --- a/tests-clar/config/stress.c +++ b/tests-clar/config/stress.c @@ -4,11 +4,13 @@ #include "fileops.h" #include "posix.h" +#define TEST_CONFIG "git-test-config" + void test_config_stress__initialize(void) { git_filebuf file = GIT_FILEBUF_INIT; - cl_git_pass(git_filebuf_open(&file, "git-test-config", 0)); + cl_git_pass(git_filebuf_open(&file, TEST_CONFIG, 0)); git_filebuf_printf(&file, "[color]\n\tui = auto\n"); git_filebuf_printf(&file, "[core]\n\teditor = \n"); @@ -18,19 +20,16 @@ void test_config_stress__initialize(void) void test_config_stress__cleanup(void) { - p_unlink("git-test-config"); + p_unlink(TEST_CONFIG); } void test_config_stress__dont_break_on_invalid_input(void) { const char *editor, *color; - struct git_config_file *file; git_config *config; - cl_assert(git_path_exists("git-test-config")); - cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); - cl_git_pass(git_config_new(&config)); - cl_git_pass(git_config_add_file(config, file, 0)); + cl_assert(git_path_exists(TEST_CONFIG)); + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); cl_git_pass(git_config_get_string(&color, config, "color.ui")); cl_git_pass(git_config_get_string(&editor, config, "core.editor")); @@ -40,13 +39,10 @@ void test_config_stress__dont_break_on_invalid_input(void) void test_config_stress__comments(void) { - struct git_config_file *file; git_config *config; const char *str; - cl_git_pass(git_config_file__ondisk(&file, cl_fixture("config/config12"))); - cl_git_pass(git_config_new(&config)); - cl_git_pass(git_config_add_file(config, file, 0)); + cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config12"))); cl_git_pass(git_config_get_string(&str, config, "some.section.other")); cl_assert(!strcmp(str, "hello! \" ; ; ; ")); @@ -62,21 +58,16 @@ void test_config_stress__comments(void) void test_config_stress__escape_subsection_names(void) { - struct git_config_file *file; git_config *config; const char *str; cl_assert(git_path_exists("git-test-config")); - cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); - cl_git_pass(git_config_new(&config)); - cl_git_pass(git_config_add_file(config, file, 0)); + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); cl_git_pass(git_config_set_string(config, "some.sec\\tion.other", "foo")); git_config_free(config); - cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); - cl_git_pass(git_config_new(&config)); - cl_git_pass(git_config_add_file(config, file, 0)); + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); cl_git_pass(git_config_get_string(&str, config, "some.sec\\tion.other")); cl_assert(!strcmp("foo", str)); diff --git a/tests-clar/config/write.c b/tests-clar/config/write.c index eeda4d66a..d98d1dd53 100644 --- a/tests-clar/config/write.c +++ b/tests-clar/config/write.c @@ -3,12 +3,14 @@ void test_config_write__initialize(void) { cl_fixture_sandbox("config/config9"); + cl_fixture_sandbox("config/config15"); cl_fixture_sandbox("config/config17"); } void test_config_write__cleanup(void) { cl_fixture_cleanup("config9"); + cl_fixture_cleanup("config15"); cl_fixture_cleanup("config17"); } @@ -69,6 +71,40 @@ void test_config_write__delete_value(void) git_config_free(cfg); } +/* + * At the beginning of the test: + * - config9 has: core.dummy2=42 + * - config15 has: core.dummy2=7 + */ +void test_config_write__delete_value_at_specific_level(void) +{ + git_config *cfg, *cfg_specific; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config15")); + cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); + cl_assert(i == 7); + git_config_free(cfg); + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config9", + GIT_CONFIG_LEVEL_LOCAL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config15", + GIT_CONFIG_LEVEL_GLOBAL, 0)); + + cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); + + cl_git_pass(git_config_delete(cfg_specific, "core.dummy2")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config15")); + cl_assert(git_config_get_int32(&i, cfg, "core.dummy2") == GIT_ENOTFOUND); + cl_git_pass(git_config_set_int32(cfg, "core.dummy2", 7)); + + git_config_free(cfg_specific); + git_config_free(cfg); +} + void test_config_write__write_subsection(void) { git_config *cfg; @@ -139,7 +175,45 @@ void test_config_write__escape_value(void) git_config_free(cfg); } -void test_config_write__add_value_in_file_with_no_clrf_at_the_end(void) +void test_config_write__add_value_at_specific_level(void) +{ + git_config *cfg, *cfg_specific; + int i; + int64_t l, expected = +9223372036854775803; + const char *s; + + // open config15 as global level config file + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config9", + GIT_CONFIG_LEVEL_LOCAL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config15", + GIT_CONFIG_LEVEL_GLOBAL, 0)); + + cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); + + cl_git_pass(git_config_set_int32(cfg_specific, "core.int32global", 28)); + cl_git_pass(git_config_set_int64(cfg_specific, "core.int64global", expected)); + cl_git_pass(git_config_set_bool(cfg_specific, "core.boolglobal", true)); + cl_git_pass(git_config_set_string(cfg_specific, "core.stringglobal", "I'm a global config value!")); + git_config_free(cfg_specific); + git_config_free(cfg); + + // open config15 as local level config file + cl_git_pass(git_config_open_ondisk(&cfg, "config15")); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.int32global")); + cl_assert_equal_i(28, i); + cl_git_pass(git_config_get_int64(&l, cfg, "core.int64global")); + cl_assert(l == expected); + cl_git_pass(git_config_get_bool(&i, cfg, "core.boolglobal")); + cl_assert_equal_b(true, i); + cl_git_pass(git_config_get_string(&s, cfg, "core.stringglobal")); + cl_assert_equal_s("I'm a global config value!", s); + + git_config_free(cfg); +} + +void test_config_write__add_value_at_file_with_no_clrf_at_the_end(void) { git_config *cfg; int i; diff --git a/tests-clar/resources/config/config15 b/tests-clar/resources/config/config15 new file mode 100644 index 000000000..6d34f817b --- /dev/null +++ b/tests-clar/resources/config/config15 @@ -0,0 +1,3 @@ +[core] + dummy2 = 7 + global = 17 diff --git a/tests-clar/resources/config/config16 b/tests-clar/resources/config/config16 new file mode 100644 index 000000000..f25cdb728 --- /dev/null +++ b/tests-clar/resources/config/config16 @@ -0,0 +1,3 @@ +[core] + dummy2 = 28 + system = 11 diff --git a/tests-clar/resources/config/config18 b/tests-clar/resources/config/config18 new file mode 100644 index 000000000..cb6fd5ebc --- /dev/null +++ b/tests-clar/resources/config/config18 @@ -0,0 +1,5 @@ +[core] + int32global = 28 + int64global = 9223372036854775803 + boolglobal = true + stringglobal = I'm a global config value! \ No newline at end of file diff --git a/tests-clar/resources/config/config19 b/tests-clar/resources/config/config19 new file mode 100644 index 000000000..f3ae5a640 --- /dev/null +++ b/tests-clar/resources/config/config19 @@ -0,0 +1,5 @@ +[core] + int32global = -1 + int64global = -2 + boolglobal = false + stringglobal = don't find me! \ No newline at end of file diff --git a/tests-clar/submodule/modify.c b/tests-clar/submodule/modify.c index 0fd732cc3..bfb8c8aaf 100644 --- a/tests-clar/submodule/modify.c +++ b/tests-clar/submodule/modify.c @@ -73,12 +73,10 @@ void test_submodule_modify__add(void) git_config_free(cfg); } -static int delete_one_config( - const char *var_name, const char *value, void *payload) +static int delete_one_config(const git_config_entry *entry, void *payload) { git_config *cfg = payload; - GIT_UNUSED(value); - return git_config_delete(cfg, var_name); + return git_config_delete(cfg, entry->name); } static int init_one_submodule(