From d6b5f5cca9904763b117efb0d0d4bad7f2412b7b Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sat, 5 Mar 2011 23:54:49 +0200 Subject: [PATCH 01/57] Add `git_oid_shorten` Set of methods to find the minimal-length to uniquely identify every OID in a list. Includes stress test. Signed-off-by: Vicent Marti --- include/git2/oid.h | 54 ++++++++++++++ src/oid.c | 178 +++++++++++++++++++++++++++++++++++++++++++++ tests/t01-rawobj.c | 92 +++++++++++++++++++++++ 3 files changed, 324 insertions(+) diff --git a/include/git2/oid.h b/include/git2/oid.h index 5cac46f3b..4538c6147 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -132,6 +132,60 @@ GIT_EXTERN(void) git_oid_cpy(git_oid *out, const git_oid *src); */ GIT_EXTERN(int) git_oid_cmp(const git_oid *a, const git_oid *b); +/** + * OID Shortener object + */ +typedef struct git_oid_shorten git_oid_shorten; + +/** + * Create a new OID shortener. + * + * The OID shortener is used to process a list of OIDs + * in text form and return the shortest length that would + * uniquely identify all of them. + * + * E.g. look at the result of `git log --abbrev`. + * + * @param min_length The minimal length for all identifiers, + * which will be used even if shorter OIDs would still + * be unique. + * @return a `git_oid_shorten` instance, NULL if OOM + */ +git_oid_shorten *git_oid_shorten_new(size_t min_length); + +/** + * Add a new OID to set of shortened OIDs and calculate + * the minimal length to uniquely identify all the OIDs in + * the set. + * + * The OID is expected to be a 40-char hexadecimal string. + * The OID is owned by the user and will not be modified + * or freed. + * + * For performance reasons, there is a hard-limit of how many + * OIDs can be added to a single set (around ~22000, assuming + * a mostly randomized distribution), which should be enough + * for any kind of program, and keeps the algorithm fast and + * memory-efficient. + * + * Attempting to add more than those OIDs will result in a + * GIT_ENOMEM error + * + * @param os a `git_oid_shorten` instance + * @param text_oid an OID in text form + * @return the minimal length to uniquely identify all OIDs + * added so far to the set; or an error code (<0) if an + * error occurs. + */ +int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid); + +/** + * Free an OID shortener instance + * + * @param os a `git_oid_shorten` instance + */ +void git_oid_shorten_free(git_oid_shorten *os); + /** @} */ GIT_END_DECL #endif diff --git a/src/oid.c b/src/oid.c index 698d0f927..7841d3755 100644 --- a/src/oid.c +++ b/src/oid.c @@ -27,6 +27,7 @@ #include "git2/oid.h" #include "repository.h" #include +#include static signed char from_hex[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ @@ -166,3 +167,180 @@ int git_oid_cmp(const git_oid *a, const git_oid *b) { return memcmp(a->id, b->id, sizeof(a->id)); } + + +typedef short node_index; + +typedef union { + const char *tail; + node_index children[16]; +} trie_node; + +struct git_oid_shorten { + trie_node *nodes; + size_t node_count, size; + int min_length, full; +}; + +static int resize_trie(git_oid_shorten *self, size_t new_size) +{ + self->nodes = realloc(self->nodes, new_size * sizeof(trie_node)); + if (self->nodes == NULL) + return GIT_ENOMEM; + + if (new_size > self->size) { + memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node)); + } + + self->size = new_size; + return GIT_SUCCESS; +} + +static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid) +{ + trie_node *node, *leaf; + node_index idx_leaf; + + if (os->node_count >= os->size) { + if (resize_trie(os, os->size * 2) < GIT_SUCCESS) + return NULL; + } + + idx_leaf = os->node_count++; + + if (os->node_count == SHRT_MAX) + os->full = 1; + + node = &os->nodes[idx]; + node->children[push_at] = -idx_leaf; + + leaf = &os->nodes[idx_leaf]; + leaf->tail = oid; + + return node; +} + +git_oid_shorten *git_oid_shorten_new(size_t min_length) +{ + git_oid_shorten *os; + + os = git__malloc(sizeof(git_oid_shorten)); + if (os == NULL) + return NULL; + + memset(os, 0x0, sizeof(git_oid_shorten)); + + if (resize_trie(os, 16) < GIT_SUCCESS) { + free(os); + return NULL; + } + + os->node_count = 1; + os->min_length = min_length; + + return os; +} + +void git_oid_shorten_free(git_oid_shorten *os) +{ + free(os->nodes); + free(os); +} + + +/* + * What wizardry is this? + * + * This is just a memory-optimized trie: basically a very fancy + * 16-ary tree, which is used to store the prefixes of the OID + * strings. + * + * Read more: http://en.wikipedia.org/wiki/Trie + * + * Magic that happens in this method: + * + * - Each node in the trie is an union, so it can work both as + * a normal node, or as a leaf. + * + * - Each normal node points to 16 children (one for each possible + * character in the oid). This is *not* stored in an array of + * pointers, because in a 64-bit arch this would be sucking + * 16*sizeof(void*) = 128 bytes of memory per node, which is fucking + * insane. What we do is store Node Indexes, and use these indexes + * to look up each node in the om->index array. These indexes are + * signed shorts, so this limits the amount of unique OIDs that + * fit in the structure to about 20000 (assuming a more or less uniform + * distribution). + * + * - All the nodes in om->index array are stored contiguously in + * memory, and each of them is 32 bytes, so we fit 2x nodes per + * cache line. Convenient for speed. + * + * - To differentiate the leafs from the normal nodes, we store all + * the indexes towards a leaf as a negative index (indexes to normal + * nodes are positives). When we find that one of the children for + * a node has a negative value, that means it's going to be a leaf. + * This reduces the amount of indexes we have by two, but also reduces + * the size of each node by 1-4 bytes (the amount we would need to + * add a `is_leaf` field): this is good because it allows the nodes + * to fit cleanly in cache lines. + * + * - Once we reach an empty children, instead of continuing to insert + * new nodes for each remaining character of the OID, we store a pointer + * to the tail in the leaf; if the leaf is reached again, we turn it + * into a normal node and use the tail to create a new leaf. + * + * This is a pretty good balance between performance and memory usage. + */ +int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) +{ + int i, is_leaf; + node_index idx; + + if (os->full) + return GIT_ENOMEM; + + idx = 0; + is_leaf = 0; + + for (i = 0; i < GIT_OID_HEXSZ; ++i) { + int c = from_hex[(int)text_oid[i]]; + trie_node *node; + + if (c == -1) + return GIT_ENOTOID; + + node = &os->nodes[idx]; + + if (is_leaf) { + const char *tail; + + tail = node->tail; + node->tail = NULL; + + node = push_leaf(os, idx, from_hex[(int)tail[0]], &tail[1]); + if (node == NULL) + return GIT_ENOMEM; + } + + if (node->children[c] == 0) { + if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) + return GIT_ENOMEM; + break; + } + + idx = node->children[c]; + is_leaf = 0; + + if (idx < 0) { + node->children[c] = idx = -idx; + is_leaf = 1; + } + } + + if (++i > os->min_length) + os->min_length = i; + + return os->min_length; +} + diff --git a/tests/t01-rawobj.c b/tests/t01-rawobj.c index cc4641589..c2123cd3c 100644 --- a/tests/t01-rawobj.c +++ b/tests/t01-rawobj.c @@ -300,6 +300,96 @@ BEGIN_TEST(oid15, "convert raw oid to string (big)") must_be_true(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z'); END_TEST + +BEGIN_TEST(oid16, "make sure the OID shortener doesn't choke on duplicate sha1s") + + git_oid_shorten *os; + int min_len; + + os = git_oid_shorten_new(0); + must_be_true(os != NULL); + + git_oid_shorten_add(os, "22596363b3de40b06f981fb85d82312e8c0ed511"); + git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + git_oid_shorten_add(os, "16a0123456789abcdef4b775213c23a8bd74f5e0"); + min_len = git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + + must_be_true(min_len == GIT_OID_HEXSZ + 1); + + git_oid_shorten_free(os); +END_TEST + +BEGIN_TEST(oid17, "stress test for the git_oid_shorten object") + +#define MAX_OIDS 1000 + + git_oid_shorten *os; + char *oids[MAX_OIDS]; + char number_buffer[16]; + git_oid oid; + size_t i, j; + + int min_len, found_collision; + + os = git_oid_shorten_new(0); + must_be_true(os != NULL); + + /* + * Insert in the shortener 1000 unique SHA1 ids + */ + for (i = 0; i < MAX_OIDS; ++i) { + char *oid_text; + + sprintf(number_buffer, "%u", (unsigned int)i); + git_hash_buf(&oid, number_buffer, strlen(number_buffer)); + + oid_text = git__malloc(GIT_OID_HEXSZ + 1); + git_oid_fmt(oid_text, &oid); + oid_text[GIT_OID_HEXSZ] = 0; + + min_len = git_oid_shorten_add(os, oid_text); + must_be_true(min_len >= 0); + + oids[i] = oid_text; + } + + /* + * Compare the first `min_char - 1` characters of each + * SHA1 OID. If the minimizer worked, we should find at + * least one collision + */ + found_collision = 0; + for (i = 0; i < MAX_OIDS; ++i) { + for (j = 0; j < MAX_OIDS; ++j) { + if (i != j && memcmp(oids[i], oids[j], min_len - 1) == 0) + found_collision = 1; + } + } + must_be_true(found_collision == 1); + + /* + * Compare the first `min_char` characters of each + * SHA1 OID. If the minimizer worked, every single preffix + * should be unique. + */ + found_collision = 0; + for (i = 0; i < MAX_OIDS; ++i) { + for (j = 0; j < MAX_OIDS; ++j) { + if (i != j && memcmp(oids[i], oids[j], min_len) == 0) + found_collision = 1; + } + } + must_be_true(found_collision == 0); + + /* cleanup */ + for (i = 0; i < MAX_OIDS; ++i) + free(oids[i]); + + git_oid_shorten_free(os); + +#undef MAX_OIDS +END_TEST + static char *hello_id = "22596363b3de40b06f981fb85d82312e8c0ed511"; static char *hello_text = "hello world\n"; @@ -518,6 +608,8 @@ BEGIN_SUITE(rawobjects) ADD_TEST(oid13); ADD_TEST(oid14); ADD_TEST(oid15); + ADD_TEST(oid16); + ADD_TEST(oid17); ADD_TEST(hash0); ADD_TEST(hash1); From 86627121c8611a1dd5d2daa2e81000ab29847de5 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sun, 6 Mar 2011 00:03:31 +0200 Subject: [PATCH 02/57] Fix type-conversion warning in MSVC Signed-off-by: Vicent Marti --- src/oid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oid.c b/src/oid.c index 7841d3755..81b7d6005 100644 --- a/src/oid.c +++ b/src/oid.c @@ -206,7 +206,7 @@ static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, co return NULL; } - idx_leaf = os->node_count++; + idx_leaf = (node_index)os->node_count++; if (os->node_count == SHRT_MAX) os->full = 1; From a3002d5694bf27b9c49ff585faa3ceade3af5dd6 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sat, 29 Jan 2011 01:58:55 +0200 Subject: [PATCH 03/57] First version - WIP Signed-off-by: Vicent Marti --- src/config.c | 417 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 src/config.c diff --git a/src/config.c b/src/config.c new file mode 100644 index 000000000..627bb371b --- /dev/null +++ b/src/config.c @@ -0,0 +1,417 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "fileops.h" +#include "hashtable.h" +#include "config.h" + +struct git_config { + char *file_path; + + struct { + gitfo_buf buffer; + char *read_ptr; + int line_number; + int eof; + } reader; + + git_hashtable *vars; +}; + +typedef enum { + GIT_VAR_INT, + GIT_VAR_BOOL, + GIT_VAR_STR +} git_config_type; + +struct git_config_var { + git_config_type type; + char *name; + union { + unsigned char boolean; + long integer; + char *string; + } value; +}; + +typedef struct git_config git_config; + + +uint32_t config_table_hash(const void *key) +{ + const char *var_name = (char *)key; + return git__hash(key, strlen(var_name), 0x5273eae3); +} + +int config_table_haskey(void *object, const void *key) +{ + git_config_var *var = (git_config_var *)object; + const char *var_name = (const char *)key; + + return (strcmp(var->name, var_name) == 0); +} + +int git_config_open(git_config **cfg_out, const char *path) +{ + git_config *cfg; + + assert(cfg_out && path); + + cfg = git__malloc(sizeof(git_config)); + if (cfg == NULL) + return GIT_ENOMEM; + + memset(cfg, 0x0, sizeof(git_config)); + + cfg->file_path = git__strdup(path); + if (cfg->file_path == NULL) + return GIT_ENOMEM; + + cfg->vars = git_hashtable_alloc(16, config_table_hash, config_table_haskey); + if (cfg->vars == NULL) + return GIT_ENOMEM; + + *cfg_out = cfg; + return GIT_SUCCESS; +} + +void git_config_free(git_config *cfg) +{ + if (cfg == NULL) + return; + + free(cfg->file_path); + git_hashtable_free(cfg->vars); + gitfo_free_buf(&cfg->reader.buffer); + + free(cfg); +} + +static int cfg_getchar_raw(git_config *cfg) +{ + int c; + + c = *cfg->reader.read_ptr++; + + /* + Win 32 line breaks: if we find a \r\n sequence, + return only the \n as a newline + */ + if (c == '\r' && *cfg->reader.read_ptr == '\n') { + cfg->reader.read_ptr++; + c = '\n'; + } + + if (c == '\n') + cfg->reader.line_number++; + + if (c == 0) { + cfg->reader.eof = 1; + c = '\n'; + } + + return c; +} + +#define SKIP_WHITESPACE (1 << 1) +#define SKIP_COMMENTS (1 << 2) + +static int cfg_getchar(git_config *cfg_file, int flags) +{ + const int skip_whitespace = (flags & SKIP_WHITESPACE); + const int skip_comments = (flags & SKIP_COMMENTS); + int c; + + assert(cfg_file->reader.read_ptr); + + do c = cfg_getchar_raw(cfg_file); + while (skip_whitespace && isspace(c)); + + if (skip_comments && (c == '#' || c == ';')) { + do c = cfg_getchar_raw(cfg_file); + while (c != '\n'); + } + + return c; +} + +static const char *LINEBREAK_UNIX = "\\\n"; +static const char *LINEBREAK_WIN32 = "\\\r\n"; + +static int is_linebreak(const char *pos) +{ + return memcmp(pos - 1, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0 || + memcmp(pos - 2, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0; +} + +static char *cfg_readline(git_config *cfg) +{ + char *line = NULL; + char *line_src, *line_end; + int line_len; + + line_src = cfg->reader.read_ptr; + line_end = strchr(line_src, '\n'); + + while (is_linebreak(line_end)) + line_end = strchr(line_end + 1, '\n'); + + /* no newline at EOF */ + if (line_end == NULL) + line_end = strchr(line_src, 0); + + while (line_src < line_end && isspace(*line_src)) + line_src++; + + line = (char *)git__malloc((size_t)(line_end - line_src) + 1); + if (line == NULL) + return NULL; + + line_len = 0; + while (line_src < line_end) { + + if (memcmp(line_src, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0) { + line_src += sizeof(LINEBREAK_UNIX); + continue; + } + + if (memcmp(line_src, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0) { + line_src += sizeof(LINEBREAK_WIN32); + continue; + } + + line[line_len++] = *line_src++; + } + + line[line_len] = '\0'; + + while (--line_len >= 0 && isspace(line[line_len])) + line[line_len] = '\0'; + + if (*line_end == '\n') + line_end++; + + if (*line_end == '\0') + cfg->reader.eof = 1; + + cfg->reader.line_number++; + cfg->reader.read_ptr = line_end; + + return line; +} + +static inline int config_keychar(int c) +{ + return isalnum(c) || c == '-'; +} + +static char *parse_section_header_ext(char *base_name, git_config *cfg) +{ + return base_name; +} + +static int parse_section_header(char **section_out, const char *line) +{ + char *name, *name_start, *name_end; + int name_length, c; + + /* find the end of the variable's name */ + name_end = strchr(name_start, ']'); + if (name_end == NULL) + return NULL; + + name = (char *)git__malloc((size_t)(name_end - name_start) + 1); + if (name == NULL) + return NULL; + + name_length = 0; + c = cfg_getchar(cfg, SKIP_WHITESPACE | SKIP_COMMENTS); + + do { + if (cfg->reader.eof) + goto error; + + if (isspace(c)) + return parse_section_name_ext(name, cfg); + + if (!config_keychar(c) && c != '.') + goto error; + + name[name_length++] = tolower(c); + + } while ((c = cfg_getchar(cfg, SKIP_COMMENTS)) != ']'); + + name[name_length] = 0; + return name; + +error: + free(name); + return NULL; +} + +static int skip_bom(git_config *cfg) +{ + static const unsigned char *utf8_bom = "\xef\xbb\xbf"; + + if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) + cfg->reader.read_ptr += sizeof(utf8_bom); + + /* TODO: the reference implementation does pretty stupid + shit with the BoM + */ + + return GIT_SUCCESS; +} + +/* + (* basic types *) + digit = "0".."9" + integer = digit { digit } + alphabet = "a".."z" + "A" .. "Z" + + section_char = alphabet | "." | "-" + extension_char = (* any character except newline *) + any_char = (* any character *) + variable_char = "alphabet" | "-" + + + (* actual grammar *) + config = { section } + + section = header { definition } + + header = "[" section [subsection | subsection_ext] "]" + + subsection = "." section + subsection_ext = "\"" extension "\"" + + section = section_char { section_char } + extension = extension_char { extension_char } + + definition = variable_name ["=" variable_value] "\n" + + variable_name = variable_char { variable_char } + variable_value = string | boolean | integer + + string = quoted_string | plain_string + quoted_string = "\"" plain_string "\"" + plain_string = { any_char } + + boolean = boolean_true | boolean_false + boolean_true = "yes" | "1" | "true" | "on" + boolean_false = "no" | "0" | "false" | "off" +*/ + +static void strip_comments(char *line) +{ + int quote_count = 0; + char *ptr; + + for (ptr = line; *ptr; ++ptr) { + if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\') + quote_count++; + + if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) { + ptr[0] = '\0'; + break; + } + } + + if (isspace(ptr[-1])) { + /* TODO skip whitespace */ + } +} + +static int config_parse(git_config *cfg_file) +{ + int error = GIT_SUCCESS; + char *current_section = NULL; + + skip_bom(cfg_file); + + while (error == GIT_SUCCESS && !cfg_file->reader.eof) { + + char *line = cfg_readline(cfg_file); + + /* not enough memory to allocate line */ + if (line == NULL) + return GIT_ENOMEM; + + strip_comments(line); + + switch (line[0]) { + case '\0': /* empty line (only whitespace) */ + break; + + case '[': /* section header, new section begins */ + error = parse_section_header(¤t_section, line); + break; + + default: /* assume variable declaration */ + error = parse_variable(cfg_file, current_section, line); + break; + } + + free(line); + } + + return error; +} + +static int parse_variable(git_config *cfg, const char *section_name, const char *line) +{ + int error; + int has_value = 1; + + const char *var_end = NULL; + const char *value_start = NULL; + + var_end = strchr(line, '='); + + if (var_end == NULL) + var_end = strchr(line, '\0'); + else + value_start = var_end + 1; + + if (isspace(var_end[-1])) { + do var_end--; + while (isspace(var_end[0])); + } + + if (value_start != NULL) { + + while (isspace(value_start[0])) + value_start++; + + if (value_start[0] == '\0') + goto error; + } + + return GIT_SUCCESS; + +error: + return GIT_EOBJCORRUPTED; +} From 5d4cd0030569f0551cb0b7e432d0f496c50a118c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 28 Mar 2011 17:02:45 +0200 Subject: [PATCH 04/57] Move the struct declaration outside config.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- include/git2.h | 1 + include/git2/config.h | 53 +++++++++++++++++++++++++++++++++++++++++++ include/git2/types.h | 6 +++++ src/config.c | 31 +------------------------ src/config.h | 33 +++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 include/git2/config.h create mode 100644 src/config.h diff --git a/include/git2.h b/include/git2.h index b08c25ed1..87b770161 100644 --- a/include/git2.h +++ b/include/git2.h @@ -52,5 +52,6 @@ #include "git2/tree.h" #include "git2/index.h" +#include "git2/config.h" #endif diff --git a/include/git2/config.h b/include/git2/config.h new file mode 100644 index 000000000..c43d27fa8 --- /dev/null +++ b/include/git2/config.h @@ -0,0 +1,53 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDE_git_config_h__ +#define INCLUDE_git_config_h__ + +#include "common.h" + +/** + * @file git2/refs.h + * @brief Git config management routines + * @defgroup git_reference Git config management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Open a configuration file + * + * @param cfg_out pointer to the configuration data + */ +GIT_EXTERN(int) git_config_open(git_config **cfg_out, const char *path); + +/** + * Free the configuration and its associated memory + */ +GIT_EXTERN(void) git_config_free(git_config *cfg); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/types.h b/include/git2/types.h index 62467ec45..a99195fc4 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -122,6 +122,12 @@ typedef struct git_tree git_tree; /** Memory representation of an index file. */ typedef struct git_index git_index; +/** Memory representation of a config file */ +typedef struct git_config git_config; + +/** Memory representation of a config variable */ +typedef struct git_config_var git_config_var; + /** Time in a signature */ typedef struct git_time { time_t time; /** time in seconds from epoch */ diff --git a/src/config.c b/src/config.c index 627bb371b..ad0603513 100644 --- a/src/config.c +++ b/src/config.c @@ -28,36 +28,7 @@ #include "hashtable.h" #include "config.h" -struct git_config { - char *file_path; - - struct { - gitfo_buf buffer; - char *read_ptr; - int line_number; - int eof; - } reader; - - git_hashtable *vars; -}; - -typedef enum { - GIT_VAR_INT, - GIT_VAR_BOOL, - GIT_VAR_STR -} git_config_type; - -struct git_config_var { - git_config_type type; - char *name; - union { - unsigned char boolean; - long integer; - char *string; - } value; -}; - -typedef struct git_config git_config; +#include uint32_t config_table_hash(const void *key) diff --git a/src/config.h b/src/config.h new file mode 100644 index 000000000..07d123ef5 --- /dev/null +++ b/src/config.h @@ -0,0 +1,33 @@ +#ifndef INCLUDE_tag_h__ +#define INCLUDE_tag_h__ + +struct git_config { + char *file_path; + + struct { + gitfo_buf buffer; + char *read_ptr; + int line_number; + int eof; + } reader; + + git_hashtable *vars; +}; + +typedef enum { + GIT_VAR_INT, + GIT_VAR_BOOL, + GIT_VAR_STR +} git_config_type; + +struct git_config_var { + git_config_type type; + char *name; + union { + unsigned char boolean; + long integer; + char *string; + } value; +}; + +#endif From a69053c715f1e1780766201ffa9d720f91aa5736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 28 Mar 2011 17:12:53 +0200 Subject: [PATCH 05/57] Convert config.c to LF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 776 +++++++++++++++++++++++++-------------------------- 1 file changed, 388 insertions(+), 388 deletions(-) diff --git a/src/config.c b/src/config.c index ad0603513..656f64eb0 100644 --- a/src/config.c +++ b/src/config.c @@ -1,388 +1,388 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "common.h" -#include "fileops.h" -#include "hashtable.h" -#include "config.h" - -#include - - -uint32_t config_table_hash(const void *key) -{ - const char *var_name = (char *)key; - return git__hash(key, strlen(var_name), 0x5273eae3); -} - -int config_table_haskey(void *object, const void *key) -{ - git_config_var *var = (git_config_var *)object; - const char *var_name = (const char *)key; - - return (strcmp(var->name, var_name) == 0); -} - -int git_config_open(git_config **cfg_out, const char *path) -{ - git_config *cfg; - - assert(cfg_out && path); - - cfg = git__malloc(sizeof(git_config)); - if (cfg == NULL) - return GIT_ENOMEM; - - memset(cfg, 0x0, sizeof(git_config)); - - cfg->file_path = git__strdup(path); - if (cfg->file_path == NULL) - return GIT_ENOMEM; - - cfg->vars = git_hashtable_alloc(16, config_table_hash, config_table_haskey); - if (cfg->vars == NULL) - return GIT_ENOMEM; - - *cfg_out = cfg; - return GIT_SUCCESS; -} - -void git_config_free(git_config *cfg) -{ - if (cfg == NULL) - return; - - free(cfg->file_path); - git_hashtable_free(cfg->vars); - gitfo_free_buf(&cfg->reader.buffer); - - free(cfg); -} - -static int cfg_getchar_raw(git_config *cfg) -{ - int c; - - c = *cfg->reader.read_ptr++; - - /* - Win 32 line breaks: if we find a \r\n sequence, - return only the \n as a newline - */ - if (c == '\r' && *cfg->reader.read_ptr == '\n') { - cfg->reader.read_ptr++; - c = '\n'; - } - - if (c == '\n') - cfg->reader.line_number++; - - if (c == 0) { - cfg->reader.eof = 1; - c = '\n'; - } - - return c; -} - -#define SKIP_WHITESPACE (1 << 1) -#define SKIP_COMMENTS (1 << 2) - -static int cfg_getchar(git_config *cfg_file, int flags) -{ - const int skip_whitespace = (flags & SKIP_WHITESPACE); - const int skip_comments = (flags & SKIP_COMMENTS); - int c; - - assert(cfg_file->reader.read_ptr); - - do c = cfg_getchar_raw(cfg_file); - while (skip_whitespace && isspace(c)); - - if (skip_comments && (c == '#' || c == ';')) { - do c = cfg_getchar_raw(cfg_file); - while (c != '\n'); - } - - return c; -} - -static const char *LINEBREAK_UNIX = "\\\n"; -static const char *LINEBREAK_WIN32 = "\\\r\n"; - -static int is_linebreak(const char *pos) -{ - return memcmp(pos - 1, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0 || - memcmp(pos - 2, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0; -} - -static char *cfg_readline(git_config *cfg) -{ - char *line = NULL; - char *line_src, *line_end; - int line_len; - - line_src = cfg->reader.read_ptr; - line_end = strchr(line_src, '\n'); - - while (is_linebreak(line_end)) - line_end = strchr(line_end + 1, '\n'); - - /* no newline at EOF */ - if (line_end == NULL) - line_end = strchr(line_src, 0); - - while (line_src < line_end && isspace(*line_src)) - line_src++; - - line = (char *)git__malloc((size_t)(line_end - line_src) + 1); - if (line == NULL) - return NULL; - - line_len = 0; - while (line_src < line_end) { - - if (memcmp(line_src, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0) { - line_src += sizeof(LINEBREAK_UNIX); - continue; - } - - if (memcmp(line_src, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0) { - line_src += sizeof(LINEBREAK_WIN32); - continue; - } - - line[line_len++] = *line_src++; - } - - line[line_len] = '\0'; - - while (--line_len >= 0 && isspace(line[line_len])) - line[line_len] = '\0'; - - if (*line_end == '\n') - line_end++; - - if (*line_end == '\0') - cfg->reader.eof = 1; - - cfg->reader.line_number++; - cfg->reader.read_ptr = line_end; - - return line; -} - -static inline int config_keychar(int c) -{ - return isalnum(c) || c == '-'; -} - -static char *parse_section_header_ext(char *base_name, git_config *cfg) -{ - return base_name; -} - -static int parse_section_header(char **section_out, const char *line) -{ - char *name, *name_start, *name_end; - int name_length, c; - - /* find the end of the variable's name */ - name_end = strchr(name_start, ']'); - if (name_end == NULL) - return NULL; - - name = (char *)git__malloc((size_t)(name_end - name_start) + 1); - if (name == NULL) - return NULL; - - name_length = 0; - c = cfg_getchar(cfg, SKIP_WHITESPACE | SKIP_COMMENTS); - - do { - if (cfg->reader.eof) - goto error; - - if (isspace(c)) - return parse_section_name_ext(name, cfg); - - if (!config_keychar(c) && c != '.') - goto error; - - name[name_length++] = tolower(c); - - } while ((c = cfg_getchar(cfg, SKIP_COMMENTS)) != ']'); - - name[name_length] = 0; - return name; - -error: - free(name); - return NULL; -} - -static int skip_bom(git_config *cfg) -{ - static const unsigned char *utf8_bom = "\xef\xbb\xbf"; - - if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) - cfg->reader.read_ptr += sizeof(utf8_bom); - - /* TODO: the reference implementation does pretty stupid - shit with the BoM - */ - - return GIT_SUCCESS; -} - -/* - (* basic types *) - digit = "0".."9" - integer = digit { digit } - alphabet = "a".."z" + "A" .. "Z" - - section_char = alphabet | "." | "-" - extension_char = (* any character except newline *) - any_char = (* any character *) - variable_char = "alphabet" | "-" - - - (* actual grammar *) - config = { section } - - section = header { definition } - - header = "[" section [subsection | subsection_ext] "]" - - subsection = "." section - subsection_ext = "\"" extension "\"" - - section = section_char { section_char } - extension = extension_char { extension_char } - - definition = variable_name ["=" variable_value] "\n" - - variable_name = variable_char { variable_char } - variable_value = string | boolean | integer - - string = quoted_string | plain_string - quoted_string = "\"" plain_string "\"" - plain_string = { any_char } - - boolean = boolean_true | boolean_false - boolean_true = "yes" | "1" | "true" | "on" - boolean_false = "no" | "0" | "false" | "off" -*/ - -static void strip_comments(char *line) -{ - int quote_count = 0; - char *ptr; - - for (ptr = line; *ptr; ++ptr) { - if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\') - quote_count++; - - if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) { - ptr[0] = '\0'; - break; - } - } - - if (isspace(ptr[-1])) { - /* TODO skip whitespace */ - } -} - -static int config_parse(git_config *cfg_file) -{ - int error = GIT_SUCCESS; - char *current_section = NULL; - - skip_bom(cfg_file); - - while (error == GIT_SUCCESS && !cfg_file->reader.eof) { - - char *line = cfg_readline(cfg_file); - - /* not enough memory to allocate line */ - if (line == NULL) - return GIT_ENOMEM; - - strip_comments(line); - - switch (line[0]) { - case '\0': /* empty line (only whitespace) */ - break; - - case '[': /* section header, new section begins */ - error = parse_section_header(¤t_section, line); - break; - - default: /* assume variable declaration */ - error = parse_variable(cfg_file, current_section, line); - break; - } - - free(line); - } - - return error; -} - -static int parse_variable(git_config *cfg, const char *section_name, const char *line) -{ - int error; - int has_value = 1; - - const char *var_end = NULL; - const char *value_start = NULL; - - var_end = strchr(line, '='); - - if (var_end == NULL) - var_end = strchr(line, '\0'); - else - value_start = var_end + 1; - - if (isspace(var_end[-1])) { - do var_end--; - while (isspace(var_end[0])); - } - - if (value_start != NULL) { - - while (isspace(value_start[0])) - value_start++; - - if (value_start[0] == '\0') - goto error; - } - - return GIT_SUCCESS; - -error: - return GIT_EOBJCORRUPTED; -} +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "fileops.h" +#include "hashtable.h" +#include "config.h" + +#include + + +uint32_t config_table_hash(const void *key) +{ + const char *var_name = (char *)key; + return git__hash(key, strlen(var_name), 0x5273eae3); +} + +int config_table_haskey(void *object, const void *key) +{ + git_config_var *var = (git_config_var *)object; + const char *var_name = (const char *)key; + + return (strcmp(var->name, var_name) == 0); +} + +int git_config_open(git_config **cfg_out, const char *path) +{ + git_config *cfg; + + assert(cfg_out && path); + + cfg = git__malloc(sizeof(git_config)); + if (cfg == NULL) + return GIT_ENOMEM; + + memset(cfg, 0x0, sizeof(git_config)); + + cfg->file_path = git__strdup(path); + if (cfg->file_path == NULL) + return GIT_ENOMEM; + + cfg->vars = git_hashtable_alloc(16, config_table_hash, config_table_haskey); + if (cfg->vars == NULL) + return GIT_ENOMEM; + + *cfg_out = cfg; + return GIT_SUCCESS; +} + +void git_config_free(git_config *cfg) +{ + if (cfg == NULL) + return; + + free(cfg->file_path); + git_hashtable_free(cfg->vars); + gitfo_free_buf(&cfg->reader.buffer); + + free(cfg); +} + +static int cfg_getchar_raw(git_config *cfg) +{ + int c; + + c = *cfg->reader.read_ptr++; + + /* + Win 32 line breaks: if we find a \r\n sequence, + return only the \n as a newline + */ + if (c == '\r' && *cfg->reader.read_ptr == '\n') { + cfg->reader.read_ptr++; + c = '\n'; + } + + if (c == '\n') + cfg->reader.line_number++; + + if (c == 0) { + cfg->reader.eof = 1; + c = '\n'; + } + + return c; +} + +#define SKIP_WHITESPACE (1 << 1) +#define SKIP_COMMENTS (1 << 2) + +static int cfg_getchar(git_config *cfg_file, int flags) +{ + const int skip_whitespace = (flags & SKIP_WHITESPACE); + const int skip_comments = (flags & SKIP_COMMENTS); + int c; + + assert(cfg_file->reader.read_ptr); + + do c = cfg_getchar_raw(cfg_file); + while (skip_whitespace && isspace(c)); + + if (skip_comments && (c == '#' || c == ';')) { + do c = cfg_getchar_raw(cfg_file); + while (c != '\n'); + } + + return c; +} + +static const char *LINEBREAK_UNIX = "\\\n"; +static const char *LINEBREAK_WIN32 = "\\\r\n"; + +static int is_linebreak(const char *pos) +{ + return memcmp(pos - 1, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0 || + memcmp(pos - 2, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0; +} + +static char *cfg_readline(git_config *cfg) +{ + char *line = NULL; + char *line_src, *line_end; + int line_len; + + line_src = cfg->reader.read_ptr; + line_end = strchr(line_src, '\n'); + + while (is_linebreak(line_end)) + line_end = strchr(line_end + 1, '\n'); + + /* no newline at EOF */ + if (line_end == NULL) + line_end = strchr(line_src, 0); + + while (line_src < line_end && isspace(*line_src)) + line_src++; + + line = (char *)git__malloc((size_t)(line_end - line_src) + 1); + if (line == NULL) + return NULL; + + line_len = 0; + while (line_src < line_end) { + + if (memcmp(line_src, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0) { + line_src += sizeof(LINEBREAK_UNIX); + continue; + } + + if (memcmp(line_src, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0) { + line_src += sizeof(LINEBREAK_WIN32); + continue; + } + + line[line_len++] = *line_src++; + } + + line[line_len] = '\0'; + + while (--line_len >= 0 && isspace(line[line_len])) + line[line_len] = '\0'; + + if (*line_end == '\n') + line_end++; + + if (*line_end == '\0') + cfg->reader.eof = 1; + + cfg->reader.line_number++; + cfg->reader.read_ptr = line_end; + + return line; +} + +static inline int config_keychar(int c) +{ + return isalnum(c) || c == '-'; +} + +static char *parse_section_header_ext(char *base_name, git_config *cfg) +{ + return base_name; +} + +static int parse_section_header(char **section_out, const char *line) +{ + char *name, *name_start, *name_end; + int name_length, c; + + /* find the end of the variable's name */ + name_end = strchr(name_start, ']'); + if (name_end == NULL) + return NULL; + + name = (char *)git__malloc((size_t)(name_end - name_start) + 1); + if (name == NULL) + return NULL; + + name_length = 0; + c = cfg_getchar(cfg, SKIP_WHITESPACE | SKIP_COMMENTS); + + do { + if (cfg->reader.eof) + goto error; + + if (isspace(c)) + return parse_section_name_ext(name, cfg); + + if (!config_keychar(c) && c != '.') + goto error; + + name[name_length++] = tolower(c); + + } while ((c = cfg_getchar(cfg, SKIP_COMMENTS)) != ']'); + + name[name_length] = 0; + return name; + +error: + free(name); + return NULL; +} + +static int skip_bom(git_config *cfg) +{ + static const unsigned char *utf8_bom = "\xef\xbb\xbf"; + + if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) + cfg->reader.read_ptr += sizeof(utf8_bom); + + /* TODO: the reference implementation does pretty stupid + shit with the BoM + */ + + return GIT_SUCCESS; +} + +/* + (* basic types *) + digit = "0".."9" + integer = digit { digit } + alphabet = "a".."z" + "A" .. "Z" + + section_char = alphabet | "." | "-" + extension_char = (* any character except newline *) + any_char = (* any character *) + variable_char = "alphabet" | "-" + + + (* actual grammar *) + config = { section } + + section = header { definition } + + header = "[" section [subsection | subsection_ext] "]" + + subsection = "." section + subsection_ext = "\"" extension "\"" + + section = section_char { section_char } + extension = extension_char { extension_char } + + definition = variable_name ["=" variable_value] "\n" + + variable_name = variable_char { variable_char } + variable_value = string | boolean | integer + + string = quoted_string | plain_string + quoted_string = "\"" plain_string "\"" + plain_string = { any_char } + + boolean = boolean_true | boolean_false + boolean_true = "yes" | "1" | "true" | "on" + boolean_false = "no" | "0" | "false" | "off" +*/ + +static void strip_comments(char *line) +{ + int quote_count = 0; + char *ptr; + + for (ptr = line; *ptr; ++ptr) { + if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\') + quote_count++; + + if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) { + ptr[0] = '\0'; + break; + } + } + + if (isspace(ptr[-1])) { + /* TODO skip whitespace */ + } +} + +static int config_parse(git_config *cfg_file) +{ + int error = GIT_SUCCESS; + char *current_section = NULL; + + skip_bom(cfg_file); + + while (error == GIT_SUCCESS && !cfg_file->reader.eof) { + + char *line = cfg_readline(cfg_file); + + /* not enough memory to allocate line */ + if (line == NULL) + return GIT_ENOMEM; + + strip_comments(line); + + switch (line[0]) { + case '\0': /* empty line (only whitespace) */ + break; + + case '[': /* section header, new section begins */ + error = parse_section_header(¤t_section, line); + break; + + default: /* assume variable declaration */ + error = parse_variable(cfg_file, current_section, line); + break; + } + + free(line); + } + + return error; +} + +static int parse_variable(git_config *cfg, const char *section_name, const char *line) +{ + int error; + int has_value = 1; + + const char *var_end = NULL; + const char *value_start = NULL; + + var_end = strchr(line, '='); + + if (var_end == NULL) + var_end = strchr(line, '\0'); + else + value_start = var_end + 1; + + if (isspace(var_end[-1])) { + do var_end--; + while (isspace(var_end[0])); + } + + if (value_start != NULL) { + + while (isspace(value_start[0])) + value_start++; + + if (value_start[0] == '\0') + goto error; + } + + return GIT_SUCCESS; + +error: + return GIT_EOBJCORRUPTED; +} From e4c796f1a2f767d56f32d237d797109c96fd23ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 28 Mar 2011 17:51:18 +0200 Subject: [PATCH 06/57] Read and parse the confguration when openingt the config file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/config.c b/src/config.c index 656f64eb0..e615b0327 100644 --- a/src/config.c +++ b/src/config.c @@ -30,7 +30,11 @@ #include - +/********************** + * Forward declarations + ***********************/ +static int config_parse(git_config *cfg_file); +static int parse_variable(git_config *cfg, const char *section_name, const char *line); uint32_t config_table_hash(const void *key) { const char *var_name = (char *)key; @@ -48,6 +52,7 @@ int config_table_haskey(void *object, const void *key) int git_config_open(git_config **cfg_out, const char *path) { git_config *cfg; + int error = GIT_SUCCESS; assert(cfg_out && path); @@ -58,15 +63,35 @@ int git_config_open(git_config **cfg_out, const char *path) memset(cfg, 0x0, sizeof(git_config)); cfg->file_path = git__strdup(path); - if (cfg->file_path == NULL) - return GIT_ENOMEM; + if (cfg->file_path == NULL){ + error = GIT_ENOMEM; + goto cleanup; + } cfg->vars = git_hashtable_alloc(16, config_table_hash, config_table_haskey); - if (cfg->vars == NULL) - return GIT_ENOMEM; + if (cfg->vars == NULL){ + error = GIT_ENOMEM; + goto cleanup; + } *cfg_out = cfg; - return GIT_SUCCESS; + + error = gitfo_read_file(&cfg->reader.buffer, cfg->file_path); + if(error < GIT_SUCCESS) + goto cleanup; + + /* Initialise the reading position */ + cfg->reader.read_ptr = cfg->reader.buffer.data; + return config_parse(cfg); + + cleanup: + if(cfg->vars) + git_hashtable_free(cfg->vars); + if(cfg->file_path) + free(cfg->file_path); + free(cfg); + + return error; } void git_config_free(git_config *cfg) From 908afb771ad6a788dd250beede65dd129962da00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 28 Mar 2011 17:53:04 +0200 Subject: [PATCH 07/57] parse_section_header: save the name where it belongs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Save the location of the name in section_out instead of returning it as an int. Use the return code to signal success or failure. Signed-off-by: Carlos Martín Nieto --- src/config.c | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/config.c b/src/config.c index e615b0327..a3d7f8e73 100644 --- a/src/config.c +++ b/src/config.c @@ -229,43 +229,68 @@ static char *parse_section_header_ext(char *base_name, git_config *cfg) return base_name; } -static int parse_section_header(char **section_out, const char *line) +static int parse_section_header(git_config *cfg, char **section_out, const char *line) { - char *name, *name_start, *name_end; + char *name, *name_end; int name_length, c; + int error = GIT_SUCCESS; /* find the end of the variable's name */ - name_end = strchr(name_start, ']'); + name_end = strchr(line, ']'); if (name_end == NULL) - return NULL; + return GIT_EOBJCORRUPTED; - name = (char *)git__malloc((size_t)(name_end - name_start) + 1); + name = (char *)git__malloc((size_t)(name_end - line) + 1); if (name == NULL) - return NULL; + return GIT_EOBJCORRUPTED; name_length = 0; + + /* Make sure we were given a section header */ + c = cfg_getchar(cfg, SKIP_WHITESPACE | SKIP_COMMENTS); + if(c != '['){ + error = GIT_EOBJCORRUPTED; + goto error; + } + c = cfg_getchar(cfg, SKIP_WHITESPACE | SKIP_COMMENTS); do { - if (cfg->reader.eof) + if (cfg->reader.eof){ + error = GIT_EOBJCORRUPTED; goto error; + } - if (isspace(c)) - return parse_section_name_ext(name, cfg); + if (isspace(c)){ + *section_out = parse_section_header_ext(name, cfg); + return GIT_SUCCESS; + } - if (!config_keychar(c) && c != '.') + if (!config_keychar(c) && c != '.'){ + error = GIT_EOBJCORRUPTED; goto error; + } name[name_length++] = tolower(c); } while ((c = cfg_getchar(cfg, SKIP_COMMENTS)) != ']'); + /* + * Here, we enforce that a section name needs to be on its own + * line + */ + if(cfg_getchar(cfg, SKIP_COMMENTS) != '\n'){ + error = GIT_EOBJCORRUPTED; + goto error; + } + name[name_length] = 0; - return name; + *section_out = name; + return GIT_SUCCESS; error: free(name); - return NULL; + return error; } static int skip_bom(git_config *cfg) From 3b4835c25a41781d27a667dbd02ff03d54d221b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 28 Mar 2011 18:07:22 +0200 Subject: [PATCH 08/57] Correctly parse the section header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If cfg_readline consumes the line, then parse_section_header will read past it and if we read a character, parse_variable won't have the full name. This solution is a bit hackish, but it's the simplest way to get the code to parse correctly. Signed-off-by: Carlos Martín Nieto --- src/config.c | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index a3d7f8e73..f49620246 100644 --- a/src/config.c +++ b/src/config.c @@ -163,6 +163,9 @@ static int is_linebreak(const char *pos) memcmp(pos - 2, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0; } +/* + * Read a line, but don't consume it + */ static char *cfg_readline(git_config *cfg) { char *line = NULL; @@ -213,12 +216,41 @@ static char *cfg_readline(git_config *cfg) if (*line_end == '\0') cfg->reader.eof = 1; + /* cfg->reader.line_number++; cfg->reader.read_ptr = line_end; + */ return line; } +/* + * Consume a line, without storing it anywhere + */ +void cfg_consume_line(git_config *cfg) +{ + char *line_start, *line_end; + int len; + + line_start = cfg->reader.read_ptr; + line_end = strchr(line_start, '\n'); + /* No newline at EOF */ + if(line_end == NULL){ + line_end = strchr(line_start, '\0'); + } + + len = line_end - line_start; + + if (*line_end == '\n') + line_end++; + + if (*line_end == '\0') + cfg->reader.eof = 1; + + cfg->reader.line_number++; + cfg->reader.read_ptr = line_end; +} + static inline int config_keychar(int c) { return isalnum(c) || c == '-'; @@ -388,17 +420,23 @@ static int config_parse(git_config *cfg_file) break; case '[': /* section header, new section begins */ - error = parse_section_header(¤t_section, line); + if (current_section) + free(current_section); + error = parse_section_header(cfg_file, ¤t_section, line); break; default: /* assume variable declaration */ error = parse_variable(cfg_file, current_section, line); + cfg_consume_line(cfg_file); break; } free(line); } + if(current_section) + free(current_section); + return error; } From 4e02504f5232e38d116830aaf25bcea4d5123a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 29 Mar 2011 12:10:30 +0200 Subject: [PATCH 09/57] Move config to support the new hash code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hashes have been copied from the references code Signed-off-by: Carlos Martín Nieto --- src/config.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/config.c b/src/config.c index f49620246..852bcd3b1 100644 --- a/src/config.c +++ b/src/config.c @@ -35,18 +35,17 @@ ***********************/ static int config_parse(git_config *cfg_file); static int parse_variable(git_config *cfg, const char *section_name, const char *line); -uint32_t config_table_hash(const void *key) -{ - const char *var_name = (char *)key; - return git__hash(key, strlen(var_name), 0x5273eae3); -} -int config_table_haskey(void *object, const void *key) +uint32_t config_table_hash(const void *key, int hash_id) { - git_config_var *var = (git_config_var *)object; + static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { + 2147483647, + 0x5d20bb23, + 0x7daaab3c + }; + const char *var_name = (const char *)key; - - return (strcmp(var->name, var_name) == 0); + return git__hash(key, strlen(var_name), hash_seeds[hash_id]); } int git_config_open(git_config **cfg_out, const char *path) @@ -68,7 +67,8 @@ int git_config_open(git_config **cfg_out, const char *path) goto cleanup; } - cfg->vars = git_hashtable_alloc(16, config_table_hash, config_table_haskey); + cfg->vars = git_hashtable_alloc(16, config_table_hash, + (git_hash_keyeq_ptr) strcmp); if (cfg->vars == NULL){ error = GIT_ENOMEM; goto cleanup; From 9f7f4122cfa49f8932fd65f9751d96125c8155bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 29 Mar 2011 12:19:53 +0200 Subject: [PATCH 10/57] Don't leak if config parsing fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index 852bcd3b1..22ba5f56d 100644 --- a/src/config.c +++ b/src/config.c @@ -74,15 +74,20 @@ int git_config_open(git_config **cfg_out, const char *path) goto cleanup; } - *cfg_out = cfg; - error = gitfo_read_file(&cfg->reader.buffer, cfg->file_path); if(error < GIT_SUCCESS) goto cleanup; /* Initialise the reading position */ cfg->reader.read_ptr = cfg->reader.buffer.data; - return config_parse(cfg); + + error = config_parse(cfg); + if(error < GIT_SUCCESS) + git_config_free(cfg); + else + *cfg_out = cfg; + + return error; cleanup: if(cfg->vars) From 05314b5bf449211726ca7d84f15db3077375a3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 29 Mar 2011 12:25:46 +0200 Subject: [PATCH 11/57] Make GIT_EINVALIDTYPE available for use in config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- include/git2/common.h | 2 +- src/errors.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/git2/common.h b/include/git2/common.h index 34efe808b..ec338db4e 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -115,7 +115,7 @@ /** The specified repository is invalid */ #define GIT_ENOTAREPO (GIT_ERROR - 7) -/** The object type is invalid or doesn't match */ +/** The object or config variable type is invalid or doesn't match */ #define GIT_EINVALIDTYPE (GIT_ERROR - 8) /** The object cannot be written that because it's missing internal data */ diff --git a/src/errors.c b/src/errors.c index 880163f78..1dc54f945 100644 --- a/src/errors.c +++ b/src/errors.c @@ -13,7 +13,7 @@ static struct { {GIT_EOBJTYPE, "The specified object is of invalid type"}, {GIT_EOBJCORRUPTED, "The specified object has its data corrupted"}, {GIT_ENOTAREPO, "The specified repository is invalid"}, - {GIT_EINVALIDTYPE, "The object type is invalid or doesn't match"}, + {GIT_EINVALIDTYPE, "The object or config variable type is invalid or doesn't match"}, {GIT_EMISSINGOBJDATA, "The object cannot be written that because it's missing internal data"}, {GIT_EPACKCORRUPTED, "The packfile for the ODB is corrupted"}, {GIT_EFLOCKFAIL, "Failed to adquire or release a file lock"}, From 238df5590cfb9f1cfc340938188b2425f9510f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 29 Mar 2011 12:29:21 +0200 Subject: [PATCH 12/57] Rename git_config_{type,var} to git_cvar{_type,} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- include/git2/types.h | 2 +- src/config.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/git2/types.h b/include/git2/types.h index a99195fc4..aa53909d2 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -126,7 +126,7 @@ typedef struct git_index git_index; typedef struct git_config git_config; /** Memory representation of a config variable */ -typedef struct git_config_var git_config_var; +typedef struct git_cvar git_cvar; /** Time in a signature */ typedef struct git_time { diff --git a/src/config.h b/src/config.h index 07d123ef5..2aa9ec804 100644 --- a/src/config.h +++ b/src/config.h @@ -18,10 +18,10 @@ typedef enum { GIT_VAR_INT, GIT_VAR_BOOL, GIT_VAR_STR -} git_config_type; +} git_cvar_type; -struct git_config_var { - git_config_type type; +struct git_cvar { + git_cvar_type type; char *name; union { unsigned char boolean; From 3d23b74af75bc35b3676c1a7fc1f5a0299c9f4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 29 Mar 2011 13:50:37 +0200 Subject: [PATCH 13/57] Free the config var hash contents in git_config_free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/config.c b/src/config.c index 22ba5f56d..5c500c47f 100644 --- a/src/config.c +++ b/src/config.c @@ -35,6 +35,15 @@ ***********************/ static int config_parse(git_config *cfg_file); static int parse_variable(git_config *cfg, const char *section_name, const char *line); +void git_config_free(git_config *cfg); + +static void cvar_free(git_cvar *var) +{ + if(var->type == GIT_VAR_STR) + free(var->value.string); + + free(var); +} uint32_t config_table_hash(const void *key, int hash_id) { @@ -101,10 +110,18 @@ int git_config_open(git_config **cfg_out, const char *path) void git_config_free(git_config *cfg) { + git_cvar *var; + const void *_unused; + if (cfg == NULL) return; free(cfg->file_path); + + GIT_HASHTABLE_FOREACH(cfg->vars, _unused, var, + cvar_free(var); + ); + git_hashtable_free(cfg->vars); gitfo_free_buf(&cfg->reader.buffer); From 6d7bb4e039f6f6cf4e8542024550840a4ba50624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 29 Mar 2011 17:35:02 +0200 Subject: [PATCH 14/57] Move git_cvar_type to include/git2/config.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Include it in src/config.h and fix the header name #define. Signed-off-by: Carlos Martín Nieto --- include/git2/types.h | 7 +++++++ src/config.h | 12 ++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/include/git2/types.h b/include/git2/types.h index aa53909d2..269c48249 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -153,6 +153,13 @@ typedef enum { GIT_REF_HAS_PEEL = 8, } git_rtype; +/** Config variable type */ +typedef enum { + GIT_VAR_INT, /** Stores an integer value */ + GIT_VAR_BOOL, /** Stores a boolean value */ + GIT_VAR_STR /** Stores a string */ +} git_cvar_type; + /** @} */ GIT_END_DECL diff --git a/src/config.h b/src/config.h index 2aa9ec804..3b9da5241 100644 --- a/src/config.h +++ b/src/config.h @@ -1,5 +1,7 @@ -#ifndef INCLUDE_tag_h__ -#define INCLUDE_tag_h__ +#ifndef INCLUDE_config_h__ +#define INCLUDE_config_h__ + +#include "git2/config.h" struct git_config { char *file_path; @@ -14,12 +16,6 @@ struct git_config { git_hashtable *vars; }; -typedef enum { - GIT_VAR_INT, - GIT_VAR_BOOL, - GIT_VAR_STR -} git_cvar_type; - struct git_cvar { git_cvar_type type; char *name; From e15afc8e7cf6f6508d402134e0df772c4012ce77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 29 Mar 2011 17:37:03 +0200 Subject: [PATCH 15/57] cvar_free: also free the config var's name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.c b/src/config.c index 5c500c47f..8d01d7ba7 100644 --- a/src/config.c +++ b/src/config.c @@ -42,6 +42,7 @@ static void cvar_free(git_cvar *var) if(var->type == GIT_VAR_STR) free(var->value.string); + free(var->name); free(var); } From 26faa3668fedf1ffc95ef747b28f08b7131870f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 29 Mar 2011 17:59:13 +0200 Subject: [PATCH 16/57] Add build_varname to make a full var name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/config.c b/src/config.c index 8d01d7ba7..5f0c34ab7 100644 --- a/src/config.c +++ b/src/config.c @@ -463,10 +463,25 @@ static int config_parse(git_config *cfg_file) return error; } +static const char *build_varname(const char *section, const char *name, int len) +{ + static char varname[1024]; /* TODO: What's the longest we should allow? */ + + if(strlen(section) + len + 2 > sizeof(varname)) + return NULL; + + strcpy(varname, section); + strcat(varname, "."); + strncat(varname, name, len); + + return varname; +} + static int parse_variable(git_config *cfg, const char *section_name, const char *line) { int error; int has_value = 1; + const char *varname; const char *var_end = NULL; const char *value_start = NULL; @@ -492,6 +507,8 @@ static int parse_variable(git_config *cfg, const char *section_name, const char goto error; } + varname = build_varname(section_name, line, var_end - line + 1); + return GIT_SUCCESS; error: From 9a3c5e55fd7894b8732a8010c94d034d674adbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 29 Mar 2011 17:44:10 +0200 Subject: [PATCH 17/57] Expose config API for setters, getters and foreach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These functions can be used to query or modify the variables in a given configuration. No sanity checking is done on the variable names. This is mostly meant as an API preview. Signed-off-by: Carlos Martín Nieto --- include/git2/config.h | 128 +++++++++++++++++++++++++++++++++++++++- src/config.c | 133 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+), 2 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index c43d27fa8..c91482636 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -26,11 +26,12 @@ #define INCLUDE_git_config_h__ #include "common.h" +#include "types.h" /** - * @file git2/refs.h + * @file git2/config.h * @brief Git config management routines - * @defgroup git_reference Git config management routines + * @defgroup git_config Git config management routines * @ingroup Git * @{ */ @@ -40,14 +41,137 @@ GIT_BEGIN_DECL * Open a configuration file * * @param cfg_out pointer to the configuration data + * @param path where to load the confiration from */ GIT_EXTERN(int) git_config_open(git_config **cfg_out, const char *path); /** * Free the configuration and its associated memory + * + * @param cfg the configuration to free */ GIT_EXTERN(void) git_config_free(git_config *cfg); +/** + * Get the value of an integer or boolean config variable. + * + * This is a more general function to retrieve the value of a integer + * or boolean variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @param type either GIT_VAR_INT or GIT_VAR_BOOL + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_get(git_config *cfg, const char *name, int *out, git_cvar_type type); + +/** + * Get the value of an integer config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_INLINE(int) git_config_get_int(git_config *cfg, const char *name, int *out) +{ + return git_config_get(cfg, name, out, GIT_VAR_INT); +} + +/** + * Get the value of a boolean config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_INLINE(int) git_config_get_bool(git_config *cfg, const char *name, int *out) +{ + return git_config_get(cfg, name, out, GIT_VAR_BOOL); +} + +/** + * Get the value of a string config variable. + * + * The string is owned by the variable and should not be freed by the + * user. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable's value + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_get_string(git_config *cfg, const char *name, const char **out); +/** + * Set the value of an integer or boolean config variable. + * + * This is a more general function to set the value of a integer or + * boolean variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param value the value to store + * @param type either GIT_VAR_INT or GIT_VAR_BOOL + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_set(git_config *cfg, const char *name, int value, git_cvar_type type); + +/** + * Set the value of an integer config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_INLINE(int) git_config_set_int(git_config *cfg, const char *name, int value) +{ + return git_config_set(cfg, name, value, GIT_VAR_INT); +} + +/** + * Set the value of a boolean config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param value the value to store + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_INLINE(int) git_config_set_bool(git_config *cfg, const char *name, int value) +{ + return git_config_set(cfg, name, value, GIT_VAR_BOOL); +} + +/** + * Set the value of a string config variable. + * + * A copy of the string is made and the user is free to use it + * afterwards. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param value the string to store. + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const char *value); + +/** + * Perform an operation on each config variable. + * + * The callback is passed a pointer to a config variable and the data + * pointer passed to this function. As soon as one of the callback + * functions returns something other than 0, this function returns + * that value. + * + * @param cfg where to get the variables from + * @param callback the function to call on each variable + * @param data the data to pass to the callback + * @return GIT_SUCCESS or the return value of the callback which didn't return 0 + */ +GIT_EXTERN(int) git_config_foreach(git_config *cfg, int (*callback)(git_cvar *, void *data), void *data); + /** @} */ GIT_END_DECL #endif diff --git a/src/config.c b/src/config.c index 5f0c34ab7..f9dbda7e9 100644 --- a/src/config.c +++ b/src/config.c @@ -129,6 +129,139 @@ void git_config_free(git_config *cfg) free(cfg); } +/* + * Loop over all the variables + */ + +int git_config_foreach(git_config *cfg, int (*fn)(git_cvar *, void *), void *data) +{ + int ret = GIT_SUCCESS; + git_cvar *var; + void *_unused; + + GIT_HASHTABLE_FOREACH(cfg->vars, _unused, var, + ret = fn(var, data); + if(ret) break; + ); + + return ret; +} + +/* + * Setters + */ + +int git_config_set(git_config *cfg, const char *name, + int value, git_cvar_type type) +{ + git_cvar *var = NULL; + int error = GIT_SUCCESS; + + var = git__malloc(sizeof(git_cvar)); + if(var == NULL){ + error = GIT_ENOMEM; + goto out; + } + + var->name = git__strdup(name); + if(var->name == NULL){ + error = GIT_ENOMEM; + free(var); + goto out; + } + + var->type = type; + if(type == GIT_VAR_BOOL) + var->value.boolean = value; + else + var->value.integer = value; + + error = git_hashtable_insert(cfg->vars, var->name, var); + if(error < GIT_SUCCESS) + cvar_free(var); + + out: + return error; +} + +int git_config_set_string(git_config *cfg, const char *name, const char *value) +{ + git_cvar *var = NULL; + int error = GIT_SUCCESS; + + var = git__malloc(sizeof(git_cvar)); + if(var == NULL){ + error = GIT_ENOMEM; + goto out; + } + + var->name = git__strdup(name); + if(var->name == NULL){ + error = GIT_ENOMEM; + free(var); + goto out; + } + + var->value.string = git__strdup(value); + if(var->value.string == NULL){ + error = GIT_ENOMEM; + cvar_free(var); + goto out; + } + + var->type = GIT_VAR_STR; + + error = git_hashtable_insert(cfg->vars, var->name, var); + if(error < GIT_SUCCESS) + cvar_free(var); + + out: + return error; +} + + +/* + * Get a config variable's data. + */ +int git_config_get(git_config *cfg, const char *name, + int *out, git_cvar_type type) +{ + git_cvar *var; + int error = GIT_SUCCESS; + + var = git_hashtable_lookup(cfg->vars, name); + if (var == NULL) { + error = GIT_ENOTFOUND; + } else { + if (var->type == type) + *out = type == GIT_VAR_INT ? + var->value.integer : var->value.boolean; + else + error = GIT_EINVALIDTYPE; + } + return error; +} + +int git_config_get_string(git_config *cfg, const char *name, const char **out) +{ + git_cvar *var; + int error = GIT_SUCCESS; + + var = git_hashtable_lookup(cfg->vars, name); + if (var == NULL) { + error = GIT_ENOTFOUND; + goto out; + } else if (var->type != GIT_VAR_STR) { + error = GIT_EINVALIDTYPE; + goto out; + } + + *out = var->value.string; + + out: + return error; +} + static int cfg_getchar_raw(git_config *cfg) { int c; From 2e445cacd23dd494e182024cf1b229a3727339aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 11:07:09 +0200 Subject: [PATCH 18/57] build_varname: allocate memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/config.c b/src/config.c index f9dbda7e9..6679207c0 100644 --- a/src/config.c +++ b/src/config.c @@ -596,16 +596,24 @@ static int config_parse(git_config *cfg_file) return error; } -static const char *build_varname(const char *section, const char *name, int len) +/* + * Gives $section.$name back, using only name_len chars from the name, + * which is useful so we don't have to copy the variable name twice. + * Don't forget to free the memory you get. + */ +static char *build_varname(const char *section, const char *name, int name_len) { - static char varname[1024]; /* TODO: What's the longest we should allow? */ + char *varname; + int section_len, ret; + size_t total_len; - if(strlen(section) + len + 2 > sizeof(varname)) + section_len = strlen(section); + total_len = section_len + name_len + 2; + varname = malloc(total_len); + if(varname == NULL) return NULL; - strcpy(varname, section); - strcat(varname, "."); - strncat(varname, name, len); + ret = snprintf(varname, total_len, "%s.%s", section, name); return varname; } From 2974aa94c33b3845db3c999354d4202ff2b5bd2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 11:30:40 +0200 Subject: [PATCH 19/57] Determine variable type at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Config variables should be interpreted at run-time, as we don't know if a zero means false or zero, or if yes means true or "yes". As a variable has no intrinsic type, git_cvtype is gone and the public API takes care of enforcing a few rules. Signed-off-by: Carlos Martín Nieto --- include/git2/config.h | 60 +++----------- include/git2/types.h | 7 -- src/config.c | 184 +++++++++++++++++++++++++----------------- src/config.h | 7 +- 4 files changed, 125 insertions(+), 133 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index c91482636..e1e78858b 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -52,20 +52,6 @@ GIT_EXTERN(int) git_config_open(git_config **cfg_out, const char *path); */ GIT_EXTERN(void) git_config_free(git_config *cfg); -/** - * Get the value of an integer or boolean config variable. - * - * This is a more general function to retrieve the value of a integer - * or boolean variable. - * - * @param cfg where to look for the variable - * @param name the variable's name - * @param out pointer to the variable where the value should be stored - * @param type either GIT_VAR_INT or GIT_VAR_BOOL - * @return GIT_SUCCESS on success; error code otherwise - */ -GIT_EXTERN(int) git_config_get(git_config *cfg, const char *name, int *out, git_cvar_type type); - /** * Get the value of an integer config variable. * @@ -74,23 +60,20 @@ GIT_EXTERN(int) git_config_get(git_config *cfg, const char *name, int *out, git_ * @param out pointer to the variable where the value should be stored * @return GIT_SUCCESS on success; error code otherwise */ -GIT_INLINE(int) git_config_get_int(git_config *cfg, const char *name, int *out) -{ - return git_config_get(cfg, name, out, GIT_VAR_INT); -} +GIT_EXTERN(int) git_config_get_int(git_config *cfg, const char *name, int *out); /** * Get the value of a boolean config variable. * + * This function uses the usual C convention of 0 being false and + * anything else true. + * * @param cfg where to look for the variable * @param name the variable's name * @param out pointer to the variable where the value should be stored * @return GIT_SUCCESS on success; error code otherwise */ -GIT_INLINE(int) git_config_get_bool(git_config *cfg, const char *name, int *out) -{ - return git_config_get(cfg, name, out, GIT_VAR_BOOL); -} +GIT_EXTERN(int) git_config_get_bool(git_config *cfg, const char *name, int *out); /** * Get the value of a string config variable. @@ -104,19 +87,6 @@ GIT_INLINE(int) git_config_get_bool(git_config *cfg, const char *name, int *out) * @return GIT_SUCCESS on success; error code otherwise */ GIT_EXTERN(int) git_config_get_string(git_config *cfg, const char *name, const char **out); -/** - * Set the value of an integer or boolean config variable. - * - * This is a more general function to set the value of a integer or - * boolean variable. - * - * @param cfg where to look for the variable - * @param name the variable's name - * @param value the value to store - * @param type either GIT_VAR_INT or GIT_VAR_BOOL - * @return GIT_SUCCESS on success; error code otherwise - */ -GIT_EXTERN(int) git_config_set(git_config *cfg, const char *name, int value, git_cvar_type type); /** * Set the value of an integer config variable. @@ -126,10 +96,7 @@ GIT_EXTERN(int) git_config_set(git_config *cfg, const char *name, int value, git * @param out pointer to the variable where the value should be stored * @return GIT_SUCCESS on success; error code otherwise */ -GIT_INLINE(int) git_config_set_int(git_config *cfg, const char *name, int value) -{ - return git_config_set(cfg, name, value, GIT_VAR_INT); -} +GIT_EXTERN(int) git_config_set_int(git_config *cfg, const char *name, int value); /** * Set the value of a boolean config variable. @@ -139,10 +106,7 @@ GIT_INLINE(int) git_config_set_int(git_config *cfg, const char *name, int value) * @param value the value to store * @return GIT_SUCCESS on success; error code otherwise */ -GIT_INLINE(int) git_config_set_bool(git_config *cfg, const char *name, int value) -{ - return git_config_set(cfg, name, value, GIT_VAR_BOOL); -} +GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value); /** * Set the value of a string config variable. @@ -160,17 +124,17 @@ GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const c /** * Perform an operation on each config variable. * - * The callback is passed a pointer to a config variable and the data - * pointer passed to this function. As soon as one of the callback - * functions returns something other than 0, this function returns - * that value. + * The callback is passed a pointer to a config variable name and the + * data pointer passed to this function. As soon as one of the + * callback functions returns something other than 0, this function + * returns that value. * * @param cfg where to get the variables from * @param callback the function to call on each variable * @param data the data to pass to the callback * @return GIT_SUCCESS or the return value of the callback which didn't return 0 */ -GIT_EXTERN(int) git_config_foreach(git_config *cfg, int (*callback)(git_cvar *, void *data), void *data); +GIT_EXTERN(int) git_config_foreach(git_config *cfg, int (*callback)(const char *, void *data), void *data); /** @} */ GIT_END_DECL diff --git a/include/git2/types.h b/include/git2/types.h index 269c48249..aa53909d2 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -153,13 +153,6 @@ typedef enum { GIT_REF_HAS_PEEL = 8, } git_rtype; -/** Config variable type */ -typedef enum { - GIT_VAR_INT, /** Stores an integer value */ - GIT_VAR_BOOL, /** Stores a boolean value */ - GIT_VAR_STR /** Stores a string */ -} git_cvar_type; - /** @} */ GIT_END_DECL diff --git a/src/config.c b/src/config.c index 6679207c0..a49191148 100644 --- a/src/config.c +++ b/src/config.c @@ -39,10 +39,8 @@ void git_config_free(git_config *cfg); static void cvar_free(git_cvar *var) { - if(var->type == GIT_VAR_STR) - free(var->value.string); - free(var->name); + free(var->value); free(var); } @@ -133,26 +131,28 @@ void git_config_free(git_config *cfg) * Loop over all the variables */ -int git_config_foreach(git_config *cfg, int (*fn)(git_cvar *, void *), void *data) +int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *data) { int ret = GIT_SUCCESS; git_cvar *var; - void *_unused; + const void *_unused; GIT_HASHTABLE_FOREACH(cfg->vars, _unused, var, - ret = fn(var, data); + ret = fn(var->name, data); if(ret) break; ); return ret; } -/* +/************** * Setters - */ + **************/ -int git_config_set(git_config *cfg, const char *name, - int value, git_cvar_type type) +/* + * Internal function to actually set the string value of a variable + */ +static int config_set(git_config *cfg, const char *name, const char *value) { git_cvar *var = NULL; int error = GIT_SUCCESS; @@ -170,11 +170,12 @@ int git_config_set(git_config *cfg, const char *name, goto out; } - var->type = type; - if(type == GIT_VAR_BOOL) - var->value.boolean = value; - else - var->value.integer = value; + var->value = git__strdup(value); + if(var->value == NULL){ + error = GIT_ENOMEM; + cvar_free(var); + goto out; + } error = git_hashtable_insert(cfg->vars, var->name, var); if(error < GIT_SUCCESS) @@ -184,82 +185,121 @@ int git_config_set(git_config *cfg, const char *name, return error; } +int git_config_set_int(git_config *cfg, const char *name, int value) +{ + char str_value[5]; /* Most numbers should fit in here */ + int buf_len = sizeof(str_value), ret; + char *help_buf; + + if((ret = snprintf(str_value, buf_len, "%d", value)) >= buf_len - 1){ + /* The number is too large, we need to allocate more memory */ + buf_len = ret + 1; + help_buf = git__malloc(buf_len); + snprintf(help_buf, buf_len, "%d", value); + } + + return config_set(cfg, name, str_value); +} + +int git_config_set_bool(git_config *cfg, const char *name, int value) +{ + const char *str_value; + + if(value == 0) + str_value = "false"; + else + str_value = "true"; + + return config_set(cfg, name, str_value); +} + int git_config_set_string(git_config *cfg, const char *name, const char *value) { - git_cvar *var = NULL; - int error = GIT_SUCCESS; - - var = git__malloc(sizeof(git_cvar)); - if(var == NULL){ - error = GIT_ENOMEM; - goto out; - } - - var->name = git__strdup(name); - if(var->name == NULL){ - error = GIT_ENOMEM; - free(var); - goto out; - } - - var->value.string = git__strdup(value); - if(var->value.string == NULL){ - error = GIT_ENOMEM; - cvar_free(var); - goto out; - } - - var->type = GIT_VAR_STR; - - error = git_hashtable_insert(cfg->vars, var->name, var); - if(error < GIT_SUCCESS) - cvar_free(var); - - out: - return error; + return config_set(cfg, name, value); } +/*********** + * Getters + ***********/ /* - * Get a config variable's data. + * Internal function that actually gets the value in string form */ -int git_config_get(git_config *cfg, const char *name, - int *out, git_cvar_type type) +static int config_get(git_config *cfg, const char *name, const char **out) { git_cvar *var; int error = GIT_SUCCESS; var = git_hashtable_lookup(cfg->vars, name); - if (var == NULL) { - error = GIT_ENOTFOUND; - } else { - if (var->type == type) - *out = type == GIT_VAR_INT ? - var->value.integer : var->value.boolean; + if (var == NULL) + return GIT_ENOTFOUND; + + *out = var->value; + + return error; +} + +int git_config_get_int(git_config *cfg, const char *name, int *out) +{ + const char *value; + int ret; + + ret = config_get(cfg, name, &value); + if(ret < GIT_SUCCESS) + return ret; + + ret = sscanf(value, "%d", out); + if (ret == 0) /* No items were matched i.e. value isn't a number */ + return GIT_EINVALIDTYPE; + if (ret < 0) { + if (errno == EINVAL) /* Format was NULL */ + return GIT_EINVALIDTYPE; else - error = GIT_EINVALIDTYPE; + return GIT_EOSERR; } + + return GIT_SUCCESS; +} + +int git_config_get_bool(git_config *cfg, const char *name, int *out) +{ + const char *value; + int error = GIT_SUCCESS; + + error = config_get(cfg, name, &value); + if (error < GIT_SUCCESS) + return error; + + /* A missing value means true */ + if (value == NULL) { + *out = 1; + return GIT_SUCCESS; + } + + if (!strcasecmp(value, "true") || + !strcasecmp(value, "yes") || + !strcasecmp(value, "on")){ + *out = 1; + return GIT_SUCCESS; + } + if (!strcasecmp(value, "false") || + !strcasecmp(value, "no") || + !strcasecmp(value, "off")){ + *out = 0; + return GIT_SUCCESS; + } + + /* Try to parse it as an integer */ + error = git_config_get_int(cfg, name, out); + if (error == GIT_SUCCESS) + *out = !!(*out); + return error; } int git_config_get_string(git_config *cfg, const char *name, const char **out) { - git_cvar *var; - int error = GIT_SUCCESS; - - var = git_hashtable_lookup(cfg->vars, name); - if (var == NULL) { - error = GIT_ENOTFOUND; - goto out; - } else if (var->type != GIT_VAR_STR) { - error = GIT_EINVALIDTYPE; - goto out; - } - - *out = var->value.string; - - out: - return error; + return config_get(cfg, name, out); } static int cfg_getchar_raw(git_config *cfg) diff --git a/src/config.h b/src/config.h index 3b9da5241..2be013a72 100644 --- a/src/config.h +++ b/src/config.h @@ -17,13 +17,8 @@ struct git_config { }; struct git_cvar { - git_cvar_type type; char *name; - union { - unsigned char boolean; - long integer; - char *string; - } value; + char *value; }; #endif From 934fcf78f2877008979a6729fab5e81de2fd1d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 11:32:08 +0200 Subject: [PATCH 20/57] Initialise the config reader in config_parse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git_config_open shouldn't have to initialise variables that are only used inside config_parse and its callees. Signed-off-by: Carlos Martín Nieto --- src/config.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index a49191148..da0390dc5 100644 --- a/src/config.c +++ b/src/config.c @@ -86,9 +86,6 @@ int git_config_open(git_config **cfg_out, const char *path) if(error < GIT_SUCCESS) goto cleanup; - /* Initialise the reading position */ - cfg->reader.read_ptr = cfg->reader.buffer.data; - error = config_parse(cfg); if(error < GIT_SUCCESS) git_config_free(cfg); @@ -599,6 +596,10 @@ static int config_parse(git_config *cfg_file) int error = GIT_SUCCESS; char *current_section = NULL; + /* Initialise the reading position */ + cfg_file->reader.read_ptr = cfg_file->reader.buffer.data; + cfg_file->reader.eof = 0; + skip_bom(cfg_file); while (error == GIT_SUCCESS && !cfg_file->reader.eof) { From d28830c2555820925d2d5ecf10d07436385d37d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 13:40:19 +0200 Subject: [PATCH 21/57] Store the parsed variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store the key-value pair as strings. Signed-off-by: Carlos Martín Nieto --- src/config.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index da0390dc5..8cf98f5f6 100644 --- a/src/config.c +++ b/src/config.c @@ -661,9 +661,9 @@ static char *build_varname(const char *section, const char *name, int name_len) static int parse_variable(git_config *cfg, const char *section_name, const char *line) { - int error; + int error = GIT_SUCCESS; int has_value = 1; - const char *varname; + char *varname; const char *var_end = NULL; const char *value_start = NULL; @@ -690,8 +690,14 @@ static int parse_variable(git_config *cfg, const char *section_name, const char } varname = build_varname(section_name, line, var_end - line + 1); + if(varname == NULL) + return GIT_ENOMEM; - return GIT_SUCCESS; + config_set(cfg, varname, value_start); + + free(varname); + + return error; error: return GIT_EOBJCORRUPTED; From dadc0158fb9fd06f564ce92495dfe3ad9c4a9de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 15:05:15 +0200 Subject: [PATCH 22/57] config: use a singly-linked list instead of a hash table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Such a list preserves the order the variables were first read in which will be useful later for merging different data-sets. Furthermore, reading and writing out the same configuration should not reorganize the variables, which could happen when iterating through all the items in a hash table. A hash table is overkill for this small a data-set anyway. Signed-off-by: Carlos Martín Nieto --- src/config.c | 107 ++++++++++++++++++++++++++++++++++----------------- src/config.h | 11 +++++- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/src/config.c b/src/config.c index 8cf98f5f6..b7af99c79 100644 --- a/src/config.c +++ b/src/config.c @@ -37,23 +37,37 @@ static int config_parse(git_config *cfg_file); static int parse_variable(git_config *cfg, const char *section_name, const char *line); void git_config_free(git_config *cfg); -static void cvar_free(git_cvar *var) +static git_cvar *cvar_free(git_cvar *var) { + git_cvar *next = var->next; + free(var->name); free(var->value); free(var); + + return next; } -uint32_t config_table_hash(const void *key, int hash_id) +static void cvar_list_free(git_cvar *start) { - static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { - 2147483647, - 0x5d20bb23, - 0x7daaab3c - }; + git_cvar *iter = start; - const char *var_name = (const char *)key; - return git__hash(key, strlen(var_name), hash_seeds[hash_id]); + while ((iter = cvar_free(iter)) != NULL); +} + +/* + * FIXME: Only the section name is case-insensitive + */ +static git_cvar *cvar_list_find(git_cvar *start, const char *name) +{ + git_cvar *iter; + + CVAR_LIST_FOREACH (start, iter) { + if (!strcasecmp(name, iter->name)) + return iter; + } + + return NULL; } int git_config_open(git_config **cfg_out, const char *path) @@ -75,20 +89,13 @@ int git_config_open(git_config **cfg_out, const char *path) goto cleanup; } - cfg->vars = git_hashtable_alloc(16, config_table_hash, - (git_hash_keyeq_ptr) strcmp); - if (cfg->vars == NULL){ - error = GIT_ENOMEM; - goto cleanup; - } - error = gitfo_read_file(&cfg->reader.buffer, cfg->file_path); if(error < GIT_SUCCESS) goto cleanup; error = config_parse(cfg); if(error < GIT_SUCCESS) - git_config_free(cfg); + goto cleanup; else *cfg_out = cfg; @@ -96,7 +103,7 @@ int git_config_open(git_config **cfg_out, const char *path) cleanup: if(cfg->vars) - git_hashtable_free(cfg->vars); + cvar_list_free(cfg->vars); if(cfg->file_path) free(cfg->file_path); free(cfg); @@ -106,19 +113,11 @@ int git_config_open(git_config **cfg_out, const char *path) void git_config_free(git_config *cfg) { - git_cvar *var; - const void *_unused; - if (cfg == NULL) return; free(cfg->file_path); - - GIT_HASHTABLE_FOREACH(cfg->vars, _unused, var, - cvar_free(var); - ); - - git_hashtable_free(cfg->vars); + cvar_list_free(cfg->vars); gitfo_free_buf(&cfg->reader.buffer); free(cfg); @@ -132,12 +131,12 @@ int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *d { int ret = GIT_SUCCESS; git_cvar *var; - const void *_unused; - GIT_HASHTABLE_FOREACH(cfg->vars, _unused, var, + CVAR_LIST_FOREACH(cfg->vars, var) { ret = fn(var->name, data); - if(ret) break; - ); + if (ret) + break; + } return ret; } @@ -152,8 +151,28 @@ int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *d static int config_set(git_config *cfg, const char *name, const char *value) { git_cvar *var = NULL; + git_cvar *existing = NULL; int error = GIT_SUCCESS; + /* + * If it already exists, we just need to update its value. + */ + existing = cvar_list_find(cfg->vars, name); + if (existing != NULL) { + char *tmp = git__strdup(value); + if (tmp == NULL) + return GIT_ENOMEM; + + free(var->value); + var->value = tmp; + + return GIT_SUCCESS; + } + + /* + * Otherwise, create it and stick it at the end of the queue. + */ + var = git__malloc(sizeof(git_cvar)); if(var == NULL){ error = GIT_ENOMEM; @@ -174,19 +193,29 @@ static int config_set(git_config *cfg, const char *name, const char *value) goto out; } - error = git_hashtable_insert(cfg->vars, var->name, var); + var->next = NULL; + + if (cfg->vars_tail == NULL) { + cfg->vars = cfg->vars_tail = var; + } + else { + cfg->vars_tail->next = var; + cfg->vars_tail = var; + } + + out: if(error < GIT_SUCCESS) cvar_free(var); - out: return error; + } int git_config_set_int(git_config *cfg, const char *name, int value) { char str_value[5]; /* Most numbers should fit in here */ int buf_len = sizeof(str_value), ret; - char *help_buf; + char *help_buf = NULL; if((ret = snprintf(str_value, buf_len, "%d", value)) >= buf_len - 1){ /* The number is too large, we need to allocate more memory */ @@ -195,7 +224,12 @@ int git_config_set_int(git_config *cfg, const char *name, int value) snprintf(help_buf, buf_len, "%d", value); } - return config_set(cfg, name, str_value); + ret = config_set(cfg, name, str_value); + + if (help_buf != NULL) + free(help_buf); + + return ret; } int git_config_set_bool(git_config *cfg, const char *name, int value) @@ -227,7 +261,8 @@ static int config_get(git_config *cfg, const char *name, const char **out) git_cvar *var; int error = GIT_SUCCESS; - var = git_hashtable_lookup(cfg->vars, name); + var = cvar_list_find(cfg->vars, name); + if (var == NULL) return GIT_ENOTFOUND; diff --git a/src/config.h b/src/config.h index 2be013a72..5c16a1bc3 100644 --- a/src/config.h +++ b/src/config.h @@ -13,12 +13,21 @@ struct git_config { int eof; } reader; - git_hashtable *vars; + git_cvar *vars; + git_cvar *vars_tail; }; struct git_cvar { + git_cvar *next; char *name; char *value; }; +/* + * If you're going to delete something inside this loop, it's such a + * hassle that you should use the for-loop directly. + */ +#define CVAR_LIST_FOREACH(start, iter) \ + for ((iter) = (start); (iter) != NULL; (iter) = (iter)->next) + #endif From e21881d1db1bcb8b379743b6b28c17187e8ec1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 15:16:25 +0200 Subject: [PATCH 23/57] git_config: reorder fields according to use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.h b/src/config.h index 5c16a1bc3..b718453a4 100644 --- a/src/config.h +++ b/src/config.h @@ -4,7 +4,8 @@ #include "git2/config.h" struct git_config { - char *file_path; + git_cvar *vars; + git_cvar *vars_tail; struct { gitfo_buf buffer; @@ -13,8 +14,7 @@ struct git_config { int eof; } reader; - git_cvar *vars; - git_cvar *vars_tail; + char *file_path; }; struct git_cvar { From 8ecc5ae5cb5a1efed3c570511fecb6bfdd9cdbc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 16:48:14 +0200 Subject: [PATCH 24/57] git_config_set_int: use the right buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/config.c b/src/config.c index b7af99c79..13c32bc6f 100644 --- a/src/config.c +++ b/src/config.c @@ -222,12 +222,11 @@ int git_config_set_int(git_config *cfg, const char *name, int value) buf_len = ret + 1; help_buf = git__malloc(buf_len); snprintf(help_buf, buf_len, "%d", value); - } - - ret = config_set(cfg, name, str_value); - - if (help_buf != NULL) + ret = config_set(cfg, name, help_buf); free(help_buf); + } else { + ret = config_set(cfg, name, str_value); + } return ret; } From df22949a3566e2514c963d9eec2f0e82c05a18e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 31 Mar 2011 12:51:17 +0200 Subject: [PATCH 25/57] config_set: really replace the value on overwrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.c b/src/config.c index 13c32bc6f..0870ce620 100644 --- a/src/config.c +++ b/src/config.c @@ -163,8 +163,8 @@ static int config_set(git_config *cfg, const char *name, const char *value) if (tmp == NULL) return GIT_ENOMEM; - free(var->value); - var->value = tmp; + free(existing->value); + existing->value = tmp; return GIT_SUCCESS; } From 923fe4557f441f1cb94e02acda527d2785726afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 16:02:57 +0200 Subject: [PATCH 26/57] Add strtolower and strntolower functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As parts of variable names are case-sensitive, we need these functions. Signed-off-by: Carlos Martín Nieto --- src/config.c | 15 +++++++++++++++ src/config.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/src/config.c b/src/config.c index 0870ce620..4c4db91af 100644 --- a/src/config.c +++ b/src/config.c @@ -70,6 +70,20 @@ static git_cvar *cvar_list_find(git_cvar *start, const char *name) return NULL; } +void strntolower(char *str, int len) +{ + int i; + + for (i = 0; i < len; ++i) { + str[len] = tolower(str[len]); + } +} + +void strtolower(char *str) +{ + strntolower(str, strlen(str)); +} + int git_config_open(git_config **cfg_out, const char *path) { git_config *cfg; @@ -544,6 +558,7 @@ static int parse_section_header(git_config *cfg, char **section_out, const char } name[name_length] = 0; + strtolower(name); *section_out = name; return GIT_SUCCESS; diff --git a/src/config.h b/src/config.h index b718453a4..c8e9fe062 100644 --- a/src/config.h +++ b/src/config.h @@ -30,4 +30,7 @@ struct git_cvar { #define CVAR_LIST_FOREACH(start, iter) \ for ((iter) = (start); (iter) != NULL; (iter) = (iter)->next) +void strtolower(char *str); +void strntolower(char *str, int len); + #endif From 0bbaf9aaef875daf6e5db6bec143b7b84d906524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 16:11:55 +0200 Subject: [PATCH 27/57] config_parse: no need to check if current_section is non-null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config.c b/src/config.c index 4c4db91af..71a173f47 100644 --- a/src/config.c +++ b/src/config.c @@ -666,8 +666,7 @@ static int config_parse(git_config *cfg_file) break; case '[': /* section header, new section begins */ - if (current_section) - free(current_section); + free(current_section); error = parse_section_header(cfg_file, ¤t_section, line); break; From d7354d70b0cc56f16b3dece3bf271eaffaeaf029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 16:22:31 +0200 Subject: [PATCH 28/57] build_varname: lowercase the variable name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index 71a173f47..41c4d3a8e 100644 --- a/src/config.c +++ b/src/config.c @@ -686,9 +686,10 @@ static int config_parse(git_config *cfg_file) } /* - * Gives $section.$name back, using only name_len chars from the name, - * which is useful so we don't have to copy the variable name twice. - * Don't forget to free the memory you get. + * Returns $section.$name, using only name_len chars from the name, + * which is useful so we don't have to copy the variable name + * twice. The name of the variable is set to lowercase. + *Don't forget to free the buffer. */ static char *build_varname(const char *section, const char *name, int name_len) { @@ -703,6 +704,9 @@ static char *build_varname(const char *section, const char *name, int name_len) return NULL; ret = snprintf(varname, total_len, "%s.%s", section, name); + if(ret >= 0){ + strtolower(varname + section_len + 1); + } return varname; } From 6482929b5fda7e63d07b8bf76b2a273585e15bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Mar 2011 18:51:02 +0200 Subject: [PATCH 29/57] move build_varname above parse_section --- src/config.c | 54 +++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/config.c b/src/config.c index 41c4d3a8e..8ffbf5317 100644 --- a/src/config.c +++ b/src/config.c @@ -497,6 +497,34 @@ static inline int config_keychar(int c) return isalnum(c) || c == '-'; } +/* + * Returns $section.$name, using only name_len chars from the name, + * which is useful so we don't have to copy the variable name + * twice. The name of the variable is set to lowercase. + * Don't forget to free the buffer. + */ +static char *build_varname(const char *section, const char *name) +{ + char *varname; + int section_len, ret; + int name_len; + size_t total_len; + + name_len = strlen(name); + section_len = strlen(section); + total_len = section_len + name_len + 2; + varname = malloc(total_len); + if(varname == NULL) + return NULL; + + ret = snprintf(varname, total_len, "%s.%s", section, name); + if(ret >= 0){ + strtolower(varname + section_len + 1); + } + + return varname; +} + static char *parse_section_header_ext(char *base_name, git_config *cfg) { return base_name; @@ -685,32 +713,6 @@ static int config_parse(git_config *cfg_file) return error; } -/* - * Returns $section.$name, using only name_len chars from the name, - * which is useful so we don't have to copy the variable name - * twice. The name of the variable is set to lowercase. - *Don't forget to free the buffer. - */ -static char *build_varname(const char *section, const char *name, int name_len) -{ - char *varname; - int section_len, ret; - size_t total_len; - - section_len = strlen(section); - total_len = section_len + name_len + 2; - varname = malloc(total_len); - if(varname == NULL) - return NULL; - - ret = snprintf(varname, total_len, "%s.%s", section, name); - if(ret >= 0){ - strtolower(varname + section_len + 1); - } - - return varname; -} - static int parse_variable(git_config *cfg, const char *section_name, const char *line) { int error = GIT_SUCCESS; From 11d0e70578baf47fb1cb565e0336e18d417e5da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 31 Mar 2011 10:50:11 +0200 Subject: [PATCH 30/57] Add support for subsections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A variable name is stored internally with its section the way it appeared in the configuration file in order to have the information about what parts are case-sensitive inline. Really implement parse_section_header_ext and move the assignment of variables to config_parse. The variable name matching is now done in a case-away way by cvar_name_match and cvar_section_match. Before the user sees it, it's normalized to the two- or three-dot version. Signed-off-by: Carlos Martín Nieto --- src/config.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 218 insertions(+), 20 deletions(-) diff --git a/src/config.c b/src/config.c index 8ffbf5317..c3f36f8a2 100644 --- a/src/config.c +++ b/src/config.c @@ -34,7 +34,7 @@ * Forward declarations ***********************/ static int config_parse(git_config *cfg_file); -static int parse_variable(git_config *cfg, const char *section_name, const char *line); +static int parse_variable(const char *line, char **var_name, char **var_value); void git_config_free(git_config *cfg); static git_cvar *cvar_free(git_cvar *var) @@ -56,20 +56,112 @@ static void cvar_list_free(git_cvar *start) } /* - * FIXME: Only the section name is case-insensitive + * The order is importart. The first parameter is the name we want to + * match against, and the second one is what we're looking for */ +static int cvar_section_match(const char *local, const char *input) +{ + char *input_dot = strrchr(input, '.'); + char *local_last_dot = strrchr(local, '.'); + char *local_sp = strchr(local, ' '); + int comparison_len; + + /* + * If the local section name doesn't contain a space, then we can + * just do a case-insensitive compare. + */ + if (local_sp == NULL) + return !strncasecmp(local, input, local_last_dot - local); + + /* Anything before the space in local is case-insensitive */ + if (strncasecmp(local, input, local_sp - local)) { + fprintf(stderr, "copmparison of %s and %s failed\n", local, input); + return 0; + } + + /* + * We compare starting from the first character after the + * quotation marks, which is two characters beyond the space. For + * the input, we start one character beyond the first dot. + * The length is given by the length between the quotation marks. + * + * this "that".var + * ^ ^ + * a b + * + * where a is (local_sp + 2) and b is local_last_dot. The comparison + * length is given by b - 1 - a. + */ + input_dot = strchr(input, '.'); + comparison_len = local_last_dot - 1 - (local_sp + 2); + return !strncmp(local_sp + 2, input_dot + 1, comparison_len); +} + +static int cvar_name_match(const char *local, const char *input) +{ + char *input_dot = strrchr(input, '.'); + char *local_dot = strrchr(local, '.'); + + /* + * First try to match the section name + */ + if (!cvar_section_match(local, input)) + return 0; + + /* + * Anything after the last (possibly only) dot is case-insensitive + */ + if (!strcmp(input_dot, local_dot)) + return 1; + + return 0; +} + static git_cvar *cvar_list_find(git_cvar *start, const char *name) { git_cvar *iter; CVAR_LIST_FOREACH (start, iter) { - if (!strcasecmp(name, iter->name)) + if (cvar_name_match(iter->name, name)) return iter; } return NULL; } +static int cvar_name_normalize(const char *input, char **output) +{ + char *input_sp = strchr(input, ' '); + char *quote, *str; + int i; + + /* We need to make a copy anyway */ + str = git__strdup(input); + if (str == NULL) + return GIT_ENOMEM; + + *output = str; + + /* If there aren't any spaces, we don't need to do anything */ + if (input_sp == NULL) + return GIT_SUCCESS; + + /* + * If there are spaces, we replace the space by a dot, move the + * variable name so that the dot before it replaces the last + * quotation mark and repeat so that the first quotation mark + * disappears. + */ + str[input_sp - input] = '.'; + + for (i = 0; i < 2; ++i) { + quote = strrchr(str, '"'); + memmove(quote, quote + 1, strlen(quote)); + } + + return GIT_SUCCESS; +} + void strntolower(char *str, int len) { int i; @@ -145,9 +237,15 @@ int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *d { int ret = GIT_SUCCESS; git_cvar *var; + char *normalized; CVAR_LIST_FOREACH(cfg->vars, var) { - ret = fn(var->name, data); + ret = cvar_name_normalize(var->name, &normalized); + if (ret < GIT_SUCCESS) + return ret; + + ret = fn(normalized, data); + free(normalized); if (ret) break; } @@ -518,16 +616,88 @@ static char *build_varname(const char *section, const char *name) return NULL; ret = snprintf(varname, total_len, "%s.%s", section, name); - if(ret >= 0){ - strtolower(varname + section_len + 1); + if(ret >= 0){ /* lowercase from the last dot onwards */ + char *dot = strrchr(varname, '.'); + if (dot != NULL) + strtolower(dot); } return varname; } -static char *parse_section_header_ext(char *base_name, git_config *cfg) +static int parse_section_header_ext(git_config *cfg, const char *base_name, const char *line, char **section_name) { - return base_name; + int buf_len, total_len, pos; + int c; + char *subsection; + int error = GIT_SUCCESS; + int quote_marks; + /* + * base_name is what came before the space. We should be at the + * first quotation mark, except for now, line isn't being kept in + * sync so we only really use it to calculate the length. + */ + + buf_len = strrchr(line, '"') - strchr(line, '"') + 2; + if(!buf_len) + return GIT_EOBJCORRUPTED; + + subsection = git__malloc(buf_len + 2); + if(subsection == NULL) + return GIT_ENOMEM; + + pos = 0; + c = cfg_getchar(cfg, 0); + quote_marks = 0; + + /* + * At the end of each iteration, whatever is stored in c will be + * added to the string. In case of error, jump to out + */ + do { + switch(c) { + case '"': + if (quote_marks++ >= 2) + return GIT_EOBJCORRUPTED; + break; + case '\\': + c = cfg_getchar(cfg, 0); + switch (c) { + case '"': + case '\\': + break; + default: + error = GIT_EOBJCORRUPTED; + goto out; + } + default: + break; + } + + subsection[pos++] = c; + } while ((c = cfg_getchar(cfg, 0)) != ']'); + + subsection[pos] = '\0'; + + if (cfg_getchar(cfg, 0) != '\n'){ + error = GIT_EOBJCORRUPTED; + goto out; + } + + total_len = strlen(base_name) + strlen(subsection) + 2; + *section_name = git__malloc(total_len); + if (*section_name == NULL) { + error = GIT_ENOMEM; + goto out; + } + + sprintf(*section_name, "%s %s", base_name, subsection); + strntolower(*section_name, strchr(*section_name, ' ') - *section_name); + + out: + free(subsection); + + return error; } static int parse_section_header(git_config *cfg, char **section_out, const char *line) @@ -563,8 +733,10 @@ static int parse_section_header(git_config *cfg, char **section_out, const char } if (isspace(c)){ - *section_out = parse_section_header_ext(name, cfg); - return GIT_SUCCESS; + name[name_length] = '\0'; + error = parse_section_header_ext(cfg, name, line, section_out); + free(name); + return error; } if (!config_keychar(c) && c != '.'){ @@ -672,6 +844,9 @@ static int config_parse(git_config *cfg_file) { int error = GIT_SUCCESS; char *current_section = NULL; + char *var_name; + char *var_value; + char *full_name; /* Initialise the reading position */ cfg_file->reader.read_ptr = cfg_file->reader.buffer.data; @@ -699,8 +874,25 @@ static int config_parse(git_config *cfg_file) break; default: /* assume variable declaration */ - error = parse_variable(cfg_file, current_section, line); + error = parse_variable(line, &var_name, &var_value); cfg_consume_line(cfg_file); + + if (error < GIT_SUCCESS) + break; + + full_name = build_varname(current_section, var_name); + if (full_name == NULL) { + error = GIT_ENOMEM; + free(var_name); + free(var_value); + break; + } + + config_set(cfg_file, full_name, var_value); + free(var_name); + free(var_value); + free(full_name); + break; } @@ -713,11 +905,9 @@ static int config_parse(git_config *cfg_file) return error; } -static int parse_variable(git_config *cfg, const char *section_name, const char *line) +static int parse_variable(const char *line, char **var_name, char **var_value) { - int error = GIT_SUCCESS; - int has_value = 1; - char *varname; + char *tmp; const char *var_end = NULL; const char *value_start = NULL; @@ -743,15 +933,23 @@ static int parse_variable(git_config *cfg, const char *section_name, const char goto error; } - varname = build_varname(section_name, line, var_end - line + 1); - if(varname == NULL) + tmp = strndup(line, var_end - line + 1); + if (tmp == NULL) return GIT_ENOMEM; - config_set(cfg, varname, value_start); + *var_name = tmp; - free(varname); + if (value_start != NULL) { + tmp = strdup(value_start); + if (tmp == NULL) { + free(*var_name); + return GIT_ENOMEM; + } - return error; + *var_value = tmp; + } + + return GIT_SUCCESS; error: return GIT_EOBJCORRUPTED; From fe116e261ffb7d643c9a6baee70fbfa07a20588f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Apr 2011 15:33:14 +0200 Subject: [PATCH 31/57] config: Fix typo and remove debug statement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/config.c b/src/config.c index c3f36f8a2..4aa0e3691 100644 --- a/src/config.c +++ b/src/config.c @@ -56,7 +56,7 @@ static void cvar_list_free(git_cvar *start) } /* - * The order is importart. The first parameter is the name we want to + * The order is important. The first parameter is the name we want to * match against, and the second one is what we're looking for */ static int cvar_section_match(const char *local, const char *input) @@ -74,10 +74,8 @@ static int cvar_section_match(const char *local, const char *input) return !strncasecmp(local, input, local_last_dot - local); /* Anything before the space in local is case-insensitive */ - if (strncasecmp(local, input, local_sp - local)) { - fprintf(stderr, "copmparison of %s and %s failed\n", local, input); + if (strncasecmp(local, input, local_sp - local)) return 0; - } /* * We compare starting from the first character after the From 9f1b54d6d01bb25c06b2e9d86db922616e2bb566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Apr 2011 15:07:47 +0200 Subject: [PATCH 32/57] config: also free the file buffer on error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On error, the buffer containing the file contents also needs to be freed. Signed-off-by: Carlos Martín Nieto --- src/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.c b/src/config.c index 4aa0e3691..a4bded952 100644 --- a/src/config.c +++ b/src/config.c @@ -210,6 +210,7 @@ int git_config_open(git_config **cfg_out, const char *path) cvar_list_free(cfg->vars); if(cfg->file_path) free(cfg->file_path); + gitfo_free_buf(&cfg->reader.buffer); free(cfg); return error; From 2454ce784f3f4f4c26188526ce340f0a9d87fef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Apr 2011 11:25:55 +0200 Subject: [PATCH 33/57] config: don't mix buffer reading methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make header and variable parse functions use their own buffers instead of giving them the line they need to read as a parameter which they mostly ignore. This is in preparation for multiline configuration variables. Signed-off-by: Carlos Martín Nieto --- src/config.c | 126 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 48 deletions(-) diff --git a/src/config.c b/src/config.c index a4bded952..8c509be33 100644 --- a/src/config.c +++ b/src/config.c @@ -34,7 +34,7 @@ * Forward declarations ***********************/ static int config_parse(git_config *cfg_file); -static int parse_variable(const char *line, char **var_name, char **var_value); +static int parse_variable(git_config *cfg, char **var_name, char **var_value); void git_config_free(git_config *cfg); static git_cvar *cvar_free(git_cvar *var) @@ -492,6 +492,30 @@ static int cfg_getchar(git_config *cfg_file, int flags) return c; } +/* + * Read the next char, but don't move the reading pointer. + */ +static int cfg_peek(git_config *cfg, int flags) +{ + void *old_read_ptr; + int old_lineno, old_eof; + int ret; + + assert(cfg->reader.read_ptr); + + old_read_ptr = cfg->reader.read_ptr; + old_lineno = cfg->reader.line_number; + old_eof = cfg->reader.eof; + + ret = cfg_getchar(cfg, flags); + + cfg->reader.read_ptr = old_read_ptr; + cfg->reader.line_number = old_lineno; + cfg->reader.eof = old_eof; + + return ret; +} + static const char *LINEBREAK_UNIX = "\\\n"; static const char *LINEBREAK_WIN32 = "\\\r\n"; @@ -502,7 +526,7 @@ static int is_linebreak(const char *pos) } /* - * Read a line, but don't consume it + * Read and consume a line, returning it in newly-allocated memory. */ static char *cfg_readline(git_config *cfg) { @@ -554,10 +578,8 @@ static char *cfg_readline(git_config *cfg) if (*line_end == '\0') cfg->reader.eof = 1; - /* cfg->reader.line_number++; cfg->reader.read_ptr = line_end; - */ return line; } @@ -624,11 +646,11 @@ static char *build_varname(const char *section, const char *name) return varname; } -static int parse_section_header_ext(git_config *cfg, const char *base_name, const char *line, char **section_name) +static int parse_section_header_ext(const char *line, const char *base_name, char **section_name) { - int buf_len, total_len, pos; + int buf_len, total_len, pos, rpos; int c; - char *subsection; + char *subsection, *first_quote, *last_quote; int error = GIT_SUCCESS; int quote_marks; /* @@ -637,18 +659,25 @@ static int parse_section_header_ext(git_config *cfg, const char *base_name, cons * sync so we only really use it to calculate the length. */ - buf_len = strrchr(line, '"') - strchr(line, '"') + 2; - if(!buf_len) + first_quote = strchr(line, '"'); + last_quote = strrchr(line, '"'); + + if (last_quote - first_quote == 0) return GIT_EOBJCORRUPTED; + buf_len = last_quote - first_quote + 2; + subsection = git__malloc(buf_len + 2); if(subsection == NULL) return GIT_ENOMEM; pos = 0; - c = cfg_getchar(cfg, 0); + rpos = 0; quote_marks = 0; + line = first_quote; + c = line[rpos++]; + /* * At the end of each iteration, whatever is stored in c will be * added to the string. In case of error, jump to out @@ -660,7 +689,7 @@ static int parse_section_header_ext(git_config *cfg, const char *base_name, cons return GIT_EOBJCORRUPTED; break; case '\\': - c = cfg_getchar(cfg, 0); + c = line[rpos++]; switch (c) { case '"': case '\\': @@ -674,15 +703,10 @@ static int parse_section_header_ext(git_config *cfg, const char *base_name, cons } subsection[pos++] = c; - } while ((c = cfg_getchar(cfg, 0)) != ']'); + } while ((c = line[rpos++]) != ']'); subsection[pos] = '\0'; - if (cfg_getchar(cfg, 0) != '\n'){ - error = GIT_EOBJCORRUPTED; - goto out; - } - total_len = strlen(base_name) + strlen(subsection) + 2; *section_name = git__malloc(total_len); if (*section_name == NULL) { @@ -699,11 +723,16 @@ static int parse_section_header_ext(git_config *cfg, const char *base_name, cons return error; } -static int parse_section_header(git_config *cfg, char **section_out, const char *line) +static int parse_section_header(git_config *cfg, char **section_out) { char *name, *name_end; - int name_length, c; + int name_length, c, pos; int error = GIT_SUCCESS; + char *line; + + line = cfg_readline(cfg); + if (line == NULL) + return GIT_ENOMEM; /* find the end of the variable's name */ name_end = strchr(line, ']'); @@ -715,15 +744,16 @@ static int parse_section_header(git_config *cfg, char **section_out, const char return GIT_EOBJCORRUPTED; name_length = 0; + pos = 0; /* Make sure we were given a section header */ - c = cfg_getchar(cfg, SKIP_WHITESPACE | SKIP_COMMENTS); + c = line[pos++]; if(c != '['){ error = GIT_EOBJCORRUPTED; goto error; } - c = cfg_getchar(cfg, SKIP_WHITESPACE | SKIP_COMMENTS); + c = line[pos++]; do { if (cfg->reader.eof){ @@ -733,7 +763,8 @@ static int parse_section_header(git_config *cfg, char **section_out, const char if (isspace(c)){ name[name_length] = '\0'; - error = parse_section_header_ext(cfg, name, line, section_out); + error = parse_section_header_ext(line, name, section_out); + free(line); free(name); return error; } @@ -745,23 +776,16 @@ static int parse_section_header(git_config *cfg, char **section_out, const char name[name_length++] = tolower(c); - } while ((c = cfg_getchar(cfg, SKIP_COMMENTS)) != ']'); - - /* - * Here, we enforce that a section name needs to be on its own - * line - */ - if(cfg_getchar(cfg, SKIP_COMMENTS) != '\n'){ - error = GIT_EOBJCORRUPTED; - goto error; - } + } while ((c = line[pos++]) != ']'); name[name_length] = 0; + free(line); strtolower(name); *section_out = name; return GIT_SUCCESS; error: + free(line); free(name); return error; } @@ -841,7 +865,7 @@ static void strip_comments(char *line) static int config_parse(git_config *cfg_file) { - int error = GIT_SUCCESS; + int error = GIT_SUCCESS, c; char *current_section = NULL; char *var_name; char *var_value; @@ -855,26 +879,24 @@ static int config_parse(git_config *cfg_file) while (error == GIT_SUCCESS && !cfg_file->reader.eof) { - char *line = cfg_readline(cfg_file); + c = cfg_peek(cfg_file, SKIP_WHITESPACE); - /* not enough memory to allocate line */ - if (line == NULL) - return GIT_ENOMEM; - - strip_comments(line); - - switch (line[0]) { - case '\0': /* empty line (only whitespace) */ + switch (c) { + case '\0': /* We've arrived at the end of the file */ break; case '[': /* section header, new section begins */ free(current_section); - error = parse_section_header(cfg_file, ¤t_section, line); + error = parse_section_header(cfg_file, ¤t_section); + break; + + case ';': + case '#': + cfg_consume_line(cfg_file); break; default: /* assume variable declaration */ - error = parse_variable(line, &var_name, &var_value); - cfg_consume_line(cfg_file); + error = parse_variable(cfg_file, &var_name, &var_value); if (error < GIT_SUCCESS) break; @@ -894,8 +916,6 @@ static int config_parse(git_config *cfg_file) break; } - - free(line); } if(current_section) @@ -904,12 +924,19 @@ static int config_parse(git_config *cfg_file) return error; } -static int parse_variable(const char *line, char **var_name, char **var_value) +static int parse_variable(git_config *cfg, char **var_name, char **var_value) { char *tmp; const char *var_end = NULL; const char *value_start = NULL; + char *line; + + line = cfg_readline(cfg); + if (line == NULL) + return GIT_ENOMEM; + + strip_comments(line); var_end = strchr(line, '='); @@ -948,8 +975,11 @@ static int parse_variable(const char *line, char **var_name, char **var_value) *var_value = tmp; } + free(line); + return GIT_SUCCESS; error: + free(line); return GIT_EOBJCORRUPTED; } From 72946881b598c133ff1d522d06c083690e260947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Apr 2011 15:26:43 +0200 Subject: [PATCH 34/57] config: support multiline values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a variable value has the traditional continuation character (\) as the last non-space character in the line, then we continue reading the value on the next line. Using more than two lines is also supported. Signed-off-by: Carlos Martín Nieto --- src/config.c | 114 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 98 insertions(+), 16 deletions(-) diff --git a/src/config.c b/src/config.c index 8c509be33..0052c659c 100644 --- a/src/config.c +++ b/src/config.c @@ -924,10 +924,85 @@ static int config_parse(git_config *cfg_file) return error; } +static int is_multiline_var(const char *str) +{ + char *end = strrchr(str, '\0') - 1; + + while (isspace(*end)) + --end; + + return *end == '\\'; +} + +static int parse_multiline_variable(git_config *cfg, const char *first, char **out) +{ + char *line = NULL, *end; + int error = GIT_SUCCESS, len, ret; + char *buf; + + /* Check that the next line exists */ + line = cfg_readline(cfg); + if (line == NULL) + return GIT_ENOMEM; + + /* We've reached the end of the file, there is input missing */ + if (line[0] == '\0') { + error = GIT_EOBJCORRUPTED; + goto out; + } + + strip_comments(line); + + /* If it was just a comment, pretend it didn't exist */ + if (line[0] == '\0') { + error = parse_multiline_variable(cfg, first, out); + goto out; + } + + /* Find the continuation character '\' and strip the whitespace */ + end = strrchr(first, '\\'); + while (isspace(end[-1])) + --end; + + *end = '\0'; /* Terminate the string here */ + + len = strlen(first) + strlen(line) + 2; + buf = git__malloc(len); + if (buf == NULL) { + error = GIT_ENOMEM; + goto out; + } + + ret = snprintf(buf, len, "%s %s", first, line); + if (ret < 0) { + error = GIT_EOSERR; + free(buf); + goto out; + } + + /* + * If we need to continue reading the next line, pretend + * everything we've read up to now was in one line and call + * ourselves. + */ + if (is_multiline_var(buf)) { + char *final_val; + error = parse_multiline_variable(cfg, buf, &final_val); + free(buf); + buf = final_val; + } + + *out = buf; + + out: + free(line); + return error; +} + static int parse_variable(git_config *cfg, char **var_name, char **var_value) { char *tmp; - + int error = GIT_SUCCESS; const char *var_end = NULL; const char *value_start = NULL; char *line; @@ -950,36 +1025,43 @@ static int parse_variable(git_config *cfg, char **var_name, char **var_value) while (isspace(var_end[0])); } + tmp = strndup(line, var_end - line + 1); + if (tmp == NULL) { + error = GIT_ENOMEM; + goto out; + } + + *var_name = tmp; + + /* + * Now, let's try to parse the value + */ if (value_start != NULL) { while (isspace(value_start[0])) value_start++; if (value_start[0] == '\0') - goto error; - } + goto out; - tmp = strndup(line, var_end - line + 1); - if (tmp == NULL) - return GIT_ENOMEM; + if (is_multiline_var(value_start)) { + error = parse_multiline_variable(cfg, value_start, var_value); + if (error < GIT_SUCCESS) + free(*var_name); + goto out; + } - *var_name = tmp; - - if (value_start != NULL) { tmp = strdup(value_start); if (tmp == NULL) { free(*var_name); - return GIT_ENOMEM; + error = GIT_ENOMEM; + goto out; } *var_value = tmp; } + out: free(line); - - return GIT_SUCCESS; - -error: - free(line); - return GIT_EOBJCORRUPTED; + return error; } From 9b7a6a99807862778706c455d6ba361a3b1f8f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Apr 2011 16:17:39 +0200 Subject: [PATCH 35/57] config: check for EOF before newline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a line ends at EOF there is no need to check for the newline character and doing so will cause us to read memory beyond the allocatd memory as we check for the Windows-style new-line, which is two bytes long. Signed-off-by: Carlos Martín Nieto --- src/config.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index 0052c659c..93f15fb08 100644 --- a/src/config.c +++ b/src/config.c @@ -537,12 +537,13 @@ static char *cfg_readline(git_config *cfg) line_src = cfg->reader.read_ptr; line_end = strchr(line_src, '\n'); - while (is_linebreak(line_end)) - line_end = strchr(line_end + 1, '\n'); - /* no newline at EOF */ if (line_end == NULL) line_end = strchr(line_src, 0); + else + while (is_linebreak(line_end)) + line_end = strchr(line_end + 1, '\n'); + while (line_src < line_end && isspace(*line_src)) line_src++; From 0bf8ca8820489ff7e154964771688c74eaf1933d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Apr 2011 16:44:23 +0200 Subject: [PATCH 36/57] config: add tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These tests are basic, but they should tell us when we've broken something. Signed-off-by: Carlos Martín Nieto --- tests/resources/config/config0 | Bin 0 -> 295 bytes tests/resources/config/config1 | Bin 0 -> 98 bytes tests/resources/config/config2 | Bin 0 -> 96 bytes tests/resources/config/config3 | Bin 0 -> 87 bytes tests/t14-config.c | 121 +++++++++++++++++++++++++++++++++ tests/test_main.c | 2 + 6 files changed, 123 insertions(+) create mode 100644 tests/resources/config/config0 create mode 100644 tests/resources/config/config1 create mode 100644 tests/resources/config/config2 create mode 100644 tests/resources/config/config3 create mode 100644 tests/t14-config.c diff --git a/tests/resources/config/config0 b/tests/resources/config/config0 new file mode 100644 index 0000000000000000000000000000000000000000..85235c5012392fb8578a6d86e1207fa87b04a448 GIT binary patch literal 295 zcmd6jK?(vv3{}Mr R_yV;Dt2$&|Dvv^b#~-9dLT&&6 literal 0 HcmV?d00001 diff --git a/tests/resources/config/config1 b/tests/resources/config/config1 new file mode 100644 index 0000000000000000000000000000000000000000..211dc9e7d4c0692f767736df89e45954cf74ec47 GIT binary patch literal 98 zcmZX~F$#b%5Cp*5ykd#(@E7UCB7~DW0tw_+A-@+3E9;pRNzKulps1AUIQu}!E^ujX cwA(nt&UZy>W<0_IbgB$&;JD<|^wTLG9%Zs0+5i9m literal 0 HcmV?d00001 diff --git a/tests/resources/config/config2 b/tests/resources/config/config2 new file mode 100644 index 0000000000000000000000000000000000000000..60a389827c2c3c549fb77a489e8461a93b8836c3 GIT binary patch literal 96 zcmcCk2+7DSR>;pwRVYa%P&&MEzK#(%*h0bl_lnsrWSKWmw*&0g=8d_D8+Jd hDkvo8r6|~fb%At(a12*TdA5OESw+x!~%9 WOPyfqxi~q?5{ndUf% + +#define CONFIG_BASE TEST_RESOURCES "/config" + +/* + * This one is so we know the code isn't completely broken + */ +BEGIN_TEST(config0, "read a simple configuration") + git_config *cfg; + int i; + + must_pass(git_config_open(&cfg, CONFIG_BASE "/config0")); + must_pass(git_config_get_int(cfg, "core.repositoryformatversion", &i)); + must_be_true(i == 0); + must_pass(git_config_get_bool(cfg, "core.filemode", &i)); + must_be_true(i == 1); + must_pass(git_config_get_bool(cfg, "core.bare", &i)); + must_be_true(i == 0); + must_pass(git_config_get_bool(cfg, "core.logallrefupdates", &i)); + must_be_true(i == 1); + + git_config_free(cfg); +END_TEST + +/* + * [this "that"] and [this "That] are different namespaces. Make sure + * each returns the correct one. + */ +BEGIN_TEST(config1, "case sensitivity") + git_config *cfg; + int i; + const char *str; + + must_pass(git_config_open(&cfg, CONFIG_BASE "/config1")); + + must_pass(git_config_get_string(cfg, "this.that.other", &str)); + must_be_true(!strcmp(str, "true")); + must_pass(git_config_get_string(cfg, "this.That.other", &str)); + must_be_true(!strcmp(str, "yes")); + + must_pass(git_config_get_bool(cfg, "this.that.other", &i)); + must_be_true(i == 1); + must_pass(git_config_get_bool(cfg, "this.That.other", &i)); + must_be_true(i == 1); + + /* This one doesn't exist */ + must_fail(git_config_get_bool(cfg, "this.thaT.other", &i)); + + git_config_free(cfg); +END_TEST + +/* + * If \ is the last non-space character on the line, we read the next + * one, separating each line with SP. + */ +BEGIN_TEST(config2, "parse a multiline value") + git_config *cfg; + const char *str; + + must_pass(git_config_open(&cfg, CONFIG_BASE "/config2")); + + must_pass(git_config_get_string(cfg, "this.That.and", &str)); + must_be_true(!strcmp(str, "one one one two two three three")); + + git_config_free(cfg); +END_TEST + +/* + * This kind of subsection declaration is case-insensitive + */ +BEGIN_TEST(config3, "parse a [section.subsection] header") + git_config *cfg; + const char *str; + + must_pass(git_config_open(&cfg, CONFIG_BASE "/config3")); + + must_pass(git_config_get_string(cfg, "section.subsection.var", &str)); + must_be_true(!strcmp(str, "hello")); + + /* Avoid a false positive */ + str = "nohello"; + must_pass(git_config_get_string(cfg, "section.subSectIon.var", &str)); + must_be_true(!strcmp(str, "hello")); + + git_config_free(cfg); +END_TEST + + +BEGIN_SUITE(config) + ADD_TEST(config0); + ADD_TEST(config1); + ADD_TEST(config2); + ADD_TEST(config3); +END_SUITE diff --git a/tests/test_main.c b/tests/test_main.c index f2a623a48..c99722e80 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -43,6 +43,7 @@ DECLARE_SUITE(refs); DECLARE_SUITE(sqlite); DECLARE_SUITE(repository); DECLARE_SUITE(threads); +DECLARE_SUITE(config); static libgit2_suite suite_methods[]= { SUITE_NAME(core), @@ -59,6 +60,7 @@ static libgit2_suite suite_methods[]= { SUITE_NAME(sqlite), SUITE_NAME(repository), SUITE_NAME(threads), + SUITE_NAME(config), }; #define GIT_SUITE_COUNT (ARRAY_SIZE(suite_methods)) From 2470be13f28c6599d25bf23b61a3f1b369c9f400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Apr 2011 17:06:31 +0200 Subject: [PATCH 37/57] config: variable name on its own means true MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a variable name appears on its own in a line, it's assumed the value is true. Store the variable name as NULL in that case. Signed-off-by: Carlos Martín Nieto --- src/config.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/config.c b/src/config.c index 93f15fb08..bd0886e8d 100644 --- a/src/config.c +++ b/src/config.c @@ -270,8 +270,8 @@ static int config_set(git_config *cfg, const char *name, const char *value) */ existing = cvar_list_find(cfg->vars, name); if (existing != NULL) { - char *tmp = git__strdup(value); - if (tmp == NULL) + char *tmp = value ? git__strdup(value) : NULL; + if (tmp == NULL && value != NULL) return GIT_ENOMEM; free(existing->value); @@ -297,8 +297,8 @@ static int config_set(git_config *cfg, const char *name, const char *value) goto out; } - var->value = git__strdup(value); - if(var->value == NULL){ + var->value = value ? git__strdup(value) : NULL; + if(var->value == NULL && value != NULL){ error = GIT_ENOMEM; cvar_free(var); goto out; @@ -1060,6 +1060,9 @@ static int parse_variable(git_config *cfg, char **var_name, char **var_value) } *var_value = tmp; + } else { + /* If thre is no value, boolean true is assumed */ + *var_value = NULL; } out: From 8cd767ef52ad35331f082394ec93df8e57757120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Apr 2011 17:07:47 +0200 Subject: [PATCH 38/57] config: test for a variable on its own MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a variable is on its own, truth should be assumed. Check this is true in our code. Signed-off-by: Carlos Martín Nieto --- tests/resources/config/config4 | Bin 0 -> 64 bytes tests/t14-config.c | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/resources/config/config4 diff --git a/tests/resources/config/config4 b/tests/resources/config/config4 new file mode 100644 index 0000000000000000000000000000000000000000..741fa0ffd01c4ae00a1271de09ca27a70c8ff3f3 GIT binary patch literal 64 zcmY#Za8xKuEXqtw%1KqoOUzAG$j?*AEGbsVF9*`aK)IaE6t3vv{M=N%;?(4l%>2Aq KE>2FE7A^q3#TC8) literal 0 HcmV?d00001 diff --git a/tests/t14-config.c b/tests/t14-config.c index 6428ceaa3..2cbd05896 100644 --- a/tests/t14-config.c +++ b/tests/t14-config.c @@ -112,10 +112,27 @@ BEGIN_TEST(config3, "parse a [section.subsection] header") git_config_free(cfg); END_TEST +BEGIN_TEST(config4, "a variable name on its own is valid") + git_config *cfg; +const char *str; +int i; + + must_pass(git_config_open(&cfg, CONFIG_BASE "/config4")); + + must_pass(git_config_get_string(cfg, "some.section.variable", &str)); + must_be_true(str == NULL); + + must_pass(git_config_get_bool(cfg, "some.section.variable", &i)); + must_be_true(i == 1); + + + git_config_free(cfg); +END_TEST BEGIN_SUITE(config) ADD_TEST(config0); ADD_TEST(config1); ADD_TEST(config2); ADD_TEST(config3); + ADD_TEST(config4); END_SUITE From 6776fd514bbfe46f59cd292fa806b42127ec2b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 6 Apr 2011 15:17:06 +0200 Subject: [PATCH 39/57] config: really compare the variable name case-insensitively MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make cvar_name_match really compare the last part of the variable ignoring the case. Signed-off-by: Carlos Martín Nieto --- src/config.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/config.c b/src/config.c index bd0886e8d..6dd2426be 100644 --- a/src/config.c +++ b/src/config.c @@ -109,10 +109,7 @@ static int cvar_name_match(const char *local, const char *input) /* * Anything after the last (possibly only) dot is case-insensitive */ - if (!strcmp(input_dot, local_dot)) - return 1; - - return 0; + return !strcasecmp(input_dot, local_dot); } static git_cvar *cvar_list_find(git_cvar *start, const char *name) From aa793424d3b27351db44e60121598fcc738540e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 6 Apr 2011 15:27:12 +0200 Subject: [PATCH 40/57] config: coding style fixes --- src/config.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/config.c b/src/config.c index 6dd2426be..69024c125 100644 --- a/src/config.c +++ b/src/config.c @@ -185,7 +185,7 @@ int git_config_open(git_config **cfg_out, const char *path) memset(cfg, 0x0, sizeof(git_config)); cfg->file_path = git__strdup(path); - if (cfg->file_path == NULL){ + if (cfg->file_path == NULL) { error = GIT_ENOMEM; goto cleanup; } @@ -195,7 +195,7 @@ int git_config_open(git_config **cfg_out, const char *path) goto cleanup; error = config_parse(cfg); - if(error < GIT_SUCCESS) + if (error < GIT_SUCCESS) goto cleanup; else *cfg_out = cfg; @@ -203,9 +203,9 @@ int git_config_open(git_config **cfg_out, const char *path) return error; cleanup: - if(cfg->vars) + if (cfg->vars) cvar_list_free(cfg->vars); - if(cfg->file_path) + if (cfg->file_path) free(cfg->file_path); gitfo_free_buf(&cfg->reader.buffer); free(cfg); @@ -295,7 +295,7 @@ static int config_set(git_config *cfg, const char *name, const char *value) } var->value = value ? git__strdup(value) : NULL; - if(var->value == NULL && value != NULL){ + if (var->value == NULL && value != NULL) { error = GIT_ENOMEM; cvar_free(var); goto out; @@ -312,7 +312,7 @@ static int config_set(git_config *cfg, const char *name, const char *value) } out: - if(error < GIT_SUCCESS) + if (error < GIT_SUCCESS) cvar_free(var); return error; @@ -325,7 +325,7 @@ int git_config_set_int(git_config *cfg, const char *name, int value) int buf_len = sizeof(str_value), ret; char *help_buf = NULL; - if((ret = snprintf(str_value, buf_len, "%d", value)) >= buf_len - 1){ + if ((ret = snprintf(str_value, buf_len, "%d", value)) >= buf_len - 1){ /* The number is too large, we need to allocate more memory */ buf_len = ret + 1; help_buf = git__malloc(buf_len); @@ -343,7 +343,7 @@ int git_config_set_bool(git_config *cfg, const char *name, int value) { const char *str_value; - if(value == 0) + if (value == 0) str_value = "false"; else str_value = "true"; @@ -384,7 +384,7 @@ int git_config_get_int(git_config *cfg, const char *name, int *out) int ret; ret = config_get(cfg, name, &value); - if(ret < GIT_SUCCESS) + if (ret < GIT_SUCCESS) return ret; ret = sscanf(value, "%d", out); @@ -417,13 +417,13 @@ int git_config_get_bool(git_config *cfg, const char *name, int *out) if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || - !strcasecmp(value, "on")){ + !strcasecmp(value, "on")) { *out = 1; return GIT_SUCCESS; } if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || - !strcasecmp(value, "off")){ + !strcasecmp(value, "off")) { *out = 0; return GIT_SUCCESS; } @@ -635,7 +635,7 @@ static char *build_varname(const char *section, const char *name) return NULL; ret = snprintf(varname, total_len, "%s.%s", section, name); - if(ret >= 0){ /* lowercase from the last dot onwards */ + if (ret >= 0) { /* lowercase from the last dot onwards */ char *dot = strrchr(varname, '.'); if (dot != NULL) strtolower(dot); @@ -666,7 +666,7 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha buf_len = last_quote - first_quote + 2; subsection = git__malloc(buf_len + 2); - if(subsection == NULL) + if (subsection == NULL) return GIT_ENOMEM; pos = 0; @@ -681,7 +681,7 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha * added to the string. In case of error, jump to out */ do { - switch(c) { + switch (c) { case '"': if (quote_marks++ >= 2) return GIT_EOBJCORRUPTED; @@ -746,7 +746,7 @@ static int parse_section_header(git_config *cfg, char **section_out) /* Make sure we were given a section header */ c = line[pos++]; - if(c != '['){ + if (c != '[') { error = GIT_EOBJCORRUPTED; goto error; } @@ -767,7 +767,7 @@ static int parse_section_header(git_config *cfg, char **section_out) return error; } - if (!config_keychar(c) && c != '.'){ + if (!config_keychar(c) && c != '.') { error = GIT_EOBJCORRUPTED; goto error; } @@ -916,7 +916,7 @@ static int config_parse(git_config *cfg_file) } } - if(current_section) + if (current_section) free(current_section); return error; From acab3bc474760216ccafcad9d6cdaf381cdafc72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 6 Apr 2011 15:31:42 +0200 Subject: [PATCH 41/57] config: move str(n)tolower to the git__ namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Non-static functions in a library should always have a prefix namespace. Signed-off-by: Carlos Martín Nieto --- src/config.c | 12 ++++++------ src/config.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/config.c b/src/config.c index 69024c125..e164dc2b9 100644 --- a/src/config.c +++ b/src/config.c @@ -157,7 +157,7 @@ static int cvar_name_normalize(const char *input, char **output) return GIT_SUCCESS; } -void strntolower(char *str, int len) +void git__strntolower(char *str, int len) { int i; @@ -166,9 +166,9 @@ void strntolower(char *str, int len) } } -void strtolower(char *str) +void git__strtolower(char *str) { - strntolower(str, strlen(str)); + git__strntolower(str, strlen(str)); } int git_config_open(git_config **cfg_out, const char *path) @@ -638,7 +638,7 @@ static char *build_varname(const char *section, const char *name) if (ret >= 0) { /* lowercase from the last dot onwards */ char *dot = strrchr(varname, '.'); if (dot != NULL) - strtolower(dot); + git__strtolower(dot); } return varname; @@ -713,7 +713,7 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha } sprintf(*section_name, "%s %s", base_name, subsection); - strntolower(*section_name, strchr(*section_name, ' ') - *section_name); + git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name); out: free(subsection); @@ -778,7 +778,7 @@ static int parse_section_header(git_config *cfg, char **section_out) name[name_length] = 0; free(line); - strtolower(name); + git__strtolower(name); *section_out = name; return GIT_SUCCESS; diff --git a/src/config.h b/src/config.h index c8e9fe062..1e954ff81 100644 --- a/src/config.h +++ b/src/config.h @@ -30,7 +30,7 @@ struct git_cvar { #define CVAR_LIST_FOREACH(start, iter) \ for ((iter) = (start); (iter) != NULL; (iter) = (iter)->next) -void strtolower(char *str); -void strntolower(char *str, int len); +void git__strtolower(char *str); +void git__strntolower(char *str, int len); #endif From 956ad0ed6f6a4d0008f00d573972f8f7fa654811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 6 Apr 2011 15:51:10 +0200 Subject: [PATCH 42/57] config: free the file buffer earlier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no need to keep config file in memory until the the configuration is freed. Free the buffer immediately after the configuration has been parsed. Signed-off-by: Carlos Martín Nieto --- src/config.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index e164dc2b9..0704f074d 100644 --- a/src/config.c +++ b/src/config.c @@ -200,6 +200,8 @@ int git_config_open(git_config **cfg_out, const char *path) else *cfg_out = cfg; + gitfo_free_buf(&cfg->reader.buffer); + return error; cleanup: @@ -220,7 +222,6 @@ void git_config_free(git_config *cfg) free(cfg->file_path); cvar_list_free(cfg->vars); - gitfo_free_buf(&cfg->reader.buffer); free(cfg); } From 0d280ea457c8ee8809062266fa365c440d35ee6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 6 Apr 2011 16:31:06 +0200 Subject: [PATCH 43/57] config: use snprintf instead of sprintf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Due to the preconditions, there should never be an error, but it pays to be paranoid. Signed-off-by: Carlos Martín Nieto --- src/config.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/config.c b/src/config.c index 0704f074d..41db3c5ca 100644 --- a/src/config.c +++ b/src/config.c @@ -648,7 +648,7 @@ static char *build_varname(const char *section, const char *name) static int parse_section_header_ext(const char *line, const char *base_name, char **section_name) { int buf_len, total_len, pos, rpos; - int c; + int c, ret; char *subsection, *first_quote, *last_quote; int error = GIT_SUCCESS; int quote_marks; @@ -713,7 +713,16 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha goto out; } - sprintf(*section_name, "%s %s", base_name, subsection); + ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection); + if (ret >= total_len) { + /* If this fails, we've checked the length wrong */ + error = GIT_ERROR; + goto out; + } else if (ret < 0) { + error = GIT_EOSERR; + goto out; + } + git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name); out: From 6b45cb8a89cd3b133183a093719a144a402450e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 6 Apr 2011 18:27:31 +0200 Subject: [PATCH 44/57] config: use and implement list macros Use list macros instead of manually changing the head and/or tail of the variable list. --- src/config.c | 33 +++++++++++++++------------------ src/config.h | 48 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/config.c b/src/config.c index 41db3c5ca..d537fd8a5 100644 --- a/src/config.c +++ b/src/config.c @@ -48,11 +48,15 @@ static git_cvar *cvar_free(git_cvar *var) return next; } -static void cvar_list_free(git_cvar *start) +static void cvar_list_free(git_cvar_list *list) { - git_cvar *iter = start; + git_cvar *cur; - while ((iter = cvar_free(iter)) != NULL); + while (!CVAR_LIST_EMPTY(list)) { + cur = CVAR_LIST_HEAD(list); + CVAR_LIST_REMOVE_HEAD(list); + cvar_free(cur); + } } /* @@ -112,11 +116,11 @@ static int cvar_name_match(const char *local, const char *input) return !strcasecmp(input_dot, local_dot); } -static git_cvar *cvar_list_find(git_cvar *start, const char *name) +static git_cvar *cvar_list_find(git_cvar_list *list, const char *name) { git_cvar *iter; - CVAR_LIST_FOREACH (start, iter) { + CVAR_LIST_FOREACH (list, iter) { if (cvar_name_match(iter->name, name)) return iter; } @@ -205,8 +209,7 @@ int git_config_open(git_config **cfg_out, const char *path) return error; cleanup: - if (cfg->vars) - cvar_list_free(cfg->vars); + cvar_list_free(&cfg->var_list); if (cfg->file_path) free(cfg->file_path); gitfo_free_buf(&cfg->reader.buffer); @@ -221,7 +224,7 @@ void git_config_free(git_config *cfg) return; free(cfg->file_path); - cvar_list_free(cfg->vars); + cvar_list_free(&cfg->var_list); free(cfg); } @@ -236,7 +239,7 @@ int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *d git_cvar *var; char *normalized; - CVAR_LIST_FOREACH(cfg->vars, var) { + CVAR_LIST_FOREACH(&cfg->var_list, var) { ret = cvar_name_normalize(var->name, &normalized); if (ret < GIT_SUCCESS) return ret; @@ -266,7 +269,7 @@ static int config_set(git_config *cfg, const char *name, const char *value) /* * If it already exists, we just need to update its value. */ - existing = cvar_list_find(cfg->vars, name); + existing = cvar_list_find(&cfg->var_list, name); if (existing != NULL) { char *tmp = value ? git__strdup(value) : NULL; if (tmp == NULL && value != NULL) @@ -304,13 +307,7 @@ static int config_set(git_config *cfg, const char *name, const char *value) var->next = NULL; - if (cfg->vars_tail == NULL) { - cfg->vars = cfg->vars_tail = var; - } - else { - cfg->vars_tail->next = var; - cfg->vars_tail = var; - } + CVAR_LIST_APPEND(&cfg->var_list, var); out: if (error < GIT_SUCCESS) @@ -369,7 +366,7 @@ static int config_get(git_config *cfg, const char *name, const char **out) git_cvar *var; int error = GIT_SUCCESS; - var = cvar_list_find(cfg->vars, name); + var = cvar_list_find(&cfg->var_list, name); if (var == NULL) return GIT_ENOTFOUND; diff --git a/src/config.h b/src/config.h index 1e954ff81..e54933d5f 100644 --- a/src/config.h +++ b/src/config.h @@ -3,9 +3,13 @@ #include "git2/config.h" +typedef struct { + git_cvar *head; + git_cvar *tail; +} git_cvar_list; + struct git_config { - git_cvar *vars; - git_cvar *vars_tail; + git_cvar_list var_list; struct { gitfo_buf buffer; @@ -23,12 +27,44 @@ struct git_cvar { char *value; }; +#define CVAR_LIST_HEAD(list) ((list)->head) + +#define CVAR_LIST_TAIL(list) ((list)->tail) + +#define CVAR_LIST_NEXT(var) ((var)->next) + +#define CVAR_LIST_EMPTY(list) ((list)->head == NULL) + +#define CVAR_LIST_APPEND(list, var) do {\ + if (CVAR_LIST_EMPTY(list)) {\ + CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\ + } else {\ + CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\ + CVAR_LIST_TAIL(list) = var;\ + }\ +} while(0) + +#define CVAR_LIST_REMOVE_HEAD(list) do {\ + CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\ +} while(0) + +#define CVAR_LIST_REMOVE_AFTER(var) do {\ + CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\ +} while(0) + +#define CVAR_LIST_FOREACH(list, iter)\ + for ((iter) = CVAR_LIST_HEAD(list);\ + (iter) != NULL;\ + (iter) = CVAR_LIST_NEXT(iter)) + /* - * If you're going to delete something inside this loop, it's such a - * hassle that you should use the for-loop directly. + * Inspired by the FreeBSD functions */ -#define CVAR_LIST_FOREACH(start, iter) \ - for ((iter) = (start); (iter) != NULL; (iter) = (iter)->next) +#define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\ + for ((iter) = CVAR_LIST_HEAD(vars);\ + (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\ + (iter) = (tmp)) + void git__strtolower(char *str); void git__strntolower(char *str, int len); From 493384e39c1ddfcc4badf962706e02302577d89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 7 Apr 2011 11:24:16 +0200 Subject: [PATCH 45/57] config: make cvar_free behave more like other free functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make cvar_free return void instad of the next element, as it was mostly a hack to make cvar_list_free shorter but it's now using the list macros. Also check if the input is NULL and return immediately in that case. Signed-off-by: Carlos Martín Nieto --- src/config.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/config.c b/src/config.c index d537fd8a5..5f0bcd880 100644 --- a/src/config.c +++ b/src/config.c @@ -37,15 +37,14 @@ static int config_parse(git_config *cfg_file); static int parse_variable(git_config *cfg, char **var_name, char **var_value); void git_config_free(git_config *cfg); -static git_cvar *cvar_free(git_cvar *var) +static void cvar_free(git_cvar *var) { - git_cvar *next = var->next; + if (var == NULL) + return; free(var->name); free(var->value); free(var); - - return next; } static void cvar_list_free(git_cvar_list *list) From 7a4dfd6028499d6f1089c87aa779d686e15f9285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 7 Apr 2011 11:30:02 +0200 Subject: [PATCH 46/57] Simplify error path in config_set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Many error paths freed their local data althought it is freed later on when the end of the function notices that there was an error. This can cause double frees and invalid memory access. Signed-off-by: Carlos Martín Nieto --- src/config.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/config.c b/src/config.c index 5f0bcd880..92d8115e5 100644 --- a/src/config.c +++ b/src/config.c @@ -285,27 +285,23 @@ static int config_set(git_config *cfg, const char *name, const char *value) */ var = git__malloc(sizeof(git_cvar)); - if(var == NULL){ - error = GIT_ENOMEM; - goto out; - } + if (var == NULL) + return GIT_ENOMEM; + + memset(var, 0x0, sizeof(git_cvar)); var->name = git__strdup(name); - if(var->name == NULL){ + if (var->name == NULL) { error = GIT_ENOMEM; - free(var); goto out; } var->value = value ? git__strdup(value) : NULL; if (var->value == NULL && value != NULL) { error = GIT_ENOMEM; - cvar_free(var); goto out; } - var->next = NULL; - CVAR_LIST_APPEND(&cfg->var_list, var); out: @@ -313,7 +309,6 @@ static int config_set(git_config *cfg, const char *name, const char *value) cvar_free(var); return error; - } int git_config_set_int(git_config *cfg, const char *name, int value) From b075b9910c56c356d53439fd34486a905146211a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 7 Apr 2011 16:54:10 +0200 Subject: [PATCH 47/57] Add getting and setting of long int variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit long int is a safer type than int unless the user knows that the variable is going to be quite small. The code has been reworked to use strtol instead of the more complicated sscanf. Signed-off-by: Carlos Martín Nieto --- src/config.c | 54 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/src/config.c b/src/config.c index 92d8115e5..78a31aee7 100644 --- a/src/config.c +++ b/src/config.c @@ -311,17 +311,17 @@ static int config_set(git_config *cfg, const char *name, const char *value) return error; } -int git_config_set_int(git_config *cfg, const char *name, int value) +int git_config_set_long(git_config *cfg, const char *name, long int value) { char str_value[5]; /* Most numbers should fit in here */ int buf_len = sizeof(str_value), ret; char *help_buf = NULL; - if ((ret = snprintf(str_value, buf_len, "%d", value)) >= buf_len - 1){ + if ((ret = snprintf(str_value, buf_len, "%ld", value)) >= buf_len - 1){ /* The number is too large, we need to allocate more memory */ buf_len = ret + 1; help_buf = git__malloc(buf_len); - snprintf(help_buf, buf_len, "%d", value); + snprintf(help_buf, buf_len, "%ld", value); ret = config_set(cfg, name, help_buf); free(help_buf); } else { @@ -331,6 +331,11 @@ int git_config_set_int(git_config *cfg, const char *name, int value) return ret; } +int git_config_set_int(git_config *cfg, const char *name, int value) +{ + return git_config_set_long(cfg, name, value); +} + int git_config_set_bool(git_config *cfg, const char *name, int value) { const char *str_value; @@ -370,28 +375,55 @@ static int config_get(git_config *cfg, const char *name, const char **out) return error; } -int git_config_get_int(git_config *cfg, const char *name, int *out) +int git_config_get_long(git_config *cfg, const char *name, long int *out) { const char *value; + char *num_end; int ret; + long int num; ret = config_get(cfg, name, &value); if (ret < GIT_SUCCESS) return ret; - ret = sscanf(value, "%d", out); - if (ret == 0) /* No items were matched i.e. value isn't a number */ + errno = 0; + num = strtol(value, &num_end, 0); + + /* There was some error */ + if (num_end == value || errno != 0) + return GIT_EINVALIDTYPE; + + switch (*num_end) { + case 'k': + num *= 1024; + break; + case 'm': + num *= 1024 * 1024; + break; + case 'g': + num *= 1024 * 1024 * 1024; + break; + default: return GIT_EINVALIDTYPE; - if (ret < 0) { - if (errno == EINVAL) /* Format was NULL */ - return GIT_EINVALIDTYPE; - else - return GIT_EOSERR; } + *out = num; + return GIT_SUCCESS; } +int git_config_get_int(git_config *cfg, const char *name, int *out) +{ + long int tmp; + int ret; + + ret = git_config_get_long(cfg, name, &tmp); + + *out = (int) tmp; + + return ret; +} + int git_config_get_bool(git_config *cfg, const char *name, int *out) { const char *value; From 631752aaf61f207143dc2058782b0a97effee6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 11 Apr 2011 17:49:47 +0200 Subject: [PATCH 48/57] Fix number suffix detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow a number not to have a suffix. This broke when adding the suffixes. Signed-off-by: Carlos Martín Nieto --- src/config.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config.c b/src/config.c index 78a31aee7..6b4007d62 100644 --- a/src/config.c +++ b/src/config.c @@ -394,6 +394,8 @@ int git_config_get_long(git_config *cfg, const char *name, long int *out) return GIT_EINVALIDTYPE; switch (*num_end) { + case '\0': + break; case 'k': num *= 1024; break; From 52ca4f8a3992a8ca1672abb6263455f01a03549b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 11 Apr 2011 17:51:05 +0200 Subject: [PATCH 49/57] Use internal strtol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/config.c b/src/config.c index 6b4007d62..9e62df0c5 100644 --- a/src/config.c +++ b/src/config.c @@ -386,12 +386,9 @@ int git_config_get_long(git_config *cfg, const char *name, long int *out) if (ret < GIT_SUCCESS) return ret; - errno = 0; - num = strtol(value, &num_end, 0); - - /* There was some error */ - if (num_end == value || errno != 0) - return GIT_EINVALIDTYPE; + ret = git__strtol32(&num, value, &num_end, 0); + if (ret < GIT_SUCCESS) + return ret; switch (*num_end) { case '\0': From 53345e1f1fedf63f8d21b5c2959ae6bca3dabde1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 11 Apr 2011 18:01:01 +0200 Subject: [PATCH 50/57] config: add tests for number suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- tests/resources/config/config5 | Bin 0 -> 76 bytes tests/t15-config.c | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/resources/config/config5 diff --git a/tests/resources/config/config5 b/tests/resources/config/config5 new file mode 100644 index 0000000000000000000000000000000000000000..645fe7645d5f90353da4fef491ea32302c5a428a GIT binary patch literal 76 zcmY#Z2uUq2QAo=#QphXKO-e0NC@xJ)%dAK(=8A?0#By Date: Tue, 19 Apr 2011 16:34:22 +0200 Subject: [PATCH 51/57] config: allow uppercase number suffixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 3 +++ tests/resources/config/config5 | Bin 76 -> 106 bytes tests/t15-config.c | 9 +++++++++ 3 files changed, 12 insertions(+) diff --git a/src/config.c b/src/config.c index 9e62df0c5..ec350f8fd 100644 --- a/src/config.c +++ b/src/config.c @@ -394,12 +394,15 @@ int git_config_get_long(git_config *cfg, const char *name, long int *out) case '\0': break; case 'k': + case 'K': num *= 1024; break; case 'm': + case 'M': num *= 1024 * 1024; break; case 'g': + case 'G': num *= 1024 * 1024 * 1024; break; default: diff --git a/tests/resources/config/config5 b/tests/resources/config/config5 index 645fe7645d5f90353da4fef491ea32302c5a428a..8ab60ccec89ed41a4cebff13847e19a1d1b9420e 100644 GIT binary patch delta 51 pcmeatnqZ@sovmQ2VCc=o$(ajc delta 21 acmd1unP4N3t6-~On9Ie Date: Tue, 19 Apr 2011 16:38:52 +0200 Subject: [PATCH 52/57] config: export git_config_[sg]et_long MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- include/git2/config.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/git2/config.h b/include/git2/config.h index e1e78858b..abf59fa9e 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -62,6 +62,16 @@ GIT_EXTERN(void) git_config_free(git_config *cfg); */ GIT_EXTERN(int) git_config_get_int(git_config *cfg, const char *name, int *out); +/** + * Get the value of a long integer config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_get_long(git_config *cfg, const char *name, long int *out); + /** * Get the value of a boolean config variable. * @@ -98,6 +108,16 @@ GIT_EXTERN(int) git_config_get_string(git_config *cfg, const char *name, const c */ GIT_EXTERN(int) git_config_set_int(git_config *cfg, const char *name, int value); +/** + * Set the value of a long integer config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_set_long(git_config *cfg, const char *name, long int value); + /** * Set the value of a boolean config variable. * From a68cf94b37b59fbc5a00dca9e7488da717bfea4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 19 Apr 2011 16:40:52 +0200 Subject: [PATCH 53/57] Fix const char ** warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/config.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config.c b/src/config.c index ec350f8fd..3bde15b36 100644 --- a/src/config.c +++ b/src/config.c @@ -377,8 +377,7 @@ static int config_get(git_config *cfg, const char *name, const char **out) int git_config_get_long(git_config *cfg, const char *name, long int *out) { - const char *value; - char *num_end; + const char *value, *num_end; int ret; long int num; From 0130d8184e7a0087a5b95700e9a19d60f35b869f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 27 Apr 2011 11:20:38 +0200 Subject: [PATCH 54/57] Fix git__strntolower MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Obviously, the whole string should be lower-cased and not just the last char. Signed-off-by: Carlos Martín Nieto --- src/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index 3bde15b36..1ac6ed8c8 100644 --- a/src/config.c +++ b/src/config.c @@ -165,7 +165,7 @@ void git__strntolower(char *str, int len) int i; for (i = 0; i < len; ++i) { - str[len] = tolower(str[len]); + str[i] = tolower(str[i]); } } From 094aaaaee92f4fc98a6c3c3af36183cb217948a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 5 May 2011 15:16:15 +0200 Subject: [PATCH 55/57] config: store the section name separately The section and variable names use different rules, so store them as two different variables internally. This will simplify the configuration-writing code as well later on, but even with parsing, the code is simpler. Take this opportunity to add a variable to the list directly when parsing instead of passing through config_set. --- src/config.c | 141 +++++++++++++++++++++++++-------------------------- src/config.h | 1 + src/util.h | 1 + 3 files changed, 71 insertions(+), 72 deletions(-) diff --git a/src/config.c b/src/config.c index 1ac6ed8c8..654b70b3e 100644 --- a/src/config.c +++ b/src/config.c @@ -42,6 +42,7 @@ static void cvar_free(git_cvar *var) if (var == NULL) return; + free(var->section); free(var->name); free(var->value); free(var); @@ -59,13 +60,15 @@ static void cvar_list_free(git_cvar_list *list) } /* - * The order is important. The first parameter is the name we want to - * match against, and the second one is what we're looking for + * Compare two strings according to the git section-subsection + * rules. The order of the strings is important because local is + * assumed to have the internal format (only the section name and with + * case information) and input the normalized one (only dots, no case + * information). */ -static int cvar_section_match(const char *local, const char *input) +static int cvar_match_section(const char *local, const char *input) { - char *input_dot = strrchr(input, '.'); - char *local_last_dot = strrchr(local, '.'); + char *first_dot, *last_dot; char *local_sp = strchr(local, ' '); int comparison_len; @@ -74,45 +77,48 @@ static int cvar_section_match(const char *local, const char *input) * just do a case-insensitive compare. */ if (local_sp == NULL) - return !strncasecmp(local, input, local_last_dot - local); + return !strncasecmp(local, input, strlen(local)); - /* Anything before the space in local is case-insensitive */ + /* + * From here onwards, there is a space diving the section and the + * subsection. Anything before the space in local is + * case-insensitive. + */ if (strncasecmp(local, input, local_sp - local)) return 0; /* * We compare starting from the first character after the * quotation marks, which is two characters beyond the space. For - * the input, we start one character beyond the first dot. + * the input, we start one character beyond the dot. If the names + * have different lengths, then we can fail early, as we know they + * can't be the same. * The length is given by the length between the quotation marks. - * - * this "that".var - * ^ ^ - * a b - * - * where a is (local_sp + 2) and b is local_last_dot. The comparison - * length is given by b - 1 - a. */ - input_dot = strchr(input, '.'); - comparison_len = local_last_dot - 1 - (local_sp + 2); - return !strncmp(local_sp + 2, input_dot + 1, comparison_len); -} -static int cvar_name_match(const char *local, const char *input) -{ - char *input_dot = strrchr(input, '.'); - char *local_dot = strrchr(local, '.'); + first_dot = strchr(input, '.'); + last_dot = strrchr(input, '.'); + comparison_len = strlen(local_sp + 2) - 1; - /* - * First try to match the section name - */ - if (!cvar_section_match(local, input)) + if (last_dot == first_dot || last_dot - first_dot - 1 != comparison_len) return 0; - /* - * Anything after the last (possibly only) dot is case-insensitive - */ - return !strcasecmp(input_dot, local_dot); + return !strncmp(local_sp + 2, first_dot + 1, comparison_len); +} + +static int cvar_match_name(const git_cvar *var, const char *str) +{ + const char *name_start; + + if (!cvar_match_section(var->section, str)) { + return 0; + } + /* Early exit if the lengths are different */ + name_start = strrchr(str, '.') + 1; + if (strlen(var->name) != strlen(name_start)) + return 0; + + return !strcasecmp(var->name, name_start); } static git_cvar *cvar_list_find(git_cvar_list *list, const char *name) @@ -120,7 +126,7 @@ static git_cvar *cvar_list_find(git_cvar_list *list, const char *name) git_cvar *iter; CVAR_LIST_FOREACH (list, iter) { - if (cvar_name_match(iter->name, name)) + if (cvar_match_name(iter, name)) return iter; } @@ -264,6 +270,7 @@ static int config_set(git_config *cfg, const char *name, const char *value) git_cvar *var = NULL; git_cvar *existing = NULL; int error = GIT_SUCCESS; + const char *last_dot; /* * If it already exists, we just need to update its value. @@ -290,7 +297,19 @@ static int config_set(git_config *cfg, const char *name, const char *value) memset(var, 0x0, sizeof(git_cvar)); - var->name = git__strdup(name); + last_dot = strrchr(name, '.'); + if (last_dot == NULL) { + error = GIT_ERROR; + goto out; + } + + var->section = git__strndup(name, last_dot - name); + if (var->section == NULL) { + error = GIT_ENOMEM; + goto out; + } + + var->name = git__strdup(last_dot + 1); if (var->name == NULL) { error = GIT_ENOMEM; goto out; @@ -639,36 +658,6 @@ static inline int config_keychar(int c) return isalnum(c) || c == '-'; } -/* - * Returns $section.$name, using only name_len chars from the name, - * which is useful so we don't have to copy the variable name - * twice. The name of the variable is set to lowercase. - * Don't forget to free the buffer. - */ -static char *build_varname(const char *section, const char *name) -{ - char *varname; - int section_len, ret; - int name_len; - size_t total_len; - - name_len = strlen(name); - section_len = strlen(section); - total_len = section_len + name_len + 2; - varname = malloc(total_len); - if(varname == NULL) - return NULL; - - ret = snprintf(varname, total_len, "%s.%s", section, name); - if (ret >= 0) { /* lowercase from the last dot onwards */ - char *dot = strrchr(varname, '.'); - if (dot != NULL) - git__strtolower(dot); - } - - return varname; -} - static int parse_section_header_ext(const char *line, const char *base_name, char **section_name) { int buf_len, total_len, pos, rpos; @@ -901,7 +890,7 @@ static int config_parse(git_config *cfg_file) char *current_section = NULL; char *var_name; char *var_value; - char *full_name; + git_cvar *var; /* Initialise the reading position */ cfg_file->reader.read_ptr = cfg_file->reader.buffer.data; @@ -933,18 +922,26 @@ static int config_parse(git_config *cfg_file) if (error < GIT_SUCCESS) break; - full_name = build_varname(current_section, var_name); - if (full_name == NULL) { + var = malloc(sizeof(git_cvar)); + if (var == NULL) { error = GIT_ENOMEM; - free(var_name); - free(var_value); break; } - config_set(cfg_file, full_name, var_value); - free(var_name); - free(var_value); - free(full_name); + memset(var, 0x0, sizeof(git_cvar)); + + var->section = git__strdup(current_section); + if (var->section == NULL) { + error = GIT_ENOMEM; + free(var); + break; + } + + var->name = var_name; + var->value = var_value; + git__strtolower(var->name); + + CVAR_LIST_APPEND(&cfg_file->var_list, var); break; } diff --git a/src/config.h b/src/config.h index e54933d5f..82a66bfbf 100644 --- a/src/config.h +++ b/src/config.h @@ -23,6 +23,7 @@ struct git_config { struct git_cvar { git_cvar *next; + char *section; char *name; char *value; }; diff --git a/src/util.h b/src/util.h index 3c606493f..3dcdc3674 100644 --- a/src/util.h +++ b/src/util.h @@ -16,6 +16,7 @@ #define git__calloc calloc #define git__realloc realloc #define git__strdup strdup +#define git__strndup strndup extern int git__fmt(char *, size_t, const char *, ...) GIT_FORMAT_PRINTF(3, 4); From c0335005495c1b49986d19031557f9df6bf49922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 6 May 2011 12:42:47 +0200 Subject: [PATCH 56/57] Move config to a backend structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Configuration options can come from different sources. Currently, there is only support for reading them from a flat file, but it might make sense to read it from a database at some point. Move the parsing code into src/config_file.c and create an include file include/git2/config_backend.h to allow for other backends to be developed. Signed-off-by: Carlos Martín Nieto --- include/git2/config.h | 12 +- include/git2/config_backend.h | 57 ++ include/git2/types.h | 3 + src/config.c | 998 ++++---------------------------- src/config.h | 64 +-- src/config_file.c | 1012 +++++++++++++++++++++++++++++++++ tests/t15-config.c | 12 +- 7 files changed, 1209 insertions(+), 949 deletions(-) create mode 100644 include/git2/config_backend.h create mode 100644 src/config_file.c diff --git a/include/git2/config.h b/include/git2/config.h index abf59fa9e..3ebbe64de 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -37,13 +37,23 @@ */ GIT_BEGIN_DECL +/** + * Allocate a new configuration + */ +GIT_EXTERN(int) git_config_new(git_config **out); + /** * Open a configuration file * * @param cfg_out pointer to the configuration data * @param path where to load the confiration from */ -GIT_EXTERN(int) git_config_open(git_config **cfg_out, const char *path); +GIT_EXTERN(int) git_config_open_bare(git_config **cfg_out, const char *path); + +/** + * + */ +GIT_EXTERN(int) git_config_add_backend(git_config *cfg, git_config_backend *backend, int priority); /** * Free the configuration and its associated memory diff --git a/include/git2/config_backend.h b/include/git2/config_backend.h new file mode 100644 index 000000000..427cd95dd --- /dev/null +++ b/include/git2/config_backend.h @@ -0,0 +1,57 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDE_git_config_backend_h__ +#define INCLUDE_git_config_backend_h__ + +#include "common.h" +#include "types.h" +#include "config.h" + +GIT_BEGIN_DECL + +struct git_config; + +struct git_config_backend { + struct git_config *cfg; + /* Open means open the file/database and parse if necessary */ + int (*open)(struct git_config_backend *); + int (* get)(struct git_config_backend *, const char *key, const char **value); + int (* set)(struct git_config_backend *, const char *key, const char *value); + int (*foreach)(struct git_config_backend *, int (*fn)(const char *, void *), void *data); + void (*free)(struct git_config_backend *); +}; + +/** + * Create a file-backed configuration backend + * + * @param out the new backend + * @path where the config file is located + */ +GIT_EXTERN(int) git_config_backend_file(struct git_config_backend **out, const char *path); + +GIT_END_DECL + +#endif diff --git a/include/git2/types.h b/include/git2/types.h index 84e3d5b7f..49e80dd95 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -133,6 +133,9 @@ typedef struct git_index git_index; /** Memory representation of a config file */ typedef struct git_config git_config; +/** A specific implementation of a config backend */ +typedef struct git_config_backend git_config_backend; + /** Memory representation of a config variable */ typedef struct git_cvar git_cvar; diff --git a/src/config.c b/src/config.c index 654b70b3e..d839e3892 100644 --- a/src/config.c +++ b/src/config.c @@ -27,144 +27,15 @@ #include "fileops.h" #include "hashtable.h" #include "config.h" +#include "git2/config_backend.h" +#include "vector.h" #include -/********************** - * Forward declarations - ***********************/ -static int config_parse(git_config *cfg_file); -static int parse_variable(git_config *cfg, char **var_name, char **var_value); -void git_config_free(git_config *cfg); - -static void cvar_free(git_cvar *var) -{ - if (var == NULL) - return; - - free(var->section); - free(var->name); - free(var->value); - free(var); -} - -static void cvar_list_free(git_cvar_list *list) -{ - git_cvar *cur; - - while (!CVAR_LIST_EMPTY(list)) { - cur = CVAR_LIST_HEAD(list); - CVAR_LIST_REMOVE_HEAD(list); - cvar_free(cur); - } -} - -/* - * Compare two strings according to the git section-subsection - * rules. The order of the strings is important because local is - * assumed to have the internal format (only the section name and with - * case information) and input the normalized one (only dots, no case - * information). - */ -static int cvar_match_section(const char *local, const char *input) -{ - char *first_dot, *last_dot; - char *local_sp = strchr(local, ' '); - int comparison_len; - - /* - * If the local section name doesn't contain a space, then we can - * just do a case-insensitive compare. - */ - if (local_sp == NULL) - return !strncasecmp(local, input, strlen(local)); - - /* - * From here onwards, there is a space diving the section and the - * subsection. Anything before the space in local is - * case-insensitive. - */ - if (strncasecmp(local, input, local_sp - local)) - return 0; - - /* - * We compare starting from the first character after the - * quotation marks, which is two characters beyond the space. For - * the input, we start one character beyond the dot. If the names - * have different lengths, then we can fail early, as we know they - * can't be the same. - * The length is given by the length between the quotation marks. - */ - - first_dot = strchr(input, '.'); - last_dot = strrchr(input, '.'); - comparison_len = strlen(local_sp + 2) - 1; - - if (last_dot == first_dot || last_dot - first_dot - 1 != comparison_len) - return 0; - - return !strncmp(local_sp + 2, first_dot + 1, comparison_len); -} - -static int cvar_match_name(const git_cvar *var, const char *str) -{ - const char *name_start; - - if (!cvar_match_section(var->section, str)) { - return 0; - } - /* Early exit if the lengths are different */ - name_start = strrchr(str, '.') + 1; - if (strlen(var->name) != strlen(name_start)) - return 0; - - return !strcasecmp(var->name, name_start); -} - -static git_cvar *cvar_list_find(git_cvar_list *list, const char *name) -{ - git_cvar *iter; - - CVAR_LIST_FOREACH (list, iter) { - if (cvar_match_name(iter, name)) - return iter; - } - - return NULL; -} - -static int cvar_name_normalize(const char *input, char **output) -{ - char *input_sp = strchr(input, ' '); - char *quote, *str; - int i; - - /* We need to make a copy anyway */ - str = git__strdup(input); - if (str == NULL) - return GIT_ENOMEM; - - *output = str; - - /* If there aren't any spaces, we don't need to do anything */ - if (input_sp == NULL) - return GIT_SUCCESS; - - /* - * If there are spaces, we replace the space by a dot, move the - * variable name so that the dot before it replaces the last - * quotation mark and repeat so that the first quotation mark - * disappears. - */ - str[input_sp - input] = '.'; - - for (i = 0; i < 2; ++i) { - quote = strrchr(str, '"'); - memmove(quote, quote + 1, strlen(quote)); - } - - return GIT_SUCCESS; -} +typedef struct { + git_config_backend *backend; + int priority; +} backend_internal; void git__strntolower(char *str, int len) { @@ -180,12 +51,65 @@ void git__strtolower(char *str) git__strntolower(str, strlen(str)); } -int git_config_open(git_config **cfg_out, const char *path) +int git_config_open_bare(git_config **out, const char *path) { - git_config *cfg; + git_config_backend *backend = NULL; + git_config *cfg = NULL; int error = GIT_SUCCESS; - assert(cfg_out && path); + error = git_config_new(&cfg); + if (error < GIT_SUCCESS) + goto error; + + error = git_config_backend_file(&backend, path); + if (error < GIT_SUCCESS) + goto error; + + git_config_add_backend(cfg, backend, 1); + + error = backend->open(backend); + if (error < GIT_SUCCESS) + goto error; + + *out = cfg; + + return error; + + error: + if(backend) + backend->free(backend); + + return error; +} + +void git_config_free(git_config *cfg) +{ + unsigned int i; + git_config_backend *backend; + backend_internal *internal; + + for(i = 0; i < cfg->backends.length; ++i){ + internal = git_vector_get(&cfg->backends, i); + backend = internal->backend; + backend->free(backend); + free(internal); + } + + git_vector_free(&cfg->backends); + free(cfg); +} + +static int config_backend_cmp(const void *a, const void *b) +{ + const backend_internal *bk_a = *(const backend_internal **)(a); + const backend_internal *bk_b = *(const backend_internal **)(b); + + return bk_b->priority - bk_a->priority; +} + +int git_config_new(git_config **out) +{ + git_config *cfg; cfg = git__malloc(sizeof(git_config)); if (cfg == NULL) @@ -193,45 +117,38 @@ int git_config_open(git_config **cfg_out, const char *path) memset(cfg, 0x0, sizeof(git_config)); - cfg->file_path = git__strdup(path); - if (cfg->file_path == NULL) { - error = GIT_ENOMEM; - goto cleanup; + if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) { + free(cfg); + return GIT_ENOMEM; } - error = gitfo_read_file(&cfg->reader.buffer, cfg->file_path); - if(error < GIT_SUCCESS) - goto cleanup; + *out = cfg; - error = config_parse(cfg); - if (error < GIT_SUCCESS) - goto cleanup; - else - *cfg_out = cfg; - - gitfo_free_buf(&cfg->reader.buffer); - - return error; - - cleanup: - cvar_list_free(&cfg->var_list); - if (cfg->file_path) - free(cfg->file_path); - gitfo_free_buf(&cfg->reader.buffer); - free(cfg); - - return error; + return GIT_SUCCESS; } -void git_config_free(git_config *cfg) +int git_config_add_backend(git_config *cfg, git_config_backend *backend, int priority) { - if (cfg == NULL) - return; + backend_internal *internal; - free(cfg->file_path); - cvar_list_free(&cfg->var_list); + assert(cfg && backend); - free(cfg); + internal = git__malloc(sizeof(backend_internal)); + if (internal == NULL) + return GIT_ENOMEM; + + internal->backend = backend; + internal->priority = priority; + + if (git_vector_insert(&cfg->backends, internal) < 0) { + free(internal); + return GIT_ENOMEM; + } + + git_vector_sort(&cfg->backends); + internal->backend->cfg = cfg; + + return GIT_SUCCESS; } /* @@ -241,23 +158,20 @@ void git_config_free(git_config *cfg) int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *data) { int ret = GIT_SUCCESS; - git_cvar *var; - char *normalized; + unsigned int i; + backend_internal *internal; + git_config_backend *backend; - CVAR_LIST_FOREACH(&cfg->var_list, var) { - ret = cvar_name_normalize(var->name, &normalized); - if (ret < GIT_SUCCESS) - return ret; - - ret = fn(normalized, data); - free(normalized); - if (ret) - break; + for(i = 0; i < cfg->backends.length && ret == 0; ++i) { + internal = git_vector_get(&cfg->backends, i); + backend = internal->backend; + ret = backend->foreach(backend, fn, data); } return ret; } + /************** * Setters **************/ @@ -265,70 +179,6 @@ int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *d /* * Internal function to actually set the string value of a variable */ -static int config_set(git_config *cfg, const char *name, const char *value) -{ - git_cvar *var = NULL; - git_cvar *existing = NULL; - int error = GIT_SUCCESS; - const char *last_dot; - - /* - * If it already exists, we just need to update its value. - */ - existing = cvar_list_find(&cfg->var_list, name); - if (existing != NULL) { - char *tmp = value ? git__strdup(value) : NULL; - if (tmp == NULL && value != NULL) - return GIT_ENOMEM; - - free(existing->value); - existing->value = tmp; - - return GIT_SUCCESS; - } - - /* - * Otherwise, create it and stick it at the end of the queue. - */ - - var = git__malloc(sizeof(git_cvar)); - if (var == NULL) - return GIT_ENOMEM; - - memset(var, 0x0, sizeof(git_cvar)); - - last_dot = strrchr(name, '.'); - if (last_dot == NULL) { - error = GIT_ERROR; - goto out; - } - - var->section = git__strndup(name, last_dot - name); - if (var->section == NULL) { - error = GIT_ENOMEM; - goto out; - } - - var->name = git__strdup(last_dot + 1); - if (var->name == NULL) { - error = GIT_ENOMEM; - goto out; - } - - var->value = value ? git__strdup(value) : NULL; - if (var->value == NULL && value != NULL) { - error = GIT_ENOMEM; - goto out; - } - - CVAR_LIST_APPEND(&cfg->var_list, var); - - out: - if (error < GIT_SUCCESS) - cvar_free(var); - - return error; -} int git_config_set_long(git_config *cfg, const char *name, long int value) { @@ -341,10 +191,10 @@ int git_config_set_long(git_config *cfg, const char *name, long int value) buf_len = ret + 1; help_buf = git__malloc(buf_len); snprintf(help_buf, buf_len, "%ld", value); - ret = config_set(cfg, name, help_buf); + ret = git_config_set_string(cfg, name, help_buf); free(help_buf); } else { - ret = config_set(cfg, name, str_value); + ret = git_config_set_string(cfg, name, str_value); } return ret; @@ -364,43 +214,33 @@ int git_config_set_bool(git_config *cfg, const char *name, int value) else str_value = "true"; - return config_set(cfg, name, str_value); + return git_config_set_string(cfg, name, str_value); } int git_config_set_string(git_config *cfg, const char *name, const char *value) { - return config_set(cfg, name, value); + backend_internal *internal; + git_config_backend *backend; + + assert(cfg->backends.length > 0); + + internal = git_vector_get(&cfg->backends, 0); + backend = internal->backend; + + return backend->set(backend, name, value); } /*********** * Getters ***********/ -/* - * Internal function that actually gets the value in string form - */ -static int config_get(git_config *cfg, const char *name, const char **out) -{ - git_cvar *var; - int error = GIT_SUCCESS; - - var = cvar_list_find(&cfg->var_list, name); - - if (var == NULL) - return GIT_ENOTFOUND; - - *out = var->value; - - return error; -} - int git_config_get_long(git_config *cfg, const char *name, long int *out) { const char *value, *num_end; int ret; long int num; - ret = config_get(cfg, name, &value); + ret = git_config_get_string(cfg, name, &value); if (ret < GIT_SUCCESS) return ret; @@ -449,7 +289,7 @@ int git_config_get_bool(git_config *cfg, const char *name, int *out) const char *value; int error = GIT_SUCCESS; - error = config_get(cfg, name, &value); + error = git_config_get_string(cfg, name, &value); if (error < GIT_SUCCESS) return error; @@ -482,618 +322,14 @@ int git_config_get_bool(git_config *cfg, const char *name, int *out) int git_config_get_string(git_config *cfg, const char *name, const char **out) { - return config_get(cfg, name, out); + backend_internal *internal; + git_config_backend *backend; + + assert(cfg->backends.length > 0); + + internal = git_vector_get(&cfg->backends, 0); + backend = internal->backend; + + return backend->get(backend, name, out); } -static int cfg_getchar_raw(git_config *cfg) -{ - int c; - - c = *cfg->reader.read_ptr++; - - /* - Win 32 line breaks: if we find a \r\n sequence, - return only the \n as a newline - */ - if (c == '\r' && *cfg->reader.read_ptr == '\n') { - cfg->reader.read_ptr++; - c = '\n'; - } - - if (c == '\n') - cfg->reader.line_number++; - - if (c == 0) { - cfg->reader.eof = 1; - c = '\n'; - } - - return c; -} - -#define SKIP_WHITESPACE (1 << 1) -#define SKIP_COMMENTS (1 << 2) - -static int cfg_getchar(git_config *cfg_file, int flags) -{ - const int skip_whitespace = (flags & SKIP_WHITESPACE); - const int skip_comments = (flags & SKIP_COMMENTS); - int c; - - assert(cfg_file->reader.read_ptr); - - do c = cfg_getchar_raw(cfg_file); - while (skip_whitespace && isspace(c)); - - if (skip_comments && (c == '#' || c == ';')) { - do c = cfg_getchar_raw(cfg_file); - while (c != '\n'); - } - - return c; -} - -/* - * Read the next char, but don't move the reading pointer. - */ -static int cfg_peek(git_config *cfg, int flags) -{ - void *old_read_ptr; - int old_lineno, old_eof; - int ret; - - assert(cfg->reader.read_ptr); - - old_read_ptr = cfg->reader.read_ptr; - old_lineno = cfg->reader.line_number; - old_eof = cfg->reader.eof; - - ret = cfg_getchar(cfg, flags); - - cfg->reader.read_ptr = old_read_ptr; - cfg->reader.line_number = old_lineno; - cfg->reader.eof = old_eof; - - return ret; -} - -static const char *LINEBREAK_UNIX = "\\\n"; -static const char *LINEBREAK_WIN32 = "\\\r\n"; - -static int is_linebreak(const char *pos) -{ - return memcmp(pos - 1, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0 || - memcmp(pos - 2, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0; -} - -/* - * Read and consume a line, returning it in newly-allocated memory. - */ -static char *cfg_readline(git_config *cfg) -{ - char *line = NULL; - char *line_src, *line_end; - int line_len; - - line_src = cfg->reader.read_ptr; - line_end = strchr(line_src, '\n'); - - /* no newline at EOF */ - if (line_end == NULL) - line_end = strchr(line_src, 0); - else - while (is_linebreak(line_end)) - line_end = strchr(line_end + 1, '\n'); - - - while (line_src < line_end && isspace(*line_src)) - line_src++; - - line = (char *)git__malloc((size_t)(line_end - line_src) + 1); - if (line == NULL) - return NULL; - - line_len = 0; - while (line_src < line_end) { - - if (memcmp(line_src, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0) { - line_src += sizeof(LINEBREAK_UNIX); - continue; - } - - if (memcmp(line_src, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0) { - line_src += sizeof(LINEBREAK_WIN32); - continue; - } - - line[line_len++] = *line_src++; - } - - line[line_len] = '\0'; - - while (--line_len >= 0 && isspace(line[line_len])) - line[line_len] = '\0'; - - if (*line_end == '\n') - line_end++; - - if (*line_end == '\0') - cfg->reader.eof = 1; - - cfg->reader.line_number++; - cfg->reader.read_ptr = line_end; - - return line; -} - -/* - * Consume a line, without storing it anywhere - */ -void cfg_consume_line(git_config *cfg) -{ - char *line_start, *line_end; - int len; - - line_start = cfg->reader.read_ptr; - line_end = strchr(line_start, '\n'); - /* No newline at EOF */ - if(line_end == NULL){ - line_end = strchr(line_start, '\0'); - } - - len = line_end - line_start; - - if (*line_end == '\n') - line_end++; - - if (*line_end == '\0') - cfg->reader.eof = 1; - - cfg->reader.line_number++; - cfg->reader.read_ptr = line_end; -} - -static inline int config_keychar(int c) -{ - return isalnum(c) || c == '-'; -} - -static int parse_section_header_ext(const char *line, const char *base_name, char **section_name) -{ - int buf_len, total_len, pos, rpos; - int c, ret; - char *subsection, *first_quote, *last_quote; - int error = GIT_SUCCESS; - int quote_marks; - /* - * base_name is what came before the space. We should be at the - * first quotation mark, except for now, line isn't being kept in - * sync so we only really use it to calculate the length. - */ - - first_quote = strchr(line, '"'); - last_quote = strrchr(line, '"'); - - if (last_quote - first_quote == 0) - return GIT_EOBJCORRUPTED; - - buf_len = last_quote - first_quote + 2; - - subsection = git__malloc(buf_len + 2); - if (subsection == NULL) - return GIT_ENOMEM; - - pos = 0; - rpos = 0; - quote_marks = 0; - - line = first_quote; - c = line[rpos++]; - - /* - * At the end of each iteration, whatever is stored in c will be - * added to the string. In case of error, jump to out - */ - do { - switch (c) { - case '"': - if (quote_marks++ >= 2) - return GIT_EOBJCORRUPTED; - break; - case '\\': - c = line[rpos++]; - switch (c) { - case '"': - case '\\': - break; - default: - error = GIT_EOBJCORRUPTED; - goto out; - } - default: - break; - } - - subsection[pos++] = c; - } while ((c = line[rpos++]) != ']'); - - subsection[pos] = '\0'; - - total_len = strlen(base_name) + strlen(subsection) + 2; - *section_name = git__malloc(total_len); - if (*section_name == NULL) { - error = GIT_ENOMEM; - goto out; - } - - ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection); - if (ret >= total_len) { - /* If this fails, we've checked the length wrong */ - error = GIT_ERROR; - goto out; - } else if (ret < 0) { - error = GIT_EOSERR; - goto out; - } - - git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name); - - out: - free(subsection); - - return error; -} - -static int parse_section_header(git_config *cfg, char **section_out) -{ - char *name, *name_end; - int name_length, c, pos; - int error = GIT_SUCCESS; - char *line; - - line = cfg_readline(cfg); - if (line == NULL) - return GIT_ENOMEM; - - /* find the end of the variable's name */ - name_end = strchr(line, ']'); - if (name_end == NULL) - return GIT_EOBJCORRUPTED; - - name = (char *)git__malloc((size_t)(name_end - line) + 1); - if (name == NULL) - return GIT_EOBJCORRUPTED; - - name_length = 0; - pos = 0; - - /* Make sure we were given a section header */ - c = line[pos++]; - if (c != '[') { - error = GIT_EOBJCORRUPTED; - goto error; - } - - c = line[pos++]; - - do { - if (cfg->reader.eof){ - error = GIT_EOBJCORRUPTED; - goto error; - } - - if (isspace(c)){ - name[name_length] = '\0'; - error = parse_section_header_ext(line, name, section_out); - free(line); - free(name); - return error; - } - - if (!config_keychar(c) && c != '.') { - error = GIT_EOBJCORRUPTED; - goto error; - } - - name[name_length++] = tolower(c); - - } while ((c = line[pos++]) != ']'); - - name[name_length] = 0; - free(line); - git__strtolower(name); - *section_out = name; - return GIT_SUCCESS; - -error: - free(line); - free(name); - return error; -} - -static int skip_bom(git_config *cfg) -{ - static const unsigned char *utf8_bom = "\xef\xbb\xbf"; - - if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) - cfg->reader.read_ptr += sizeof(utf8_bom); - - /* TODO: the reference implementation does pretty stupid - shit with the BoM - */ - - return GIT_SUCCESS; -} - -/* - (* basic types *) - digit = "0".."9" - integer = digit { digit } - alphabet = "a".."z" + "A" .. "Z" - - section_char = alphabet | "." | "-" - extension_char = (* any character except newline *) - any_char = (* any character *) - variable_char = "alphabet" | "-" - - - (* actual grammar *) - config = { section } - - section = header { definition } - - header = "[" section [subsection | subsection_ext] "]" - - subsection = "." section - subsection_ext = "\"" extension "\"" - - section = section_char { section_char } - extension = extension_char { extension_char } - - definition = variable_name ["=" variable_value] "\n" - - variable_name = variable_char { variable_char } - variable_value = string | boolean | integer - - string = quoted_string | plain_string - quoted_string = "\"" plain_string "\"" - plain_string = { any_char } - - boolean = boolean_true | boolean_false - boolean_true = "yes" | "1" | "true" | "on" - boolean_false = "no" | "0" | "false" | "off" -*/ - -static void strip_comments(char *line) -{ - int quote_count = 0; - char *ptr; - - for (ptr = line; *ptr; ++ptr) { - if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\') - quote_count++; - - if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) { - ptr[0] = '\0'; - break; - } - } - - if (isspace(ptr[-1])) { - /* TODO skip whitespace */ - } -} - -static int config_parse(git_config *cfg_file) -{ - int error = GIT_SUCCESS, c; - char *current_section = NULL; - char *var_name; - char *var_value; - git_cvar *var; - - /* Initialise the reading position */ - cfg_file->reader.read_ptr = cfg_file->reader.buffer.data; - cfg_file->reader.eof = 0; - - skip_bom(cfg_file); - - while (error == GIT_SUCCESS && !cfg_file->reader.eof) { - - c = cfg_peek(cfg_file, SKIP_WHITESPACE); - - switch (c) { - case '\0': /* We've arrived at the end of the file */ - break; - - case '[': /* section header, new section begins */ - free(current_section); - error = parse_section_header(cfg_file, ¤t_section); - break; - - case ';': - case '#': - cfg_consume_line(cfg_file); - break; - - default: /* assume variable declaration */ - error = parse_variable(cfg_file, &var_name, &var_value); - - if (error < GIT_SUCCESS) - break; - - var = malloc(sizeof(git_cvar)); - if (var == NULL) { - error = GIT_ENOMEM; - break; - } - - memset(var, 0x0, sizeof(git_cvar)); - - var->section = git__strdup(current_section); - if (var->section == NULL) { - error = GIT_ENOMEM; - free(var); - break; - } - - var->name = var_name; - var->value = var_value; - git__strtolower(var->name); - - CVAR_LIST_APPEND(&cfg_file->var_list, var); - - break; - } - } - - if (current_section) - free(current_section); - - return error; -} - -static int is_multiline_var(const char *str) -{ - char *end = strrchr(str, '\0') - 1; - - while (isspace(*end)) - --end; - - return *end == '\\'; -} - -static int parse_multiline_variable(git_config *cfg, const char *first, char **out) -{ - char *line = NULL, *end; - int error = GIT_SUCCESS, len, ret; - char *buf; - - /* Check that the next line exists */ - line = cfg_readline(cfg); - if (line == NULL) - return GIT_ENOMEM; - - /* We've reached the end of the file, there is input missing */ - if (line[0] == '\0') { - error = GIT_EOBJCORRUPTED; - goto out; - } - - strip_comments(line); - - /* If it was just a comment, pretend it didn't exist */ - if (line[0] == '\0') { - error = parse_multiline_variable(cfg, first, out); - goto out; - } - - /* Find the continuation character '\' and strip the whitespace */ - end = strrchr(first, '\\'); - while (isspace(end[-1])) - --end; - - *end = '\0'; /* Terminate the string here */ - - len = strlen(first) + strlen(line) + 2; - buf = git__malloc(len); - if (buf == NULL) { - error = GIT_ENOMEM; - goto out; - } - - ret = snprintf(buf, len, "%s %s", first, line); - if (ret < 0) { - error = GIT_EOSERR; - free(buf); - goto out; - } - - /* - * If we need to continue reading the next line, pretend - * everything we've read up to now was in one line and call - * ourselves. - */ - if (is_multiline_var(buf)) { - char *final_val; - error = parse_multiline_variable(cfg, buf, &final_val); - free(buf); - buf = final_val; - } - - *out = buf; - - out: - free(line); - return error; -} - -static int parse_variable(git_config *cfg, char **var_name, char **var_value) -{ - char *tmp; - int error = GIT_SUCCESS; - const char *var_end = NULL; - const char *value_start = NULL; - char *line; - - line = cfg_readline(cfg); - if (line == NULL) - return GIT_ENOMEM; - - strip_comments(line); - - var_end = strchr(line, '='); - - if (var_end == NULL) - var_end = strchr(line, '\0'); - else - value_start = var_end + 1; - - if (isspace(var_end[-1])) { - do var_end--; - while (isspace(var_end[0])); - } - - tmp = strndup(line, var_end - line + 1); - if (tmp == NULL) { - error = GIT_ENOMEM; - goto out; - } - - *var_name = tmp; - - /* - * Now, let's try to parse the value - */ - if (value_start != NULL) { - - while (isspace(value_start[0])) - value_start++; - - if (value_start[0] == '\0') - goto out; - - if (is_multiline_var(value_start)) { - error = parse_multiline_variable(cfg, value_start, var_value); - if (error < GIT_SUCCESS) - free(*var_name); - goto out; - } - - tmp = strdup(value_start); - if (tmp == NULL) { - free(*var_name); - error = GIT_ENOMEM; - goto out; - } - - *var_value = tmp; - } else { - /* If thre is no value, boolean true is assumed */ - *var_value = NULL; - } - - out: - free(line); - return error; -} diff --git a/src/config.h b/src/config.h index 82a66bfbf..e786e8a49 100644 --- a/src/config.h +++ b/src/config.h @@ -1,72 +1,14 @@ #ifndef INCLUDE_config_h__ #define INCLUDE_config_h__ +#include "git2.h" #include "git2/config.h" - -typedef struct { - git_cvar *head; - git_cvar *tail; -} git_cvar_list; +#include "vector.h" struct git_config { - git_cvar_list var_list; - - struct { - gitfo_buf buffer; - char *read_ptr; - int line_number; - int eof; - } reader; - - char *file_path; + git_vector backends; }; -struct git_cvar { - git_cvar *next; - char *section; - char *name; - char *value; -}; - -#define CVAR_LIST_HEAD(list) ((list)->head) - -#define CVAR_LIST_TAIL(list) ((list)->tail) - -#define CVAR_LIST_NEXT(var) ((var)->next) - -#define CVAR_LIST_EMPTY(list) ((list)->head == NULL) - -#define CVAR_LIST_APPEND(list, var) do {\ - if (CVAR_LIST_EMPTY(list)) {\ - CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\ - } else {\ - CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\ - CVAR_LIST_TAIL(list) = var;\ - }\ -} while(0) - -#define CVAR_LIST_REMOVE_HEAD(list) do {\ - CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\ -} while(0) - -#define CVAR_LIST_REMOVE_AFTER(var) do {\ - CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\ -} while(0) - -#define CVAR_LIST_FOREACH(list, iter)\ - for ((iter) = CVAR_LIST_HEAD(list);\ - (iter) != NULL;\ - (iter) = CVAR_LIST_NEXT(iter)) - -/* - * Inspired by the FreeBSD functions - */ -#define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\ - for ((iter) = CVAR_LIST_HEAD(vars);\ - (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\ - (iter) = (tmp)) - - void git__strtolower(char *str); void git__strntolower(char *str, int len); diff --git a/src/config_file.c b/src/config_file.c new file mode 100644 index 000000000..05a57cd2b --- /dev/null +++ b/src/config_file.c @@ -0,0 +1,1012 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "config.h" +#include "fileops.h" +#include "git2/config_backend.h" +#include "git2/types.h" + +#include + +struct git_cvar { + git_cvar *next; + char *section; + char *name; + char *value; +}; + +typedef struct { + git_cvar *head; + git_cvar *tail; +} git_cvar_list; + +#define CVAR_LIST_HEAD(list) ((list)->head) + +#define CVAR_LIST_TAIL(list) ((list)->tail) + +#define CVAR_LIST_NEXT(var) ((var)->next) + +#define CVAR_LIST_EMPTY(list) ((list)->head == NULL) + +#define CVAR_LIST_APPEND(list, var) do {\ + if (CVAR_LIST_EMPTY(list)) {\ + CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\ + } else {\ + CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\ + CVAR_LIST_TAIL(list) = var;\ + }\ +} while(0) + +#define CVAR_LIST_REMOVE_HEAD(list) do {\ + CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\ +} while(0) + +#define CVAR_LIST_REMOVE_AFTER(var) do {\ + CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\ +} while(0) + +#define CVAR_LIST_FOREACH(list, iter)\ + for ((iter) = CVAR_LIST_HEAD(list);\ + (iter) != NULL;\ + (iter) = CVAR_LIST_NEXT(iter)) + +/* + * Inspired by the FreeBSD functions + */ +#define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\ + for ((iter) = CVAR_LIST_HEAD(vars);\ + (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\ + (iter) = (tmp)) + + + +typedef struct { + git_config_backend parent; + + git_cvar_list var_list; + + struct { + gitfo_buf buffer; + char *read_ptr; + int line_number; + int eof; + } reader; + + char *file_path; +} file_backend; + +static int config_parse(file_backend *cfg_file); +static int parse_variable(file_backend *cfg, char **var_name, char **var_value); + +static void cvar_free(git_cvar *var) +{ + if (var == NULL) + return; + + free(var->section); + free(var->name); + free(var->value); + free(var); +} + +static void cvar_list_free(git_cvar_list *list) +{ + git_cvar *cur; + + while (!CVAR_LIST_EMPTY(list)) { + cur = CVAR_LIST_HEAD(list); + CVAR_LIST_REMOVE_HEAD(list); + cvar_free(cur); + } +} + +/* + * Compare two strings according to the git section-subsection + * rules. The order of the strings is important because local is + * assumed to have the internal format (only the section name and with + * case information) and input the normalized one (only dots, no case + * information). + */ +static int cvar_match_section(const char *local, const char *input) +{ + char *first_dot, *last_dot; + char *local_sp = strchr(local, ' '); + int comparison_len; + + /* + * If the local section name doesn't contain a space, then we can + * just do a case-insensitive compare. + */ + if (local_sp == NULL) + return !strncasecmp(local, input, strlen(local)); + + /* + * From here onwards, there is a space diving the section and the + * subsection. Anything before the space in local is + * case-insensitive. + */ + if (strncasecmp(local, input, local_sp - local)) + return 0; + + /* + * We compare starting from the first character after the + * quotation marks, which is two characters beyond the space. For + * the input, we start one character beyond the dot. If the names + * have different lengths, then we can fail early, as we know they + * can't be the same. + * The length is given by the length between the quotation marks. + */ + + first_dot = strchr(input, '.'); + last_dot = strrchr(input, '.'); + comparison_len = strlen(local_sp + 2) - 1; + + if (last_dot == first_dot || last_dot - first_dot - 1 != comparison_len) + return 0; + + return !strncmp(local_sp + 2, first_dot + 1, comparison_len); +} + +static int cvar_match_name(const git_cvar *var, const char *str) +{ + const char *name_start; + + if (!cvar_match_section(var->section, str)) { + return 0; + } + /* Early exit if the lengths are different */ + name_start = strrchr(str, '.') + 1; + if (strlen(var->name) != strlen(name_start)) + return 0; + + return !strcasecmp(var->name, name_start); +} + +static git_cvar *cvar_list_find(git_cvar_list *list, const char *name) +{ + git_cvar *iter; + + CVAR_LIST_FOREACH (list, iter) { + if (cvar_match_name(iter, name)) + return iter; + } + + return NULL; +} + +static int cvar_name_normalize(const char *input, char **output) +{ + char *input_sp = strchr(input, ' '); + char *quote, *str; + int i; + + /* We need to make a copy anyway */ + str = git__strdup(input); + if (str == NULL) + return GIT_ENOMEM; + + *output = str; + + /* If there aren't any spaces, we don't need to do anything */ + if (input_sp == NULL) + return GIT_SUCCESS; + + /* + * If there are spaces, we replace the space by a dot, move the + * variable name so that the dot before it replaces the last + * quotation mark and repeat so that the first quotation mark + * disappears. + */ + str[input_sp - input] = '.'; + + for (i = 0; i < 2; ++i) { + quote = strrchr(str, '"'); + memmove(quote, quote + 1, strlen(quote)); + } + + return GIT_SUCCESS; +} + +static int config_open(git_config_backend *cfg) +{ + int error; + file_backend *b = (file_backend *)cfg; + + error = gitfo_read_file(&b->reader.buffer, b->file_path); + if(error < GIT_SUCCESS) + goto cleanup; + + error = config_parse(b); + if (error < GIT_SUCCESS) + goto cleanup; + + gitfo_free_buf(&b->reader.buffer); + + return error; + + cleanup: + cvar_list_free(&b->var_list); + gitfo_free_buf(&b->reader.buffer); + free(cfg); + + return error; +} + +static void backend_free(git_config_backend *_backend) +{ + file_backend *backend = (file_backend *)_backend; + + if (backend == NULL) + return; + + free(backend->file_path); + cvar_list_free(&backend->var_list); + + free(backend); +} + +static int file_foreach(git_config_backend *backend, int (*fn)(const char *, void *), void *data) +{ + int ret; + git_cvar *var; + char *normalized; + file_backend *b = (file_backend *)backend; + + CVAR_LIST_FOREACH(&b->var_list, var) { + ret = cvar_name_normalize(var->name, &normalized); + if (ret < GIT_SUCCESS) + return ret; + + ret = fn(normalized, data); + free(normalized); + if (ret) + break; + } + + return ret; +} + +static int config_set(git_config_backend *cfg, const char *name, const char *value) +{ + git_cvar *var = NULL; + git_cvar *existing = NULL; + int error = GIT_SUCCESS; + const char *last_dot; + file_backend *b = (file_backend *)cfg; + + /* + * If it already exists, we just need to update its value. + */ + existing = cvar_list_find(&b->var_list, name); + if (existing != NULL) { + char *tmp = value ? git__strdup(value) : NULL; + if (tmp == NULL && value != NULL) + return GIT_ENOMEM; + + free(existing->value); + existing->value = tmp; + + return GIT_SUCCESS; + } + + /* + * Otherwise, create it and stick it at the end of the queue. + */ + + var = git__malloc(sizeof(git_cvar)); + if (var == NULL) + return GIT_ENOMEM; + + memset(var, 0x0, sizeof(git_cvar)); + + last_dot = strrchr(name, '.'); + if (last_dot == NULL) { + error = GIT_ERROR; + goto out; + } + + var->section = git__strndup(name, last_dot - name); + if (var->section == NULL) { + error = GIT_ENOMEM; + goto out; + } + + var->name = git__strdup(last_dot + 1); + if (var->name == NULL) { + error = GIT_ENOMEM; + goto out; + } + + var->value = value ? git__strdup(value) : NULL; + if (var->value == NULL && value != NULL) { + error = GIT_ENOMEM; + goto out; + } + + CVAR_LIST_APPEND(&b->var_list, var); + + out: + if (error < GIT_SUCCESS) + cvar_free(var); + + return error; +} + +/* + * Internal function that actually gets the value in string form + */ +static int config_get(git_config_backend *cfg, const char *name, const char **out) +{ + git_cvar *var; + int error = GIT_SUCCESS; + file_backend *b = (file_backend *)cfg; + + var = cvar_list_find(&b->var_list, name); + + if (var == NULL) + return GIT_ENOTFOUND; + + *out = var->value; + + return error; +} + +int git_config_backend_file(git_config_backend **out, const char *path) +{ + file_backend *backend; + + backend = git__malloc(sizeof(file_backend)); + if (backend == NULL) + return GIT_ENOMEM; + + memset(backend, 0x0, sizeof(file_backend)); + + backend->file_path = git__strdup(path); + if (backend->file_path == NULL) { + free(backend); + return GIT_ENOMEM; + } + + backend->parent.open = config_open; + backend->parent.get = config_get; + backend->parent.set = config_set; + backend->parent.foreach = file_foreach; + backend->parent.free = backend_free; + + *out = (git_config_backend *)backend; + + return GIT_SUCCESS; +} + +static int cfg_getchar_raw(file_backend *cfg) +{ + int c; + + c = *cfg->reader.read_ptr++; + + /* + Win 32 line breaks: if we find a \r\n sequence, + return only the \n as a newline + */ + if (c == '\r' && *cfg->reader.read_ptr == '\n') { + cfg->reader.read_ptr++; + c = '\n'; + } + + if (c == '\n') + cfg->reader.line_number++; + + if (c == 0) { + cfg->reader.eof = 1; + c = '\n'; + } + + return c; +} + +#define SKIP_WHITESPACE (1 << 1) +#define SKIP_COMMENTS (1 << 2) + +static int cfg_getchar(file_backend *cfg_file, int flags) +{ + const int skip_whitespace = (flags & SKIP_WHITESPACE); + const int skip_comments = (flags & SKIP_COMMENTS); + int c; + + assert(cfg_file->reader.read_ptr); + + do c = cfg_getchar_raw(cfg_file); + while (skip_whitespace && isspace(c)); + + if (skip_comments && (c == '#' || c == ';')) { + do c = cfg_getchar_raw(cfg_file); + while (c != '\n'); + } + + return c; +} + +/* + * Read the next char, but don't move the reading pointer. + */ +static int cfg_peek(file_backend *cfg, int flags) +{ + void *old_read_ptr; + int old_lineno, old_eof; + int ret; + + assert(cfg->reader.read_ptr); + + old_read_ptr = cfg->reader.read_ptr; + old_lineno = cfg->reader.line_number; + old_eof = cfg->reader.eof; + + ret = cfg_getchar(cfg, flags); + + cfg->reader.read_ptr = old_read_ptr; + cfg->reader.line_number = old_lineno; + cfg->reader.eof = old_eof; + + return ret; +} + +static const char *LINEBREAK_UNIX = "\\\n"; +static const char *LINEBREAK_WIN32 = "\\\r\n"; + +static int is_linebreak(const char *pos) +{ + return memcmp(pos - 1, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0 || + memcmp(pos - 2, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0; +} + +/* + * Read and consume a line, returning it in newly-allocated memory. + */ +static char *cfg_readline(file_backend *cfg) +{ + char *line = NULL; + char *line_src, *line_end; + int line_len; + + line_src = cfg->reader.read_ptr; + line_end = strchr(line_src, '\n'); + + /* no newline at EOF */ + if (line_end == NULL) + line_end = strchr(line_src, 0); + else + while (is_linebreak(line_end)) + line_end = strchr(line_end + 1, '\n'); + + + while (line_src < line_end && isspace(*line_src)) + line_src++; + + line = (char *)git__malloc((size_t)(line_end - line_src) + 1); + if (line == NULL) + return NULL; + + line_len = 0; + while (line_src < line_end) { + + if (memcmp(line_src, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0) { + line_src += sizeof(LINEBREAK_UNIX); + continue; + } + + if (memcmp(line_src, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0) { + line_src += sizeof(LINEBREAK_WIN32); + continue; + } + + line[line_len++] = *line_src++; + } + + line[line_len] = '\0'; + + while (--line_len >= 0 && isspace(line[line_len])) + line[line_len] = '\0'; + + if (*line_end == '\n') + line_end++; + + if (*line_end == '\0') + cfg->reader.eof = 1; + + cfg->reader.line_number++; + cfg->reader.read_ptr = line_end; + + return line; +} + +/* + * Consume a line, without storing it anywhere + */ +void cfg_consume_line(file_backend *cfg) +{ + char *line_start, *line_end; + + line_start = cfg->reader.read_ptr; + line_end = strchr(line_start, '\n'); + /* No newline at EOF */ + if(line_end == NULL){ + line_end = strchr(line_start, '\0'); + } + + if (*line_end == '\n') + line_end++; + + if (*line_end == '\0') + cfg->reader.eof = 1; + + cfg->reader.line_number++; + cfg->reader.read_ptr = line_end; +} + +static inline int config_keychar(int c) +{ + return isalnum(c) || c == '-'; +} + +static int parse_section_header_ext(const char *line, const char *base_name, char **section_name) +{ + int buf_len, total_len, pos, rpos; + int c, ret; + char *subsection, *first_quote, *last_quote; + int error = GIT_SUCCESS; + int quote_marks; + /* + * base_name is what came before the space. We should be at the + * first quotation mark, except for now, line isn't being kept in + * sync so we only really use it to calculate the length. + */ + + first_quote = strchr(line, '"'); + last_quote = strrchr(line, '"'); + + if (last_quote - first_quote == 0) + return GIT_EOBJCORRUPTED; + + buf_len = last_quote - first_quote + 2; + + subsection = git__malloc(buf_len + 2); + if (subsection == NULL) + return GIT_ENOMEM; + + pos = 0; + rpos = 0; + quote_marks = 0; + + line = first_quote; + c = line[rpos++]; + + /* + * At the end of each iteration, whatever is stored in c will be + * added to the string. In case of error, jump to out + */ + do { + switch (c) { + case '"': + if (quote_marks++ >= 2) + return GIT_EOBJCORRUPTED; + break; + case '\\': + c = line[rpos++]; + switch (c) { + case '"': + case '\\': + break; + default: + error = GIT_EOBJCORRUPTED; + goto out; + } + default: + break; + } + + subsection[pos++] = c; + } while ((c = line[rpos++]) != ']'); + + subsection[pos] = '\0'; + + total_len = strlen(base_name) + strlen(subsection) + 2; + *section_name = git__malloc(total_len); + if (*section_name == NULL) { + error = GIT_ENOMEM; + goto out; + } + + ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection); + if (ret >= total_len) { + /* If this fails, we've checked the length wrong */ + error = GIT_ERROR; + goto out; + } else if (ret < 0) { + error = GIT_EOSERR; + goto out; + } + + git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name); + + out: + free(subsection); + + return error; +} + +static int parse_section_header(file_backend *cfg, char **section_out) +{ + char *name, *name_end; + int name_length, c, pos; + int error = GIT_SUCCESS; + char *line; + + line = cfg_readline(cfg); + if (line == NULL) + return GIT_ENOMEM; + + /* find the end of the variable's name */ + name_end = strchr(line, ']'); + if (name_end == NULL) + return GIT_EOBJCORRUPTED; + + name = (char *)git__malloc((size_t)(name_end - line) + 1); + if (name == NULL) + return GIT_EOBJCORRUPTED; + + name_length = 0; + pos = 0; + + /* Make sure we were given a section header */ + c = line[pos++]; + if (c != '[') { + error = GIT_EOBJCORRUPTED; + goto error; + } + + c = line[pos++]; + + do { + if (cfg->reader.eof){ + error = GIT_EOBJCORRUPTED; + goto error; + } + + if (isspace(c)){ + name[name_length] = '\0'; + error = parse_section_header_ext(line, name, section_out); + free(line); + free(name); + return error; + } + + if (!config_keychar(c) && c != '.') { + error = GIT_EOBJCORRUPTED; + goto error; + } + + name[name_length++] = tolower(c); + + } while ((c = line[pos++]) != ']'); + + name[name_length] = 0; + free(line); + git__strtolower(name); + *section_out = name; + return GIT_SUCCESS; + +error: + free(line); + free(name); + return error; +} + +static int skip_bom(file_backend *cfg) +{ + static const unsigned char *utf8_bom = "\xef\xbb\xbf"; + + if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) + cfg->reader.read_ptr += sizeof(utf8_bom); + + /* TODO: the reference implementation does pretty stupid + shit with the BoM + */ + + return GIT_SUCCESS; +} + +/* + (* basic types *) + digit = "0".."9" + integer = digit { digit } + alphabet = "a".."z" + "A" .. "Z" + + section_char = alphabet | "." | "-" + extension_char = (* any character except newline *) + any_char = (* any character *) + variable_char = "alphabet" | "-" + + + (* actual grammar *) + config = { section } + + section = header { definition } + + header = "[" section [subsection | subsection_ext] "]" + + subsection = "." section + subsection_ext = "\"" extension "\"" + + section = section_char { section_char } + extension = extension_char { extension_char } + + definition = variable_name ["=" variable_value] "\n" + + variable_name = variable_char { variable_char } + variable_value = string | boolean | integer + + string = quoted_string | plain_string + quoted_string = "\"" plain_string "\"" + plain_string = { any_char } + + boolean = boolean_true | boolean_false + boolean_true = "yes" | "1" | "true" | "on" + boolean_false = "no" | "0" | "false" | "off" +*/ + +static void strip_comments(char *line) +{ + int quote_count = 0; + char *ptr; + + for (ptr = line; *ptr; ++ptr) { + if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\') + quote_count++; + + if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) { + ptr[0] = '\0'; + break; + } + } + + if (isspace(ptr[-1])) { + /* TODO skip whitespace */ + } +} + +static int config_parse(file_backend *cfg_file) +{ + int error = GIT_SUCCESS, c; + char *current_section = NULL; + char *var_name; + char *var_value; + git_cvar *var; + + /* Initialise the reading position */ + cfg_file->reader.read_ptr = cfg_file->reader.buffer.data; + cfg_file->reader.eof = 0; + + skip_bom(cfg_file); + + while (error == GIT_SUCCESS && !cfg_file->reader.eof) { + + c = cfg_peek(cfg_file, SKIP_WHITESPACE); + + switch (c) { + case '\0': /* We've arrived at the end of the file */ + break; + + case '[': /* section header, new section begins */ + free(current_section); + error = parse_section_header(cfg_file, ¤t_section); + break; + + case ';': + case '#': + cfg_consume_line(cfg_file); + break; + + default: /* assume variable declaration */ + error = parse_variable(cfg_file, &var_name, &var_value); + + if (error < GIT_SUCCESS) + break; + + var = malloc(sizeof(git_cvar)); + if (var == NULL) { + error = GIT_ENOMEM; + break; + } + + memset(var, 0x0, sizeof(git_cvar)); + + var->section = git__strdup(current_section); + if (var->section == NULL) { + error = GIT_ENOMEM; + free(var); + break; + } + + var->name = var_name; + var->value = var_value; + git__strtolower(var->name); + + CVAR_LIST_APPEND(&cfg_file->var_list, var); + + break; + } + } + + if (current_section) + free(current_section); + + return error; +} + +static int is_multiline_var(const char *str) +{ + char *end = strrchr(str, '\0') - 1; + + while (isspace(*end)) + --end; + + return *end == '\\'; +} + +static int parse_multiline_variable(file_backend *cfg, const char *first, char **out) +{ + char *line = NULL, *end; + int error = GIT_SUCCESS, len, ret; + char *buf; + + /* Check that the next line exists */ + line = cfg_readline(cfg); + if (line == NULL) + return GIT_ENOMEM; + + /* We've reached the end of the file, there is input missing */ + if (line[0] == '\0') { + error = GIT_EOBJCORRUPTED; + goto out; + } + + strip_comments(line); + + /* If it was just a comment, pretend it didn't exist */ + if (line[0] == '\0') { + error = parse_multiline_variable(cfg, first, out); + goto out; + } + + /* Find the continuation character '\' and strip the whitespace */ + end = strrchr(first, '\\'); + while (isspace(end[-1])) + --end; + + *end = '\0'; /* Terminate the string here */ + + len = strlen(first) + strlen(line) + 2; + buf = git__malloc(len); + if (buf == NULL) { + error = GIT_ENOMEM; + goto out; + } + + ret = snprintf(buf, len, "%s %s", first, line); + if (ret < 0) { + error = GIT_EOSERR; + free(buf); + goto out; + } + + /* + * If we need to continue reading the next line, pretend + * everything we've read up to now was in one line and call + * ourselves. + */ + if (is_multiline_var(buf)) { + char *final_val; + error = parse_multiline_variable(cfg, buf, &final_val); + free(buf); + buf = final_val; + } + + *out = buf; + + out: + free(line); + return error; +} + +static int parse_variable(file_backend *cfg, char **var_name, char **var_value) +{ + char *tmp; + int error = GIT_SUCCESS; + const char *var_end = NULL; + const char *value_start = NULL; + char *line; + + line = cfg_readline(cfg); + if (line == NULL) + return GIT_ENOMEM; + + strip_comments(line); + + var_end = strchr(line, '='); + + if (var_end == NULL) + var_end = strchr(line, '\0'); + else + value_start = var_end + 1; + + if (isspace(var_end[-1])) { + do var_end--; + while (isspace(var_end[0])); + } + + tmp = strndup(line, var_end - line + 1); + if (tmp == NULL) { + error = GIT_ENOMEM; + goto out; + } + + *var_name = tmp; + + /* + * Now, let's try to parse the value + */ + if (value_start != NULL) { + + while (isspace(value_start[0])) + value_start++; + + if (value_start[0] == '\0') + goto out; + + if (is_multiline_var(value_start)) { + error = parse_multiline_variable(cfg, value_start, var_value); + if (error < GIT_SUCCESS) + free(*var_name); + goto out; + } + + tmp = strdup(value_start); + if (tmp == NULL) { + free(*var_name); + error = GIT_ENOMEM; + goto out; + } + + *var_value = tmp; + } else { + /* If thre is no value, boolean true is assumed */ + *var_value = NULL; + } + + out: + free(line); + return error; +} diff --git a/tests/t15-config.c b/tests/t15-config.c index 8e0d3215c..1b971bd74 100644 --- a/tests/t15-config.c +++ b/tests/t15-config.c @@ -36,7 +36,7 @@ BEGIN_TEST(config0, "read a simple configuration") git_config *cfg; int i; - must_pass(git_config_open(&cfg, CONFIG_BASE "/config0")); + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config0")); must_pass(git_config_get_int(cfg, "core.repositoryformatversion", &i)); must_be_true(i == 0); must_pass(git_config_get_bool(cfg, "core.filemode", &i)); @@ -58,7 +58,7 @@ BEGIN_TEST(config1, "case sensitivity") int i; const char *str; - must_pass(git_config_open(&cfg, CONFIG_BASE "/config1")); + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config1")); must_pass(git_config_get_string(cfg, "this.that.other", &str)); must_be_true(!strcmp(str, "true")); @@ -84,7 +84,7 @@ BEGIN_TEST(config2, "parse a multiline value") git_config *cfg; const char *str; - must_pass(git_config_open(&cfg, CONFIG_BASE "/config2")); + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config2")); must_pass(git_config_get_string(cfg, "this.That.and", &str)); must_be_true(!strcmp(str, "one one one two two three three")); @@ -99,7 +99,7 @@ BEGIN_TEST(config3, "parse a [section.subsection] header") git_config *cfg; const char *str; - must_pass(git_config_open(&cfg, CONFIG_BASE "/config3")); + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config3")); must_pass(git_config_get_string(cfg, "section.subsection.var", &str)); must_be_true(!strcmp(str, "hello")); @@ -117,7 +117,7 @@ BEGIN_TEST(config4, "a variable name on its own is valid") const char *str; int i; - must_pass(git_config_open(&cfg, CONFIG_BASE "/config4")); + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config4")); must_pass(git_config_get_string(cfg, "some.section.variable", &str)); must_be_true(str == NULL); @@ -134,7 +134,7 @@ BEGIN_TEST(config5, "test number suffixes") const char *str; long int i; - must_pass(git_config_open(&cfg, CONFIG_BASE "/config5")); + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config5")); must_pass(git_config_get_long(cfg, "number.simple", &i)); must_be_true(i == 1); From 29dca0883faaaa9831c9d4fd3baf6f9afcc2d2e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 17 May 2011 13:38:19 +0200 Subject: [PATCH 57/57] Move config to the new error methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Take this opportunity to fix an instance of returning GIT_EOBJCORRUPTED when malloc failed. Signed-off-by: Carlos Martín Nieto --- src/config.c | 4 +++- src/config_file.c | 37 ++++++++++++++++++------------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/config.c b/src/config.c index d839e3892..234c5303f 100644 --- a/src/config.c +++ b/src/config.c @@ -65,7 +65,9 @@ int git_config_open_bare(git_config **out, const char *path) if (error < GIT_SUCCESS) goto error; - git_config_add_backend(cfg, backend, 1); + error = git_config_add_backend(cfg, backend, 1); + if (error < GIT_SUCCESS) + goto error; error = backend->open(backend); if (error < GIT_SUCCESS) diff --git a/src/config_file.c b/src/config_file.c index 05a57cd2b..d66dd20e3 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -316,18 +316,17 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val * Otherwise, create it and stick it at the end of the queue. */ + last_dot = strrchr(name, '.'); + if (last_dot == NULL) { + return git__throw(GIT_EINVALIDTYPE, "Variables without section aren't allowed"); + } + var = git__malloc(sizeof(git_cvar)); if (var == NULL) return GIT_ENOMEM; memset(var, 0x0, sizeof(git_cvar)); - last_dot = strrchr(name, '.'); - if (last_dot == NULL) { - error = GIT_ERROR; - goto out; - } - var->section = git__strndup(name, last_dot - name); if (var->section == NULL) { error = GIT_ENOMEM; @@ -367,7 +366,7 @@ static int config_get(git_config_backend *cfg, const char *name, const char **ou var = cvar_list_find(&b->var_list, name); if (var == NULL) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name); *out = var->value; @@ -588,7 +587,7 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha last_quote = strrchr(line, '"'); if (last_quote - first_quote == 0) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. There is no final quotation mark"); buf_len = last_quote - first_quote + 2; @@ -611,7 +610,7 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha switch (c) { case '"': if (quote_marks++ >= 2) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. Too many quotes"); break; case '\\': c = line[rpos++]; @@ -620,7 +619,7 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha case '\\': break; default: - error = GIT_EOBJCORRUPTED; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. Unsupported escape char \\%c", c); goto out; } default: @@ -642,10 +641,10 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection); if (ret >= total_len) { /* If this fails, we've checked the length wrong */ - error = GIT_ERROR; + error = git__thow(GIT_ERROR, "Failed to parse ext header. Wrong total lenght calculation"); goto out; } else if (ret < 0) { - error = GIT_EOSERR; + error = git__throw(GIT_EOSERR, "Failed to parse ext header. OS error: %s", strerror(errno)); goto out; } @@ -671,11 +670,11 @@ static int parse_section_header(file_backend *cfg, char **section_out) /* find the end of the variable's name */ name_end = strchr(line, ']'); if (name_end == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Can't find header name end"); name = (char *)git__malloc((size_t)(name_end - line) + 1); if (name == NULL) - return GIT_EOBJCORRUPTED; + return GIT_ENOMEM; name_length = 0; pos = 0; @@ -683,7 +682,7 @@ static int parse_section_header(file_backend *cfg, char **section_out) /* Make sure we were given a section header */ c = line[pos++]; if (c != '[') { - error = GIT_EOBJCORRUPTED; + error = git__throw(GIT_ERROR, "Failed to parse header. Didn't get section header. This is a bug"); goto error; } @@ -691,7 +690,7 @@ static int parse_section_header(file_backend *cfg, char **section_out) do { if (cfg->reader.eof){ - error = GIT_EOBJCORRUPTED; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Config file ended unexpectedly"); goto error; } @@ -704,7 +703,7 @@ static int parse_section_header(file_backend *cfg, char **section_out) } if (!config_keychar(c) && c != '.') { - error = GIT_EOBJCORRUPTED; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Wrong format on header"); goto error; } @@ -889,7 +888,7 @@ static int parse_multiline_variable(file_backend *cfg, const char *first, char * /* We've reached the end of the file, there is input missing */ if (line[0] == '\0') { - error = GIT_EOBJCORRUPTED; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse multiline var. File ended unexpectedly"); goto out; } @@ -917,7 +916,7 @@ static int parse_multiline_variable(file_backend *cfg, const char *first, char * ret = snprintf(buf, len, "%s %s", first, line); if (ret < 0) { - error = GIT_EOSERR; + error = git__throw(GIT_EOSERR, "Failed to parse multiline var. Failed to put together two lines. OS err: %s", strerror(errno)); free(buf); goto out; }