From d6b5f5cca9904763b117efb0d0d4bad7f2412b7b Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sat, 5 Mar 2011 23:54:49 +0200 Subject: [PATCH 001/120] 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 002/120] 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 003/120] 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 004/120] 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 005/120] 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 006/120] 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 007/120] 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 008/120] 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 009/120] 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 010/120] 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 011/120] 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 012/120] 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 013/120] 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 014/120] 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 015/120] 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 016/120] 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 017/120] 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 018/120] 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 019/120] 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 020/120] 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 021/120] 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 022/120] 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 023/120] 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 024/120] 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 025/120] 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 026/120] 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 027/120] 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 028/120] 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 029/120] 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 030/120] 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 031/120] 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 032/120] 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 033/120] 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 034/120] 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 035/120] 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 7c7fcdae0e112f56188f13971acae26470287dd3 Mon Sep 17 00:00:00 2001 From: Jakob Pfender Date: Mon, 4 Apr 2011 16:20:09 +0200 Subject: [PATCH 036/120] index.h: Add IDXENTRY flags needed for index operations Add several IDXENTRY flags that need to be checked in order to properly implement update-index --refresh. --- include/git2/index.h | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/include/git2/index.h b/include/git2/index.h index 599512f8a..8a84f507b 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -44,6 +44,46 @@ GIT_BEGIN_DECL #define GIT_IDXENTRY_VALID (0x8000) #define GIT_IDXENTRY_STAGESHIFT 12 +/* + * Flags are divided into two parts: in-memory flags and + * on-disk ones. Flags in GIT_IDXENTRY_EXTENDED_FLAGS + * will get saved on-disk. + * + * In-memory only flags: + */ +#define GIT_IDXENTRY_UPDATE (1 << 16) +#define GIT_IDXENTRY_REMOVE (1 << 17) +#define GIT_IDXENTRY_UPTODATE (1 << 18) +#define GIT_IDXENTRY_ADDED (1 << 19) + +#define GIT_IDXENTRY_HASHED (1 << 20) +#define GIT_IDXENTRY_UNHASHED (1 << 21) +#define GIT_IDXENTRY_WT_REMOVE (1 << 22) /* remove in work directory */ +#define GIT_IDXENTRY_CONFLICTED (1 << 23) + +#define GIT_IDXENTRY_UNPACKED (1 << 24) +#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 25) + +/* + * Extended on-disk flags: + */ +#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 29) +#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 30) +/* GIT_IDXENTRY_EXTENDED2 is for future extension */ +#define GIT_IDXENTRY_EXTENDED2 (1 << 31) + +#define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE) + +/* + * Safeguard to avoid saving wrong flags: + * - GIT_IDXENTRY_EXTENDED2 won't get saved until its semantic is known + * - Bits in 0x0000FFFF have been saved in flags already + * - Bits in 0x003F0000 are currently in-memory flags + */ +#if GIT_IDXENTRY_EXTENDED_FLAGS & 0x803FFFFF +#error "GIT_IDXENTRY_EXTENDED_FLAGS out of range" +#endif + /** Time used in a git index entry */ typedef struct { git_time_t seconds; 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 037/120] 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 038/120] 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 039/120] 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 511b2370d942ec9833540c5b11dacc2531844e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Apr 2011 17:24:37 +0200 Subject: [PATCH 040/120] tests: update NAMING file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- tests/NAMING | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/NAMING b/tests/NAMING index ab425e23e..e5a6e9956 100644 --- a/tests/NAMING +++ b/tests/NAMING @@ -23,14 +23,22 @@ Categories 04__: Parsing and loading commit data -05__: To be described +05__: Revision walking -06__: To be described +06__: Index reading, writing and searching -07__: To be described +07__: Tests for the internal hashtable code -08__: To be described +08__: Tag reading and writing -09__: To be described +09__: Reading tree objects -10__: Symbolic, loose and packed references reading and writing. \ No newline at end of file +10__: Symbolic, loose and packed references reading and writing. + +11__: SQLite backend + +12__: Repository init and opening + +13__: Threads, empty as of now + +14__: Configuration parsing 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 041/120] 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 042/120] 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 043/120] 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 044/120] 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 045/120] 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 a5a546b36c184890dee09bd2bd92836c60eec122 Mon Sep 17 00:00:00 2001 From: Jakob Pfender Date: Thu, 7 Apr 2011 16:53:50 +0200 Subject: [PATCH 046/120] index.h: Correct values for extended flags As libgit2 separates an index entry's 32-bit flag into two 16-bit values flags and flags_extended, the values of flags_extended need to be adjusted. --- include/git2/index.h | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/include/git2/index.h b/include/git2/index.h index 8a84f507b..d69e58b1c 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -51,39 +51,29 @@ GIT_BEGIN_DECL * * In-memory only flags: */ -#define GIT_IDXENTRY_UPDATE (1 << 16) -#define GIT_IDXENTRY_REMOVE (1 << 17) -#define GIT_IDXENTRY_UPTODATE (1 << 18) -#define GIT_IDXENTRY_ADDED (1 << 19) +#define GIT_IDXENTRY_UPDATE (1 << 0) +#define GIT_IDXENTRY_REMOVE (1 << 1) +#define GIT_IDXENTRY_UPTODATE (1 << 2) +#define GIT_IDXENTRY_ADDED (1 << 3) -#define GIT_IDXENTRY_HASHED (1 << 20) -#define GIT_IDXENTRY_UNHASHED (1 << 21) -#define GIT_IDXENTRY_WT_REMOVE (1 << 22) /* remove in work directory */ -#define GIT_IDXENTRY_CONFLICTED (1 << 23) +#define GIT_IDXENTRY_HASHED (1 << 4) +#define GIT_IDXENTRY_UNHASHED (1 << 5) +#define GIT_IDXENTRY_WT_REMOVE (1 << 6) /* remove in work directory */ +#define GIT_IDXENTRY_CONFLICTED (1 << 7) -#define GIT_IDXENTRY_UNPACKED (1 << 24) -#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 25) +#define GIT_IDXENTRY_UNPACKED (1 << 8) +#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 9) /* * Extended on-disk flags: */ -#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 29) -#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 30) +#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13) +#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14) /* GIT_IDXENTRY_EXTENDED2 is for future extension */ -#define GIT_IDXENTRY_EXTENDED2 (1 << 31) +#define GIT_IDXENTRY_EXTENDED2 (1 << 15) #define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE) -/* - * Safeguard to avoid saving wrong flags: - * - GIT_IDXENTRY_EXTENDED2 won't get saved until its semantic is known - * - Bits in 0x0000FFFF have been saved in flags already - * - Bits in 0x003F0000 are currently in-memory flags - */ -#if GIT_IDXENTRY_EXTENDED_FLAGS & 0x803FFFFF -#error "GIT_IDXENTRY_EXTENDED_FLAGS out of range" -#endif - /** Time used in a git index entry */ typedef struct { git_time_t seconds; From fd279b262df31c9a5d88db78e9e9a6de07e62979 Mon Sep 17 00:00:00 2001 From: Jakob Pfender Date: Thu, 7 Apr 2011 16:58:42 +0200 Subject: [PATCH 047/120] index.c: Correctly check whether index contains extended entries Although write_index() supports writing extended header versions for index, this was never done as there was no check for extended index entries. Introduce function is_index_extended() that checks whether an index contains extended entries and check whether an index is extended before writing it to disk, adjusting its version number if necessary. --- src/index.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/index.c b/src/index.c index 6a31dd5cb..6a67e9c7c 100644 --- a/src/index.c +++ b/src/index.c @@ -101,6 +101,7 @@ static int read_tree(git_index *index, const char *buffer, size_t buffer_size); static git_index_tree *read_tree_internal(const char **, const char *, git_index_tree *); static int parse_index(git_index *index, const char *buffer, size_t buffer_size); +static int is_index_extended(git_index *index); static void sort_index(git_index *index); static int write_index(git_index *index, git_filebuf *file); @@ -674,6 +675,24 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) return GIT_SUCCESS; } +static int is_index_extended(git_index *index) +{ + unsigned int i, extended; + + extended = 0; + + for (i = 0; i < index->entries.length; ++i) { + git_index_entry *entry; + entry = git_vector_get(&index->entries, i); + entry->flags &= ~GIT_IDXENTRY_EXTENDED; + if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) { + extended++; + entry->flags |= GIT_IDXENTRY_EXTENDED; + } + } + return extended; +} + static int write_disk_entry(git_filebuf *file, git_index_entry *entry) { struct entry_short *ondisk; @@ -742,12 +761,14 @@ static int write_index(git_index *index, git_filebuf *file) struct index_header header; - int is_extended = 1; + int is_extended; assert(index && file); + is_extended = is_index_extended(index); + header.signature = htonl(INDEX_HEADER_SIG); - header.version = htonl(is_extended ? INDEX_VERSION_NUMBER : INDEX_VERSION_NUMBER_EXT); + header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER); header.entry_count = htonl(index->entries.length); git_filebuf_write(file, &header, sizeof(struct index_header)); 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 048/120] 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 049/120] 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 050/120] 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 051/120] 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 052/120] 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 053/120] 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 054/120] 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: Wed, 13 Apr 2011 17:44:08 +0200 Subject: [PATCH 055/120] tests: don't leak objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we don't create any leaks in the tests, we can use them to search for leaks in the implementation. Signed-off-by: Carlos Martín Nieto --- tests/t04-commit.c | 11 ++++++++++- tests/t09-tree.c | 8 ++++++++ tests/t10-refs.c | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/t04-commit.c b/tests/t04-commit.c index bcc0417c8..3e02df996 100644 --- a/tests/t04-commit.c +++ b/tests/t04-commit.c @@ -368,7 +368,7 @@ BEGIN_TEST(details0, "query the details on a parsed commit") const char *message, *message_short; git_time_t commit_time; unsigned int parents, p; - git_commit *parent; + git_commit *parent = NULL, *old_parent = NULL; git_oid_mkstr(&id, commit_ids[i]); @@ -390,11 +390,19 @@ BEGIN_TEST(details0, "query the details on a parsed commit") must_be_true(commit_time > 0); must_be_true(parents <= 2); for (p = 0;p < parents;p++) { + if (old_parent != NULL) + git_commit_close(old_parent); + + old_parent = parent; must_pass(git_commit_parent(&parent, commit, p)); must_be_true(parent != NULL); must_be_true(git_commit_author(parent) != NULL); // is it really a commit? } + git_commit_close(old_parent); + git_commit_close(parent); + must_fail(git_commit_parent(&parent, commit, parents)); + git_commit_close(commit); } git_repository_free(repo); @@ -462,6 +470,7 @@ BEGIN_TEST(write0, "write a new commit object from memory to disk") must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)commit)); + git_commit_close(commit); git_repository_free(repo); END_TEST diff --git a/tests/t09-tree.c b/tests/t09-tree.c index bd88642fa..9b39dfd07 100644 --- a/tests/t09-tree.c +++ b/tests/t09-tree.c @@ -82,6 +82,7 @@ BEGIN_TEST(read0, "acces randomly the entries on a loaded tree") must_be_true(git_tree_entry_byindex(tree, 3) == NULL); must_be_true(git_tree_entry_byindex(tree, -1) == NULL); + git_tree_close(tree); git_repository_free(repo); END_TEST @@ -102,7 +103,9 @@ BEGIN_TEST(read1, "read a tree from the repository") /* GH-86: git_object_lookup() should also check the type if the object comes from the cache */ must_be_true(git_object_lookup(&obj, repo, &id, GIT_OBJ_TREE) == 0); + git_object_close(obj); must_be_true(git_object_lookup(&obj, repo, &id, GIT_OBJ_BLOB) == GIT_EINVALIDTYPE); + git_object_close(obj); entry = git_tree_entry_byname(tree, "README"); must_be_true(entry != NULL); @@ -111,6 +114,8 @@ BEGIN_TEST(read1, "read a tree from the repository") must_pass(git_tree_entry_2object(&obj, repo, entry)); + git_object_close(obj); + git_tree_close(tree); git_repository_free(repo); END_TEST @@ -148,6 +153,9 @@ BEGIN_TEST(write2, "write a tree from a memory") must_pass(git_treebuilder_write(&rid,repo,builder)); must_be_true(git_oid_cmp(&rid, &id2) == 0); + + git_treebuilder_free(builder); + git_tree_close(tree); close_temp_repo(repo); END_TEST diff --git a/tests/t10-refs.c b/tests/t10-refs.c index a6a560193..d3f5620c9 100644 --- a/tests/t10-refs.c +++ b/tests/t10-refs.c @@ -51,6 +51,7 @@ BEGIN_TEST(readtag0, "lookup a loose tag reference") git__joinpath(ref_name_from_tag_name, GIT_REFS_TAGS_DIR, git_tag_name((git_tag *)object)); must_be_true(strcmp(ref_name_from_tag_name, loose_tag_ref_name) == 0); + git_object_close(object); git_repository_free(repo); END_TEST @@ -91,6 +92,7 @@ BEGIN_TEST(readsym0, "lookup a symbolic reference") git_oid_mkstr(&id, current_master_tip); must_be_true(git_oid_cmp(&id, git_object_id(object)) == 0); + git_object_close(object); git_repository_free(repo); END_TEST @@ -117,6 +119,7 @@ BEGIN_TEST(readsym1, "lookup a nested symbolic reference") git_oid_mkstr(&id, current_master_tip); must_be_true(git_oid_cmp(&id, git_object_id(object)) == 0); + git_object_close(object); git_repository_free(repo); END_TEST @@ -175,6 +178,7 @@ BEGIN_TEST(readpacked0, "lookup a packed reference") must_be_true(object != NULL); must_be_true(git_object_type(object) == GIT_OBJ_COMMIT); + git_object_close(object); git_repository_free(repo); END_TEST From 7c1c15a7fb86ff1422e1adcfceb9dbb7555a8449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 13 Apr 2011 21:55:43 +0200 Subject: [PATCH 056/120] tests: free the test suite name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- tests/test_lib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_lib.c b/tests/test_lib.c index c9c6141c6..5778404c1 100755 --- a/tests/test_lib.c +++ b/tests/test_lib.c @@ -130,6 +130,7 @@ static void free_suite(git_testsuite *ts) if (ts->list[n]) test_free(ts->list[n]); + free(ts->name); free(ts); } From 1bfa053e8cc472cf04329c38614795719b04fd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 13 Apr 2011 21:57:35 +0200 Subject: [PATCH 057/120] Close an object in packed_find_peel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/refs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/refs.c b/src/refs.c index 00b9ff6b2..ce8d97256 100644 --- a/src/refs.c +++ b/src/refs.c @@ -787,6 +787,8 @@ static int packed_find_peel(reference_oid *ref) */ } + git_object_close(object); + return GIT_SUCCESS; } From a99264bff6bc4a16c335a09dab1b6c6c9a763a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 19 Apr 2011 16:34:22 +0200 Subject: [PATCH 058/120] 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 059/120] 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 060/120] 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 34a29926651160568e95c864514b7fc7ccf0546c Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Wed, 20 Apr 2011 22:25:30 -0700 Subject: [PATCH 061/120] fix solaris build Before this patch the build failure looked like: ... ../../deps/zlib/inftrees.c:330: warning: visibility attribute not supported in this configuration; ignored ... ld: fatal: relocation error: R_386_GOTOFF: file deps/zlib/deflate.c.0.o: symbol zcfree: a GOT relative relocation must reference a local symbol ... --- wscript | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wscript b/wscript index d97538ab0..f4f8da989 100644 --- a/wscript +++ b/wscript @@ -65,6 +65,9 @@ def configure(conf): else: conf.env.PLATFORM = 'unix' + if conf.env.DEST_OS == 'sunos': + conf.env.DEFINES += ['NO_VIZ'] + if conf.options.threadsafe: if conf.env.PLATFORM == 'unix': conf.check_cc(lib='pthread', uselib_store='pthread') From df30eac1eba7b11a04d35e4e5a3f31c9ae58aa0a Mon Sep 17 00:00:00 2001 From: Jakob Pfender Date: Thu, 21 Apr 2011 10:38:37 +0200 Subject: [PATCH 062/120] refs: Allow MERGE_HEAD in normalize_name() Do not return with EINVALIDREFNAME when trying to normalize MERGE_HEAD's name. --- src/refs.c | 5 +++-- src/refs.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/refs.c b/src/refs.c index 00b9ff6b2..17ec2915f 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1692,8 +1692,9 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref) } /* Object id refname have to contain at least one slash, except - * for HEAD in a detached state */ - if (is_oid_ref && !contains_a_slash && strcmp(name, GIT_HEAD_FILE)) + * for HEAD in a detached state or MERGE_HEAD if we're in the + * middle of a merge */ + if (is_oid_ref && !contains_a_slash && (strcmp(name, GIT_HEAD_FILE) && strcmp(name, GIT_MERGE_HEAD_FILE))) return GIT_EINVALIDREFNAME; /* A refname can not end with ".lock" */ diff --git a/src/refs.h b/src/refs.h index bebb1b97d..b8f3e2f6d 100644 --- a/src/refs.h +++ b/src/refs.h @@ -17,6 +17,7 @@ #define MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH 100 #define GIT_HEAD_FILE "HEAD" +#define GIT_MERGE_HEAD_FILE "MERGE_HEAD" #define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master" struct git_reference { From 729b6f490068642f35ca933ef44830fda11b6aef Mon Sep 17 00:00:00 2001 From: Jakob Pfender Date: Thu, 21 Apr 2011 10:40:54 +0200 Subject: [PATCH 063/120] index: Allow user to toggle whether to replace an index entry When in the middle of a merge, the index needs to contain several files with the same name. git_index_insert() used to prevent this by not adding a new entry if an entry with the same name already existed. --- include/git2/index.h | 9 +++++---- src/index.c | 12 +++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/include/git2/index.h b/include/git2/index.h index 8a84f507b..adc4cb435 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -203,15 +203,16 @@ GIT_EXTERN(int) git_index_remove(git_index *index, int position); /** * Insert an entry into the index. * A full copy (including the 'path' string) of the given - * 'source_entry' will be inserted on the index; if the index - * already contains an entry for the same path, the entry - * will be updated. + * 'source_entry' will be inserted on the index; if the + * replace flag is not set and the index already contains + * an entry for the same path, the entry will be updated. * * @param index an existing index object * @param source_entry new entry object + * @param replace if set, existing entries will be replaced * @return 0 on success, otherwise an error code */ -GIT_EXTERN(int) git_index_insert(git_index *index, const git_index_entry *source_entry); +GIT_EXTERN(int) git_index_insert(git_index *index, const git_index_entry *source_entry, int replace); /** * Get a pointer to one of the entries in the index diff --git a/src/index.c b/src/index.c index 68bb9e2b9..c066d24d7 100644 --- a/src/index.c +++ b/src/index.c @@ -330,7 +330,7 @@ int git_index_add(git_index *index, const char *rel_path, int stage) entry.flags |= (stage << GIT_IDXENTRY_STAGESHIFT); entry.path = (char *)rel_path; /* do not duplicate; index_insert already does this */ - return git_index_insert(index, &entry); + return git_index_insert(index, &entry, 1); } void sort_index(git_index *index) @@ -338,7 +338,7 @@ void sort_index(git_index *index) git_vector_sort(&index->entries); } -int git_index_insert(git_index *index, const git_index_entry *source_entry) +int git_index_insert(git_index *index, const git_index_entry *source_entry, int replace) { git_index_entry *entry; size_t path_length; @@ -374,13 +374,15 @@ int git_index_insert(git_index *index, const git_index_entry *source_entry) /* look if an entry with this path already exists */ position = git_index_find(index, source_entry->path); - /* if no entry exists, add the entry at the end; + /* if no entry exists and replace is not set, + * add the entry at the end; * the index is no longer sorted */ - if (position == GIT_ENOTFOUND) { + if (!replace || position == GIT_ENOTFOUND) { if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) return GIT_ENOMEM; - /* if a previous entry exists, replace it */ + /* if a previous entry exists and replace is set, + * replace it */ } else { git_index_entry **entry_array = (git_index_entry **)index->entries.contents; From 5adb48e4a8d15cbbf0647f6982023d3a24b8c380 Mon Sep 17 00:00:00 2001 From: Emmanuel Rodriguez Date: Thu, 21 Apr 2011 21:21:44 +0200 Subject: [PATCH 064/120] LIBGIT2_VER_MINOR now matches LIBGIT2_VERSION (0.11.0) LIBGIT2_VER_MINOR was left at 10 instead of 11. --- include/git2.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2.h b/include/git2.h index 29fa98e18..5756d7e90 100644 --- a/include/git2.h +++ b/include/git2.h @@ -28,7 +28,7 @@ #define LIBGIT2_VERSION "0.11.0" #define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 10 +#define LIBGIT2_VER_MINOR 11 #define LIBGIT2_VER_REVISION 0 #include "git2/common.h" From a3ced63792b570b7a5927f6a75cf996233b9f3aa Mon Sep 17 00:00:00 2001 From: Sergey Nikishin Date: Fri, 22 Apr 2011 17:36:28 +0400 Subject: [PATCH 065/120] Fix going into infinite loop in read_header_loose() read_header_loose causes infinite loop on this file: $ cat ../libcppgit/bin/sample-repo/test_mailbox/.git/objects/8f/e274605cbc740a2a957f44b2722a8a73915a09 | base64 eAErKUpNVTAzYzA0MDAzMVHISUxKzSlmWLgkuyN5+rxr6juMPR2EmN8s7Vl9D6oiN7UkkcHJdLbl 7Z3N/oxfE0W8wrSbuFRkAwDFfBn1 --- src/odb_loose.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/odb_loose.c b/src/odb_loose.c index 8ee01cd2c..4f475f2c3 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -434,6 +434,9 @@ static int read_header_loose(git_rawobj *out, const char *loc) if ((read_bytes = read(fd, raw_buffer, sizeof(raw_buffer))) > 0) { set_stream_input(&zs, raw_buffer, read_bytes); z_return = inflate(&zs, 0); + } else { + z_return = Z_STREAM_END; + break; } } while (z_return == Z_OK); From 1d0087816e0b6e22cb08a734e440b718e59ffdc0 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sat, 23 Apr 2011 23:59:38 +0300 Subject: [PATCH 066/120] Fix conversion warning in MSVC --- src/odb_pack.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/odb_pack.c b/src/odb_pack.c index 561b32935..40ef48faf 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -113,7 +113,7 @@ struct pack_backend { git_vector packs; struct pack_file *last_found; char *pack_folder; - off_t pack_folder_size; + size_t pack_folder_size; size_t window_size; /* needs default value */ @@ -874,7 +874,7 @@ static int packfile_refresh_all(struct pack_backend *backend) if (gitfo_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) return GIT_ENOTFOUND; - if (st.st_size != backend->pack_folder_size) { + if ((size_t)st.st_size != backend->pack_folder_size) { char path[GIT_PATH_MAX]; strcpy(path, backend->pack_folder); @@ -884,7 +884,7 @@ static int packfile_refresh_all(struct pack_backend *backend) return error; git_vector_sort(&backend->packs); - backend->pack_folder_size = st.st_size; + backend->pack_folder_size = (size_t)st.st_size; } return GIT_SUCCESS; @@ -1408,7 +1408,7 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) git__joinpath(path, objects_dir, "pack"); if (gitfo_isdir(path) == GIT_SUCCESS) { backend->pack_folder = git__strdup(path); - backend->pack_folder_size = -1; + backend->pack_folder_size = 0; if (backend->pack_folder == NULL) { free(backend); From f7a5058aaf51635b3171eda182820a8f8c750060 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sun, 24 Apr 2011 00:31:43 +0300 Subject: [PATCH 067/120] index: Refactor add/replace methods Removed the optional `replace` argument, we now have 4 add methods: `git_index_add`: add or update from path `git_index_add2`: add or update from struct `git_index_append`: add without replacing from path `git_index_append2`: add without replacing from struct Yes, this breaks the bindings. --- include/git2/index.h | 68 +++++++++++++++++++----- src/index.c | 123 +++++++++++++++++++++++++++---------------- 2 files changed, 131 insertions(+), 60 deletions(-) diff --git a/include/git2/index.h b/include/git2/index.h index adc4cb435..98a17a18c 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -182,7 +182,12 @@ GIT_EXTERN(int) git_index_write(git_index *index); GIT_EXTERN(int) git_index_find(git_index *index, const char *path); /** - * Add or update an index entry from a file in disk. + * Add or update an index entry from a file in disk + * + * The file `path` must be relative to the repository's + * working folder and must be readable. + * + * This method will fail in bare index instances. * * @param index an existing index object * @param path filename to add @@ -191,6 +196,54 @@ GIT_EXTERN(int) git_index_find(git_index *index, const char *path); */ GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); +/** + * Add or update an index entry from an in-memory struct + * + * A full copy (including the 'path' string) of the given + * 'source_entry' will be inserted on the index. + * + * @param index an existing index object + * @param source_entry new entry object + * @return 0 on success, otherwise an error code + */ +GIT_EXTERN(int) git_index_add2(git_index *index, const git_index_entry *source_entry); + +/** + * Add (append) an index entry from a file in disk + * + * A new entry will always be inserted into the index; + * if the index already contains an entry for such + * path, the old entry will **not** be replaced. + * + * The file `path` must be relative to the repository's + * working folder and must be readable. + * + * This method will fail in bare index instances. + * + * @param index an existing index object + * @param path filename to add + * @param stage stage for the entry + * @return 0 on success, otherwise an error code + */ +GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage); + +/** + * Add (append) an index entry from an in-memory struct + * + * A new entry will always be inserted into the index; + * if the index already contains an entry for the path + * in the `entry` struct, the old entry will **not** be + * replaced. + * + * A full copy (including the 'path' string) of the given + * 'source_entry' will be inserted on the index. + * + * @param index an existing index object + * @param source_entry new entry object + * @return 0 on success, otherwise an error code + */ +GIT_EXTERN(int) git_index_apppend2(git_index *index, const git_index_entry *source_entry); + /** * Remove an entry from the index * @@ -200,19 +253,6 @@ GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); */ GIT_EXTERN(int) git_index_remove(git_index *index, int position); -/** - * Insert an entry into the index. - * A full copy (including the 'path' string) of the given - * 'source_entry' will be inserted on the index; if the - * replace flag is not set and the index already contains - * an entry for the same path, the entry will be updated. - * - * @param index an existing index object - * @param source_entry new entry object - * @param replace if set, existing entries will be replaced - * @return 0 on success, otherwise an error code - */ -GIT_EXTERN(int) git_index_insert(git_index *index, const git_index_entry *source_entry, int replace); /** * Get a pointer to one of the entries in the index diff --git a/src/index.c b/src/index.c index c066d24d7..850f5de85 100644 --- a/src/index.c +++ b/src/index.c @@ -289,56 +289,12 @@ git_index_entry *git_index_get(git_index *index, int n) return git_vector_get(&index->entries, (unsigned int)n); } -int git_index_add(git_index *index, const char *rel_path, int stage) -{ - git_index_entry entry; - char full_path[GIT_PATH_MAX]; - struct stat st; - int error; - - if (index->repository == NULL) - return GIT_EBAREINDEX; - - git__joinpath(full_path, index->repository->path_workdir, rel_path); - - if (gitfo_exists(full_path) < 0) - return GIT_ENOTFOUND; - - if (gitfo_stat(full_path, &st) < 0) - return GIT_EOSERR; - - if (stage < 0 || stage > 3) - return GIT_ERROR; - - memset(&entry, 0x0, sizeof(git_index_entry)); - - entry.ctime.seconds = (git_time_t)st.st_ctime; - entry.mtime.seconds = (git_time_t)st.st_mtime; - /* entry.mtime.nanoseconds = st.st_mtimensec; */ - /* entry.ctime.nanoseconds = st.st_ctimensec; */ - entry.dev= st.st_rdev; - entry.ino = st.st_ino; - entry.mode = st.st_mode; - entry.uid = st.st_uid; - entry.gid = st.st_gid; - entry.file_size = st.st_size; - - /* write the blob to disk and get the oid */ - if ((error = git_blob_create_fromfile(&entry.oid, index->repository, rel_path)) < GIT_SUCCESS) - return error; - - entry.flags |= (stage << GIT_IDXENTRY_STAGESHIFT); - entry.path = (char *)rel_path; /* do not duplicate; index_insert already does this */ - - return git_index_insert(index, &entry, 1); -} - -void sort_index(git_index *index) +static void sort_index(git_index *index) { git_vector_sort(&index->entries); } -int git_index_insert(git_index *index, const git_index_entry *source_entry, int replace) +static int index_insert(git_index *index, const git_index_entry *source_entry, int replace) { git_index_entry *entry; size_t path_length; @@ -395,6 +351,81 @@ int git_index_insert(git_index *index, const git_index_entry *source_entry, int return GIT_SUCCESS; } +static int index_init_entry(git_index_entry *entry, git_index *index, const char *rel_path, int stage) +{ + char full_path[GIT_PATH_MAX]; + struct stat st; + int error; + + if (index->repository == NULL) + return GIT_EBAREINDEX; + + git__joinpath(full_path, index->repository->path_workdir, rel_path); + + if (gitfo_exists(full_path) < 0) + return GIT_ENOTFOUND; + + if (gitfo_stat(full_path, &st) < 0) + return GIT_EOSERR; + + if (stage < 0 || stage > 3) + return GIT_ERROR; + + memset(entry, 0x0, sizeof(git_index_entry)); + + entry->ctime.seconds = (git_time_t)st.st_ctime; + entry->mtime.seconds = (git_time_t)st.st_mtime; + /* entry.mtime.nanoseconds = st.st_mtimensec; */ + /* entry.ctime.nanoseconds = st.st_ctimensec; */ + entry->dev= st.st_rdev; + entry->ino = st.st_ino; + entry->mode = st.st_mode; + entry->uid = st.st_uid; + entry->gid = st.st_gid; + entry->file_size = st.st_size; + + /* write the blob to disk and get the oid */ + if ((error = git_blob_create_fromfile(&entry->oid, index->repository, rel_path)) < GIT_SUCCESS) + return error; + + entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); + entry->path = (char *)rel_path; /* do not duplicate; index_insert already does this */ + return GIT_SUCCESS; +} + +int git_index_add(git_index *index, const char *path, int stage) +{ + int error; + git_index_entry entry; + + if ((error = index_init_entry(&entry, index, path, stage)) < GIT_SUCCESS) + return error; + + return index_insert(index, &entry, 1); +} + +int git_index_append(git_index *index, const char *path, int stage) +{ + int error; + git_index_entry entry; + + if ((error = index_init_entry(&entry, index, path, stage)) < GIT_SUCCESS) + return error; + + return index_insert(index, &entry, 0); +} + +int git_index_add2(git_index *index, const git_index_entry *source_entry) +{ + return index_insert(index, source_entry, 1); +} + +int git_index_apppend2(git_index *index, const git_index_entry *source_entry) +{ + return index_insert(index, source_entry, 0); +} + + int git_index_remove(git_index *index, int position) { assert(index); From 402a47a7fa3ab2b549c5183d1d5fc82d95f64663 Mon Sep 17 00:00:00 2001 From: schu Date: Tue, 26 Apr 2011 11:29:05 +0200 Subject: [PATCH 068/120] Fix -Wunused-but-set-variable warnings As of gcc 4.6 -Wall includes -Wunused-but-set-variable. Use GIT_UNUSED or remove actually unused variables to prevent those warnings. --- src/refs.c | 8 ++++---- src/repository.c | 3 --- src/revwalk.c | 4 ++-- tests/t07-hashtable.c | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/refs.c b/src/refs.c index 17ec2915f..e5fc79dc8 100644 --- a/src/refs.c +++ b/src/refs.c @@ -684,7 +684,7 @@ static int packed_loadloose(git_repository *repository) /* Remove any loose references from the cache */ { - const void *_unused; + const void *GIT_UNUSED(_unused); git_reference *reference; GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference, @@ -868,7 +868,7 @@ static int packed_write(git_repository *repo) /* Load all the packfile into a vector */ { git_reference *reference; - const void *_unused; + const void *GIT_UNUSED(_unused); GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference, git_vector_insert(&packing_list, reference); /* cannot fail: vector already has the right size */ @@ -1518,7 +1518,7 @@ int git_reference_listcb(git_repository *repo, unsigned int list_flags, int (*ca /* list all the packed references first */ if (list_flags & GIT_REF_PACKED) { const char *ref_name; - void *_unused; + void *GIT_UNUSED(_unused); if ((error = packed_load(repo)) < GIT_SUCCESS) return error; @@ -1597,7 +1597,7 @@ int git_repository__refcache_init(git_refcache *refs) void git_repository__refcache_free(git_refcache *refs) { git_reference *reference; - const void *_unused; + const void *GIT_UNUSED(_unused); assert(refs); diff --git a/src/repository.c b/src/repository.c index c428b00af..8cc2644ca 100644 --- a/src/repository.c +++ b/src/repository.c @@ -58,7 +58,6 @@ static int assign_repository_dirs( const char *git_work_tree) { char path_aux[GIT_PATH_MAX]; - size_t git_dir_path_len; int error = GIT_SUCCESS; assert(repo); @@ -70,8 +69,6 @@ static int assign_repository_dirs( if (error < GIT_SUCCESS) return error; - git_dir_path_len = strlen(path_aux); - /* store GIT_DIR */ repo->path_repository = git__strdup(path_aux); if (repo->path_repository == NULL) diff --git a/src/revwalk.c b/src/revwalk.c index b62b09961..78798480f 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -483,7 +483,7 @@ int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) void git_revwalk_free(git_revwalk *walk) { unsigned int i; - const void *_unused; + const void *GIT_UNUSED(_unused); commit_object *commit; if (walk == NULL) @@ -558,7 +558,7 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) void git_revwalk_reset(git_revwalk *walk) { - const void *_unused; + const void *GIT_UNUSED(_unused); commit_object *commit; assert(walk); diff --git a/tests/t07-hashtable.c b/tests/t07-hashtable.c index 597136965..0b362cafd 100644 --- a/tests/t07-hashtable.c +++ b/tests/t07-hashtable.c @@ -155,7 +155,7 @@ BEGIN_TEST(tableit0, "iterate through all the contents of the table") const int objects_n = 32; int i; table_item *objects, *ob; - const void *_unused; + const void *GIT_UNUSED(_unused); git_hashtable *table = NULL; From 555ce568196f62558eb5a9084ba61add7e6b1ba1 Mon Sep 17 00:00:00 2001 From: Sergey Nikishin Date: Tue, 26 Apr 2011 13:22:45 +0400 Subject: [PATCH 069/120] Fix tree-entry attribute convertion (fix corrupted trees) Magic constant replaced by direct to-string covertion because of: 1) with value length 6 (040000 - subtree) final tree will be corrupted; 2) for wrong values length <6 final tree will be corrupted too. --- src/tree.c | 3 ++- tests/t09-tree.c | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 64f81d780..b7daf39c4 100644 --- a/src/tree.c +++ b/src/tree.c @@ -424,7 +424,8 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b if (entry->removed) continue; - size += (entry->attr > 0x7FF) ? 7 : 6; + snprintf(filemode, sizeof(filemode), "%o ", entry->attr); + size += strlen(filemode); size += entry->filename_len + 1; size += GIT_OID_RAWSZ; } diff --git a/tests/t09-tree.c b/tests/t09-tree.c index 9b39dfd07..af992fdb3 100644 --- a/tests/t09-tree.c +++ b/tests/t09-tree.c @@ -32,6 +32,7 @@ static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92"; static const char *first_tree = "181037049a54a1eb5fab404658a3a250b44335d7"; static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1"; +static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488"; #if 0 static int print_tree(git_repository *repo, const git_oid *tree_oid, int depth) @@ -159,6 +160,44 @@ BEGIN_TEST(write2, "write a tree from a memory") close_temp_repo(repo); END_TEST +BEGIN_TEST(write3, "write a hierarchical tree from a memory") + git_repository *repo; + git_treebuilder *builder; + git_tree *tree; + git_oid id, bid, subtree_id, id2, id3; + git_oid id_hiearar; + + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); + git_oid_mkstr(&id, first_tree); + git_oid_mkstr(&id2, second_tree); + git_oid_mkstr(&id3, third_tree); + git_oid_mkstr(&bid, blob_oid); + + //create subtree + must_pass(git_treebuilder_create(&builder, NULL)); + must_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); + must_pass(git_treebuilder_write(&subtree_id,repo,builder)); + git_treebuilder_free(builder); + + // create parent tree + must_pass(git_tree_lookup(&tree, repo, &id)); + must_pass(git_treebuilder_create(&builder, tree)); + must_pass(git_treebuilder_insert(NULL,builder,"new",&subtree_id,040000)); + must_pass(git_treebuilder_write(&id_hiearar,repo,builder)); + git_treebuilder_free(builder); + git_tree_close(tree); + + must_be_true(git_oid_cmp(&id_hiearar, &id3) == 0); + + // check data is correct + must_pass(git_tree_lookup(&tree, repo, &id_hiearar)); + must_be_true(2 == git_tree_entrycount(tree)); + git_tree_close(tree); + + close_temp_repo(repo); + +END_TEST + BEGIN_SUITE(tree) //ADD_TEST(print0); ADD_TEST(read0); @@ -166,5 +205,6 @@ BEGIN_SUITE(tree) //ADD_TEST(write0); //ADD_TEST(write1); ADD_TEST(write2); + ADD_TEST(write3); END_SUITE 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 070/120] 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 ed6c462c2043330ab7cda09097ffb95943919caa Mon Sep 17 00:00:00 2001 From: Sergey Nikishin Date: Wed, 27 Apr 2011 17:30:45 +0400 Subject: [PATCH 071/120] Fix memory leak in pack_backend__free --- src/odb_pack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/odb_pack.c b/src/odb_pack.c index 40ef48faf..6537bbeea 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -1385,6 +1385,7 @@ void pack_backend__free(git_odb_backend *_backend) } git_vector_free(&backend->packs); + free(backend->pack_folder); free(backend); } From 68a146c1ae580f0828dd193a749aac4063aa4500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 29 Apr 2011 11:45:42 +0200 Subject: [PATCH 072/120] refs: don't loose info on resolve error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Typical use is git_reference_resolve(&ref, ref). Currently, if there is an error, ref will point to NULL, causing the user to lose that reference. Always update resolved_ref instead of just on finding an OID ref, storing the last valid reference in it. This change helps simplify the code for allowing root commits. Signed-off-by: Carlos Martín Nieto --- src/refs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/refs.c b/src/refs.c index 17ec2915f..b88e047e1 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1480,8 +1480,9 @@ int git_reference_resolve(git_reference **resolved_ref, git_reference *ref) for (i = 0; i < MAX_NESTING_LEVEL; ++i) { reference_symbolic *ref_sym; + *resolved_ref = ref; + if (ref->type & GIT_REF_OID) { - *resolved_ref = ref; return GIT_SUCCESS; } From 8381238e005be4a0e53d09a46a1a40e7d3405416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 27 Apr 2011 14:59:59 +0200 Subject: [PATCH 073/120] commit: support a root commits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A root commit is a commit whose branch (usually what HEAD points to) doesn't exist (yet). This situation can happen when the commit is the first after 1) a repository is initialized or 2) a orphan checkout has been performed. Take this opportunity to remove the symbolic link check, as git_reference_resolve works on OID refs as well. Signed-off-by: Carlos Martín Nieto --- src/commit.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/commit.c b/src/commit.c index 9621703c3..0c37ec59b 100644 --- a/src/commit.c +++ b/src/commit.c @@ -224,9 +224,18 @@ int git_commit_create( if (error < GIT_SUCCESS) return error; - if (git_reference_type(head) == GIT_REF_SYMBOLIC) { - if ((error = git_reference_resolve(&head, head)) < GIT_SUCCESS) + error = git_reference_resolve(&head, head); + if (error < GIT_SUCCESS) { + if (error != GIT_ENOTFOUND) return error; + /* + * The target of the reference was not found. This can happen + * just after a repository has been initialized (the master + * branch doesn't exist yet, as it doesn't have anything to + * point to) or after an orphan checkout, so if the target + * branch doesn't exist yet, create it and return. + */ + return git_reference_create_oid_f(&head, repo, git_reference_target(head), oid); } error = git_reference_set_oid(head, oid); From 79b61557366db4550d1122bfa88b9851a5e2f5aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 28 Apr 2011 18:30:50 +0200 Subject: [PATCH 074/120] Add root commit test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- tests/t04-commit.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/t04-commit.c b/tests/t04-commit.c index 3e02df996..36f3e66b5 100644 --- a/tests/t04-commit.c +++ b/tests/t04-commit.c @@ -474,6 +474,72 @@ BEGIN_TEST(write0, "write a new commit object from memory to disk") git_repository_free(repo); END_TEST +#define ROOT_COMMIT_MESSAGE "This is a root commit\n\ +This is a root commit and should be the only one in this branch\n" + +BEGIN_TEST(root0, "create a root commit") + git_repository *repo; + git_commit *commit; + git_oid tree_id, commit_id; + const git_oid *branch_oid; + const git_signature *author, *committer; + const char *branch_name = "refs/heads/root-commit-branch"; + git_reference *head, *branch; + char *head_old; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + git_oid_mkstr(&tree_id, tree_oid); + + /* create signatures */ + committer = git_signature_new(COMMITTER_NAME, COMMITTER_EMAIL, 123456789, 60); + must_be_true(committer != NULL); + + author = git_signature_new(COMMITTER_NAME, COMMITTER_EMAIL, 987654321, 90); + must_be_true(author != NULL); + + /* First we need to update HEAD so it points to our non-existant branch */ + must_pass(git_reference_lookup(&head, repo, "HEAD")); + must_be_true(git_reference_type(head) == GIT_REF_SYMBOLIC); + head_old = git__strdup(git_reference_target(head)); + must_be_true(head_old != NULL); + + must_pass(git_reference_set_target(head, branch_name)); + + must_pass(git_commit_create_v( + &commit_id, /* out id */ + repo, + "HEAD", + author, + committer, + ROOT_COMMIT_MESSAGE, + &tree_id, + 0)); + + git_signature_free((git_signature *)committer); + git_signature_free((git_signature *)author); + + /* + * The fact that creating a commit works has already been + * tested. Here we just make sure it's our commit and that it was + * written as a root commit. + */ + must_pass(git_commit_lookup(&commit, repo, &commit_id)); + must_be_true(git_commit_parentcount(commit) == 0); + must_pass(git_reference_lookup(&branch, repo, branch_name)); + branch_oid = git_reference_oid(branch); + must_pass(git_oid_cmp(branch_oid, &commit_id)); + must_be_true(!strcmp(git_commit_message(commit), ROOT_COMMIT_MESSAGE)); + + /* Remove the data we just added to the repo */ + must_pass(git_reference_lookup(&head, repo, "HEAD")); + must_pass(git_reference_set_target(head, head_old)); + must_pass(git_reference_delete(branch)); + must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)commit)); + free(head_old); + git_commit_close(commit); + git_repository_free(repo); +END_TEST BEGIN_SUITE(commit) ADD_TEST(parse0); @@ -483,4 +549,6 @@ BEGIN_SUITE(commit) ADD_TEST(write0); //ADD_TEST(write1); + + ADD_TEST(root0); END_SUITE From c7b79af3f035b06095ba9d9ad61c3c766764b818 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sun, 1 May 2011 21:31:58 +0300 Subject: [PATCH 075/120] pack-odb: Check `mtime` instead of folder size Do not check the folder's size to detect new packfiles at runtime. This doesn't work on Win32. --- src/odb_pack.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/odb_pack.c b/src/odb_pack.c index 40ef48faf..940750e1c 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -113,7 +113,7 @@ struct pack_backend { git_vector packs; struct pack_file *last_found; char *pack_folder; - size_t pack_folder_size; + time_t pack_folder_mtime; size_t window_size; /* needs default value */ @@ -874,7 +874,7 @@ static int packfile_refresh_all(struct pack_backend *backend) if (gitfo_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) return GIT_ENOTFOUND; - if ((size_t)st.st_size != backend->pack_folder_size) { + if (st.st_mtime != backend->pack_folder_mtime) { char path[GIT_PATH_MAX]; strcpy(path, backend->pack_folder); @@ -884,7 +884,7 @@ static int packfile_refresh_all(struct pack_backend *backend) return error; git_vector_sort(&backend->packs); - backend->pack_folder_size = (size_t)st.st_size; + backend->pack_folder_mtime = st.st_mtime; } return GIT_SUCCESS; @@ -1408,7 +1408,7 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) git__joinpath(path, objects_dir, "pack"); if (gitfo_isdir(path) == GIT_SUCCESS) { backend->pack_folder = git__strdup(path); - backend->pack_folder_size = 0; + backend->pack_folder_mtime = 0; if (backend->pack_folder == NULL) { free(backend); From 34e5d87e0512f2a3dfd6fa0ead1fb46814627a1f Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 1 May 2011 21:35:32 +0200 Subject: [PATCH 076/120] Change implementation of refs tests that alter the current repository to make them run against a temporary clone of the test repository --- tests/t10-refs.c | 46 +++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/tests/t10-refs.c b/tests/t10-refs.c index d3f5620c9..db767a107 100644 --- a/tests/t10-refs.c +++ b/tests/t10-refs.c @@ -198,7 +198,7 @@ END_TEST BEGIN_TEST(create0, "create a new symbolic reference") git_reference *new_reference, *looked_up_ref, *resolved_ref; - git_repository *repo; + git_repository *repo, *repo2; git_oid id; char ref_path[GIT_PATH_MAX]; @@ -206,7 +206,7 @@ BEGIN_TEST(create0, "create a new symbolic reference") git_oid_mkstr(&id, current_master_tip); - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); /* Retrieve the physical path to the symbolic ref for further cleaning */ git__joinpath(ref_path, repo->path_repository, new_head_tracker); @@ -230,14 +230,13 @@ BEGIN_TEST(create0, "create a new symbolic reference") git_repository_free(repo); /* Similar test with a fresh new repository */ - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER)); - must_pass(git_reference_lookup(&looked_up_ref, repo, new_head_tracker)); + must_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker)); must_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); - git_reference_delete(looked_up_ref); - git_repository_free(repo); + close_temp_repo(repo2); END_TEST BEGIN_TEST(create1, "create a deep symbolic reference") @@ -250,7 +249,7 @@ BEGIN_TEST(create1, "create a deep symbolic reference") git_oid_mkstr(&id, current_master_tip); - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); git__joinpath(ref_path, repo->path_repository, new_head_tracker); must_pass(git_reference_create_symbolic(&new_reference, repo, new_head_tracker, current_head_target)); @@ -258,13 +257,12 @@ BEGIN_TEST(create1, "create a deep symbolic reference") must_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); - git_reference_delete(looked_up_ref); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(create2, "create a new OID reference") git_reference *new_reference, *looked_up_ref; - git_repository *repo; + git_repository *repo, *repo2; git_oid id; char ref_path[GIT_PATH_MAX]; @@ -272,7 +270,7 @@ BEGIN_TEST(create2, "create a new OID reference") git_oid_mkstr(&id, current_master_tip); - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); /* Retrieve the physical path to the symbolic ref for further cleaning */ git__joinpath(ref_path, repo->path_repository, new_head); @@ -292,13 +290,12 @@ BEGIN_TEST(create2, "create a new OID reference") git_repository_free(repo); /* Similar test with a fresh new repository */ - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER)); must_pass(git_reference_lookup(&looked_up_ref, repo, new_head)); must_be_true(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0); - git_reference_delete(looked_up_ref); - git_repository_free(repo); + close_temp_repo(repo2); END_TEST BEGIN_TEST(create3, "Can not create a new OID reference which targets at an unknown id") @@ -329,7 +326,7 @@ BEGIN_TEST(overwrite0, "Overwrite an existing symbolic reference") git_reference *ref, *branch_ref; git_repository *repo; - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); /* The target needds to exist and we need to check the name has changed */ must_pass(git_reference_create_symbolic(&branch_ref, repo, ref_branch_name, ref_master_name)); @@ -348,9 +345,7 @@ BEGIN_TEST(overwrite0, "Overwrite an existing symbolic reference") must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC); must_be_true(!strcmp(git_reference_target(ref), ref_master_name)); - must_pass(git_reference_delete(ref)); - must_pass(git_reference_delete(branch_ref)); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(overwrite1, "Overwrite an existing object id reference") @@ -358,7 +353,7 @@ BEGIN_TEST(overwrite1, "Overwrite an existing object id reference") git_repository *repo; git_oid id; - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); must_pass(git_reference_lookup(&ref, repo, ref_master_name)); must_be_true(ref->type & GIT_REF_OID); @@ -379,8 +374,7 @@ BEGIN_TEST(overwrite1, "Overwrite an existing object id reference") must_pass(git_reference_lookup(&ref, repo, ref_name)); must_be_true(!git_oid_cmp(&id, git_reference_oid(ref))); - git_reference_delete(ref); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(overwrite2, "Overwrite an existing object id reference with a symbolic one") @@ -388,7 +382,7 @@ BEGIN_TEST(overwrite2, "Overwrite an existing object id reference with a symboli git_repository *repo; git_oid id; - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); must_pass(git_reference_lookup(&ref, repo, ref_master_name)); must_be_true(ref->type & GIT_REF_OID); @@ -403,8 +397,7 @@ BEGIN_TEST(overwrite2, "Overwrite an existing object id reference with a symboli must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC); must_be_true(!strcmp(git_reference_target(ref), ref_master_name)); - git_reference_delete(ref); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(overwrite3, "Overwrite an existing symbolic reference with an object id one") @@ -412,7 +405,7 @@ BEGIN_TEST(overwrite3, "Overwrite an existing symbolic reference with an object git_repository *repo; git_oid id; - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); must_pass(git_reference_lookup(&ref, repo, ref_master_name)); must_be_true(ref->type & GIT_REF_OID); @@ -429,8 +422,7 @@ BEGIN_TEST(overwrite3, "Overwrite an existing symbolic reference with an object must_be_true(git_reference_type(ref) & GIT_REF_OID); must_be_true(!git_oid_cmp(git_reference_oid(ref), &id)); - git_reference_delete(ref); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(pack0, "create a packfile for an empty folder") From 1648fbd34457c287ffb961457349689c67f3ad97 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Mon, 2 May 2011 01:12:53 +0300 Subject: [PATCH 077/120] Re-apply missing patches --- include/git2/index.h | 36 +++++++++++++----------------------- src/index.c | 25 +++++++++++++++++++++++-- src/refs.c | 2 ++ 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/include/git2/index.h b/include/git2/index.h index 98a17a18c..7991de92e 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -51,39 +51,29 @@ GIT_BEGIN_DECL * * In-memory only flags: */ -#define GIT_IDXENTRY_UPDATE (1 << 16) -#define GIT_IDXENTRY_REMOVE (1 << 17) -#define GIT_IDXENTRY_UPTODATE (1 << 18) -#define GIT_IDXENTRY_ADDED (1 << 19) +#define GIT_IDXENTRY_UPDATE (1 << 0) +#define GIT_IDXENTRY_REMOVE (1 << 1) +#define GIT_IDXENTRY_UPTODATE (1 << 2) +#define GIT_IDXENTRY_ADDED (1 << 3) -#define GIT_IDXENTRY_HASHED (1 << 20) -#define GIT_IDXENTRY_UNHASHED (1 << 21) -#define GIT_IDXENTRY_WT_REMOVE (1 << 22) /* remove in work directory */ -#define GIT_IDXENTRY_CONFLICTED (1 << 23) +#define GIT_IDXENTRY_HASHED (1 << 4) +#define GIT_IDXENTRY_UNHASHED (1 << 5) +#define GIT_IDXENTRY_WT_REMOVE (1 << 6) /* remove in work directory */ +#define GIT_IDXENTRY_CONFLICTED (1 << 7) -#define GIT_IDXENTRY_UNPACKED (1 << 24) -#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 25) +#define GIT_IDXENTRY_UNPACKED (1 << 8) +#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 9) /* * Extended on-disk flags: */ -#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 29) -#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 30) +#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13) +#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14) /* GIT_IDXENTRY_EXTENDED2 is for future extension */ -#define GIT_IDXENTRY_EXTENDED2 (1 << 31) +#define GIT_IDXENTRY_EXTENDED2 (1 << 15) #define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE) -/* - * Safeguard to avoid saving wrong flags: - * - GIT_IDXENTRY_EXTENDED2 won't get saved until its semantic is known - * - Bits in 0x0000FFFF have been saved in flags already - * - Bits in 0x003F0000 are currently in-memory flags - */ -#if GIT_IDXENTRY_EXTENDED_FLAGS & 0x803FFFFF -#error "GIT_IDXENTRY_EXTENDED_FLAGS out of range" -#endif - /** Time used in a git index entry */ typedef struct { git_time_t seconds; diff --git a/src/index.c b/src/index.c index 850f5de85..130d1fd36 100644 --- a/src/index.c +++ b/src/index.c @@ -101,6 +101,7 @@ static int read_tree(git_index *index, const char *buffer, size_t buffer_size); static git_index_tree *read_tree_internal(const char **, const char *, git_index_tree *); static int parse_index(git_index *index, const char *buffer, size_t buffer_size); +static int is_index_extended(git_index *index); static void sort_index(git_index *index); static int write_index(git_index *index, git_filebuf *file); @@ -718,6 +719,24 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) return GIT_SUCCESS; } +static int is_index_extended(git_index *index) +{ + unsigned int i, extended; + + extended = 0; + + for (i = 0; i < index->entries.length; ++i) { + git_index_entry *entry; + entry = git_vector_get(&index->entries, i); + entry->flags &= ~GIT_IDXENTRY_EXTENDED; + if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) { + extended++; + entry->flags |= GIT_IDXENTRY_EXTENDED; + } + } + return extended; +} + static int write_disk_entry(git_filebuf *file, git_index_entry *entry) { struct entry_short *ondisk; @@ -786,12 +805,14 @@ static int write_index(git_index *index, git_filebuf *file) struct index_header header; - int is_extended = 1; + int is_extended; assert(index && file); + is_extended = is_index_extended(index); + header.signature = htonl(INDEX_HEADER_SIG); - header.version = htonl(is_extended ? INDEX_VERSION_NUMBER : INDEX_VERSION_NUMBER_EXT); + header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER); header.entry_count = htonl(index->entries.length); git_filebuf_write(file, &header, sizeof(struct index_header)); diff --git a/src/refs.c b/src/refs.c index e5fc79dc8..8c845401f 100644 --- a/src/refs.c +++ b/src/refs.c @@ -787,6 +787,8 @@ static int packed_find_peel(reference_oid *ref) */ } + git_object_close(object); + return GIT_SUCCESS; } From 773bc20dd3f78d4e307ff54edd1e09f508f2a06f Mon Sep 17 00:00:00 2001 From: "Jason R. McNeil" Date: Tue, 3 May 2011 22:22:42 -0700 Subject: [PATCH 078/120] Fix misspelling of git_index_append2 (was git_index_apppend2). --- include/git2/index.h | 4 ++-- src/index.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/git2/index.h b/include/git2/index.h index 7991de92e..2d8975ca1 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -232,10 +232,10 @@ GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage); * @param source_entry new entry object * @return 0 on success, otherwise an error code */ -GIT_EXTERN(int) git_index_apppend2(git_index *index, const git_index_entry *source_entry); +GIT_EXTERN(int) git_index_append2(git_index *index, const git_index_entry *source_entry); /** - * Remove an entry from the index + * Remove an entry from the index * * @param index an existing index object * @param position position of the entry to remove diff --git a/src/index.c b/src/index.c index 130d1fd36..f643e73fb 100644 --- a/src/index.c +++ b/src/index.c @@ -421,7 +421,7 @@ int git_index_add2(git_index *index, const git_index_entry *source_entry) return index_insert(index, source_entry, 1); } -int git_index_apppend2(git_index *index, const git_index_entry *source_entry) +int git_index_append2(git_index *index, const git_index_entry *source_entry) { return index_insert(index, source_entry, 0); } From bbd68c6768dc351da7d327682cc904b16c6d73ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 5 May 2011 11:38:23 +0200 Subject: [PATCH 079/120] ref test: update a forgotten repo -> repo2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 34e5d87e0512f2a3dfd6 left one of these unchanged we're trying to read from a free'd repository. Signed-off-by: Carlos Martín Nieto --- tests/t10-refs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/t10-refs.c b/tests/t10-refs.c index db767a107..ee006a8ce 100644 --- a/tests/t10-refs.c +++ b/tests/t10-refs.c @@ -292,7 +292,7 @@ BEGIN_TEST(create2, "create a new OID reference") /* Similar test with a fresh new repository */ must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER)); - must_pass(git_reference_lookup(&looked_up_ref, repo, new_head)); + must_pass(git_reference_lookup(&looked_up_ref, repo2, new_head)); must_be_true(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0); close_temp_repo(repo2); 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 080/120] 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 39e1032cfcc27ddfeb9e8d2d7ab5d7d1c51da0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 6 May 2011 12:43:37 +0200 Subject: [PATCH 081/120] odb backend_sort_cmp should be static MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/odb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/odb.c b/src/odb.c index 43d0ef9a4..e9e495eb1 100644 --- a/src/odb.c +++ b/src/odb.c @@ -221,7 +221,7 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend * ***********************************************************/ -int backend_sort_cmp(const void *a, const void *b) +static int backend_sort_cmp(const void *a, const void *b) { const backend_internal *backend_a = *(const backend_internal **)(a); const backend_internal *backend_b = *(const backend_internal **)(b); From d8e1d038b3e77fdcec01e662dc9bc6708ebcd24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 6 May 2011 12:47:21 +0200 Subject: [PATCH 082/120] Fix two warnings from Clang MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both are about not reading the value stored in a variable. Signed-off-by: Carlos Martín Nieto --- src/commit.c | 2 +- src/odb_loose.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/commit.c b/src/commit.c index 0c37ec59b..54d7a47fe 100644 --- a/src/commit.c +++ b/src/commit.c @@ -286,7 +286,7 @@ int commit_parse_buffer(git_commit *commit, const void *data, size_t len) if (buffer < buffer_end) { const char *line_end; - size_t message_len = buffer_end - buffer; + size_t message_len; /* Long message */ message_len = buffer_end - buffer; diff --git a/src/odb_loose.c b/src/odb_loose.c index 4f475f2c3..873dbfa0a 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -336,7 +336,6 @@ static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) { unsigned char head[64], *buf; z_stream zs; - int z_status; obj_hdr hdr; size_t used; @@ -350,7 +349,7 @@ static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) * inflate the initial part of the io buffer in order * to parse the object header (type and size). */ - if ((z_status = start_inflate(&zs, obj, head, sizeof(head))) < Z_OK) + if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK) return GIT_ERROR; if ((used = get_object_header(&hdr, head)) == 0) From 560cd27ef3bbaac66a858ab86b3be04148281c6e Mon Sep 17 00:00:00 2001 From: "kelly.leahy" Date: Sun, 8 May 2011 12:30:16 -0700 Subject: [PATCH 083/120] Add git attributes settings for *.c and *.h to force line endings to LF. --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f90540b55 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.c eol=lf +*.h eol=lf From 16a5c3046595284c1f2f8b9eb8621d76239e4b61 Mon Sep 17 00:00:00 2001 From: "kelly.leahy" Date: Sun, 8 May 2011 12:32:35 -0700 Subject: [PATCH 084/120] Fix bug in the way pthead_mutex_t was being destroyed in win32. Win32 critical section objects (CRITICAL_SECTION) are not kernel objects. Only kernel objects are destroyed by using CloseHandle. Critical sections are supposed to be deleted with the DeleteCriticalSection API (http://msdn.microsoft.com/en-us/library/ms682552(VS.85).aspx). --- src/win32/pthread.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/win32/pthread.c b/src/win32/pthread.c index f47364a76..7e17b6bdf 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -48,16 +48,15 @@ int pthread_join(pthread_t thread, void **value_ptr) int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex, const pthread_mutexattr_t *GIT_RESTRICT GIT_UNUSED(mutexattr)) { - GIT_UNUSED_ARG(mutexattr); + GIT_UNUSED_ARG(mutexattr); InitializeCriticalSection(mutex); return 0; } int pthread_mutex_destroy(pthread_mutex_t *mutex) { - int ret; - ret = CloseHandle(mutex); - return -(!ret); + DeleteCriticalSection(mutex); + return 0; } int pthread_mutex_lock(pthread_mutex_t *mutex) From 02f9e637a1d282d03ce3362e7c3467e73c87bf2e Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Thu, 5 May 2011 01:12:17 +0300 Subject: [PATCH 085/120] errors: Add error handling function --- include/git2/thread-utils.h | 1 + src/common.h | 2 ++ src/errors.c | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/include/git2/thread-utils.h b/include/git2/thread-utils.h index fb8644b93..e26876bea 100644 --- a/include/git2/thread-utils.h +++ b/include/git2/thread-utils.h @@ -35,6 +35,7 @@ #if defined(__APPLE__) && defined(__MACH__) # undef GIT_TLS +# define GIT_TLS #elif defined(__GNUC__) || \ defined(__SUNPRO_C) || \ diff --git a/src/common.h b/src/common.h index 5ad878e26..b3f5253a9 100644 --- a/src/common.h +++ b/src/common.h @@ -56,4 +56,6 @@ typedef SSIZE_T ssize_t; #define GIT_PATH_MAX 4096 +extern int git__error(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); + #endif /* INCLUDE_common_h__ */ diff --git a/src/errors.c b/src/errors.c index c3a495cc9..b0c1c1519 100644 --- a/src/errors.c +++ b/src/errors.c @@ -1,6 +1,33 @@ +/* + * 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 "git2/thread-utils.h" /* for GIT_TLS */ #include "thread-utils.h" /* for GIT_TLS */ +#include + static struct { int num; const char *str; @@ -46,3 +73,16 @@ const char *git_strerror(int num) return "Unknown error"; } + +static GIT_TLS char g_last_error[1024]; + +int git__error(int error, const char *msg, ...) +{ + va_list va; + + va_start(va, msg); + vsnprintf(g_last_error, sizeof(g_last_error), msg, va); + va_end(va); + + return error; +} From 3f53c97113deb986b58aac8bb89f76b47e76507b Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Thu, 5 May 2011 01:20:27 +0300 Subject: [PATCH 086/120] errors: Set error messages on memory allocation --- src/common.h | 4 ++-- src/util.h | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/common.h b/src/common.h index b3f5253a9..351d6696f 100644 --- a/src/common.h +++ b/src/common.h @@ -50,12 +50,12 @@ typedef SSIZE_T ssize_t; #include "git2/common.h" #include "git2/types.h" -#include "util.h" #include "thread-utils.h" #include "bswap.h" #define GIT_PATH_MAX 4096 - extern int git__error(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); +#include "util.h" + #endif /* INCLUDE_common_h__ */ diff --git a/src/util.h b/src/util.h index 3c606493f..f5f0b8662 100644 --- a/src/util.h +++ b/src/util.h @@ -6,16 +6,41 @@ #define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits)))) /* - * Don't wrap malloc/calloc. - * Use the default versions in glibc, and make - * sure that any methods that allocate memory - * return a GIT_ENOMEM error when allocation - * fails. + * Custom memory allocation wrappers + * that set error code and error message + * on allocation failure */ -#define git__malloc malloc -#define git__calloc calloc -#define git__realloc realloc -#define git__strdup strdup +GIT_INLINE(void *) git__malloc(size_t len) +{ + void *ptr = malloc(len); + if (!ptr) + git__error(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)len); + return ptr; +} + +GIT_INLINE(void *) git__calloc(size_t nelem, size_t elsize) +{ + void *ptr = calloc(nelem, elsize); + if (!ptr) + git__error(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)elsize); + return ptr; +} + +GIT_INLINE(char *) git__strdup(const char *str) +{ + char *ptr = strdup(str); + if (!ptr) + git__error(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + return ptr; +} + +GIT_INLINE(void *) git__realloc(void *ptr, size_t size) +{ + void *new_ptr = realloc(ptr, size); + if (!new_ptr) + git__error(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)size); + return new_ptr; +} extern int git__fmt(char *, size_t, const char *, ...) GIT_FORMAT_PRINTF(3, 4); From 5eb0fab846d6b2f8bcf3caf7a9e33afa36753850 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Thu, 5 May 2011 01:49:27 +0300 Subject: [PATCH 087/120] errors: Update external API with new `git_lasterror` --- include/git2/common.h | 3 +++ include/git2/errors.h | 8 +++---- src/errors.c | 52 +++++-------------------------------------- 3 files changed, 13 insertions(+), 50 deletions(-) diff --git a/include/git2/common.h b/include/git2/common.h index 22c7cc466..1d2d3a3e9 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -170,6 +170,9 @@ /** The given literal is not a valid number */ #define GIT_ENOTNUM (GIT_ERROR - 25) +/** Streaming error */ +#define GIT_ESTREAM (GIT_ERROR - 26) + GIT_BEGIN_DECL typedef struct { diff --git a/include/git2/errors.h b/include/git2/errors.h index 627e67c70..fde0dc73d 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -34,11 +34,11 @@ GIT_BEGIN_DECL /** - * strerror() for the Git library - * @param num The error code to explain - * @return a string explaining the error code + * Return a detailed error string with the latest error + * that occurred in the library. + * @return a string explaining the error */ -GIT_EXTERN(const char *) git_strerror(int num); +GIT_EXTERN(const char *) git_lasterror(void); /** @} */ GIT_END_DECL diff --git a/src/errors.c b/src/errors.c index b0c1c1519..73df2e209 100644 --- a/src/errors.c +++ b/src/errors.c @@ -28,52 +28,6 @@ #include -static struct { - int num; - const char *str; -} error_codes[] = { - {GIT_ERROR, "Unspecified error"}, - {GIT_ENOTOID, "Input was not a properly formatted Git object id."}, - {GIT_ENOTFOUND, "Object does not exist in the scope searched."}, - {GIT_ENOMEM, "Not enough space available."}, - {GIT_EOSERR, "Consult the OS error information."}, - {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_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"}, - {GIT_EZLIB, "The Z library failed to inflate/deflate an object's data"}, - {GIT_EBUSY, "The queried object is currently busy"}, - {GIT_EINVALIDPATH, "The path is invalid"}, - {GIT_EBAREINDEX, "The index file is not backed up by an existing repository"}, - {GIT_EINVALIDREFNAME, "The name of the reference is not valid"}, - {GIT_EREFCORRUPTED, "The specified reference has its data corrupted"}, - {GIT_ETOONESTEDSYMREF, "The specified symbolic reference is too deeply nested"}, - {GIT_EPACKEDREFSCORRUPTED, "The pack-refs file is either corrupted of its format is not currently supported"}, - {GIT_EINVALIDPATH, "The path is invalid" }, - {GIT_EREVWALKOVER, "The revision walker is empty; there are no more commits left to iterate"}, - {GIT_EINVALIDREFSTATE, "The state of the reference is not valid"}, - {GIT_ENOTIMPLEMENTED, "This feature has not been implemented yet"}, - {GIT_EEXISTS, "A reference with this name already exists"}, - {GIT_EOVERFLOW, "The given integer literal is too large to be parsed"}, - {GIT_ENOTNUM, "The given literal is not a valid number"}, -}; - -const char *git_strerror(int num) -{ - size_t i; - - if (num == GIT_EOSERR) - return strerror(errno); - for (i = 0; i < ARRAY_SIZE(error_codes); i++) - if (num == error_codes[i].num) - return error_codes[i].str; - - return "Unknown error"; -} - static GIT_TLS char g_last_error[1024]; int git__error(int error, const char *msg, ...) @@ -86,3 +40,9 @@ int git__error(int error, const char *msg, ...) return error; } + +const char *git_lasterror(void) +{ + return g_last_error; +} + From fa59f18d0ddbbb98d45e33934fb0efc3e2bf1557 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Mon, 9 May 2011 20:54:04 +0300 Subject: [PATCH 088/120] Change error handling mechanism once again Ok, this is the real deal. Hopefully. Here's how it's going to work: - One main method, called `git__throw`, that sets the error code and error message when an error happens. This method must be called in every single place where an error code was being returned previously, setting an error message instead. Example, instead of: return GIT_EOBJCORRUPTED; Use: return git__throw(GIT_EOBJCORRUPTED, "The object is missing a finalizing line feed"); And instead of: [...] { error = GIT_EOBJCORRUPTED; goto cleanup; } Use: [...] { error = git__throw(GIT_EOBJCORRUPTED, "What an error!"); goto cleanup; } The **only** exception to this are the allocation methods, which return NULL on failure but already set the message manually. /* only place where an error code can be returned directly, because the error message has already been set by the wrapper */ if (foo == NULL) return GIT_ENOMEM; - One secondary method, called `git__rethrow`, which can be used to fine-grain an error message and build an error stack. Example, instead of: if ((error = foobar(baz)) < GIT_SUCCESS) return error; You can now do: if ((error = foobar(baz)) < GIT_SUCCESS) return git__rethrow(error, "Failed to do a major operation"); The return of the `git_lasterror` method will be a string in the shape of: "Failed to do a major operation. (Failed to do an internal operation)" E.g. "Failed to open the index. (Not enough permissions to access '/path/to/index')." NOTE: do not abuse this method. Try to write all `git__throw` messages in a descriptive manner, to avoid having to rethrow them to clarify their meaning. This method should only be used in the places where the original error message set by a subroutine is not specific enough. It is encouraged to continue using this style as much possible to enforce error propagation: if ((error = foobar(baz)) < GIT_SUCCESS) return error; /* `foobar` has set an error message, and we are just propagating it */ The error handling revamp will take place in two phases: - Phase 1: Replace all pieces of code that return direct error codes with calls to `git__throw`. This can be done semi-automatically using `ack` to locate all the error codes that must be replaced. - Phase 2: Add some `git__rethrow` calls in those cases where the original error messages are not specific enough. Phase 1 is the main goal. A minor libgit2 release will be shipped once Phase 1 is ready, and the work will start on gradually improving the error handling mechanism by refining specific error messages. OTHER NOTES: - When writing error messages, please refrain from using weasel words. They add verbosity to the message without giving any real information. (<3 Emeric) E.g. "The reference file appears to be missing a carriage return" Nope. "The reference file is missing a carriage return" Yes. - When calling `git__throw`, please try to use more generic error codes so we can eventually reduce the list of error codes to something more reasonable. Feel free to add new, more generic error codes if these are going to replace several of the old ones. E.g. return GIT_EREFCORRUPTED; Can be turned into: return git__throw(GIT_EOBJCORRUPTED, "The reference is corrupted"); --- include/git2/common.h | 3 +++ src/common.h | 3 ++- src/errors.c | 20 +++++++++++++++++++- src/refs.c | 33 +++++++++++++++++++-------------- src/util.h | 8 ++++---- 5 files changed, 47 insertions(+), 20 deletions(-) diff --git a/include/git2/common.h b/include/git2/common.h index 1d2d3a3e9..2aae648fb 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -173,6 +173,9 @@ /** Streaming error */ #define GIT_ESTREAM (GIT_ERROR - 26) +/** invalid arguments to function */ +#define GIT_EINVALIDARGS (GIT_ERROR - 27) + GIT_BEGIN_DECL typedef struct { diff --git a/src/common.h b/src/common.h index 351d6696f..e5b9f15ed 100644 --- a/src/common.h +++ b/src/common.h @@ -54,7 +54,8 @@ typedef SSIZE_T ssize_t; #include "bswap.h" #define GIT_PATH_MAX 4096 -extern int git__error(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); +extern int git__throw(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); +extern int git__rethrow(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); #include "util.h" diff --git a/src/errors.c b/src/errors.c index 73df2e209..40b0feb91 100644 --- a/src/errors.c +++ b/src/errors.c @@ -30,7 +30,25 @@ static GIT_TLS char g_last_error[1024]; -int git__error(int error, const char *msg, ...) +int git__rethrow(int error, const char *msg, ...) +{ + char new_error[1024]; + char *old_error = NULL; + + va_list va; + + va_start(va, msg); + vsnprintf(new_error, sizeof(new_error), msg, va); + va_end(va); + + old_error = strdup(g_last_error); + snprintf(g_last_error, sizeof(g_last_error), "%s \n - %s", new_error, old_error); + free(old_error); + + return error; +} + +int git__throw(int error, const char *msg, ...) { va_list va; diff --git a/src/refs.c b/src/refs.c index ea968196f..c4d3d6ae6 100644 --- a/src/refs.c +++ b/src/refs.c @@ -122,7 +122,8 @@ static int reference_create( else if (type == GIT_REF_OID) size = sizeof(reference_oid); else - return GIT_EINVALIDREFSTATE; + return git__throw(GIT_EINVALIDARGS, + "Invalid reference type. Use either GIT_REF_OID or GIT_REF_SYMBOLIC as type specifier"); reference = git__malloc(size); if (reference == NULL) @@ -159,11 +160,9 @@ static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *re /* Determine the full path of the file */ git__joinpath(path, repo_path, ref_name); - if (gitfo_stat(path, &st) < 0) - return GIT_ENOTFOUND; - - if (S_ISDIR(st.st_mode)) - return GIT_EOBJCORRUPTED; + if (gitfo_stat(path, &st) < 0 || S_ISDIR(st.st_mode)) + return git__throw(GIT_ENOTFOUND, + "Cannot read reference file '%s'", ref_name); if (mtime) *mtime = st.st_mtime; @@ -205,7 +204,8 @@ static int loose_update(git_reference *ref) else if (ref->type == GIT_REF_OID) error = loose_parse_oid(ref, &ref_file); else - error = GIT_EINVALIDREFSTATE; + error = git__throw(GIT_EOBJCORRUPTED, + "Invalid reference type (%d) for loose reference", ref->type); gitfo_free_buf(&ref_file); @@ -229,7 +229,8 @@ static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content) ref_sym = (reference_symbolic *)ref; if (file_content->len < (header_len + 1)) - return GIT_EREFCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, + "Failed to parse loose reference. Object too short"); /* * Assume we have already checked for the header @@ -246,7 +247,8 @@ static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content) /* remove newline at the end of file */ eol = strchr(ref_sym->target, '\n'); if (eol == NULL) - return GIT_EREFCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, + "Failed to parse loose reference. Missing EOL"); *eol = '\0'; if (eol[-1] == '\r') @@ -257,6 +259,7 @@ static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content) static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content) { + int error; reference_oid *ref_oid; char *buffer; @@ -265,17 +268,19 @@ static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content) /* File format: 40 chars (OID) + newline */ if (file_content->len < GIT_OID_HEXSZ + 1) - return GIT_EREFCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, + "Failed to parse loose reference. Reference too short"); - if (git_oid_mkstr(&ref_oid->oid, buffer) < GIT_SUCCESS) - return GIT_EREFCORRUPTED; + if ((error = git_oid_mkstr(&ref_oid->oid, buffer)) < GIT_SUCCESS) + return git__rethrow(GIT_EOBJCORRUPTED, "Failed to parse loose reference."); buffer = buffer + GIT_OID_HEXSZ; if (*buffer == '\r') buffer++; if (*buffer != '\n') - return GIT_EREFCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, + "Failed to parse loose reference. Missing EOL"); return GIT_SUCCESS; } @@ -387,7 +392,7 @@ static int loose_write(git_reference *ref) strcpy(ref_contents, GIT_SYMREF); strcat(ref_contents, ref_sym->target); } else { - error = GIT_EINVALIDREFSTATE; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to write reference. Invalid reference type"); goto unlock; } diff --git a/src/util.h b/src/util.h index f5f0b8662..6724e8d41 100644 --- a/src/util.h +++ b/src/util.h @@ -14,7 +14,7 @@ GIT_INLINE(void *) git__malloc(size_t len) { void *ptr = malloc(len); if (!ptr) - git__error(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)len); + git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)len); return ptr; } @@ -22,7 +22,7 @@ GIT_INLINE(void *) git__calloc(size_t nelem, size_t elsize) { void *ptr = calloc(nelem, elsize); if (!ptr) - git__error(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)elsize); + git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)elsize); return ptr; } @@ -30,7 +30,7 @@ GIT_INLINE(char *) git__strdup(const char *str) { char *ptr = strdup(str); if (!ptr) - git__error(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); return ptr; } @@ -38,7 +38,7 @@ GIT_INLINE(void *) git__realloc(void *ptr, size_t size) { void *new_ptr = realloc(ptr, size); if (!new_ptr) - git__error(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)size); + git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)size); return new_ptr; } From ab86f159e5edbf0ffaf3ec2eb86584a6758a7a49 Mon Sep 17 00:00:00 2001 From: "kelly.leahy" Date: Mon, 9 May 2011 23:39:32 -0700 Subject: [PATCH 089/120] Fix issue #79 - git_lasterror() isn't appearing in git2.dll in windows. The GIT_EXPORT macro is used to declare a function to be externally accessible to other libraries. This commit uses GIT_EXPORT to declare the git_lasterror() function as externally exported. I verified with depends.exe that the function is available to external callers (i.e. in the exports table of the PE file). --- include/git2/common.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/git2/common.h b/include/git2/common.h index 2aae648fb..54af72fbf 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -185,6 +185,8 @@ typedef struct { GIT_EXTERN(void) git_strarray_free(git_strarray *array); +GIT_EXTERN(const char*) git_lasterror(void); + /** @} */ GIT_END_DECL #endif 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 090/120] 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 5de24ec7365cbf2894f40a59576745a2951859e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 10 May 2011 17:38:41 +0200 Subject: [PATCH 091/120] Move signature.c to the new error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/signature.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/signature.c b/src/signature.c index e8014620a..62bd28b9a 100644 --- a/src/signature.c +++ b/src/signature.c @@ -115,22 +115,22 @@ static int parse_timezone_offset(const char *buffer, long *offset_out) } if (offset_start[0] != '-' && offset_start[0] != '+') - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It doesn't start with '+' or '-'"); if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It isn't a number"); if (offset_end - offset_start != 5) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Invalid length"); hours = dec_offset / 100; mins = dec_offset % 100; if (hours > 14) // see http://www.worldtimezone.com/faq.html - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Hour value too large");; if (mins > 59) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Minute value too large"); offset = (hours * 60) + mins; @@ -157,19 +157,19 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, line_end = memchr(buffer, '\n', buffer_end - buffer); if (!line_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. No newline found");; if (buffer + (header_len + 1) > line_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short"); if (memcmp(buffer, header, header_len) != 0) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Expected prefix '%s' doesn't match actual", header); buffer += header_len; /* Parse name */ if ((name_end = memchr(buffer, '<', buffer_end - buffer)) == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Can't find e-mail start"); name_length = name_end - buffer - 1; sig->name = git__malloc(name_length + 1); @@ -181,11 +181,11 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, buffer = name_end + 1; if (buffer >= line_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Ended unexpectedly"); /* Parse email */ if ((email_end = memchr(buffer, '>', buffer_end - buffer)) == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Can't find e-mail end"); email_length = email_end - buffer; sig->email = git__malloc(email_length + 1); @@ -197,10 +197,10 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, buffer = email_end + 1; if (buffer >= line_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Ended unexpectedly"); if (git__strtol32(&time, buffer, &buffer, 10) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Timestamp isn't a number"); sig->when.time = (time_t)time; From 44dc0d261bb273d468e80d4a8242c6a4847b55b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 10 May 2011 18:56:44 +0200 Subject: [PATCH 092/120] Move tag.c to the new error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Martín Nieto --- src/tag.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/tag.c b/src/tag.c index 148eb280c..849429b7e 100644 --- a/src/tag.c +++ b/src/tag.c @@ -90,13 +90,13 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer int error; if ((error = git__parse_oid(&tag->target, &buffer, buffer_end, "object ")) < 0) - return error; + return git__rethrow(error, "Failed to parse tag. Object field invalid"); if (buffer + 5 >= buffer_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); if (memcmp(buffer, "type ", 5) != 0) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Type field not found"); buffer += 5; tag->type = GIT_OBJ_BAD; @@ -105,7 +105,7 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer size_t type_length = strlen(tag_types[i]); if (buffer + type_length >= buffer_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); if (memcmp(buffer, tag_types[i], type_length) == 0) { tag->type = i; @@ -115,18 +115,19 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer } if (tag->type == GIT_OBJ_BAD) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Invalid object type"); if (buffer + 4 >= buffer_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); if (memcmp(buffer, "tag ", 4) != 0) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Tag field not found"); + buffer += 4; search = memchr(buffer, '\n', buffer_end - buffer); if (search == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); text_len = search - buffer; @@ -218,7 +219,7 @@ static int tag_create( } if (!git_odb_exists(repo->db, target)) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Failed to create tag. Object to tag doesn't exist"); /* Try to find out what the type is */ if (target_type == GIT_OBJ_ANY) { From f4a936b56a6bb0cbe6d5f4b5c7c73066ad2f3197 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Wed, 11 May 2011 00:35:05 +0300 Subject: [PATCH 093/120] Bring back `git_strerror` We cannot totally deprecate this until the new error handling mechanisms are all in place. --- include/git2/errors.h | 12 +++++++++++ src/errors.c | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/include/git2/errors.h b/include/git2/errors.h index fde0dc73d..47b0e240f 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -40,6 +40,18 @@ GIT_BEGIN_DECL */ GIT_EXTERN(const char *) git_lasterror(void); +/** + * strerror() for the Git library + * + * Get a string description for a given error code. + * NOTE: This method will be eventually deprecated in favor + * of the new `git_lasterror`. + * + * @param num The error code to explain + * @return a string explaining the error code + */ +GIT_EXTERN(const char *) git_strerror(int num); + /** @} */ GIT_END_DECL #endif diff --git a/src/errors.c b/src/errors.c index 40b0feb91..bf3810174 100644 --- a/src/errors.c +++ b/src/errors.c @@ -30,6 +30,52 @@ static GIT_TLS char g_last_error[1024]; +static struct { + int num; + const char *str; +} error_codes[] = { + {GIT_ERROR, "Unspecified error"}, + {GIT_ENOTOID, "Input was not a properly formatted Git object id."}, + {GIT_ENOTFOUND, "Object does not exist in the scope searched."}, + {GIT_ENOMEM, "Not enough space available."}, + {GIT_EOSERR, "Consult the OS error information."}, + {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_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"}, + {GIT_EZLIB, "The Z library failed to inflate/deflate an object's data"}, + {GIT_EBUSY, "The queried object is currently busy"}, + {GIT_EINVALIDPATH, "The path is invalid"}, + {GIT_EBAREINDEX, "The index file is not backed up by an existing repository"}, + {GIT_EINVALIDREFNAME, "The name of the reference is not valid"}, + {GIT_EREFCORRUPTED, "The specified reference has its data corrupted"}, + {GIT_ETOONESTEDSYMREF, "The specified symbolic reference is too deeply nested"}, + {GIT_EPACKEDREFSCORRUPTED, "The pack-refs file is either corrupted of its format is not currently supported"}, + {GIT_EINVALIDPATH, "The path is invalid" }, + {GIT_EREVWALKOVER, "The revision walker is empty; there are no more commits left to iterate"}, + {GIT_EINVALIDREFSTATE, "The state of the reference is not valid"}, + {GIT_ENOTIMPLEMENTED, "This feature has not been implemented yet"}, + {GIT_EEXISTS, "A reference with this name already exists"}, + {GIT_EOVERFLOW, "The given integer literal is too large to be parsed"}, + {GIT_ENOTNUM, "The given literal is not a valid number"}, +}; + +const char *git_strerror(int num) +{ + size_t i; + + if (num == GIT_EOSERR) + return strerror(errno); + for (i = 0; i < ARRAY_SIZE(error_codes); i++) + if (num == error_codes[i].num) + return error_codes[i].str; + + return "Unknown error"; +} + int git__rethrow(int error, const char *msg, ...) { char new_error[1024]; From 6810bf28f8cecaeaf9cf32bee4bf0d5b55439819 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Wed, 11 May 2011 00:40:07 +0300 Subject: [PATCH 094/120] Move all error-related defines to `git2/errors.h` --- include/git2/common.h | 94 ------------------------------------------- include/git2/errors.h | 92 ++++++++++++++++++++++++++++++++++++++++++ src/common.h | 1 + 3 files changed, 93 insertions(+), 94 deletions(-) diff --git a/include/git2/common.h b/include/git2/common.h index 54af72fbf..9a27ac2e5 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -84,98 +84,6 @@ * @{ */ -/** Operation completed successfully. */ -#define GIT_SUCCESS 0 - -/** - * Operation failed, with unspecified reason. - * This value also serves as the base error code; all other - * error codes are subtracted from it such that all errors - * are < 0, in typical POSIX C tradition. - */ -#define GIT_ERROR -1 - -/** Input was not a properly formatted Git object id. */ -#define GIT_ENOTOID (GIT_ERROR - 1) - -/** Input does not exist in the scope searched. */ -#define GIT_ENOTFOUND (GIT_ERROR - 2) - -/** Not enough space available. */ -#define GIT_ENOMEM (GIT_ERROR - 3) - -/** Consult the OS error information. */ -#define GIT_EOSERR (GIT_ERROR - 4) - -/** The specified object is of invalid type */ -#define GIT_EOBJTYPE (GIT_ERROR - 5) - -/** The specified object has its data corrupted */ -#define GIT_EOBJCORRUPTED (GIT_ERROR - 6) - -/** The specified repository is invalid */ -#define GIT_ENOTAREPO (GIT_ERROR - 7) - -/** The object type is invalid or doesn't match */ -#define GIT_EINVALIDTYPE (GIT_ERROR - 8) - -/** The object cannot be written because it's missing internal data */ -#define GIT_EMISSINGOBJDATA (GIT_ERROR - 9) - -/** The packfile for the ODB is corrupted */ -#define GIT_EPACKCORRUPTED (GIT_ERROR - 10) - -/** Failed to acquire or release a file lock */ -#define GIT_EFLOCKFAIL (GIT_ERROR - 11) - -/** The Z library failed to inflate/deflate an object's data */ -#define GIT_EZLIB (GIT_ERROR - 12) - -/** The queried object is currently busy */ -#define GIT_EBUSY (GIT_ERROR - 13) - -/** The index file is not backed up by an existing repository */ -#define GIT_EBAREINDEX (GIT_ERROR - 14) - -/** The name of the reference is not valid */ -#define GIT_EINVALIDREFNAME (GIT_ERROR - 15) - -/** The specified reference has its data corrupted */ -#define GIT_EREFCORRUPTED (GIT_ERROR - 16) - -/** The specified symbolic reference is too deeply nested */ -#define GIT_ETOONESTEDSYMREF (GIT_ERROR - 17) - -/** The pack-refs file is either corrupted or its format is not currently supported */ -#define GIT_EPACKEDREFSCORRUPTED (GIT_ERROR - 18) - -/** The path is invalid */ -#define GIT_EINVALIDPATH (GIT_ERROR - 19) - -/** The revision walker is empty; there are no more commits left to iterate */ -#define GIT_EREVWALKOVER (GIT_ERROR - 20) - -/** The state of the reference is not valid */ -#define GIT_EINVALIDREFSTATE (GIT_ERROR - 21) - -/** This feature has not been implemented yet */ -#define GIT_ENOTIMPLEMENTED (GIT_ERROR - 22) - -/** A reference with this name already exists */ -#define GIT_EEXISTS (GIT_ERROR - 23) - -/** The given integer literal is too large to be parsed */ -#define GIT_EOVERFLOW (GIT_ERROR - 24) - -/** The given literal is not a valid number */ -#define GIT_ENOTNUM (GIT_ERROR - 25) - -/** Streaming error */ -#define GIT_ESTREAM (GIT_ERROR - 26) - -/** invalid arguments to function */ -#define GIT_EINVALIDARGS (GIT_ERROR - 27) - GIT_BEGIN_DECL typedef struct { @@ -185,8 +93,6 @@ typedef struct { GIT_EXTERN(void) git_strarray_free(git_strarray *array); -GIT_EXTERN(const char*) git_lasterror(void); - /** @} */ GIT_END_DECL #endif diff --git a/include/git2/errors.h b/include/git2/errors.h index 47b0e240f..dbe565aab 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -33,6 +33,98 @@ */ GIT_BEGIN_DECL +/** Operation completed successfully. */ +#define GIT_SUCCESS 0 + +/** + * Operation failed, with unspecified reason. + * This value also serves as the base error code; all other + * error codes are subtracted from it such that all errors + * are < 0, in typical POSIX C tradition. + */ +#define GIT_ERROR -1 + +/** Input was not a properly formatted Git object id. */ +#define GIT_ENOTOID (GIT_ERROR - 1) + +/** Input does not exist in the scope searched. */ +#define GIT_ENOTFOUND (GIT_ERROR - 2) + +/** Not enough space available. */ +#define GIT_ENOMEM (GIT_ERROR - 3) + +/** Consult the OS error information. */ +#define GIT_EOSERR (GIT_ERROR - 4) + +/** The specified object is of invalid type */ +#define GIT_EOBJTYPE (GIT_ERROR - 5) + +/** The specified object has its data corrupted */ +#define GIT_EOBJCORRUPTED (GIT_ERROR - 6) + +/** The specified repository is invalid */ +#define GIT_ENOTAREPO (GIT_ERROR - 7) + +/** The object type is invalid or doesn't match */ +#define GIT_EINVALIDTYPE (GIT_ERROR - 8) + +/** The object cannot be written because it's missing internal data */ +#define GIT_EMISSINGOBJDATA (GIT_ERROR - 9) + +/** The packfile for the ODB is corrupted */ +#define GIT_EPACKCORRUPTED (GIT_ERROR - 10) + +/** Failed to acquire or release a file lock */ +#define GIT_EFLOCKFAIL (GIT_ERROR - 11) + +/** The Z library failed to inflate/deflate an object's data */ +#define GIT_EZLIB (GIT_ERROR - 12) + +/** The queried object is currently busy */ +#define GIT_EBUSY (GIT_ERROR - 13) + +/** The index file is not backed up by an existing repository */ +#define GIT_EBAREINDEX (GIT_ERROR - 14) + +/** The name of the reference is not valid */ +#define GIT_EINVALIDREFNAME (GIT_ERROR - 15) + +/** The specified reference has its data corrupted */ +#define GIT_EREFCORRUPTED (GIT_ERROR - 16) + +/** The specified symbolic reference is too deeply nested */ +#define GIT_ETOONESTEDSYMREF (GIT_ERROR - 17) + +/** The pack-refs file is either corrupted or its format is not currently supported */ +#define GIT_EPACKEDREFSCORRUPTED (GIT_ERROR - 18) + +/** The path is invalid */ +#define GIT_EINVALIDPATH (GIT_ERROR - 19) + +/** The revision walker is empty; there are no more commits left to iterate */ +#define GIT_EREVWALKOVER (GIT_ERROR - 20) + +/** The state of the reference is not valid */ +#define GIT_EINVALIDREFSTATE (GIT_ERROR - 21) + +/** This feature has not been implemented yet */ +#define GIT_ENOTIMPLEMENTED (GIT_ERROR - 22) + +/** A reference with this name already exists */ +#define GIT_EEXISTS (GIT_ERROR - 23) + +/** The given integer literal is too large to be parsed */ +#define GIT_EOVERFLOW (GIT_ERROR - 24) + +/** The given literal is not a valid number */ +#define GIT_ENOTNUM (GIT_ERROR - 25) + +/** Streaming error */ +#define GIT_ESTREAM (GIT_ERROR - 26) + +/** invalid arguments to function */ +#define GIT_EINVALIDARGS (GIT_ERROR - 27) + /** * Return a detailed error string with the latest error * that occurred in the library. diff --git a/src/common.h b/src/common.h index e5b9f15ed..f4f11fd2f 100644 --- a/src/common.h +++ b/src/common.h @@ -50,6 +50,7 @@ typedef SSIZE_T ssize_t; #include "git2/common.h" #include "git2/types.h" +#include "git2/errors.h" #include "thread-utils.h" #include "bswap.h" From 40774549e14e2d9f24b9271173d58b44f82d5254 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Wed, 11 May 2011 00:42:43 +0300 Subject: [PATCH 095/120] libgit2 v0.12.0 "absolutely no reason" Hey, welcome to yet another minor libgit2 release. Sorry for the delay from the last one. As you'll see the changelog is quite extensive -- hopefully from now on we'll stick to more frequent minor releases. Together with the usual bugfixes, here's a list of the new key features: * Distfiles This version comes with proper distfiles as requested in #131. These are available in the Downloads section of the GitHub project. * Error handling A new error handling API has been implemented that allows the library to return detailed error messages together with the generic error codes. We hope this will be a great when wrapping and integrating the library New external method to get the last detailed error message: + git_lasterror(void) The old `git_strerror` still exists, but will be deprecated in the future as soon as every method in the library returns a valid error message. The task of writing error messages for every method is quite daunting. We appreciate pull requests with more error messages. Check the new error handling documentation in the following commit: https://github.com/libgit2/libgit2/commit/fa59f18d0ddbbb98d45e33934fb0efc3e2bf1557 * Redis backend We now have a Redis backend courtesy of Dmitry Kovega. Just like the SQLite backend, this allows the library to store Git objects in a Redis key-value store. The backend requires the `hiredis` library. Use `--with-redis` when building libgit2 to enable building the backend if `hiredis` is available. * Commits New methods to access tree and parent data as a raw OID value instead of forcing a repository lookup + git_commit_tree_oid(git_commit *commit) + git_commit_parent_oid(git_commit *commit, unsigned int n) * Index The `git_index_add` method has been split into 4 different calls which allow for appending and replacing in-memory entries and on-disk files to the index. + git_index_add(git_index *index, const char *path, int stage) + git_index_add2(git_index *index, const git_index_entry *source_entry) + git_index_append(git_index *index, const char *path, int stage) + git_index_append2(git_index *index, const git_index_entry *source_entry) Index entries can now also be efficiently removed from the index: + git_index_remove(git_index *index, int position) * References Methods to force the creation and renaming of references, even if those already exist on the repository. + git_reference_create_symbolic_f(git_reference **ref_out, git_repository *repo, const char *name, const char *target) + git_reference_create_oid_f(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id) + git_reference_rename_f(git_reference *ref, const char *new_name) * Repository New auxiliary methods with repository information + git_repository_is_empty(git_repository *repo) + git_repository_path(git_repository *repo) + git_repository_workdir(git_repository *repo) * Signatures New method to create a signature with the current date/time + git_signature_now(const char *name, const char *email) * Tags Several wrappers to automate tag creation. + git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer) + git_tag_create_f(git_oid *oid, git_repository *repo, const char *tag_name, const git_oid *target, git_otype target_type, const git_signature *tagger, const char *message); + git_tag_create_fo(git_oid *oid, git_repository *repo, const char *tag_name, const git_object *target, const git_signature *tagger, const char *message) New functionality to delete and list tags in a repository without having to resort to the `references` API. + git_tag_delete(git_repository *repo, const char *tag_name) + git_tag_list(git_strarray *tag_names, git_repository *repo) * Trees All instances of `git_tree_entry` are now returned and handled as constant, to remind the user that these opaque types are not supposed to be manually free'd. The `git_tree_entry_2object` method now takes a `git_repository` argument which defines in which repository the resolved object should be looked up. (It is expected to be the same repository that contains the parent `git_tree` for the entry). + git_tree_entry_2object(git_object **object_out, git_repository *repo, const git_tree_entry *entry) New opaque type `git_treebuilder` with functionality to create and write trees on memory + git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) + git_treebuilder_clear(git_treebuilder *bld) + git_treebuilder_free(git_treebuilder *bld) + git_treebuilder_get(git_treebuilder *bld, const char *filename) + git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes) + git_treebuilder_remove(git_treebuilder *bld, const char *filename) + git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_entry *, void *), void *payload) + git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld) New method to write an index file as a tree to the ODB. + git_tree_create_fromindex(git_oid *oid, git_index *index) Thanks to the usual guility parties that make this this happen, to all the new contributors who are starting to submit pull requests, and to the bindings developers who have to keep up with our shit. Feedback and questions welcome on libgit2@librelist.org Signed-off-by: Vicent Marti --- include/git2.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/git2.h b/include/git2.h index 5756d7e90..d44c3f8df 100644 --- a/include/git2.h +++ b/include/git2.h @@ -26,9 +26,9 @@ #ifndef INCLUDE_git_git_h__ #define INCLUDE_git_git_h__ -#define LIBGIT2_VERSION "0.11.0" +#define LIBGIT2_VERSION "0.12.0" #define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 11 +#define LIBGIT2_VER_MINOR 12 #define LIBGIT2_VER_REVISION 0 #include "git2/common.h" From 0e0f4fdeee8e6009187b84a26b4b8c02452ff224 Mon Sep 17 00:00:00 2001 From: Daniel Huckstep Date: Tue, 10 May 2011 15:33:07 -0700 Subject: [PATCH 096/120] Add --with-redis flag to list of available build flags in README. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index dae6a76bf..b6779bd84 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,9 @@ The waf build system for libgit2 accepts the following flags: --with-sqlite Enable sqlite support. + --with-redis + Enable redis support. + You can run `./waf --help` to see a full list of install options and targets. From cbcaf0c09cd50ee705ea9740d57b722900f4e723 Mon Sep 17 00:00:00 2001 From: schu Date: Wed, 11 May 2011 12:09:40 +0200 Subject: [PATCH 097/120] Move blob.c to the new error handling Signed-off-by: schu --- src/blob.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blob.c b/src/blob.c index 5e3c22fbf..987169358 100644 --- a/src/blob.c +++ b/src/blob.c @@ -62,7 +62,7 @@ int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *b git_odb_stream *stream; if ((error = git_odb_open_wstream(&stream, repo->db, len, GIT_OBJ_BLOB)) < GIT_SUCCESS) - return error; + return git__rethrow(error, "Failed to create blob. Can't open write stream"); stream->write(stream, buffer, len); @@ -81,7 +81,7 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat git_odb_stream *stream; if (repo->path_workdir == NULL) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Failed to create blob. No workdir given"); git__joinpath(full_path, repo->path_workdir, path); @@ -106,7 +106,7 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat if (read_len < 0) { gitfo_close(fd); stream->free(stream); - return GIT_EOSERR; + return git__throw(GIT_EOSERR, "Failed to create blob. Can't read full file"); } stream->write(stream, buffer, read_len); From d6de92b6fe5d04111c3e9cd4a0c269c840ce59b6 Mon Sep 17 00:00:00 2001 From: schu Date: Wed, 11 May 2011 12:40:04 +0200 Subject: [PATCH 098/120] Move tree.c to the new error handling Signed-off-by: schu --- src/tree.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tree.c b/src/tree.c index b7daf39c4..6f9fc8607 100644 --- a/src/tree.c +++ b/src/tree.c @@ -151,15 +151,15 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf return GIT_ENOMEM; if (git__strtol32((long *)&entry->attr, buffer, &buffer, 8) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Can't parse attributes"); if (*buffer++ != ' ') { - error = GIT_EOBJCORRUPTED; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted"); break; } if (memchr(buffer, 0, buffer_end - buffer) == NULL) { - error = GIT_EOBJCORRUPTED; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted"); break; } @@ -274,7 +274,7 @@ int git_tree_create_fromindex(git_oid *oid, git_index *index) int error; if (index->repository == NULL) - return GIT_EBAREINDEX; + return git__throw(GIT_EBAREINDEX, "Failed to create tree. The index file is not backed up by an existing repository"); error = write_index(oid, index, "", 0, 0, git_index_entrycount(index)); return (error < GIT_SUCCESS) ? error : GIT_SUCCESS; @@ -343,7 +343,7 @@ int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, con assert(bld && id && filename); if (!valid_attributes(attributes)) - return GIT_ERROR; + return git__throw(GIT_ERROR, "Failed to insert entry. Invalid atrributes"); if ((pos = git_vector_bsearch2(&bld->entries, entry_search_cmp, filename)) != GIT_ENOTFOUND) { entry = git_vector_get(&bld->entries, pos); @@ -400,7 +400,7 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename) git_tree_entry *remove_ptr = (git_tree_entry *)git_treebuilder_get(bld, filename); if (remove_ptr == NULL || remove_ptr->removed) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Failed to remove entry. File isn't in the tree"); remove_ptr->removed = 1; bld->entry_count--; @@ -431,7 +431,7 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b } if ((error = git_odb_open_wstream(&stream, git_repository_database(repo), size, GIT_OBJ_TREE)) < GIT_SUCCESS) - return error; + return git__rethrow(error, "Failed to write tree. Can't open write stream"); for (i = 0; i < bld->entries.length; ++i) { git_tree_entry *entry = bld->entries.contents[i]; From 86f5fa7810c5675d5c789450c9c140330432bbbb Mon Sep 17 00:00:00 2001 From: schu Date: Wed, 11 May 2011 14:00:38 +0200 Subject: [PATCH 099/120] Move vector.c to the new error handling Remove "redundant" check for v->_cmp in wrapper function git_vector_bsearch(). Signed-off-by: schu --- src/vector.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vector.c b/src/vector.c index d0b0c5c56..1ddc26e3e 100644 --- a/src/vector.c +++ b/src/vector.c @@ -106,7 +106,7 @@ int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *ke /* need comparison function to sort the vector */ if (v->_cmp == NULL) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Can't sort vector. No comparison function set"); git_vector_sort(v); @@ -114,7 +114,7 @@ int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *ke if (find != NULL) return (int)(find - v->contents); - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Can't find element"); } int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key) @@ -128,7 +128,7 @@ int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key return i; } - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Can't find element"); } static int strict_comparison(const void *a, const void *b) @@ -143,9 +143,6 @@ int git_vector_search(git_vector *v, const void *entry) int git_vector_bsearch(git_vector *v, const void *key) { - if (v->_cmp == NULL) - return GIT_ENOTFOUND; - return git_vector_bsearch2(v, v->_cmp, key); } @@ -156,7 +153,7 @@ int git_vector_remove(git_vector *v, unsigned int idx) assert(v); if (idx >= v->length || v->length == 0) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Can't remove element. Index out of bounds"); for (i = idx; i < v->length - 1; ++i) v->contents[i] = v->contents[i + 1]; From b51c92693d07dab0cb31311b9ad6b96aac127779 Mon Sep 17 00:00:00 2001 From: schu Date: Wed, 11 May 2011 14:44:44 +0200 Subject: [PATCH 100/120] Move revwalk.c to the new error handling Signed-off-by: schu --- src/revwalk.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/revwalk.c b/src/revwalk.c index 78798480f..a32a16e2a 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -210,7 +210,7 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo git_oid oid; if (git_oid_mkstr(&oid, (char *)buffer + STRLEN("parent ")) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Parent object is corrupted"); commit->parents[i] = commit_lookup(walk, &oid); if (commit->parents[i] == NULL) @@ -222,14 +222,14 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo commit->out_degree = (unsigned short)parents; if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Object is corrupted"); buffer = memchr(buffer, '>', buffer_end - buffer); if (buffer == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't find author"); if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't parse commit time"); commit->time = (time_t)commit_time; commit->parsed = 1; @@ -245,11 +245,11 @@ static int commit_parse(git_revwalk *walk, commit_object *commit) return GIT_SUCCESS; if ((error = git_odb_read(&obj, walk->repo->db, &commit->oid)) < GIT_SUCCESS) - return error; + return git__rethrow(error, "Failed to parse commit. Can't read object"); if (obj->raw.type != GIT_OBJ_COMMIT) { git_odb_object_close(obj); - return GIT_EOBJTYPE; + return git__throw(GIT_EOBJTYPE, "Failed to parse commit. Object is no commit object"); } error = commit_quick_parse(walk, commit, &obj->raw); @@ -305,7 +305,7 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) commit = commit_lookup(walk, oid); if (commit == NULL) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Failed to push commit. Object not found"); commit->uninteresting = uninteresting; @@ -349,7 +349,7 @@ static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk) } } - return GIT_EREVWALKOVER; + return git__throw(GIT_EREVWALKOVER, "No more commits left to iterate"); } static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) @@ -367,7 +367,7 @@ static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) } } - return GIT_EREVWALKOVER; + return git__throw(GIT_EREVWALKOVER, "No more commits left to iterate"); } static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) @@ -378,7 +378,7 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) for (;;) { next = commit_list_pop(&walk->iterator_topo); if (next == NULL) - return GIT_EREVWALKOVER; + return git__throw(GIT_EREVWALKOVER, "No more commits left to iterate"); if (next->in_degree > 0) { next->topo_delay = 1; From f31bd03f7eed1bcabbd9561b444661e19b0f2567 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Fri, 13 May 2011 04:15:46 +0300 Subject: [PATCH 101/120] Include "common.h" in "errors.h" --- include/git2/errors.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/git2/errors.h b/include/git2/errors.h index dbe565aab..60af7b3d0 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -25,6 +25,8 @@ #ifndef INCLUDE_git_errors_h__ #define INCLUDE_git_errors_h__ +#include "common.h" + /** * @file git2/errors.h * @brief Git error handling routines and variables From 098173c52adabf313e99c48475f51cd0d1e942a5 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Fri, 13 May 2011 04:17:24 +0300 Subject: [PATCH 102/120] Check Redis replies for NULL --- src/backends/hiredis.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/backends/hiredis.c b/src/backends/hiredis.c index 707412bf6..2533a16da 100644 --- a/src/backends/hiredis.c +++ b/src/backends/hiredis.c @@ -53,7 +53,7 @@ int hiredis_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backe reply = redisCommand(backend->db, "HMGET %b %s %s", oid->id, GIT_OID_RAWSZ, "type", "size"); - if (reply->type == REDIS_REPLY_ARRAY) { + if (reply && reply->type == REDIS_REPLY_ARRAY) { if (reply->element[0]->type != REDIS_REPLY_NIL && reply->element[0]->type != REDIS_REPLY_NIL) { *type_p = (git_otype) atoi(reply->element[0]->str); @@ -83,7 +83,7 @@ int hiredis_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_o reply = redisCommand(backend->db, "HMGET %b %s %s %s", oid->id, GIT_OID_RAWSZ, "type", "size", "data"); - if (reply->type == REDIS_REPLY_ARRAY) { + if (reply && reply->type == REDIS_REPLY_ARRAY) { if (reply->element[0]->type != REDIS_REPLY_NIL && reply->element[1]->type != REDIS_REPLY_NIL && reply->element[2]->type != REDIS_REPLY_NIL) { @@ -118,7 +118,7 @@ int hiredis_backend__exists(git_odb_backend *_backend, const git_oid *oid) { found = 0; reply = redisCommand(backend->db, "exists %b", oid->id, GIT_OID_RAWSZ); - if (reply->type != REDIS_REPLY_NIL && reply->type != REDIS_REPLY_ERROR) + if (reply && reply->type != REDIS_REPLY_NIL && reply->type != REDIS_REPLY_ERROR) found = 1; @@ -144,7 +144,8 @@ int hiredis_backend__write(git_oid *id, git_odb_backend *_backend, const void *d "size %d " "data %b ", id->id, GIT_OID_RAWSZ, (int) type, len, data, len); - error = reply->type == REDIS_REPLY_ERROR ? GIT_ERROR : GIT_SUCCESS; + + error = (reply == NULL || reply->type == REDIS_REPLY_ERROR) ? GIT_ERROR : GIT_SUCCESS; freeReplyObject(reply); return error; From 77c3999ca928539c6ba9a29910bbe99d2b705efd Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 14 May 2011 14:40:56 +0200 Subject: [PATCH 103/120] Move fileops.c to the new error handling --- src/fileops.c | 61 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 5dd4a3806..45dd8f444 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -10,7 +10,7 @@ int gitfo_mkdir_2file(const char *file_path) error = git__dirname_r(target_folder_path, sizeof(target_folder_path), file_path); if (error < GIT_SUCCESS) - return error; + return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path); /* Does the containing folder exist? */ if (gitfo_isdir(target_folder_path)) { @@ -19,7 +19,7 @@ int gitfo_mkdir_2file(const char *file_path) /* Let's create the tree structure */ error = gitfo_mkdir_recurs(target_folder_path, mode); if (error < GIT_SUCCESS) - return error; + return error; /* The callee already takes care of setting the correct error message. */ } return GIT_SUCCESS; @@ -164,19 +164,19 @@ int gitfo_read_file(gitfo_buf *obj, const char *path) if (((size = gitfo_size(fd)) < 0) || !git__is_sizet(size+1)) { gitfo_close(fd); - return GIT_ERROR; + return git__throw(GIT_ERROR, "Failed to read file `%s`. Either an error occured while calculating its size or the file is too large", path); } len = (size_t) size; if ((buff = git__malloc(len + 1)) == NULL) { gitfo_close(fd); - return GIT_ERROR; + return GIT_ENOMEM; } if (gitfo_read(fd, buff, len) < 0) { gitfo_close(fd); free(buff); - return GIT_ERROR; + return git__throw(GIT_ERROR, "Failed to read file `%s`", path); } buff[len] = '\0'; @@ -197,13 +197,15 @@ void gitfo_free_buf(gitfo_buf *obj) int gitfo_mv(const char *from, const char *to) { + int error; + #ifdef GIT_WIN32 /* * Win32 POSIX compilance my ass. If the destination * file exists, the `rename` call fails. This is as * close as it gets with the Win32 API. */ - return MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR; + error = MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR; #else /* Don't even try this on Win32 */ if (!link(from, to)) { @@ -214,16 +216,21 @@ int gitfo_mv(const char *from, const char *to) if (!rename(from, to)) return GIT_SUCCESS; - return GIT_EOSERR; + error = GIT_EOSERR; #endif + + if (error < GIT_SUCCESS) + return git__throw(error, "Failed to move file from `%s`to `%s`", from, to); + + return GIT_SUCCESS; } int gitfo_mv_force(const char *from, const char *to) { if (gitfo_mkdir_2file(to) < GIT_SUCCESS) - return GIT_EOSERR; + return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */ - return gitfo_mv(from, to); + return gitfo_mv(from, to); /* The callee already takes care of setting the correct error message. */ } int gitfo_map_ro(git_map *out, git_file fd, git_off_t begin, size_t len) @@ -338,7 +345,7 @@ int gitfo_dirent( struct dirent *de; if (!wd_len || path_sz < wd_len + 2) - return GIT_ERROR; + return git__throw(GIT_EINVALIDARGS, "Failed to process `%s` tree structure. Path is either empty or buffer size is too short", path); while (path[wd_len - 1] == '/') wd_len--; @@ -347,7 +354,7 @@ int gitfo_dirent( dir = opendir(path); if (!dir) - return GIT_EOSERR; + return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure. An error occured while opening the directory", path); while ((de = readdir(dir)) != NULL) { size_t de_len; @@ -364,14 +371,14 @@ int gitfo_dirent( de_len = strlen(de->d_name); if (path_sz < wd_len + de_len + 1) { closedir(dir); - return GIT_ERROR; + return git__throw(GIT_ERROR, "Failed to process `%s` tree structure. Buffer size is too short", path); } strcpy(path + wd_len, de->d_name); result = fn(arg, path); if (result < GIT_SUCCESS) { closedir(dir); - return result; + return result; /* The callee is reponsible for setting the correct error message */ } if (result > 0) { closedir(dir); @@ -399,7 +406,7 @@ int retrieve_path_root_offset(const char *path) if (*(path + offset) == '/') return offset; - return GIT_ERROR; + return -1; /* Not a real error. Rather a signal than the path is not rooted */ } @@ -438,7 +445,11 @@ int gitfo_mkdir_recurs(const char *path, int mode) error = gitfo_mkdir(path, mode); free(path_copy); - return error; + + if (error < GIT_SUCCESS) + return git__throw(error, "Failed to recursively create `%s` tree structure", path); + + return GIT_SUCCESS; } static int retrieve_previous_path_component_start(const char *path) @@ -484,7 +495,7 @@ int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) if (root_path_offset < 0) { error = gitfo_getcwd(buffer_out, size); if (error < GIT_SUCCESS) - return error; + return error; /* The callee already takes care of setting the correct error message. */ len = strlen(buffer_out); buffer_out += len; @@ -529,7 +540,7 @@ int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) /* Are we escaping out of the root dir? */ if (len < 0) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path escapes out of the root directory", path); buffer_out = (char *)buffer_out_start + len; continue; @@ -537,7 +548,7 @@ int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) /* Guard against potential multiple dot path traversal (cf http://cwe.mitre.org/data/definitions/33.html) */ if (only_dots && segment_len > 0) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path contains a segment with three `.` or more", path); *buffer_out++ = '/'; len++; @@ -557,21 +568,21 @@ int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path) /* Let's make sure the filename isn't empty nor a dot */ if (path_len == 0 || (path_len == 1 && *path == '.')) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path is either empty or equals `.`", path); /* Let's make sure the filename doesn't end with "/", "/." or "/.." */ for (i = 1; path_len > i && i < 4; i++) { if (!strncmp(path + path_len - i, pattern, i)) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); } error = gitfo_prettify_dir_path(buffer_out, size, path); if (error < GIT_SUCCESS) - return error; + return error; /* The callee already takes care of setting the correct error message. */ path_len = strlen(buffer_out); - if (path_len < 2) - return GIT_EINVALIDPATH; + if (path_len < 2) /* TODO: Fixme. We should also take of detecting Windows rooted path (probably through usage of retrieve_path_root_offset) */ + return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); /* Remove the trailing slash */ buffer_out[path_len - 1] = '\0'; @@ -616,11 +627,11 @@ int gitfo_getcwd(char *buffer_out, size_t size) #ifdef GIT_WIN32 cwd_buffer = _getcwd(buffer_out, size); #else - cwd_buffer = getcwd(buffer_out, size); //TODO: Fixme. Ensure the required headers are correctly included + cwd_buffer = getcwd(buffer_out, size); #endif if (cwd_buffer == NULL) - return GIT_EOSERR; + return git__throw(GIT_EOSERR, "Failed to retrieve current working directory"); posixify_path(buffer_out); From 3abe3bba5ab234a8fcbf4cc7a50adc86323d7287 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 14 May 2011 16:05:33 +0200 Subject: [PATCH 104/120] Move repository.c to the new error handling --- src/cache.c | 2 +- src/repository.c | 51 ++++++++++++++++++++++++++---------------------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/cache.c b/src/cache.c index fd42e2c5b..a372df160 100644 --- a/src/cache.c +++ b/src/cache.c @@ -51,7 +51,7 @@ void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_p cache->lru_count = 0; cache->free_obj = free_ptr; - cache->nodes = git__malloc((size + 1) * sizeof(cache_node)); + cache->nodes = git__malloc((size + 1) * sizeof(cache_node)); //TODO: How should we deal with GIT_ENOMEM? for (i = 0; i < (size + 1); ++i) { git_mutex_init(&cache->nodes[i].lock); diff --git a/src/repository.c b/src/repository.c index 8cc2644ca..8a5934f59 100644 --- a/src/repository.c +++ b/src/repository.c @@ -124,16 +124,16 @@ static int check_repository_dirs(git_repository *repo) char path_aux[GIT_PATH_MAX]; if (gitfo_isdir(repo->path_repository) < GIT_SUCCESS) - return GIT_ENOTAREPO; + return git__throw(GIT_ENOTAREPO, "`%s` is not a folder", repo->path_repository); /* Ensure GIT_OBJECT_DIRECTORY exists */ if (gitfo_isdir(repo->path_odb) < GIT_SUCCESS) - return GIT_ENOTAREPO; + return git__throw(GIT_ENOTAREPO, "`%s` does not exist", repo->path_odb); /* Ensure HEAD file exists */ git__joinpath(path_aux, repo->path_repository, GIT_HEAD_FILE); if (gitfo_exists(path_aux) < 0) - return GIT_ENOTAREPO; + return git__throw(GIT_ENOTAREPO, "HEAD file is missing"); return GIT_SUCCESS; } @@ -145,12 +145,12 @@ static int guess_repository_dirs(git_repository *repo, const char *repository_pa /* Git directory name */ if (git__basename_r(buffer, sizeof(buffer), repository_path) < 0) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Unable to parse folder name from `%s`", repository_path); if (strcmp(buffer, DOT_GIT) == 0) { /* Path to working dir */ if (git__dirname_r(buffer, sizeof(buffer), repository_path) < 0) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Unable to parse parent folder name from `%s`", repository_path); path_work_tree = buffer; } @@ -177,7 +177,7 @@ static git_repository *repository_alloc() static int init_odb(git_repository *repo) { - return git_odb_open(&repo->db, repo->path_odb); + return git_odb_open(&repo->db, repo->path_odb); /* TODO: Move odb.c to new error handling */ } int git_repository_open3(git_repository **repo_out, @@ -192,7 +192,7 @@ int git_repository_open3(git_repository **repo_out, assert(repo_out); if (object_database == NULL) - return GIT_ERROR; + return git__throw(GIT_EINVALIDARGS, "Failed to open repository. `object_database` can't be null"); repo = repository_alloc(); if (repo == NULL) @@ -218,7 +218,7 @@ int git_repository_open3(git_repository **repo_out, cleanup: git_repository_free(repo); - return error; + return git__rethrow(error, "Failed to open repository"); } @@ -259,7 +259,7 @@ int git_repository_open2(git_repository **repo_out, cleanup: git_repository_free(repo); - return error; + return git__rethrow(error, "Failed to open repository"); } int git_repository_open(git_repository **repo_out, const char *path) @@ -290,7 +290,7 @@ int git_repository_open(git_repository **repo_out, const char *path) cleanup: git_repository_free(repo); - return error; + return git__rethrow(error, "Failed to open repository"); } void git_repository_free(git_repository *repo) @@ -322,7 +322,7 @@ int git_repository_index(git_index **index_out, git_repository *repo) assert(index_out && repo); if (repo->index == NULL) { - error = git_index_open_inrepo(&repo->index, repo); + error = git_index_open_inrepo(&repo->index, repo); /* TODO: move index.c to new error handling */ if (error < GIT_SUCCESS) return error; @@ -349,7 +349,7 @@ static int repo_init_reinit(repo_init *results) static int repo_init_createhead(git_repository *repo) { git_reference *head_reference; - return git_reference_create_symbolic(&head_reference, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_MASTER_FILE); + return git_reference_create_symbolic(&head_reference, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_MASTER_FILE); /* TODO: finalize moving refs.c to new error handling */ } static int repo_init_check_head_existence(char * repository_path) @@ -363,6 +363,7 @@ static int repo_init_check_head_existence(char * repository_path) static int repo_init_structure(repo_init *results) { const int mode = 0755; /* or 0777 ? */ + int error; char temp_path[GIT_PATH_MAX]; char *git_dir = results->path_repository; @@ -372,23 +373,27 @@ static int repo_init_structure(repo_init *results) /* Creates the '/objects/info/' directory */ git__joinpath(temp_path, git_dir, GIT_OBJECTS_INFO_DIR); - if (gitfo_mkdir_recurs(temp_path, mode) < GIT_SUCCESS) - return GIT_ERROR; + error = gitfo_mkdir_recurs(temp_path, mode); + if (error < GIT_SUCCESS) + return error; /* Creates the '/objects/pack/' directory */ git__joinpath(temp_path, git_dir, GIT_OBJECTS_PACK_DIR); - if (gitfo_mkdir(temp_path, mode)) - return GIT_ERROR; + error = gitfo_mkdir(temp_path, mode); + if (error < GIT_SUCCESS) + return git__throw(error, "Unable to create `%s` folder", temp_path); /* Creates the '/refs/heads/' directory */ git__joinpath(temp_path, git_dir, GIT_REFS_HEADS_DIR); - if (gitfo_mkdir_recurs(temp_path, mode)) - return GIT_ERROR; + error = gitfo_mkdir_recurs(temp_path, mode); + if (error < GIT_SUCCESS) + return error; /* Creates the '/refs/tags/' directory */ git__joinpath(temp_path, git_dir, GIT_REFS_TAGS_DIR); - if (gitfo_mkdir(temp_path, mode)) - return GIT_ERROR; + error = gitfo_mkdir(temp_path, mode); + if (error < GIT_SUCCESS) + return git__throw(error, "Unable to create `%s` folder", temp_path); /* TODO: what's left? templates? */ @@ -467,7 +472,7 @@ int git_repository_init(git_repository **repo_out, const char *path, unsigned is cleanup: free(results.path_repository); git_repository_free(repo); - return error; + return git__rethrow(error, "Failed to (re)init the repository `%s`", path); } int git_repository_is_empty(git_repository *repo) @@ -477,10 +482,10 @@ int git_repository_is_empty(git_repository *repo) error = git_reference_lookup(&head, repo, "HEAD"); if (error < GIT_SUCCESS) - return error; + return git__throw(error, "Failed to determine the emptiness of the repository. An error occured while retrieving the HEAD reference"); if (git_reference_type(head) != GIT_REF_SYMBOLIC) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to determine the emptiness of the repository. HEAD is probably in detached state"); return git_reference_resolve(&branch, head) == GIT_SUCCESS ? 0 : 1; } From 81201a4c4d1e8704260ab4572eb6c480d576fe83 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 15 May 2011 06:57:34 +0200 Subject: [PATCH 105/120] Move cache.c to the new error handling --- src/cache.c | 6 ++++-- src/cache.h | 2 +- src/odb.c | 6 +++++- src/repository.c | 8 +++++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/cache.c b/src/cache.c index a372df160..b0a093100 100644 --- a/src/cache.c +++ b/src/cache.c @@ -32,7 +32,7 @@ #define GIT_CACHE_OPENADR 3 -void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) +int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) { size_t i; @@ -51,7 +51,9 @@ void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_p cache->lru_count = 0; cache->free_obj = free_ptr; - cache->nodes = git__malloc((size + 1) * sizeof(cache_node)); //TODO: How should we deal with GIT_ENOMEM? + cache->nodes = git__malloc((size + 1) * sizeof(cache_node)); + if (cache->nodes == NULL) + return GIT_ENOMEM; for (i = 0; i < (size + 1); ++i) { git_mutex_init(&cache->nodes[i].lock); diff --git a/src/cache.h b/src/cache.h index 975aaff7e..2d9bb51eb 100644 --- a/src/cache.h +++ b/src/cache.h @@ -31,7 +31,7 @@ typedef struct { } git_cache; -void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr); +int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr); void git_cache_free(git_cache *cache); void *git_cache_try_store(git_cache *cache, void *entry); diff --git a/src/odb.c b/src/odb.c index e9e495eb1..025b591c5 100644 --- a/src/odb.c +++ b/src/odb.c @@ -234,13 +234,17 @@ static int backend_sort_cmp(const void *a, const void *b) int git_odb_new(git_odb **out) { + int error; + git_odb *db = git__calloc(1, sizeof(*db)); if (!db) return GIT_ENOMEM; git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object); + if (error < GIT_SUCCESS) + return error; - if (git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) { + if (git_vector_init(&db->backends, 4, backend_sort_cmp) < GIT_SUCCESS) { free(db); return GIT_ENOMEM; } diff --git a/src/repository.c b/src/repository.c index 8a5934f59..1072b22a1 100644 --- a/src/repository.c +++ b/src/repository.c @@ -159,13 +159,19 @@ static int guess_repository_dirs(git_repository *repo, const char *repository_pa static git_repository *repository_alloc() { + int error; + git_repository *repo = git__malloc(sizeof(git_repository)); if (!repo) return NULL; memset(repo, 0x0, sizeof(git_repository)); - git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free); + error = git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free); + if (error < GIT_SUCCESS) { + free(repo); + return NULL; + } if (git_repository__refcache_init(&repo->references) < GIT_SUCCESS) { free(repo); From 71747bcae034d2b0230b5f38c80eb3d185a63b42 Mon Sep 17 00:00:00 2001 From: Shuhei Tanuma Date: Sun, 15 May 2011 20:07:54 +0900 Subject: [PATCH 106/120] fix git_otype typo when calling `git_odb_read_header`. --- src/odb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/odb.c b/src/odb.c index e9e495eb1..3dca97764 100644 --- a/src/odb.c +++ b/src/odb.c @@ -444,7 +444,7 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git return error; *len_p = object->raw.len; - *type_p = object->raw.len; + *type_p = object->raw.type; git_odb_object_close(object); } From 4edf3e099aafbdaea2e4e128fc3230b36af38f41 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sun, 15 May 2011 23:45:24 +0300 Subject: [PATCH 107/120] Return success code on `git_cache_init` --- src/cache.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cache.c b/src/cache.c index b0a093100..0509afd46 100644 --- a/src/cache.c +++ b/src/cache.c @@ -60,6 +60,8 @@ int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_pt cache->nodes[i].ptr = NULL; cache->nodes[i].lru = 0; } + + return GIT_SUCCESS; } void git_cache_free(git_cache *cache) From 7cadd1f6a788dbb997997481f09a2e430cc94958 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sun, 15 May 2011 23:46:22 +0300 Subject: [PATCH 108/120] Check error code from `git_cache_init` --- src/odb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/odb.c b/src/odb.c index 8637534c1..f09b6d7e9 100644 --- a/src/odb.c +++ b/src/odb.c @@ -240,13 +240,13 @@ int git_odb_new(git_odb **out) if (!db) return GIT_ENOMEM; - git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object); + error = git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object); if (error < GIT_SUCCESS) return error; - if (git_vector_init(&db->backends, 4, backend_sort_cmp) < GIT_SUCCESS) { + if ((error = git_vector_init(&db->backends, 4, backend_sort_cmp)) < GIT_SUCCESS) { free(db); - return GIT_ENOMEM; + return error; } *out = db; From f87d9beb8d9e3ed2b6baedd9ca15464f7da93f25 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sun, 15 May 2011 23:46:39 +0300 Subject: [PATCH 109/120] Change error codes from DEFINEs to an enum --- include/git2/errors.h | 122 ++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 64 deletions(-) diff --git a/include/git2/errors.h b/include/git2/errors.h index 60af7b3d0..7e957b803 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -35,97 +35,91 @@ */ GIT_BEGIN_DECL -/** Operation completed successfully. */ -#define GIT_SUCCESS 0 +typedef enum { + GIT_SUCCESS = 0, + GIT_ERROR = -1, -/** - * Operation failed, with unspecified reason. - * This value also serves as the base error code; all other - * error codes are subtracted from it such that all errors - * are < 0, in typical POSIX C tradition. - */ -#define GIT_ERROR -1 + /** Input was not a properly formatted Git object id. */ + GIT_ENOTOID = -2, -/** Input was not a properly formatted Git object id. */ -#define GIT_ENOTOID (GIT_ERROR - 1) + /** Input does not exist in the scope searched. */ + GIT_ENOTFOUND = -3, -/** Input does not exist in the scope searched. */ -#define GIT_ENOTFOUND (GIT_ERROR - 2) + /** Not enough space available. */ + GIT_ENOMEM = -4, -/** Not enough space available. */ -#define GIT_ENOMEM (GIT_ERROR - 3) + /** Consult the OS error information. */ + GIT_EOSERR = -5, -/** Consult the OS error information. */ -#define GIT_EOSERR (GIT_ERROR - 4) + /** The specified object is of invalid type */ + GIT_EOBJTYPE = -6, -/** The specified object is of invalid type */ -#define GIT_EOBJTYPE (GIT_ERROR - 5) + /** The specified repository is invalid */ + GIT_ENOTAREPO = -7, -/** The specified object has its data corrupted */ -#define GIT_EOBJCORRUPTED (GIT_ERROR - 6) + /** The object type is invalid or doesn't match */ + GIT_EINVALIDTYPE = -8, -/** The specified repository is invalid */ -#define GIT_ENOTAREPO (GIT_ERROR - 7) + /** The object cannot be written because it's missing internal data */ + GIT_EMISSINGOBJDATA = -9, -/** The object type is invalid or doesn't match */ -#define GIT_EINVALIDTYPE (GIT_ERROR - 8) + /** The packfile for the ODB is corrupted */ + GIT_EPACKCORRUPTED = -10, -/** The object cannot be written because it's missing internal data */ -#define GIT_EMISSINGOBJDATA (GIT_ERROR - 9) + /** Failed to acquire or release a file lock */ + GIT_EFLOCKFAIL = -11, -/** The packfile for the ODB is corrupted */ -#define GIT_EPACKCORRUPTED (GIT_ERROR - 10) + /** The Z library failed to inflate/deflate an object's data */ + GIT_EZLIB = -12, -/** Failed to acquire or release a file lock */ -#define GIT_EFLOCKFAIL (GIT_ERROR - 11) + /** The queried object is currently busy */ + GIT_EBUSY = -13, -/** The Z library failed to inflate/deflate an object's data */ -#define GIT_EZLIB (GIT_ERROR - 12) + /** The index file is not backed up by an existing repository */ + GIT_EBAREINDEX = -14, -/** The queried object is currently busy */ -#define GIT_EBUSY (GIT_ERROR - 13) + /** The name of the reference is not valid */ + GIT_EINVALIDREFNAME = -15, -/** The index file is not backed up by an existing repository */ -#define GIT_EBAREINDEX (GIT_ERROR - 14) + /** The specified reference has its data corrupted */ + GIT_EREFCORRUPTED = -16, -/** The name of the reference is not valid */ -#define GIT_EINVALIDREFNAME (GIT_ERROR - 15) + /** The specified symbolic reference is too deeply nested */ + GIT_ETOONESTEDSYMREF = -17, -/** The specified reference has its data corrupted */ -#define GIT_EREFCORRUPTED (GIT_ERROR - 16) + /** The pack-refs file is either corrupted or its format is not currently supported */ + GIT_EPACKEDREFSCORRUPTED = -18, -/** The specified symbolic reference is too deeply nested */ -#define GIT_ETOONESTEDSYMREF (GIT_ERROR - 17) + /** The path is invalid */ + GIT_EINVALIDPATH = -19, -/** The pack-refs file is either corrupted or its format is not currently supported */ -#define GIT_EPACKEDREFSCORRUPTED (GIT_ERROR - 18) + /** The revision walker is empty; there are no more commits left to iterate */ + GIT_EREVWALKOVER = -20, -/** The path is invalid */ -#define GIT_EINVALIDPATH (GIT_ERROR - 19) + /** The state of the reference is not valid */ + GIT_EINVALIDREFSTATE = -21, -/** The revision walker is empty; there are no more commits left to iterate */ -#define GIT_EREVWALKOVER (GIT_ERROR - 20) + /** This feature has not been implemented yet */ + GIT_ENOTIMPLEMENTED = -22, -/** The state of the reference is not valid */ -#define GIT_EINVALIDREFSTATE (GIT_ERROR - 21) + /** A reference with this name already exists */ + GIT_EEXISTS = -23, -/** This feature has not been implemented yet */ -#define GIT_ENOTIMPLEMENTED (GIT_ERROR - 22) + /** The given integer literal is too large to be parsed */ + GIT_EOVERFLOW = -24, -/** A reference with this name already exists */ -#define GIT_EEXISTS (GIT_ERROR - 23) + /** The given literal is not a valid number */ + GIT_ENOTNUM = -25, -/** The given integer literal is too large to be parsed */ -#define GIT_EOVERFLOW (GIT_ERROR - 24) + /** Streaming error */ + GIT_ESTREAM = -26, -/** The given literal is not a valid number */ -#define GIT_ENOTNUM (GIT_ERROR - 25) + /** invalid arguments to function */ + GIT_EINVALIDARGS = -27, -/** Streaming error */ -#define GIT_ESTREAM (GIT_ERROR - 26) - -/** invalid arguments to function */ -#define GIT_EINVALIDARGS (GIT_ERROR - 27) + /** The specified object has its data corrupted */ + GIT_EOBJCORRUPTED = -28, +} git_error; /** * Return a detailed error string with the latest error From 5ca2f58057dfd5e0cf03a176782e4daa925a7f0b Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Sun, 15 May 2011 23:48:05 +0300 Subject: [PATCH 110/120] Do not set error message on `GIT_EREVWALKOVER` This is not really an error, just a special return code to mark the end of an iteration. --- src/revwalk.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/revwalk.c b/src/revwalk.c index a32a16e2a..8163851a3 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -349,7 +349,7 @@ static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk) } } - return git__throw(GIT_EREVWALKOVER, "No more commits left to iterate"); + return GIT_EREVWALKOVER; } static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) @@ -367,7 +367,7 @@ static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) } } - return git__throw(GIT_EREVWALKOVER, "No more commits left to iterate"); + return GIT_EREVWALKOVER; } static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) @@ -378,7 +378,7 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) for (;;) { next = commit_list_pop(&walk->iterator_topo); if (next == NULL) - return git__throw(GIT_EREVWALKOVER, "No more commits left to iterate"); + return GIT_EREVWALKOVER; if (next->in_degree > 0) { next->topo_delay = 1; From cce225bfaceed90c027d44d46680a3dbe182c93b Mon Sep 17 00:00:00 2001 From: Alexei Sholik Date: Mon, 16 May 2011 01:56:20 -0700 Subject: [PATCH 111/120] Fix a typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6779bd84..7cfde59f8 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Under Unix-like systems, like Linux, *BSD and Mac OS X, libgit2 expects `pthread they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API for threading. -Additionally, he following libraries may be used as replacement for built-in functionality: +Additionally, the following libraries may be used as replacement for built-in functionality: * LibSSL **(optional)** From 3de79280e3aa79eb9bc95fa1e5a69499ba448bd9 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 17 May 2011 00:51:52 +0300 Subject: [PATCH 112/120] cache: Fix deadlock Do not try to adquire the same node lock twice when the cuckoo hashing resolves to the same node. --- src/cache.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/cache.c b/src/cache.c index 0509afd46..6e4ad4c9e 100644 --- a/src/cache.c +++ b/src/cache.c @@ -110,7 +110,7 @@ void *git_cache_try_store(git_cache *cache, void *entry) cache_node *nodes[GIT_CACHE_OPENADR], *lru_node; const uint32_t *hash; const git_oid *oid; - size_t i; + size_t i, j, node_count; oid = &((git_cached_obj*)entry)->oid; hash = (const uint32_t *)oid->id; @@ -119,16 +119,24 @@ void *git_cache_try_store(git_cache *cache, void *entry) * the cache now owns it */ git_cached_obj_incref(entry); + node_count = 0; for (i = 0; i < GIT_CACHE_OPENADR; ++i) { size_t pos = hash[i] & cache->size_mask; + cache_node *node = &cache->nodes[pos]; - nodes[i] = &cache->nodes[pos]; - git_mutex_lock(&nodes[i]->lock); + for (j = 0; j < node_count; ++j) + if (nodes[j] == node) + break; + + if (j == node_count) { + nodes[node_count++] = node; + git_mutex_lock(node); + } } lru_node = nodes[0]; - for (i = 0; i < GIT_CACHE_OPENADR; ++i) { + for (i = 0; i < node_count; ++i) { if (nodes[i]->ptr == NULL) { nodes[i]->ptr = entry; @@ -145,7 +153,7 @@ void *git_cache_try_store(git_cache *cache, void *entry) lru_node = nodes[i]; } - if (i == GIT_CACHE_OPENADR) { + if (i == node_count) { void *old_entry = lru_node->ptr; assert(old_entry); @@ -158,7 +166,7 @@ void *git_cache_try_store(git_cache *cache, void *entry) * returning it to the user */ git_cached_obj_incref(entry); - for (i = 0; i < GIT_CACHE_OPENADR; ++i) + for (i = 0; i < node_count; ++i) git_mutex_unlock(&nodes[i]->lock); return entry; From 335d6c998041f7c44dce48d3bb087c52136d970f Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 17 May 2011 01:46:07 +0300 Subject: [PATCH 113/120] cache: Drop cuckoo hashing Now we use a simple closed-addressing cache. Cuckoo hashing was creating too many issues with race conditions. Fuck that. Let's see what happens performance wise, we may have to roll back or come up with another way to implement an efficient multi-threaded cache. --- src/cache.c | 86 +++++++++++++---------------------------------------- src/cache.h | 1 - 2 files changed, 21 insertions(+), 66 deletions(-) diff --git a/src/cache.c b/src/cache.c index 6e4ad4c9e..433fc3d9c 100644 --- a/src/cache.c +++ b/src/cache.c @@ -29,9 +29,6 @@ #include "thread-utils.h" #include "cache.h" -#define GIT_CACHE_OPENADR 3 - - int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) { size_t i; @@ -58,7 +55,6 @@ int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_pt for (i = 0; i < (size + 1); ++i) { git_mutex_init(&cache->nodes[i].lock); cache->nodes[i].ptr = NULL; - cache->nodes[i].lru = 0; } return GIT_SUCCESS; @@ -81,93 +77,53 @@ void git_cache_free(git_cache *cache) void *git_cache_get(git_cache *cache, const git_oid *oid) { const uint32_t *hash; - size_t i, pos, found = 0; cache_node *node = NULL; + void *result = NULL; hash = (const uint32_t *)oid->id; + node = &cache->nodes[hash[0] & cache->size_mask]; - for (i = 0; !found && i < GIT_CACHE_OPENADR; ++i) { - pos = hash[i] & cache->size_mask; - node = &cache->nodes[pos]; - - git_mutex_lock(&node->lock); - { - if (node->ptr && git_cached_obj_compare(node->ptr, oid) == 0) { - git_cached_obj_incref(node->ptr); - node->lru = ++cache->lru_count; - found = 1; - } + git_mutex_lock(&node->lock); + { + if (node->ptr && git_cached_obj_compare(node->ptr, oid) == 0) { + git_cached_obj_incref(node->ptr); + result = node->ptr; } - git_mutex_unlock(&node->lock); } + git_mutex_unlock(&node->lock); - - return found ? node->ptr : NULL; + return result; } void *git_cache_try_store(git_cache *cache, void *entry) { - cache_node *nodes[GIT_CACHE_OPENADR], *lru_node; const uint32_t *hash; const git_oid *oid; - size_t i, j, node_count; + cache_node *node = NULL; oid = &((git_cached_obj*)entry)->oid; hash = (const uint32_t *)oid->id; + node = &cache->nodes[hash[0] & cache->size_mask]; /* increase the refcount on this object, because * the cache now owns it */ git_cached_obj_incref(entry); + git_mutex_lock(&node->lock); - node_count = 0; - for (i = 0; i < GIT_CACHE_OPENADR; ++i) { - size_t pos = hash[i] & cache->size_mask; - cache_node *node = &cache->nodes[pos]; - - for (j = 0; j < node_count; ++j) - if (nodes[j] == node) - break; - - if (j == node_count) { - nodes[node_count++] = node; - git_mutex_lock(node); - } - } - - lru_node = nodes[0]; - - for (i = 0; i < node_count; ++i) { - - if (nodes[i]->ptr == NULL) { - nodes[i]->ptr = entry; - nodes[i]->lru = ++cache->lru_count; - break; - } else if (git_cached_obj_compare(nodes[i]->ptr, oid) == 0) { - git_cached_obj_decref(entry, cache->free_obj); - entry = nodes[i]->ptr; - nodes[i]->lru = ++cache->lru_count; - break; - } - - if (nodes[i]->lru < lru_node->lru) - lru_node = nodes[i]; - } - - if (i == node_count) { - void *old_entry = lru_node->ptr; - assert(old_entry); - - git_cached_obj_decref(old_entry, cache->free_obj); - lru_node->ptr = entry; - lru_node->lru = ++cache->lru_count; + if (node->ptr == NULL) { + node->ptr = entry; + } else if (git_cached_obj_compare(node->ptr, oid) == 0) { + git_cached_obj_decref(entry, cache->free_obj); + entry = node->ptr; + } else { + git_cached_obj_decref(node->ptr, cache->free_obj); + node->ptr = entry; } /* increase the refcount again, because we are * returning it to the user */ git_cached_obj_incref(entry); - - for (i = 0; i < node_count; ++i) - git_mutex_unlock(&nodes[i]->lock); + git_mutex_unlock(&node->lock); return entry; } diff --git a/src/cache.h b/src/cache.h index 2d9bb51eb..4794dea3a 100644 --- a/src/cache.h +++ b/src/cache.h @@ -19,7 +19,6 @@ typedef struct { typedef struct { git_cached_obj *ptr; git_mutex lock; - unsigned int lru; } cache_node; typedef struct { 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 114/120] 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; } From b3d94069b4c03374bee482df4ea20eed022743dc Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 17 May 2011 14:51:42 +0300 Subject: [PATCH 115/120] repository: Properly free the index on close --- src/repository.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/repository.c b/src/repository.c index 1072b22a1..eab56fc9a 100644 --- a/src/repository.c +++ b/src/repository.c @@ -315,8 +315,10 @@ void git_repository_free(git_repository *repo) if (repo->db != NULL) git_odb_close(repo->db); - if (repo->index != NULL) + if (repo->index != NULL) { + repo->index->repository = NULL; git_index_free(repo->index); + } free(repo); } From 6adcb5f3d2b5770995114521b4a6504d1f0cad03 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 17 May 2011 15:04:18 +0300 Subject: [PATCH 116/120] utils: Implement git__strndup --- src/util.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/util.h b/src/util.h index 6724e8d41..ff6e67f8d 100644 --- a/src/util.h +++ b/src/util.h @@ -34,6 +34,25 @@ GIT_INLINE(char *) git__strdup(const char *str) return ptr; } +GIT_INLINE(char *) git__strndup(const char *str, size_t n) +{ + size_t length; + char *ptr; + + length = strlen(str); + if (n < length) + length = n; + + ptr = malloc(length + 1); + if (!ptr) + git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + + memcpy(ptr, str, length); + ptr[length] = 0; + + return ptr; +} + GIT_INLINE(void *) git__realloc(void *ptr, size_t size) { void *new_ptr = realloc(ptr, size); From 128d37316b7499c70c2d8b44635253541647f583 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 17 May 2011 15:05:41 +0300 Subject: [PATCH 117/120] config_file: Fix compilation --- include/git2/types.h | 3 --- src/config_file.c | 52 ++++++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/include/git2/types.h b/include/git2/types.h index 49e80dd95..ab7dc523e 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -136,9 +136,6 @@ 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; - /** Time in a signature */ typedef struct git_time { git_time_t time; /** time in seconds from epoch */ diff --git a/src/config_file.c b/src/config_file.c index d66dd20e3..37bb2794e 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -31,17 +31,17 @@ #include -struct git_cvar { - git_cvar *next; +typedef struct cvar_t { + struct cvar_t *next; char *section; char *name; char *value; -}; +} cvar_t; typedef struct { - git_cvar *head; - git_cvar *tail; -} git_cvar_list; + struct cvar_t *head; + struct cvar_t *tail; +} cvar_t_list; #define CVAR_LIST_HEAD(list) ((list)->head) @@ -86,7 +86,7 @@ typedef struct { typedef struct { git_config_backend parent; - git_cvar_list var_list; + cvar_t_list var_list; struct { gitfo_buf buffer; @@ -101,7 +101,7 @@ typedef struct { 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) +static void cvar_free(cvar_t *var) { if (var == NULL) return; @@ -112,9 +112,9 @@ static void cvar_free(git_cvar *var) free(var); } -static void cvar_list_free(git_cvar_list *list) +static void cvar_list_free(cvar_t_list *list) { - git_cvar *cur; + cvar_t *cur; while (!CVAR_LIST_EMPTY(list)) { cur = CVAR_LIST_HEAD(list); @@ -170,7 +170,7 @@ static int cvar_match_section(const char *local, const char *input) return !strncmp(local_sp + 2, first_dot + 1, comparison_len); } -static int cvar_match_name(const git_cvar *var, const char *str) +static int cvar_match_name(const cvar_t *var, const char *str) { const char *name_start; @@ -185,9 +185,9 @@ static int cvar_match_name(const git_cvar *var, const char *str) return !strcasecmp(var->name, name_start); } -static git_cvar *cvar_list_find(git_cvar_list *list, const char *name) +static cvar_t *cvar_list_find(cvar_t_list *list, const char *name) { - git_cvar *iter; + cvar_t *iter; CVAR_LIST_FOREACH (list, iter) { if (cvar_match_name(iter, name)) @@ -270,8 +270,8 @@ static void backend_free(git_config_backend *_backend) static int file_foreach(git_config_backend *backend, int (*fn)(const char *, void *), void *data) { - int ret; - git_cvar *var; + int ret = GIT_SUCCESS; + cvar_t *var; char *normalized; file_backend *b = (file_backend *)backend; @@ -291,8 +291,8 @@ static int file_foreach(git_config_backend *backend, int (*fn)(const char *, voi static int config_set(git_config_backend *cfg, const char *name, const char *value) { - git_cvar *var = NULL; - git_cvar *existing = NULL; + cvar_t *var = NULL; + cvar_t *existing = NULL; int error = GIT_SUCCESS; const char *last_dot; file_backend *b = (file_backend *)cfg; @@ -321,11 +321,11 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val return git__throw(GIT_EINVALIDTYPE, "Variables without section aren't allowed"); } - var = git__malloc(sizeof(git_cvar)); + var = git__malloc(sizeof(cvar_t)); if (var == NULL) return GIT_ENOMEM; - memset(var, 0x0, sizeof(git_cvar)); + memset(var, 0x0, sizeof(cvar_t)); var->section = git__strndup(name, last_dot - name); if (var->section == NULL) { @@ -359,7 +359,7 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val */ static int config_get(git_config_backend *cfg, const char *name, const char **out) { - git_cvar *var; + cvar_t *var; int error = GIT_SUCCESS; file_backend *b = (file_backend *)cfg; @@ -641,7 +641,7 @@ 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__thow(GIT_ERROR, "Failed to parse ext header. Wrong total lenght calculation"); + error = git__throw(GIT_ERROR, "Failed to parse ext header. Wrong total lenght calculation"); goto out; } else if (ret < 0) { error = git__throw(GIT_EOSERR, "Failed to parse ext header. OS error: %s", strerror(errno)); @@ -725,7 +725,7 @@ error: static int skip_bom(file_backend *cfg) { - static const unsigned char *utf8_bom = "\xef\xbb\xbf"; + static const 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); @@ -802,7 +802,7 @@ static int config_parse(file_backend *cfg_file) char *current_section = NULL; char *var_name; char *var_value; - git_cvar *var; + cvar_t *var; /* Initialise the reading position */ cfg_file->reader.read_ptr = cfg_file->reader.buffer.data; @@ -834,13 +834,13 @@ static int config_parse(file_backend *cfg_file) if (error < GIT_SUCCESS) break; - var = malloc(sizeof(git_cvar)); + var = malloc(sizeof(cvar_t)); if (var == NULL) { error = GIT_ENOMEM; break; } - memset(var, 0x0, sizeof(git_cvar)); + memset(var, 0x0, sizeof(cvar_t)); var->section = git__strdup(current_section); if (var->section == NULL) { @@ -966,7 +966,7 @@ static int parse_variable(file_backend *cfg, char **var_name, char **var_value) while (isspace(var_end[0])); } - tmp = strndup(line, var_end - line + 1); + tmp = git__strndup(line, var_end - line + 1); if (tmp == NULL) { error = GIT_ENOMEM; goto out; From 0da2c70067135ffb095f951b30cb085db1aeccce Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 17 May 2011 15:09:30 +0300 Subject: [PATCH 118/120] utils: Move git__str[n]tolower --- src/config.c | 14 -------------- src/config.h | 3 --- src/util.c | 14 ++++++++++++++ src/util.h | 3 +++ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/config.c b/src/config.c index 234c5303f..cd0a73ccd 100644 --- a/src/config.c +++ b/src/config.c @@ -37,20 +37,6 @@ typedef struct { int priority; } backend_internal; -void git__strntolower(char *str, int len) -{ - int i; - - for (i = 0; i < len; ++i) { - str[i] = tolower(str[i]); - } -} - -void git__strtolower(char *str) -{ - git__strntolower(str, strlen(str)); -} - int git_config_open_bare(git_config **out, const char *path) { git_config_backend *backend = NULL; diff --git a/src/config.h b/src/config.h index e786e8a49..a811fd850 100644 --- a/src/config.h +++ b/src/config.h @@ -9,7 +9,4 @@ struct git_config { git_vector backends; }; -void git__strtolower(char *str); -void git__strntolower(char *str, int len); - #endif diff --git a/src/util.c b/src/util.c index 55a7ab2a9..9499ceadf 100644 --- a/src/util.c +++ b/src/util.c @@ -104,6 +104,20 @@ int git__fmt(char *buf, size_t buf_sz, const char *fmt, ...) return r; } +void git__strntolower(char *str, int len) +{ + int i; + + for (i = 0; i < len; ++i) { + str[i] = tolower(str[i]); + } +} + +void git__strtolower(char *str) +{ + git__strntolower(str, strlen(str)); +} + int git__prefixcmp(const char *str, const char *prefix) { for (;;) { diff --git a/src/util.h b/src/util.h index ff6e67f8d..e5a2ebe5f 100644 --- a/src/util.h +++ b/src/util.h @@ -142,6 +142,9 @@ GIT_INLINE(int) git__is_sizet(git_off_t p) extern char *git__strtok(char *output, char *src, char *delimit); extern char *git__strtok_keep(char *output, char *src, char *delimit); +extern void git__strntolower(char *str, int len); +extern void git__strtolower(char *str); + #define STRLEN(str) (sizeof(str) - 1) #define GIT_OID_LINE_LENGTH(header) (STRLEN(header) + 1 + GIT_OID_HEXSZ + 1) From 124fbb3d74df3ca479a76e76c44858f0d90b2a64 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 17 May 2011 15:11:43 +0300 Subject: [PATCH 119/120] tests: Update NAMING file --- tests/NAMING | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/NAMING b/tests/NAMING index e5a6e9956..df07b7d51 100644 --- a/tests/NAMING +++ b/tests/NAMING @@ -41,4 +41,6 @@ Categories 13__: Threads, empty as of now -14__: Configuration parsing +14__: Redis backend + +15__: Configuration parsing From c9662061f25ea51f39018a278b98c9a9b4410e84 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 17 May 2011 15:12:25 +0300 Subject: [PATCH 120/120] t15: Remove unused variable --- tests/t15-config.c | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/t15-config.c b/tests/t15-config.c index 1b971bd74..50d473580 100644 --- a/tests/t15-config.c +++ b/tests/t15-config.c @@ -131,7 +131,6 @@ END_TEST BEGIN_TEST(config5, "test number suffixes") git_config *cfg; - const char *str; long int i; must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config5"));