Commit 5b67d145 by Russell Belfer

Merge pull request #923 from yorah/topic/config-store

Add git_config_level support when handling config files
parents 8a89aa1f a1abe66a
...@@ -20,6 +20,28 @@ ...@@ -20,6 +20,28 @@
GIT_BEGIN_DECL 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 * Generic backend that implements the interface to
* access a configuration file * access a configuration file
*/ */
...@@ -27,13 +49,13 @@ struct git_config_file { ...@@ -27,13 +49,13 @@ struct git_config_file {
struct git_config *cfg; struct git_config *cfg;
/* Open means open the file/database and parse if necessary */ /* Open means open the file/database and parse if necessary */
int (*open)(struct git_config_file *); int (*open)(struct git_config_file *, unsigned int level);
int (*get)(struct git_config_file *, const char *key, const char **value); 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 char *, void *), void *data); 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)(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 (*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 (*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 *); void (*free)(struct git_config_file *);
}; };
...@@ -100,9 +122,9 @@ GIT_EXTERN(int) git_config_find_xdg(char *xdg_config_path, size_t length); ...@@ -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); 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 * and opens them into a single prioritized config object that can be
* used when accessing default config data outside a repository. * used when accessing default config data outside a repository.
* *
...@@ -143,14 +165,21 @@ GIT_EXTERN(int) git_config_new(git_config **out); ...@@ -143,14 +165,21 @@ GIT_EXTERN(int) git_config_new(git_config **out);
* *
* Further queries on this config object will access each * Further queries on this config object will access each
* of the config file instances in order (instances with * 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 cfg the configuration to add the file to
* @param file the configuration file (backend) to add * @param file the configuration file (backend) to add
* @param priority the priority the backend should have * @param level the priority level of the backend
* @return 0 or an error code * @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 * 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 ...@@ -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 * Further queries on this config object will access each
* of the config file instances in order (instances with * 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 cfg the configuration to add the file to
* @param path path to the configuration file (backend) to add * @param path path to the configuration file (backend) to add
* @param priority the priority the backend should have * @param level the priority level of the backend
* @return 0 or an error code * @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);
/** /**
...@@ -189,6 +225,24 @@ GIT_EXTERN(int) git_config_add_file_ondisk(git_config *cfg, const char *path, in ...@@ -189,6 +225,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); 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 * Free the configuration and its associated memory and files
* *
* @param cfg the configuration to free * @param cfg the configuration to free
...@@ -196,8 +250,25 @@ GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path); ...@@ -196,8 +250,25 @@ GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path);
GIT_EXTERN(void) git_config_free(git_config *cfg); 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. * 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 out pointer to the variable where the value should be stored
* @param cfg where to look for the variable * @param cfg where to look for the variable
* @param name the variable's name * @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 * ...@@ -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. * 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 out pointer to the variable where the value should be stored
* @param cfg where to look for the variable * @param cfg where to look for the variable
* @param name the variable's name * @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 * ...@@ -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 * This function uses the usual C convention of 0 being false and
* anything else true. * 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 out pointer to the variable where the value should be stored
* @param cfg where to look for the variable * @param cfg where to look for the variable
* @param name the variable's name * @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) ...@@ -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 * The string is owned by the variable and should not be freed by the
* user. * 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 out pointer to the variable's value
* @param cfg where to look for the variable * @param cfg where to look for the variable
* @param name the variable's name * @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 ...@@ -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 fn the function to be called on each value of the variable
* @param data opaque pointer to pass to the callback * @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 cfg where to look for the variable
* @param name the variable's name * @param name the variable's name
...@@ -266,7 +350,8 @@ GIT_EXTERN(int) git_config_get_multivar(git_config *cfg, const char *name, const ...@@ -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); 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 cfg where to look for the variable
* @param name the variable's name * @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 ...@@ -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); 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 cfg where to look for the variable
* @param name the variable's name * @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 ...@@ -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); 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 * A copy of the string is made and the user is free to use it
* afterwards. * afterwards.
...@@ -298,9 +385,8 @@ GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value ...@@ -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); 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 cfg where to look for the variable
* @param name the variable's name * @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 ...@@ -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); 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 cfg the configuration
* @param name the variable to delete * @param name the variable to delete
...@@ -332,7 +419,7 @@ GIT_EXTERN(int) git_config_delete(git_config *cfg, const char *name); ...@@ -332,7 +419,7 @@ GIT_EXTERN(int) git_config_delete(git_config *cfg, const char *name);
*/ */
GIT_EXTERN(int) git_config_foreach( GIT_EXTERN(int) git_config_foreach(
git_config *cfg, git_config *cfg,
int (*callback)(const char *var_name, const char *value, void *payload), int (*callback)(const git_config_entry *, void *payload),
void *payload); void *payload);
/** /**
...@@ -351,7 +438,7 @@ GIT_EXTERN(int) git_config_foreach( ...@@ -351,7 +438,7 @@ GIT_EXTERN(int) git_config_foreach(
GIT_EXTERN(int) git_config_foreach_match( GIT_EXTERN(int) git_config_foreach_match(
git_config *cfg, git_config *cfg,
const char *regexp, 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); void *payload);
/** /**
...@@ -390,6 +477,57 @@ GIT_EXTERN(int) git_config_foreach_match( ...@@ -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); 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 GIT_END_DECL
#endif #endif
...@@ -17,21 +17,29 @@ ...@@ -17,21 +17,29 @@
#include <ctype.h> #include <ctype.h>
typedef struct { typedef struct {
git_refcount rc;
git_config_file *file; git_config_file *file;
int priority; unsigned int level;
} file_internal; } 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) static void config_free(git_config *cfg)
{ {
unsigned int i; unsigned int i;
git_config_file *file;
file_internal *internal; file_internal *internal;
for(i = 0; i < cfg->files.length; ++i){ for(i = 0; i < cfg->files.length; ++i){
internal = git_vector_get(&cfg->files, i); internal = git_vector_get(&cfg->files, i);
file = internal->file; GIT_REFCOUNT_DEC(internal, file_internal_free);
file->free(file);
git__free(internal);
} }
git_vector_free(&cfg->files); git_vector_free(&cfg->files);
...@@ -51,7 +59,7 @@ static int config_backend_cmp(const void *a, const void *b) ...@@ -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_a = (const file_internal *)(a);
const file_internal *bk_b = (const file_internal *)(b); 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) int git_config_new(git_config **out)
...@@ -73,20 +81,25 @@ int git_config_new(git_config **out) ...@@ -73,20 +81,25 @@ int git_config_new(git_config **out)
return 0; 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; git_config_file *file = NULL;
int res;
if (git_config_file__ondisk(&file, path) < 0) if (git_config_file__ondisk(&file, path) < 0)
return -1; 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 * free manually; the file is not owned by the config
* instance yet and will not be freed on cleanup * instance yet and will not be freed on cleanup
*/ */
file->free(file); file->free(file);
return -1; return res;
} }
return 0; return 0;
...@@ -97,7 +110,7 @@ int git_config_open_ondisk(git_config **cfg, const char *path) ...@@ -97,7 +110,7 @@ int git_config_open_ondisk(git_config **cfg, const char *path)
if (git_config_new(cfg) < 0) if (git_config_new(cfg) < 0)
return -1; 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); git_config_free(*cfg);
return -1; return -1;
} }
...@@ -105,30 +118,152 @@ int git_config_open_ondisk(git_config **cfg, const char *path) ...@@ -105,30 +118,152 @@ int git_config_open_ondisk(git_config **cfg, const char *path)
return 0; 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; file_internal *internal;
int result; int result;
assert(cfg && file); assert(cfg && file);
if ((result = file->open(file)) < 0) if ((result = file->open(file, level)) < 0)
return result; return result;
internal = git__malloc(sizeof(file_internal)); internal = git__malloc(sizeof(file_internal));
GITERR_CHECK_ALLOC(internal); GITERR_CHECK_ALLOC(internal);
memset(internal, 0x0, sizeof(file_internal));
internal->file = file; 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); git__free(internal);
return -1; return result;
} }
git_vector_sort(&cfg->files);
internal->file->cfg = cfg;
return 0; return 0;
} }
...@@ -137,7 +272,7 @@ int git_config_add_file(git_config *cfg, git_config_file *file, int priority) ...@@ -137,7 +272,7 @@ int git_config_add_file(git_config *cfg, git_config_file *file, int priority)
*/ */
int git_config_foreach( 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); return git_config_foreach_match(cfg, NULL, fn, data);
} }
...@@ -145,7 +280,7 @@ int git_config_foreach( ...@@ -145,7 +280,7 @@ int git_config_foreach(
int git_config_foreach_match( int git_config_foreach_match(
git_config *cfg, git_config *cfg,
const char *regexp, const char *regexp,
int (*fn)(const char *, const char *, void *), int (*fn)(const git_config_entry *, void *),
void *data) void *data)
{ {
int ret = 0; int ret = 0;
...@@ -164,10 +299,8 @@ int git_config_foreach_match( ...@@ -164,10 +299,8 @@ int git_config_foreach_match(
int git_config_delete(git_config *cfg, const char *name) int git_config_delete(git_config *cfg, const char *name)
{ {
file_internal *internal;
git_config_file *file; git_config_file *file;
file_internal *internal;
assert(cfg->files.length);
internal = git_vector_get(&cfg->files, 0); internal = git_vector_get(&cfg->files, 0);
file = internal->file; file = internal->file;
...@@ -198,10 +331,8 @@ int git_config_set_bool(git_config *cfg, const char *name, int value) ...@@ -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) int git_config_set_string(git_config *cfg, const char *name, const char *value)
{ {
file_internal *internal;
git_config_file *file; git_config_file *file;
file_internal *internal;
assert(cfg->files.length);
internal = git_vector_get(&cfg->files, 0); internal = git_vector_get(&cfg->files, 0);
file = internal->file; file = internal->file;
...@@ -209,105 +340,9 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) ...@@ -209,105 +340,9 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
return file->set(file, name, 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 * 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 git_config_get_mapped(
int *out, int *out,
git_config *cfg, git_config *cfg,
...@@ -318,16 +353,10 @@ int git_config_get_mapped( ...@@ -318,16 +353,10 @@ int git_config_get_mapped(
const char *value; const char *value;
int ret; int ret;
ret = git_config_get_string(&value, cfg, name); if ((ret = git_config_get_string(&value, cfg, name)) < 0)
if (ret < 0)
return ret; return ret;
if (!git_config_lookup_map_value(maps, map_n, value, out)) return git_config_lookup_map_value(out, maps, map_n, value);
return 0;
giterr_set(GITERR_CONFIG,
"Failed to map the '%s' config variable with a valid value", name);
return -1;
} }
int git_config_get_int64(int64_t *out, git_config *cfg, const char *name) 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) ...@@ -335,16 +364,10 @@ int git_config_get_int64(int64_t *out, git_config *cfg, const char *name)
const char *value; const char *value;
int ret; int ret;
ret = git_config_get_string(&value, cfg, name); if ((ret = git_config_get_string(&value, cfg, name)) < 0)
if (ret < 0)
return ret; return ret;
if (parse_int64(out, value) < 0) { return git_config_parse_int64(out, value);
giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value);
return -1;
}
return 0;
} }
int git_config_get_int32(int32_t *out, git_config *cfg, const char *name) 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) ...@@ -352,16 +375,10 @@ int git_config_get_int32(int32_t *out, git_config *cfg, const char *name)
const char *value; const char *value;
int ret; int ret;
ret = git_config_get_string(&value, cfg, name); if ((ret = git_config_get_string(&value, cfg, name)) < 0)
if (ret < 0)
return ret; return ret;
if (parse_int32(out, value) < 0) { return git_config_parse_int32(out, value);
giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);
return -1;
}
return 0;
} }
int git_config_get_bool(int *out, git_config *cfg, const char *name) 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) ...@@ -369,20 +386,24 @@ int git_config_get_bool(int *out, git_config *cfg, const char *name)
const char *value; const char *value;
int ret; int ret;
ret = git_config_get_string(&value, cfg, name); if ((ret = git_config_get_string(&value, cfg, name)) < 0)
if (ret < 0)
return ret; return ret;
if (git__parse_bool(out, value) == 0) return git_config_parse_bool(out, value);
return 0; }
if (parse_int32(out, value) == 0) { static int get_string_at_file(const char **out, git_config_file *file, const char *name)
*out = !!(*out); {
return 0; const git_config_entry *entry;
} int res;
giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value); *out = NULL;
return -1;
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) 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) ...@@ -392,6 +413,23 @@ int git_config_get_string(const char **out, git_config *cfg, const char *name)
assert(cfg->files.length); 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; *out = NULL;
git_vector_foreach(&cfg->files, i, internal) { 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) ...@@ -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 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; file_internal *internal;
git_config_file *file; git_config_file *file;
...@@ -431,20 +469,13 @@ int git_config_get_multivar(git_config *cfg, const char *name, const char *regex ...@@ -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) int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
{ {
file_internal *internal;
git_config_file *file; git_config_file *file;
int ret = GIT_ENOTFOUND; file_internal *internal;
size_t i;
for (i = cfg->files.length; i > 0; --i) { internal = git_vector_get(&cfg->files, 0);
internal = git_vector_get(&cfg->files, i - 1); file = internal->file;
file = internal->file;
ret = file->set_multivar(file, name, regexp, value);
if (ret < 0 && ret != GIT_ENOTFOUND)
return ret;
}
return 0; return file->set_multivar(file, name, regexp, value);
} }
int git_config_find_global_r(git_buf *path) int git_config_find_global_r(git_buf *path)
...@@ -541,13 +572,16 @@ int git_config_open_default(git_config **out) ...@@ -541,13 +572,16 @@ int git_config_open_default(git_config **out)
error = git_config_new(&cfg); error = git_config_new(&cfg);
if (!error && !git_config_find_global_r(&buf)) 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)) 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)) 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); git_buf_free(&buf);
...@@ -560,3 +594,129 @@ int git_config_open_default(git_config **out) ...@@ -560,3 +594,129 @@ int git_config_open_default(git_config **out)
return error; 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;
}
...@@ -27,9 +27,4 @@ extern int git_config_find_global_r(git_buf *global_config_path); ...@@ -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_xdg_r(git_buf *system_config_path);
extern int git_config_find_system_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 #endif
...@@ -22,15 +22,9 @@ GIT__USE_STRMAP; ...@@ -22,15 +22,9 @@ GIT__USE_STRMAP;
typedef struct cvar_t { typedef struct cvar_t {
struct cvar_t *next; struct cvar_t *next;
char *key; /* TODO: we might be able to get rid of this */ git_config_entry *entry;
char *value;
} cvar_t; } 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_HEAD(list) ((list)->head)
#define CVAR_LIST_TAIL(list) ((list)->tail) #define CVAR_LIST_TAIL(list) ((list)->tail)
...@@ -84,7 +78,7 @@ typedef struct { ...@@ -84,7 +78,7 @@ typedef struct {
char *file_path; char *file_path;
} diskfile_backend; } 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 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 int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
static char *escape_value(const char *ptr); static char *escape_value(const char *ptr);
...@@ -100,8 +94,9 @@ static void cvar_free(cvar_t *var) ...@@ -100,8 +94,9 @@ static void cvar_free(cvar_t *var)
if (var == NULL) if (var == NULL)
return; return;
git__free(var->key); git__free((char*)var->entry->name);
git__free(var->value); git__free((char *)var->entry->value);
git__free(var->entry);
git__free(var); git__free(var);
} }
...@@ -150,7 +145,7 @@ static void free_vars(git_strmap *values) ...@@ -150,7 +145,7 @@ static void free_vars(git_strmap *values)
git_strmap_free(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; int res;
diskfile_backend *b = (diskfile_backend *)cfg; diskfile_backend *b = (diskfile_backend *)cfg;
...@@ -165,7 +160,7 @@ static int config_open(git_config_file *cfg) ...@@ -165,7 +160,7 @@ static int config_open(git_config_file *cfg)
if (res == GIT_ENOTFOUND) if (res == GIT_ENOTFOUND)
return 0; return 0;
if (res < 0 || config_parse(b) < 0) { if (res < 0 || config_parse(b, level) < 0) {
free_vars(b->values); free_vars(b->values);
b->values = NULL; b->values = NULL;
git_buf_free(&b->reader.buffer); git_buf_free(&b->reader.buffer);
...@@ -191,7 +186,7 @@ static void backend_free(git_config_file *_backend) ...@@ -191,7 +186,7 @@ static void backend_free(git_config_file *_backend)
static int file_foreach( static int file_foreach(
git_config_file *backend, git_config_file *backend,
const char *regexp, const char *regexp,
int (*fn)(const char *, const char *, void *), int (*fn)(const git_config_entry *, void *),
void *data) void *data)
{ {
diskfile_backend *b = (diskfile_backend *)backend; diskfile_backend *b = (diskfile_backend *)backend;
...@@ -220,7 +215,7 @@ static int file_foreach( ...@@ -220,7 +215,7 @@ static int file_foreach(
continue; continue;
/* abort iterator on non-zero return value */ /* abort iterator on non-zero return value */
if (fn(key, var->value, data)) { if (fn(var->entry, data)) {
giterr_clear(); giterr_clear();
result = GIT_EUSER; result = GIT_EUSER;
goto cleanup; goto cleanup;
...@@ -263,8 +258,8 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) ...@@ -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 */ /* don't update if old and new values already match */
if ((!existing->value && !value) || if ((!existing->entry->value && !value) ||
(existing->value && value && !strcmp(existing->value, value))) (existing->entry->value && value && !strcmp(existing->entry->value, value)))
return 0; return 0;
if (value) { if (value) {
...@@ -274,10 +269,10 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) ...@@ -274,10 +269,10 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
GITERR_CHECK_ALLOC(esc_value); GITERR_CHECK_ALLOC(esc_value);
} }
git__free(existing->value); git__free((void *)existing->entry->value);
existing->value = tmp; 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); git__free(esc_value);
return ret; return ret;
...@@ -285,15 +280,17 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) ...@@ -285,15 +280,17 @@ static int config_set(git_config_file *cfg, const char *name, const char *value)
var = git__malloc(sizeof(cvar_t)); var = git__malloc(sizeof(cvar_t));
GITERR_CHECK_ALLOC(var); GITERR_CHECK_ALLOC(var);
memset(var, 0x0, sizeof(cvar_t)); 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->entry->name = key;
var->value = NULL; var->entry->value = NULL;
if (value) { if (value) {
var->value = git__strdup(value); var->entry->value = git__strdup(value);
GITERR_CHECK_ALLOC(var->value); GITERR_CHECK_ALLOC(var->entry->value);
esc_value = escape_value(value); esc_value = escape_value(value);
GITERR_CHECK_ALLOC(esc_value); GITERR_CHECK_ALLOC(esc_value);
} }
...@@ -317,7 +314,7 @@ static int config_set(git_config_file *cfg, const char *name, const char *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 * 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; diskfile_backend *b = (diskfile_backend *)cfg;
char *key; char *key;
...@@ -333,7 +330,7 @@ static int config_get(git_config_file *cfg, const char *name, const char **out) ...@@ -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)) if (!git_strmap_valid_index(b->values, pos))
return GIT_ENOTFOUND; 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; return 0;
} }
...@@ -342,7 +339,7 @@ static int config_get_multivar( ...@@ -342,7 +339,7 @@ static int config_get_multivar(
git_config_file *cfg, git_config_file *cfg,
const char *name, const char *name,
const char *regex_str, const char *regex_str,
int (*fn)(const char *, void *), int (*fn)(const git_config_entry *, void *),
void *data) void *data)
{ {
cvar_t *var; cvar_t *var;
...@@ -376,10 +373,10 @@ static int config_get_multivar( ...@@ -376,10 +373,10 @@ static int config_get_multivar(
/* and throw the callback only on the variables that /* and throw the callback only on the variables that
* match the regex */ * match the regex */
do { do {
if (regexec(&regex, var->value, 0, NULL, 0) == 0) { if (regexec(&regex, var->entry->value, 0, NULL, 0) == 0) {
/* early termination by the user is not an error; /* early termination by the user is not an error;
* just break and return successfully */ * just break and return successfully */
if (fn(var->value, data) < 0) if (fn(var->entry, data) < 0)
break; break;
} }
...@@ -391,7 +388,7 @@ static int config_get_multivar( ...@@ -391,7 +388,7 @@ static int config_get_multivar(
do { do {
/* early termination by the user is not an error; /* early termination by the user is not an error;
* just break and return successfully */ * just break and return successfully */
if (fn(var->value, data) < 0) if (fn(var->entry, data) < 0)
break; break;
var = var->next; var = var->next;
...@@ -434,12 +431,12 @@ static int config_set_multivar( ...@@ -434,12 +431,12 @@ static int config_set_multivar(
} }
for (;;) { 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); char *tmp = git__strdup(value);
GITERR_CHECK_ALLOC(tmp); GITERR_CHECK_ALLOC(tmp);
git__free(var->value); git__free((void *)var->entry->value);
var->value = tmp; var->entry->value = tmp;
replaced = 1; replaced = 1;
} }
...@@ -453,14 +450,18 @@ static int config_set_multivar( ...@@ -453,14 +450,18 @@ static int config_set_multivar(
if (!replaced) { if (!replaced) {
newvar = git__malloc(sizeof(cvar_t)); newvar = git__malloc(sizeof(cvar_t));
GITERR_CHECK_ALLOC(newvar); GITERR_CHECK_ALLOC(newvar);
memset(newvar, 0x0, sizeof(cvar_t)); 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); newvar->entry->name = git__strdup(var->entry->name);
GITERR_CHECK_ALLOC(newvar->key); GITERR_CHECK_ALLOC(newvar->entry->name);
newvar->value = git__strdup(value); newvar->entry->value = git__strdup(value);
GITERR_CHECK_ALLOC(newvar->value); GITERR_CHECK_ALLOC(newvar->entry->value);
newvar->entry->level = var->entry->level;
var->next = newvar; var->next = newvar;
} }
...@@ -501,7 +502,7 @@ static int config_delete(git_config_file *cfg, const char *name) ...@@ -501,7 +502,7 @@ static int config_delete(git_config_file *cfg, const char *name)
git_strmap_delete_at(b->values, pos); 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); cvar_free(var);
return result; return result;
...@@ -898,7 +899,7 @@ static int strip_comments(char *line, int in_quotes) ...@@ -898,7 +899,7 @@ static int strip_comments(char *line, int in_quotes)
return quote_count; return quote_count;
} }
static int config_parse(diskfile_backend *cfg_file) static int config_parse(diskfile_backend *cfg_file, unsigned int level)
{ {
int c; int c;
char *current_section = NULL; char *current_section = NULL;
...@@ -946,8 +947,10 @@ static int config_parse(diskfile_backend *cfg_file) ...@@ -946,8 +947,10 @@ static int config_parse(diskfile_backend *cfg_file)
var = git__malloc(sizeof(cvar_t)); var = git__malloc(sizeof(cvar_t));
GITERR_CHECK_ALLOC(var); GITERR_CHECK_ALLOC(var);
memset(var, 0x0, sizeof(cvar_t)); 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__strtolower(var_name);
git_buf_printf(&buf, "%s.%s", current_section, var_name); git_buf_printf(&buf, "%s.%s", current_section, var_name);
...@@ -956,13 +959,14 @@ static int config_parse(diskfile_backend *cfg_file) ...@@ -956,13 +959,14 @@ static int config_parse(diskfile_backend *cfg_file)
if (git_buf_oom(&buf)) if (git_buf_oom(&buf))
return -1; return -1;
var->key = git_buf_detach(&buf); var->entry->name = git_buf_detach(&buf);
var->value = var_value; var->entry->value = var_value;
var->entry->level = level;
/* Add or append the new config option */ /* 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)) { 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) if (result < 0)
break; break;
result = 0; result = 0;
...@@ -1182,6 +1186,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p ...@@ -1182,6 +1186,10 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
goto rewrite_fail; 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); git_filebuf_printf(&file, "\t%s = %s\n", name, value);
} }
} }
......
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
#include "git2/config.h" #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) 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) ...@@ -20,7 +20,7 @@ GIT_INLINE(void) git_config_file_free(git_config_file *cfg)
} }
GIT_INLINE(int) git_config_file_get_string( 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); return cfg->get(cfg, name, out);
} }
...@@ -39,7 +39,7 @@ GIT_INLINE(int) git_config_file_delete( ...@@ -39,7 +39,7 @@ GIT_INLINE(int) git_config_file_delete(
GIT_INLINE(int) git_config_file_foreach( GIT_INLINE(int) git_config_file_foreach(
git_config_file *cfg, 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) void *data)
{ {
return cfg->foreach(cfg, NULL, fn, data); return cfg->foreach(cfg, NULL, fn, data);
...@@ -48,7 +48,7 @@ GIT_INLINE(int) git_config_file_foreach( ...@@ -48,7 +48,7 @@ GIT_INLINE(int) git_config_file_foreach(
GIT_INLINE(int) git_config_file_foreach_match( GIT_INLINE(int) git_config_file_foreach_match(
git_config_file *cfg, git_config_file *cfg,
const char *regexp, const char *regexp,
int (*fn)(const char *key, const char *value, void *data), int (*fn)(const git_config_entry *entry, void *data),
void *data) void *data)
{ {
return cfg->foreach(cfg, regexp, fn, data); return cfg->foreach(cfg, regexp, fn, data);
......
...@@ -637,12 +637,12 @@ struct cb_data { ...@@ -637,12 +637,12 @@ struct cb_data {
regex_t *preg; 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_; struct cb_data *data = (struct cb_data *)data_;
size_t nmatch = 2; size_t nmatch = 2;
regmatch_t pmatch[2]; regmatch_t pmatch[2];
GIT_UNUSED(value); const char *name = entry->name;
if (!regexec(data->preg, name, nmatch, pmatch, 0)) { 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); char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so);
......
...@@ -461,23 +461,23 @@ static int load_config( ...@@ -461,23 +461,23 @@ static int load_config(
&config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0) &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0)
goto on_error; 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; goto on_error;
git_buf_free(&config_path); git_buf_free(&config_path);
if (global_config_path != NULL) { 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; goto on_error;
} }
if (xdg_config_path != NULL) { 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; goto on_error;
} }
if (system_config_path != NULL) { 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; goto on_error;
} }
......
...@@ -72,7 +72,7 @@ static int submodule_get(git_submodule **, git_repository *, const char *, const ...@@ -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 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_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_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_load_from_wd_lite(git_submodule *, const char *, void *);
static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool); static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool);
static void submodule_mode_mismatch(git_repository *, const char *, unsigned int); 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) ...@@ -974,11 +974,12 @@ static int submodule_config_error(const char *property, const char *value)
} }
static int submodule_load_from_config( 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_repository *repo = data;
git_strmap *smcfg = repo->submodules; git_strmap *smcfg = repo->submodules;
const char *namestart, *property, *alternate = NULL; const char *namestart, *property, *alternate = NULL;
const char *key = entry->name, *value = entry->value;
git_buf name = GIT_BUF_INIT; git_buf name = GIT_BUF_INIT;
git_submodule *sm; git_submodule *sm;
bool is_path; bool is_path;
...@@ -1055,7 +1056,7 @@ static int submodule_load_from_config( ...@@ -1055,7 +1056,7 @@ static int submodule_load_from_config(
else if (strcasecmp(property, "update") == 0) { else if (strcasecmp(property, "update") == 0) {
int val; int val;
if (git_config_lookup_map_value( 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); return submodule_config_error("update", value);
sm->update_default = sm->update = (git_submodule_update_t)val; sm->update_default = sm->update = (git_submodule_update_t)val;
} }
...@@ -1066,7 +1067,7 @@ static int submodule_load_from_config( ...@@ -1066,7 +1067,7 @@ static int submodule_load_from_config(
else if (strcasecmp(property, "ignore") == 0) { else if (strcasecmp(property, "ignore") == 0) {
int val; int val;
if (git_config_lookup_map_value( 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); return submodule_config_error("ignore", value);
sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val; sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val;
} }
...@@ -1204,7 +1205,7 @@ static git_config_file *open_gitmodules( ...@@ -1204,7 +1205,7 @@ static git_config_file *open_gitmodules(
if (git_config_file__ondisk(&mods, path.ptr) < 0) if (git_config_file__ondisk(&mods, path.ptr) < 0)
mods = NULL; mods = NULL;
/* open should only fail here if the file is malformed */ /* 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); git_config_file_free(mods);
mods = NULL; mods = NULL;
} }
......
#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);
}
...@@ -12,13 +12,11 @@ void test_config_multivar__cleanup(void) ...@@ -12,13 +12,11 @@ void test_config_multivar__cleanup(void)
cl_fixture_cleanup("config"); 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; int *n = (int *) data;
GIT_UNUSED(value); if (!strcmp(entry->name, _name))
if (!strcmp(name, _name))
(*n)++; (*n)++;
return 0; return 0;
...@@ -37,11 +35,11 @@ void test_config_multivar__foreach(void) ...@@ -37,11 +35,11 @@ void test_config_multivar__foreach(void)
git_config_free(cfg); 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; int *n = (int *) data;
GIT_UNUSED(val); GIT_UNUSED(entry);
(*n)++; (*n)++;
......
...@@ -9,21 +9,16 @@ ...@@ -9,21 +9,16 @@
void test_config_new__write_new_config(void) void test_config_new__write_new_config(void)
{ {
const char *out; const char *out;
struct git_config_file *file;
git_config *config; git_config *config;
cl_git_pass(git_config_file__ondisk(&file, TEST_CONFIG)); cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
cl_git_pass(git_config_new(&config));
cl_git_pass(git_config_add_file(config, file, 0));
cl_git_pass(git_config_set_string(config, "color.ui", "auto")); cl_git_pass(git_config_set_string(config, "color.ui", "auto"));
cl_git_pass(git_config_set_string(config, "core.editor", "ed")); cl_git_pass(git_config_set_string(config, "core.editor", "ed"));
git_config_free(config); git_config_free(config);
cl_git_pass(git_config_file__ondisk(&file, TEST_CONFIG)); cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
cl_git_pass(git_config_new(&config));
cl_git_pass(git_config_add_file(config, file, 0));
cl_git_pass(git_config_get_string(&out, config, "color.ui")); cl_git_pass(git_config_get_string(&out, config, "color.ui"));
cl_assert_equal_s(out, "auto"); cl_assert_equal_s(out, "auto");
......
...@@ -191,22 +191,24 @@ void test_config_read__escaping_quotes(void) ...@@ -191,22 +191,24 @@ void test_config_read__escaping_quotes(void)
git_config_free(cfg); git_config_free(cfg);
} }
static int count_cfg_entries( static int count_cfg_entries_and_compare_levels(
const char *var_name, const char *value, void *payload) const git_config_entry *entry, void *payload)
{ {
int *count = 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)++; (*count)++;
return 0; return 0;
} }
static int cfg_callback_countdown( static int cfg_callback_countdown(const git_config_entry *entry, void *payload)
const char *var_name, const char *value, void *payload)
{ {
int *count = payload; int *count = payload;
GIT_UNUSED(var_name); GIT_UNUSED(entry);
GIT_UNUSED(value);
(*count)--; (*count)--;
if (*count == 0) if (*count == 0)
return -100; return -100;
...@@ -218,11 +220,15 @@ void test_config_read__foreach(void) ...@@ -218,11 +220,15 @@ void test_config_read__foreach(void)
git_config *cfg; git_config *cfg;
int count, ret; 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; count = 0;
cl_git_pass(git_config_foreach(cfg, count_cfg_entries, &count)); cl_git_pass(git_config_foreach(cfg, count_cfg_entries_and_compare_levels, &count));
cl_assert_equal_i(5, count); cl_assert_equal_i(7, count);
count = 3; count = 3;
cl_git_fail(ret = git_config_foreach(cfg, cfg_callback_countdown, &count)); cl_git_fail(ret = git_config_foreach(cfg, cfg_callback_countdown, &count));
...@@ -231,6 +237,14 @@ void test_config_read__foreach(void) ...@@ -231,6 +237,14 @@ void test_config_read__foreach(void)
git_config_free(cfg); 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) void test_config_read__foreach_match(void)
{ {
git_config *cfg; git_config *cfg;
...@@ -282,31 +296,129 @@ void test_config_read__whitespace_not_required_around_assignment(void) ...@@ -282,31 +296,129 @@ void test_config_read__whitespace_not_required_around_assignment(void)
git_config_free(cfg); git_config_free(cfg);
} }
#if 0 void test_config_read__read_git_config_entry(void)
{
git_config *cfg;
const git_config_entry *entry;
BEGIN_TEST(config10, "a repo's config overrides the global config") cl_git_pass(git_config_new(&cfg));
git_repository *repo; 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);
git_config_free(cfg);
}
/*
* 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; git_config *cfg;
int32_t version; 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);
git_config_free(cfg);
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);
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_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.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; git_config *cfg;
int32_t num; int32_t i;
cl_git_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); cl_git_pass(git_config_new(&cfg));
cl_git_pass(git_repository_config(&cfg, repo, GLOBAL_CONFIG, NULL)); cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"),
cl_git_pass(git_config_get_int32(cfg, "core.something", &num)); GIT_CONFIG_LEVEL_SYSTEM, 0));
cl_assert(num == 2); 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); git_config_free(cfg);
git_repository_free(repo); }
END_TEST
#endif
...@@ -4,11 +4,13 @@ ...@@ -4,11 +4,13 @@
#include "fileops.h" #include "fileops.h"
#include "posix.h" #include "posix.h"
#define TEST_CONFIG "git-test-config"
void test_config_stress__initialize(void) void test_config_stress__initialize(void)
{ {
git_filebuf file = GIT_FILEBUF_INIT; 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, "[color]\n\tui = auto\n");
git_filebuf_printf(&file, "[core]\n\teditor = \n"); git_filebuf_printf(&file, "[core]\n\teditor = \n");
...@@ -18,19 +20,16 @@ void test_config_stress__initialize(void) ...@@ -18,19 +20,16 @@ void test_config_stress__initialize(void)
void test_config_stress__cleanup(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) void test_config_stress__dont_break_on_invalid_input(void)
{ {
const char *editor, *color; const char *editor, *color;
struct git_config_file *file;
git_config *config; git_config *config;
cl_assert(git_path_exists("git-test-config")); cl_assert(git_path_exists(TEST_CONFIG));
cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
cl_git_pass(git_config_new(&config));
cl_git_pass(git_config_add_file(config, file, 0));
cl_git_pass(git_config_get_string(&color, config, "color.ui")); cl_git_pass(git_config_get_string(&color, config, "color.ui"));
cl_git_pass(git_config_get_string(&editor, config, "core.editor")); 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) ...@@ -40,13 +39,10 @@ void test_config_stress__dont_break_on_invalid_input(void)
void test_config_stress__comments(void) void test_config_stress__comments(void)
{ {
struct git_config_file *file;
git_config *config; git_config *config;
const char *str; const char *str;
cl_git_pass(git_config_file__ondisk(&file, cl_fixture("config/config12"))); cl_git_pass(git_config_open_ondisk(&config, 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_get_string(&str, config, "some.section.other")); cl_git_pass(git_config_get_string(&str, config, "some.section.other"));
cl_assert(!strcmp(str, "hello! \" ; ; ; ")); cl_assert(!strcmp(str, "hello! \" ; ; ; "));
...@@ -62,21 +58,16 @@ void test_config_stress__comments(void) ...@@ -62,21 +58,16 @@ void test_config_stress__comments(void)
void test_config_stress__escape_subsection_names(void) void test_config_stress__escape_subsection_names(void)
{ {
struct git_config_file *file;
git_config *config; git_config *config;
const char *str; const char *str;
cl_assert(git_path_exists("git-test-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_open_ondisk(&config, TEST_CONFIG));
cl_git_pass(git_config_new(&config));
cl_git_pass(git_config_add_file(config, file, 0));
cl_git_pass(git_config_set_string(config, "some.sec\\tion.other", "foo")); cl_git_pass(git_config_set_string(config, "some.sec\\tion.other", "foo"));
git_config_free(config); git_config_free(config);
cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
cl_git_pass(git_config_new(&config));
cl_git_pass(git_config_add_file(config, file, 0));
cl_git_pass(git_config_get_string(&str, config, "some.sec\\tion.other")); cl_git_pass(git_config_get_string(&str, config, "some.sec\\tion.other"));
cl_assert(!strcmp("foo", str)); cl_assert(!strcmp("foo", str));
......
...@@ -3,11 +3,15 @@ ...@@ -3,11 +3,15 @@
void test_config_write__initialize(void) void test_config_write__initialize(void)
{ {
cl_fixture_sandbox("config/config9"); cl_fixture_sandbox("config/config9");
cl_fixture_sandbox("config/config15");
cl_fixture_sandbox("config/config17");
} }
void test_config_write__cleanup(void) void test_config_write__cleanup(void)
{ {
cl_fixture_cleanup("config9"); cl_fixture_cleanup("config9");
cl_fixture_cleanup("config15");
cl_fixture_cleanup("config17");
} }
void test_config_write__replace_value(void) void test_config_write__replace_value(void)
...@@ -67,6 +71,40 @@ void test_config_write__delete_value(void) ...@@ -67,6 +71,40 @@ void test_config_write__delete_value(void)
git_config_free(cfg); 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) void test_config_write__write_subsection(void)
{ {
git_config *cfg; git_config *cfg;
...@@ -136,3 +174,57 @@ void test_config_write__escape_value(void) ...@@ -136,3 +174,57 @@ void test_config_write__escape_value(void)
cl_assert_equal_s(str, "this \"has\" quotes and \t"); cl_assert_equal_s(str, "this \"has\" quotes and \t");
git_config_free(cfg); git_config_free(cfg);
} }
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;
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);
}
[core]
dummy2 = 7
global = 17
[core]
dummy2 = 28
system = 11
[core]
dummy2 = 7
global = 17
\ No newline at end of file
[core]
int32global = 28
int64global = 9223372036854775803
boolglobal = true
stringglobal = I'm a global config value!
\ No newline at end of file
[core]
int32global = -1
int64global = -2
boolglobal = false
stringglobal = don't find me!
\ No newline at end of file
...@@ -73,12 +73,10 @@ void test_submodule_modify__add(void) ...@@ -73,12 +73,10 @@ void test_submodule_modify__add(void)
git_config_free(cfg); git_config_free(cfg);
} }
static int delete_one_config( static int delete_one_config(const git_config_entry *entry, void *payload)
const char *var_name, const char *value, void *payload)
{ {
git_config *cfg = payload; git_config *cfg = payload;
GIT_UNUSED(value); return git_config_delete(cfg, entry->name);
return git_config_delete(cfg, var_name);
} }
static int init_one_submodule( static int init_one_submodule(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment