diff options
author | Vicent Marti <tanoku@gmail.com> | 2013-04-16 19:46:41 +0400 |
---|---|---|
committer | Vicent Marti <tanoku@gmail.com> | 2013-04-16 19:46:41 +0400 |
commit | a50086d174658914d4d6462afbc83b02825b1f5b (patch) | |
tree | e8daa1c7bf678222cf351445179837bed7db3a72 /src/config.c | |
parent | 5b9fac39d8a76b9139667c26a63e6b3f204b3977 (diff) | |
parent | f124ebd457bfbf43de3516629aaba5a279636e04 (diff) |
Merge branch 'development'v0.18.0
Diffstat (limited to 'src/config.c')
-rw-r--r-- | src/config.c | 759 |
1 files changed, 530 insertions, 229 deletions
diff --git a/src/config.c b/src/config.c index 618202c34..5379b0ec5 100644 --- a/src/config.c +++ b/src/config.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 the libgit2 contributors + * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. @@ -10,6 +10,8 @@ #include "config.h" #include "git2/config.h" #include "vector.h" +#include "buf_text.h" +#include "config_file.h" #if GIT_WIN32 # include <windows.h> #endif @@ -17,21 +19,29 @@ #include <ctype.h> typedef struct { - git_config_file *file; - int priority; + git_refcount rc; + + git_config_backend *file; + unsigned int level; } file_internal; +static void file_internal_free(file_internal *internal) +{ + git_config_backend *file; + + file = internal->file; + file->free(file); + git__free(internal); +} + static void config_free(git_config *cfg) { - unsigned int i; - git_config_file *file; + size_t i; 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 +61,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,91 +83,252 @@ 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; + git_config_backend *file = NULL; + int res; + + assert(cfg && path); + + if (!git_path_isfile(path)) { + giterr_set(GITERR_CONFIG, "Cannot find config file '%s'", path); + return GIT_ENOTFOUND; + } if (git_config_file__ondisk(&file, path) < 0) return -1; - if (git_config_add_file(cfg, file, priority) < 0) { + if ((res = git_config_add_backend(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; } -int git_config_open_ondisk(git_config **cfg, const char *path) +int git_config_open_ondisk(git_config **out, const char *path) { - if (git_config_new(cfg) < 0) - return -1; + int error; + git_config *config; - if (git_config_add_file_ondisk(*cfg, path, 1) < 0) { - git_config_free(*cfg); + *out = NULL; + + if (git_config_new(&config) < 0) return -1; + + if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0) + git_config_free(config); + else + *out = config; + + return error; +} + +static int find_internal_file_by_level( + file_internal **internal_out, + const git_config *cfg, + int level) +{ + int pos = -1; + file_internal *internal; + unsigned int i; + + /* 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, + const 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, int priority) +int git_config_add_backend( + git_config *cfg, + git_config_backend *file, + unsigned int level, + int force) { file_internal *internal; int result; assert(cfg && file); - if ((result = file->open(file)) < 0) + GITERR_CHECK_VERSION(file, GIT_CONFIG_BACKEND_VERSION, "git_config_backend"); + + 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; } +int git_config_refresh(git_config *cfg) +{ + int error = 0; + size_t i; + + for (i = 0; i < cfg->files.length && !error; ++i) { + file_internal *internal = git_vector_get(&cfg->files, i); + git_config_backend *file = internal->file; + error = file->refresh(file); + } + + return error; +} + /* * Loop over all the variables */ -int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) +int git_config_foreach( + const git_config *cfg, git_config_foreach_cb cb, void *payload) +{ + return git_config_foreach_match(cfg, NULL, cb, payload); +} + +int git_config_foreach_match( + const git_config *cfg, + const char *regexp, + git_config_foreach_cb cb, + void *payload) { int ret = 0; - unsigned int i; + size_t i; file_internal *internal; - git_config_file *file; + git_config_backend *file; - for(i = 0; i < cfg->files.length && ret == 0; ++i) { + for (i = 0; i < cfg->files.length && ret == 0; ++i) { internal = git_vector_get(&cfg->files, i); file = internal->file; - ret = file->foreach(file, fn, data); + ret = file->foreach(file, regexp, cb, payload); } return ret; } -int git_config_delete(git_config *cfg, const char *name) +int git_config_delete_entry(git_config *cfg, const char *name) { + git_config_backend *file; file_internal *internal; - git_config_file *file; - - assert(cfg->files.length); internal = git_vector_get(&cfg->files, 0); file = internal->file; @@ -188,10 +359,13 @@ 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) { + git_config_backend *file; file_internal *internal; - git_config_file *file; - assert(cfg->files.length); + if (!value) { + giterr_set(GITERR_CONFIG, "The value to set cannot be NULL"); + return -1; + } internal = git_vector_get(&cfg->files, 0); file = internal->file; @@ -199,211 +373,121 @@ 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, + const git_config *cfg, const char *name, - git_cvar_map *maps, + const git_cvar_map *maps, size_t map_n) { 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) +int git_config_get_int64(int64_t *out, const 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) +int git_config_get_int32(int32_t *out, const 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 git_config_parse_int32(out, value); +} + +static int get_string_at_file(const char **out, const git_config_backend *file, const char *name) +{ + const git_config_entry *entry; + int res; + + res = file->get(file, name, &entry); + if (!res) + *out = entry->value; + + return res; +} + +static int get_string(const char **out, const git_config *cfg, const char *name) +{ + file_internal *internal; + unsigned int i; + + git_vector_foreach(&cfg->files, i, internal) { + int res = get_string_at_file(out, internal->file, name); + + if (res != GIT_ENOTFOUND) + return res; } - return 0; + return GIT_ENOTFOUND; } -int git_config_get_bool(int *out, git_config *cfg, const char *name) +int git_config_get_bool(int *out, const git_config *cfg, const char *name) { - const char *value; + const char *value = NULL; int ret; - ret = git_config_get_string(&value, cfg, name); - if (ret < 0) + if ((ret = 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; - } +int git_config_get_string(const char **out, const git_config *cfg, const char *name) +{ + int ret; + const char *str = NULL; - giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value); - return -1; + if ((ret = get_string(&str, cfg, name)) < 0) + return ret; + + *out = str == NULL ? "" : str; + return 0; } -int git_config_get_string(const char **out, git_config *cfg, const char *name) +int git_config_get_entry(const git_config_entry **out, const 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) { - git_config_file *file = internal->file; + git_config_backend *file = internal->file; int ret = file->get(file, name, out); if (ret != GIT_ENOTFOUND) return ret; } - giterr_set(GITERR_CONFIG, "Config variable '%s' not found", name); return GIT_ENOTFOUND; } -int git_config_get_multivar(git_config *cfg, const char *name, const char *regexp, - int (*fn)(const char *value, void *data), void *data) +int git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp, + git_config_foreach_cb cb, void *payload) { file_internal *internal; - git_config_file *file; + git_config_backend *file; int ret = GIT_ENOTFOUND; - unsigned int i; - - assert(cfg->files.length); + size_t i; /* * This loop runs the "wrong" way 'round because we need to @@ -412,7 +496,7 @@ int git_config_get_multivar(git_config *cfg, const char *name, const char *regex for (i = cfg->files.length; i > 0; --i) { internal = git_vector_get(&cfg->files, i - 1); file = internal->file; - ret = file->get_multivar(file, name, regexp, fn, data); + ret = file->get_multivar(file, name, regexp, cb, payload); if (ret < 0 && ret != GIT_ENOTFOUND) return ret; } @@ -422,47 +506,57 @@ 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) { + git_config_backend *file; file_internal *internal; - git_config_file *file; - int ret = GIT_ENOTFOUND; - unsigned int i; - 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 file->set_multivar(file, name, regexp, value); +} + +static int git_config__find_file_to_path( + char *out, size_t outlen, int (*find)(git_buf *buf)) +{ + int error = 0; + git_buf path = GIT_BUF_INIT; + + if ((error = find(&path)) < 0) + goto done; + + if (path.size >= outlen) { + giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path"); + error = GIT_EBUFS; + goto done; } - return 0; + git_buf_copy_cstr(out, outlen, &path); + +done: + git_buf_free(&path); + return error; } int git_config_find_global_r(git_buf *path) { - return git_futils_find_global_file(path, GIT_CONFIG_FILENAME); + return git_futils_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL); } int git_config_find_global(char *global_config_path, size_t length) { - git_buf path = GIT_BUF_INIT; - int ret = git_config_find_global_r(&path); - - if (ret < 0) { - git_buf_free(&path); - return ret; - } + return git_config__find_file_to_path( + global_config_path, length, git_config_find_global_r); +} - if (path.size >= length) { - git_buf_free(&path); - giterr_set(GITERR_NOMEMORY, - "Path is to long to fit on the given buffer"); - return -1; - } +int git_config_find_xdg_r(git_buf *path) +{ + return git_futils_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG); +} - git_buf_copy_cstr(global_config_path, length, &path); - git_buf_free(&path); - return 0; +int git_config_find_xdg(char *xdg_config_path, size_t length) +{ + return git_config__find_file_to_path( + xdg_config_path, length, git_config_find_xdg_r); } int git_config_find_system_r(git_buf *path) @@ -472,37 +566,244 @@ int git_config_find_system_r(git_buf *path) int git_config_find_system(char *system_config_path, size_t length) { - git_buf path = GIT_BUF_INIT; - int ret = git_config_find_system_r(&path); + return git_config__find_file_to_path( + system_config_path, length, git_config_find_system_r); +} - if (ret < 0) { - git_buf_free(&path); - return ret; +int git_config_open_default(git_config **out) +{ + int error; + git_config *cfg = NULL; + git_buf buf = GIT_BUF_INIT; + + error = git_config_new(&cfg); + + if (!error && !git_config_find_global_r(&buf)) + 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, + GIT_CONFIG_LEVEL_XDG, 0); + + if (!error && !git_config_find_system_r(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_SYSTEM, 0); + + git_buf_free(&buf); + + if (error && cfg) { + git_config_free(cfg); + cfg = NULL; } - if (path.size >= length) { - git_buf_free(&path); - giterr_set(GITERR_NOMEMORY, - "Path is to long to fit on the given buffer"); - return -1; + *out = cfg; + + return error; +} + +/*********** + * Parsers + ***********/ +int git_config_lookup_map_value( + int *out, + const 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) { + const 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; + } } - git_buf_copy_cstr(system_config_path, length, &path); - git_buf_free(&path); +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; } -int git_config_open_global(git_config **out) +struct rename_data { + git_config *config; + git_buf *name; + size_t old_len; + int actual_error; +}; + +static int rename_config_entries_cb( + const git_config_entry *entry, + void *payload) { - int error; - git_buf path = GIT_BUF_INIT; + int error = 0; + struct rename_data *data = (struct rename_data *)payload; + size_t base_len = git_buf_len(data->name); - if ((error = git_config_find_global_r(&path)) < 0) - return error; + if (base_len > 0 && + !(error = git_buf_puts(data->name, entry->name + data->old_len))) + { + error = git_config_set_string( + data->config, git_buf_cstr(data->name), entry->value); - error = git_config_open_ondisk(out, git_buf_cstr(&path)); - git_buf_free(&path); + git_buf_truncate(data->name, base_len); + } + + if (!error) + error = git_config_delete_entry(data->config, entry->name); + + data->actual_error = error; /* preserve actual error code */ return error; } +int git_config_rename_section( + git_repository *repo, + const char *old_section_name, + const char *new_section_name) +{ + git_config *config; + git_buf pattern = GIT_BUF_INIT, replace = GIT_BUF_INIT; + int error = 0; + struct rename_data data; + + git_buf_text_puts_escape_regex(&pattern, old_section_name); + + if ((error = git_buf_puts(&pattern, "\\..+")) < 0) + goto cleanup; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + goto cleanup; + + data.config = config; + data.name = &replace; + data.old_len = strlen(old_section_name) + 1; + data.actual_error = 0; + + if ((error = git_buf_join(&replace, '.', new_section_name, "")) < 0) + goto cleanup; + + if (new_section_name != NULL && + (error = git_config_file_normalize_section( + replace.ptr, strchr(replace.ptr, '.'))) < 0) + { + giterr_set( + GITERR_CONFIG, "Invalid config section '%s'", new_section_name); + goto cleanup; + } + + error = git_config_foreach_match( + config, git_buf_cstr(&pattern), rename_config_entries_cb, &data); + + if (error == GIT_EUSER) + error = data.actual_error; + +cleanup: + git_buf_free(&pattern); + git_buf_free(&replace); + + return error; +} |