{"thread":{"id":"37223","subject":"[PATCH v13 0/2] git config cache & special querying api utilizing the cache","startedAt":"2014-07-28T10:10:37Z","lastAt":"2014-07-29T21:37:59Z","messageCount":5,"participants":["Tanay Abhra","Matthieu Moy","Junio C Hamano"],"isPatch":true,"patchVersion":13,"patchTotal":2},"messages":[{"id":"246765","messageId":"1406542239-17024-1-git-send-email-tanayabh@gmail.com","threadId":"37223","inReplyTo":null,"subject":"[PATCH v13 0/2] git config cache & special querying api utilizing the cache","fromName":"Tanay Abhra","fromEmail":"tanayabh@gmail.com","sentAt":"2014-07-28T10:10:37Z","receivedAt":"2014-07-28T10:10:37Z","isPatch":true,"sender":{"key":"tanayabh@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2097229?v=4"},"body":"[v13]: v12 was rejected because of redundant implementation of the new functions,\n\thope this one is okay.\n\nI am attaching the v13 with two new functions git_configset_get_string() &\ngit_configset_get_string_const().\n\nDiff between v11 and v13 is appended below for easy review.\nv11 can be seen at [1].\n\n[1]:: http://thread.gmane.org/gmane.comp.version-control.git/253862/\n\nTanay Abhra (2):\n  add `config_set` API for caching config-like files\n  test-config: add tests for the config_set API\n\n .gitignore                             |   1 +\n Documentation/technical/api-config.txt | 142 +++++++++++++++++\n Makefile                               |   1 +\n cache.h                                |  32 ++++\n config.c                               | 274 +++++++++++++++++++++++++++++++++\n setup.c                                |   9 ++\n t/t1308-config-set.sh                  | 200 ++++++++++++++++++++++++\n test-config.c                          | 142 +++++++++++++++++\n 8 files changed, 801 insertions(+)\n create mode 100755 t/t1308-config-set.sh\n create mode 100644 test-config.c\n\n-- \n1.9.0.GIT\n\n-- 8< --\nt_config_get_string(const char *key, const char **dest)`::\n+`int git_config_get_string_const(const char *key, const char **dest)`::\n \n \tAllocates and copies the retrieved string into the `dest` parameter for\n \tthe configuration variable `key`; if NULL string is given, prints an\n \terror message and returns -1. When the configuration variable `key` is\n \tnot found, returns 1 without touching `dest`.\n \n+`int git_config_get_string(const char *key, char **dest)`::\n+\n+\tSimilar to `git_config_get_string_const`, except that retrieved value\n+\tcopied into the `dest` parameter is a mutable string.\n+\n `int git_config_get_pathname(const char *key, const char **dest)`::\n \n \tSimilar to `git_config_get_string`, but expands `~` or `~user` into\ndiff --git a/cache.h b/cache.h\nindex 2f63fd1..7292aef 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1361,7 +1361,8 @@ extern int git_configset_add_file(struct config_set *cs, const char *filename);\n extern int git_configset_get_value(struct config_set *cs, const char *key, const char **value);\n extern const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key);\n extern void git_configset_clear(struct config_set *cs);\n-extern int git_configset_get_string(struct config_set *cs, const char *key, const char **dest);\n+extern int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest);\n+extern int git_configset_get_string(struct config_set *cs, const char *key, char **dest);\n extern int git_configset_get_int(struct config_set *cs, const char *key, int *dest);\n extern int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest);\n extern int git_configset_get_bool(struct config_set *cs, const char *key, int *dest);\n@@ -1373,7 +1374,8 @@ extern int git_config_get_value(const char *key, const char **value);\n extern const struct string_list *git_config_get_value_multi(const char *key);\n extern void git_config_clear(void);\n extern void git_config_iter(config_fn_t fn, void *data);\n-extern int git_config_get_string(const char *key, const char **dest);\n+extern int git_config_get_string_const(const char *key, const char **dest);\n+extern int git_config_get_string(const char *key, char **dest);\n extern int git_config_get_int(const char *key, int *dest);\n extern int git_config_get_ulong(const char *key, unsigned long *dest);\n extern int git_config_get_bool(const char *key, int *dest);\ndiff --git a/config.c b/config.c\nindex 22971e9..d3ad661 100644\n--- a/config.c\n+++ b/config.c\n@@ -1332,7 +1332,7 @@ const struct string_list *git_configset_get_value_multi(struct config_set *cs, c\n \treturn e ? &e->value_list : NULL;\n }\n \n-int git_configset_get_string(struct config_set *cs, const char *key, const char **dest)\n+int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest)\n {\n \tconst char *value;\n \tif (!git_configset_get_value(cs, key, &value))\n@@ -1341,6 +1341,11 @@ int git_configset_get_string(struct config_set *cs, const char *key, const char\n \t\treturn 1;\n }\n \n+int git_configset_get_string(struct config_set *cs, const char *key, char **dest)\n+{\n+\treturn git_configset_get_string_const(cs, key, (const char **)dest);\n+}\n+\n int git_configset_get_int(struct config_set *cs, const char *key, int *dest)\n {\n \tconst char *value;\n@@ -1430,10 +1435,16 @@ const struct string_list *git_config_get_value_multi(const char *key)\n \treturn git_configset_get_value_multi(&the_config_set, key);\n }\n \n-int git_config_get_string(const char *key, const char **dest)\n+int git_config_get_string_const(const char *key, const char **dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_string_const(&the_config_set, key, dest);\n+}\n+\n+int git_config_get_string(const char *key, char **dest)\n {\n \tgit_config_check_init();\n-\treturn git_configset_get_string(&the_config_set, key, dest);\n+\treturn git_config_get_string_const(key, (const char **)dest);\n }\n \n int git_config_get_int(const char *key, int *dest)\n-- 8< --\n"},{"id":"246767","messageId":"1406542239-17024-2-git-send-email-tanayabh@gmail.com","threadId":"37223","inReplyTo":"1406542239-17024-1-git-send-email-tanayabh@gmail.com","subject":"[PATCH v13 1/2] add `config_set` API for caching config-like files","fromName":"Tanay Abhra","fromEmail":"tanayabh@gmail.com","sentAt":"2014-07-28T10:10:38Z","receivedAt":"2014-07-28T10:10:38Z","isPatch":true,"sender":{"key":"tanayabh@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2097229?v=4"},"body":"Currently `git_config()` uses a callback mechanism and file rereads for\nconfig values. Due to this approach, it is not uncommon for the config\nfiles to be parsed several times during the run of a git program, with\ndifferent callbacks picking out different variables useful to themselves.\n\nAdd a `config_set`, that can be used to construct an in-memory cache for\nconfig-like files that the caller specifies (i.e., files like `.gitmodules`,\n`~/.gitconfig` etc.). Add two external functions `git_configset_get_value`\nand `git_configset_get_value_multi` for querying from the config sets.\n`git_configset_get_value` follows `last one wins` semantic (i.e. if there\nare multiple matches for the queried key in the files of the configset the\nvalue returned will be the last entry in `value_list`).\n`git_configset_get_value_multi` returns a list of values sorted in order of\nincreasing priority (i.e. last match will be at the end of the list). Add\ntype specific query functions like `git_configset_get_bool` and similar.\n\nAdd a default `config_set`, `the_config_set` to cache all key-value pairs\nread from usual config files (repo specific .git/config, user wide\n~/.gitconfig, XDG config and the global /etc/gitconfig). `the_config_set`\nis populated using `git_config()`.\n\nAdd two external functions `git_config_get_value` and\n`git_config_get_value_multi` for querying in a non-callback manner from\n`the_config_set`. Also, add type specific query functions that are\nimplemented as a thin wrapper around the `config_set` API.\n\nSigned-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>\nSigned-off-by: Tanay Abhra <tanayabh@gmail.com>\n---\n Documentation/technical/api-config.txt | 142 +++++++++++++++++\n cache.h                                |  32 ++++\n config.c                               | 274 +++++++++++++++++++++++++++++++++\n setup.c                                |   9 ++\n 4 files changed, 457 insertions(+)\n\ndiff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt\nindex 230b3a0..815c1ee 100644\n--- a/Documentation/technical/api-config.txt\n+++ b/Documentation/technical/api-config.txt\n@@ -77,6 +77,86 @@ To read a specific file in git-config format, use\n `git_config_from_file`. This takes the same callback and data parameters\n as `git_config`.\n \n+Querying For Specific Variables\n+-------------------------------\n+\n+For programs wanting to query for specific variables in a non-callback\n+manner, the config API provides two functions `git_config_get_value`\n+and `git_config_get_value_multi`. They both read values from an internal\n+cache generated previously from reading the config files.\n+\n+`int git_config_get_value(const char *key, const char **value)`::\n+\n+\tFinds the highest-priority value for the configuration variable `key`,\n+\tstores the pointer to it in `value` and returns 0. When the\n+\tconfiguration variable `key` is not found, returns 1 without touching\n+\t`value`. The caller should not free or modify `value`, as it is owned\n+\tby the cache.\n+\n+`const struct string_list *git_config_get_value_multi(const char *key)`::\n+\n+\tFinds and returns the value list, sorted in order of increasing priority\n+\tfor the configuration variable `key`. When the configuration variable\n+\t`key` is not found, returns NULL. The caller should not free or modify\n+\tthe returned pointer, as it is owned by the cache.\n+\n+`void git_config_clear(void)`::\n+\n+\tResets and invalidates the config cache.\n+\n+The config API also provides type specific API functions which do conversion\n+as well as retrieval for the queried variable, including:\n+\n+`int git_config_get_int(const char *key, int *dest)`::\n+\n+\tFinds and parses the value to an integer for the configuration variable\n+\t`key`. Dies on error; otherwise, stores the value of the parsed integer in\n+\t`dest` and returns 0. When the configuration variable `key` is not found,\n+\treturns 1 without touching `dest`.\n+\n+`int git_config_get_ulong(const char *key, unsigned long *dest)`::\n+\n+\tSimilar to `git_config_get_int` but for unsigned longs.\n+\n+`int git_config_get_bool(const char *key, int *dest)`::\n+\n+\tFinds and parses the value into a boolean value, for the configuration\n+\tvariable `key` respecting keywords like \"true\" and \"false\". Integer\n+\tvalues are converted into true/false values (when they are non-zero or\n+\tzero, respectively). Other values cause a die(). If parsing is successful,\n+\tstores the value of the parsed result in `dest` and returns 0. When the\n+\tconfiguration variable `key` is not found, returns 1 without touching\n+\t`dest`.\n+\n+`int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)`::\n+\n+\tSimilar to `git_config_get_bool`, except that integers are copied as-is,\n+\tand `is_bool` flag is unset.\n+\n+`int git_config_get_maybe_bool(const char *key, int *dest)`::\n+\n+\tSimilar to `git_config_get_bool`, except that it returns -1 on error\n+\trather than dying.\n+\n+`int git_config_get_string_const(const char *key, const char **dest)`::\n+\n+\tAllocates and copies the retrieved string into the `dest` parameter for\n+\tthe configuration variable `key`; if NULL string is given, prints an\n+\terror message and returns -1. When the configuration variable `key` is\n+\tnot found, returns 1 without touching `dest`.\n+\n+`int git_config_get_string(const char *key, char **dest)`::\n+\n+\tSimilar to `git_config_get_string_const`, except that retrieved value\n+\tcopied into the `dest` parameter is a mutable string.\n+\n+`int git_config_get_pathname(const char *key, const char **dest)`::\n+\n+\tSimilar to `git_config_get_string`, but expands `~` or `~user` into\n+\tthe user's home directory when found at the beginning of the path.\n+\n+See test-config.c for usage examples.\n+\n Value Parsing Helpers\n ---------------------\n \n@@ -134,6 +214,68 @@ int read_file_with_include(const char *file, config_fn_t fn, void *data)\n `git_config` respects includes automatically. The lower-level\n `git_config_from_file` does not.\n \n+Custom Configsets\n+-----------------\n+\n+A `config_set` can be used to construct an in-memory cache for\n+config-like files that the caller specifies (i.e., files like `.gitmodules`,\n+`~/.gitconfig` etc.). For example,\n+\n+---------------------------------------\n+struct config_set gm_config;\n+git_configset_init(&gm_config);\n+int b;\n+/* we add config files to the config_set */\n+git_configset_add_file(&gm_config, \".gitmodules\");\n+git_configset_add_file(&gm_config, \".gitmodules_alt\");\n+\n+if (!git_configset_get_bool(gm_config, \"submodule.frotz.ignore\", &b)) {\n+\t/* hack hack hack */\n+}\n+\n+/* when we are done with the configset */\n+git_configset_clear(&gm_config);\n+----------------------------------------\n+\n+Configset API provides functions for the above mentioned work flow, including:\n+\n+`void git_configset_init(struct config_set *cs)`::\n+\n+\tInitializes the config_set `cs`.\n+\n+`int git_configset_add_file(struct config_set *cs, const char *filename)`::\n+\n+\tParses the file and adds the variable-value pairs to the `config_set`,\n+\tdies if there is an error in parsing the file. Returns 0 on success, or\n+\t-1 if the file does not exist or is inaccessible. The user has to decide\n+\tif he wants to free the incomplete configset or continue using it when\n+\tthe function returns -1.\n+\n+`int git_configset_get_value(struct config_set *cs, const char *key, const char **value)`::\n+\n+\tFinds the highest-priority value for the configuration variable `key`\n+\tand config set `cs`, stores the pointer to it in `value` and returns 0.\n+\tWhen the configuration variable `key` is not found, returns 1 without\n+\ttouching `value`. The caller should not free or modify `value`, as it\n+\tis owned by the cache.\n+\n+`const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)`::\n+\n+\tFinds and returns the value list, sorted in order of increasing priority\n+\tfor the configuration variable `key` and config set `cs`. When the\n+\tconfiguration variable `key` is not found, returns NULL. The caller\n+\tshould not free or modify the returned pointer, as it is owned by the cache.\n+\n+`void git_configset_clear(struct config_set *cs)`::\n+\n+\tClears `config_set` structure, removes all saved variable-value pairs.\n+\n+In addition to above functions, the `config_set` API provides type specific\n+functions in the vein of `git_config_get_int` and family but with an extra\n+parameter, pointer to struct `config_set`.\n+They all behave similarly to the `git_config_get*()` family described in\n+\"Querying For Specific Variables\" above.\n+\n Writing Config Files\n --------------------\n \ndiff --git a/cache.h b/cache.h\nindex fcb511d..7292aef 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1351,6 +1351,38 @@ extern int parse_config_key(const char *var,\n \t\t\t    const char **subsection, int *subsection_len,\n \t\t\t    const char **key);\n \n+struct config_set {\n+\tstruct hashmap config_hash;\n+\tint hash_initialized;\n+};\n+\n+extern void git_configset_init(struct config_set *cs);\n+extern int git_configset_add_file(struct config_set *cs, const char *filename);\n+extern int git_configset_get_value(struct config_set *cs, const char *key, const char **value);\n+extern const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key);\n+extern void git_configset_clear(struct config_set *cs);\n+extern int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest);\n+extern int git_configset_get_string(struct config_set *cs, const char *key, char **dest);\n+extern int git_configset_get_int(struct config_set *cs, const char *key, int *dest);\n+extern int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest);\n+extern int git_configset_get_bool(struct config_set *cs, const char *key, int *dest);\n+extern int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest);\n+extern int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest);\n+extern int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest);\n+\n+extern int git_config_get_value(const char *key, const char **value);\n+extern const struct string_list *git_config_get_value_multi(const char *key);\n+extern void git_config_clear(void);\n+extern void git_config_iter(config_fn_t fn, void *data);\n+extern int git_config_get_string_const(const char *key, const char **dest);\n+extern int git_config_get_string(const char *key, char **dest);\n+extern int git_config_get_int(const char *key, int *dest);\n+extern int git_config_get_ulong(const char *key, unsigned long *dest);\n+extern int git_config_get_bool(const char *key, int *dest);\n+extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest);\n+extern int git_config_get_maybe_bool(const char *key, int *dest);\n+extern int git_config_get_pathname(const char *key, const char **dest);\n+\n extern int committer_ident_sufficiently_given(void);\n extern int author_ident_sufficiently_given(void);\n \ndiff --git a/config.c b/config.c\nindex 9767c4b..d3ad661 100644\n--- a/config.c\n+++ b/config.c\n@@ -9,6 +9,8 @@\n #include \"exec_cmd.h\"\n #include \"strbuf.h\"\n #include \"quote.h\"\n+#include \"hashmap.h\"\n+#include \"string-list.h\"\n \n struct config_source {\n \tstruct config_source *prev;\n@@ -33,10 +35,23 @@ struct config_source {\n \tlong (*do_ftell)(struct config_source *c);\n };\n \n+struct config_set_element {\n+\tstruct hashmap_entry ent;\n+\tchar *key;\n+\tstruct string_list value_list;\n+};\n+\n static struct config_source *cf;\n \n static int zlib_compression_seen;\n \n+/*\n+ * Default config_set that contains key-value pairs from the usual set of config\n+ * config files (i.e repo specific .git/config, user wide ~/.gitconfig, XDG\n+ * config file and the global /etc/gitconfig)\n+ */\n+static struct config_set the_config_set;\n+\n static int config_file_fgetc(struct config_source *conf)\n {\n \treturn fgetc(conf->u.file);\n@@ -1212,6 +1227,262 @@ int git_config(config_fn_t fn, void *data)\n \treturn git_config_with_options(fn, data, NULL, 1);\n }\n \n+static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)\n+{\n+\tstruct config_set_element k;\n+\tstruct config_set_element *found_entry;\n+\tchar *normalized_key;\n+\tint ret;\n+\t/*\n+\t * `key` may come from the user, so normalize it before using it\n+\t * for querying entries from the hashmap.\n+\t */\n+\tret = git_config_parse_key(key, &normalized_key, NULL);\n+\n+\tif (ret)\n+\t\treturn NULL;\n+\n+\thashmap_entry_init(&k, strhash(normalized_key));\n+\tk.key = normalized_key;\n+\tfound_entry = hashmap_get(&cs->config_hash, &k, NULL);\n+\tfree(normalized_key);\n+\treturn found_entry;\n+}\n+\n+static int configset_add_value(struct config_set *cs, const char *key, const char *value)\n+{\n+\tstruct config_set_element *e;\n+\te = configset_find_element(cs, key);\n+\t/*\n+\t * Since the keys are being fed by git_config*() callback mechanism, they\n+\t * are already normalized. So simply add them without any further munging.\n+\t */\n+\tif (!e) {\n+\t\te = xmalloc(sizeof(*e));\n+\t\thashmap_entry_init(e, strhash(key));\n+\t\te->key = xstrdup(key);\n+\t\tstring_list_init(&e->value_list, 1);\n+\t\thashmap_add(&cs->config_hash, e);\n+\t}\n+\tstring_list_append_nodup(&e->value_list, value ? xstrdup(value) : NULL);\n+\n+\treturn 0;\n+}\n+\n+static int config_set_element_cmp(const struct config_set_element *e1,\n+\t\t\t\t const struct config_set_element *e2, const void *unused)\n+{\n+\treturn strcmp(e1->key, e2->key);\n+}\n+\n+void git_configset_init(struct config_set *cs)\n+{\n+\thashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_set_element_cmp, 0);\n+\tcs->hash_initialized = 1;\n+}\n+\n+void git_configset_clear(struct config_set *cs)\n+{\n+\tstruct config_set_element *entry;\n+\tstruct hashmap_iter iter;\n+\tif (!cs->hash_initialized)\n+\t\treturn;\n+\n+\thashmap_iter_init(&cs->config_hash, &iter);\n+\twhile ((entry = hashmap_iter_next(&iter))) {\n+\t\tfree(entry->key);\n+\t\tstring_list_clear(&entry->value_list, 0);\n+\t}\n+\thashmap_free(&cs->config_hash, 1);\n+\tcs->hash_initialized = 0;\n+}\n+\n+static int config_set_callback(const char *key, const char *value, void *cb)\n+{\n+\tstruct config_set *cs = cb;\n+\tconfigset_add_value(cs, key, value);\n+\treturn 0;\n+}\n+\n+int git_configset_add_file(struct config_set *cs, const char *filename)\n+{\n+\treturn git_config_from_file(config_set_callback, filename, cs);\n+}\n+\n+int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n+{\n+\tconst struct string_list *values = NULL;\n+\t/*\n+\t * Follows \"last one wins\" semantic, i.e., if there are multiple matches for the\n+\t * queried key in the files of the configset, the value returned will be the last\n+\t * value in the value list for that key.\n+\t */\n+\tvalues = git_configset_get_value_multi(cs, key);\n+\n+\tif (!values)\n+\t\treturn 1;\n+\tassert(values->nr > 0);\n+\t*value = values->items[values->nr - 1].string;\n+\treturn 0;\n+}\n+\n+const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)\n+{\n+\tstruct config_set_element *e = configset_find_element(cs, key);\n+\treturn e ? &e->value_list : NULL;\n+}\n+\n+int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest)\n+{\n+\tconst char *value;\n+\tif (!git_configset_get_value(cs, key, &value))\n+\t\treturn git_config_string(dest, key, value);\n+\telse\n+\t\treturn 1;\n+}\n+\n+int git_configset_get_string(struct config_set *cs, const char *key, char **dest)\n+{\n+\treturn git_configset_get_string_const(cs, key, (const char **)dest);\n+}\n+\n+int git_configset_get_int(struct config_set *cs, const char *key, int *dest)\n+{\n+\tconst char *value;\n+\tif (!git_configset_get_value(cs, key, &value)) {\n+\t\t*dest = git_config_int(key, value);\n+\t\treturn 0;\n+\t} else\n+\t\treturn 1;\n+}\n+\n+int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest)\n+{\n+\tconst char *value;\n+\tif (!git_configset_get_value(cs, key, &value)) {\n+\t\t*dest = git_config_ulong(key, value);\n+\t\treturn 0;\n+\t} else\n+\t\treturn 1;\n+}\n+\n+int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)\n+{\n+\tconst char *value;\n+\tif (!git_configset_get_value(cs, key, &value)) {\n+\t\t*dest = git_config_bool(key, value);\n+\t\treturn 0;\n+\t} else\n+\t\treturn 1;\n+}\n+\n+int git_configset_get_bool_or_int(struct config_set *cs, const char *key,\n+\t\t\t\tint *is_bool, int *dest)\n+{\n+\tconst char *value;\n+\tif (!git_configset_get_value(cs, key, &value)) {\n+\t\t*dest = git_config_bool_or_int(key, value, is_bool);\n+\t\treturn 0;\n+\t} else\n+\t\treturn 1;\n+}\n+\n+int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)\n+{\n+\tconst char *value;\n+\tif (!git_configset_get_value(cs, key, &value)) {\n+\t\t*dest = git_config_maybe_bool(key, value);\n+\t\tif (*dest == -1)\n+\t\t\treturn -1;\n+\t\treturn 0;\n+\t} else\n+\t\treturn 1;\n+}\n+\n+int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)\n+{\n+\tconst char *value;\n+\tif (!git_configset_get_value(cs, key, &value))\n+\t\treturn git_config_pathname(dest, key, value);\n+\telse\n+\t\treturn 1;\n+}\n+\n+static void git_config_check_init(void)\n+{\n+\tif (the_config_set.hash_initialized)\n+\t\treturn;\n+\tgit_configset_init(&the_config_set);\n+\tgit_config(config_set_callback, &the_config_set);\n+}\n+\n+void git_config_clear(void)\n+{\n+\tif (!the_config_set.hash_initialized)\n+\t\treturn;\n+\tgit_configset_clear(&the_config_set);\n+}\n+\n+int git_config_get_value(const char *key, const char **value)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_value(&the_config_set, key, value);\n+}\n+\n+const struct string_list *git_config_get_value_multi(const char *key)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_value_multi(&the_config_set, key);\n+}\n+\n+int git_config_get_string_const(const char *key, const char **dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_string_const(&the_config_set, key, dest);\n+}\n+\n+int git_config_get_string(const char *key, char **dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_config_get_string_const(key, (const char **)dest);\n+}\n+\n+int git_config_get_int(const char *key, int *dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_int(&the_config_set, key, dest);\n+}\n+\n+int git_config_get_ulong(const char *key, unsigned long *dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_ulong(&the_config_set, key, dest);\n+}\n+\n+int git_config_get_bool(const char *key, int *dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_bool(&the_config_set, key, dest);\n+}\n+\n+int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_bool_or_int(&the_config_set, key, is_bool, dest);\n+}\n+\n+int git_config_get_maybe_bool(const char *key, int *dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_maybe_bool(&the_config_set, key, dest);\n+}\n+\n+int git_config_get_pathname(const char *key, const char **dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_pathname(&the_config_set, key, dest);\n+}\n+\n /*\n  * Find all the stuff for git_config_set() below.\n  */\n@@ -1707,6 +1978,9 @@ int git_config_set_multivar_in_file(const char *config_filename,\n \tlock = NULL;\n \tret = 0;\n \n+\t/* Invalidate the config cache */\n+\tgit_config_clear();\n+\n out_free:\n \tif (lock)\n \t\trollback_lock_file(lock);\ndiff --git a/setup.c b/setup.c\nindex 0a22f8b..793369d 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -625,6 +625,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)\n \tint one_filesystem = 1;\n \n \t/*\n+\t * We may have read an incomplete configuration before\n+\t * setting-up the git directory. If so, clear the cache so\n+\t * that the next queries to the configuration reload complete\n+\t * configuration (including the per-repo config file that we\n+\t * ignored previously).\n+\t */\n+\tgit_config_clear();\n+\n+\t/*\n \t * Let's assume that we are in a git repository.\n \t * If it turns out later that we are somewhere else, the value will be\n \t * updated accordingly.\n-- \n1.9.0.GIT\n"},{"id":"246766","messageId":"1406542239-17024-3-git-send-email-tanayabh@gmail.com","threadId":"37223","inReplyTo":"1406542239-17024-1-git-send-email-tanayabh@gmail.com","subject":"[PATCH v13 2/2] test-config: add tests for the config_set API","fromName":"Tanay Abhra","fromEmail":"tanayabh@gmail.com","sentAt":"2014-07-28T10:10:39Z","receivedAt":"2014-07-28T10:10:39Z","isPatch":true,"sender":{"key":"tanayabh@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2097229?v=4"},"body":"Expose the `config_set` C API as a set of simple commands in order to\nfacilitate testing. Add tests for the `config_set` API as well as for\n`git_config_get_*()` family for the usual config files.\n\nSigned-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>\nSigned-off-by: Tanay Abhra <tanayabh@gmail.com>\n---\n .gitignore            |   1 +\n Makefile              |   1 +\n t/t1308-config-set.sh | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++\n test-config.c         | 142 +++++++++++++++++++++++++++++++++++\n 4 files changed, 344 insertions(+)\n create mode 100755 t/t1308-config-set.sh\n create mode 100644 test-config.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 81e12c0..5bfb234 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -178,6 +178,7 @@\n /gitweb/static/gitweb.min.*\n /test-chmtime\n /test-ctype\n+/test-config\n /test-date\n /test-delta\n /test-dump-cache-tree\ndiff --git a/Makefile b/Makefile\nindex 2320de5..b7462e3 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -551,6 +551,7 @@ PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n TEST_PROGRAMS_NEED_X += test-chmtime\n TEST_PROGRAMS_NEED_X += test-ctype\n+TEST_PROGRAMS_NEED_X += test-config\n TEST_PROGRAMS_NEED_X += test-date\n TEST_PROGRAMS_NEED_X += test-delta\n TEST_PROGRAMS_NEED_X += test-dump-cache-tree\ndiff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh\nnew file mode 100755\nindex 0000000..7fdf840\n--- /dev/null\n+++ b/t/t1308-config-set.sh\n@@ -0,0 +1,200 @@\n+#!/bin/sh\n+\n+test_description='Test git config-set API in different settings'\n+\n+. ./test-lib.sh\n+\n+# 'check_config get_* section.key value' verifies that the entry for\n+# section.key is 'value'\n+check_config () {\n+\tif test \"$1\" = expect_code\n+\tthen\n+\t\texpect_code=\"$2\" && shift && shift\n+\telse\n+\t\texpect_code=0\n+\tfi &&\n+\top=$1 key=$2 && shift && shift &&\n+\tif test $# != 0\n+\tthen\n+\t\tprintf \"%s\\n\" \"$@\"\n+\tfi >expect &&\n+\ttest_expect_code $expect_code test-config \"$op\" \"$key\" >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'setup default config' '\n+\tcat >.git/config <<\\EOF\n+\t[case]\n+\t\tpenguin = very blue\n+\t\tMovie = BadPhysics\n+\t\tUPPERCASE = true\n+\t\tMixedCase = true\n+\t\tmy =\n+\t\tfoo\n+\t\tbaz = sam\n+\t[Cores]\n+\t\tWhatEver = Second\n+\t\tbaz = bar\n+\t[cores]\n+\t\tbaz = bat\n+\t[CORES]\n+\t\tbaz = ball\n+\t[my \"Foo bAr\"]\n+\t\thi = mixed-case\n+\t[my \"FOO BAR\"]\n+\t\thi = upper-case\n+\t[my \"foo bar\"]\n+\t\thi = lower-case\n+\t[case]\n+\t\tbaz = bat\n+\t\tbaz = hask\n+\t[lamb]\n+\t\tchop = 65\n+\t\thead = none\n+\t[goat]\n+\t\tlegs = 4\n+\t\thead = true\n+\t\tskin = false\n+\t\tnose = 1\n+\t\thorns\n+\tEOF\n+'\n+\n+test_expect_success 'get value for a simple key' '\n+\tcheck_config get_value case.penguin \"very blue\"\n+'\n+\n+test_expect_success 'get value for a key with value as an empty string' '\n+\tcheck_config get_value case.my \"\"\n+'\n+\n+test_expect_success 'get value for a key with value as NULL' '\n+\tcheck_config get_value case.foo \"(NULL)\"\n+'\n+\n+test_expect_success 'upper case key' '\n+\tcheck_config get_value case.UPPERCASE \"true\" &&\n+\tcheck_config get_value case.uppercase \"true\"\n+'\n+\n+test_expect_success 'mixed case key' '\n+\tcheck_config get_value case.MixedCase \"true\" &&\n+\tcheck_config get_value case.MIXEDCASE \"true\" &&\n+\tcheck_config get_value case.mixedcase \"true\"\n+'\n+\n+test_expect_success 'key and value with mixed case' '\n+\tcheck_config get_value case.Movie \"BadPhysics\"\n+'\n+\n+test_expect_success 'key with case sensitive subsection' '\n+\tcheck_config get_value \"my.Foo bAr.hi\" \"mixed-case\" &&\n+\tcheck_config get_value \"my.FOO BAR.hi\" \"upper-case\" &&\n+\tcheck_config get_value \"my.foo bar.hi\" \"lower-case\"\n+'\n+\n+test_expect_success 'key with case insensitive section header' '\n+\tcheck_config get_value cores.baz \"ball\" &&\n+\tcheck_config get_value Cores.baz \"ball\" &&\n+\tcheck_config get_value CORES.baz \"ball\" &&\n+\tcheck_config get_value coreS.baz \"ball\"\n+'\n+\n+test_expect_success 'key with case insensitive section header & variable' '\n+\tcheck_config get_value CORES.BAZ \"ball\" &&\n+\tcheck_config get_value cores.baz \"ball\" &&\n+\tcheck_config get_value cores.BaZ \"ball\" &&\n+\tcheck_config get_value cOreS.bAz \"ball\"\n+'\n+\n+test_expect_success 'find value with misspelled key' '\n+\tcheck_config expect_code 1 get_value \"my.fOo Bar.hi\" \"Value not found for \\\"my.fOo Bar.hi\\\"\"\n+'\n+\n+test_expect_success 'find value with the highest priority' '\n+\tcheck_config get_value case.baz \"hask\"\n+'\n+\n+test_expect_success 'find integer value for a key' '\n+\tcheck_config get_int lamb.chop 65\n+'\n+\n+test_expect_success 'find integer if value is non parse-able' '\n+\tcheck_config expect_code 128 get_int lamb.head\n+'\n+\n+test_expect_success 'find bool value for the entered key' '\n+\tcheck_config get_bool goat.head 1 &&\n+\tcheck_config get_bool goat.skin 0 &&\n+\tcheck_config get_bool goat.nose 1 &&\n+\tcheck_config get_bool goat.horns 1 &&\n+\tcheck_config get_bool goat.legs 1\n+'\n+\n+test_expect_success 'find multiple values' '\n+\tcheck_config get_value_multi case.baz sam bat hask\n+'\n+\n+test_expect_success 'find value from a configset' '\n+\tcat >config2 <<-\\EOF &&\n+\t[case]\n+\t\tbaz = lama\n+\t[my]\n+\t\tnew = silk\n+\t[case]\n+\t\tbaz = ball\n+\tEOF\n+\techo silk >expect &&\n+\ttest-config configset_get_value my.new config2 .git/config >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'find value with highest priority from a configset' '\n+\techo hask >expect &&\n+\ttest-config configset_get_value case.baz config2 .git/config >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'find value_list for a key from a configset' '\n+\tcat >except <<-\\EOF &&\n+\tsam\n+\tbat\n+\thask\n+\tlama\n+\tball\n+\tEOF\n+\ttest-config configset_get_value case.baz config2 .git/config >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'proper error on non-existent files' '\n+\techo \"Error (-1) reading configuration file non-existent-file.\" >expect &&\n+\ttest_expect_code 2 test-config configset_get_value foo.bar non-existent-file 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success POSIXPERM,SANITY 'proper error on non-accessible files' '\n+\tchmod -r .git/config &&\n+\ttest_when_finished \"chmod +r .git/config\" &&\n+\techo \"Error (-1) reading configuration file .git/config.\" >expect &&\n+\ttest_expect_code 2 test-config configset_get_value foo.bar .git/config 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'proper error on error in default config files' '\n+\tcp .git/config .git/config.old &&\n+\ttest_when_finished \"mv .git/config.old .git/config\" &&\n+\techo \"[\" >>.git/config &&\n+\techo \"fatal: bad config file line 35 in .git/config\" >expect &&\n+\ttest_expect_code 128 test-config get_value foo.bar 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'proper error on error in custom config files' '\n+\techo \"[\" >>syntax-error &&\n+\techo \"fatal: bad config file line 1 in syntax-error\" >expect &&\n+\ttest_expect_code 128 test-config configset_get_value foo.bar syntax-error 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/test-config.c b/test-config.c\nnew file mode 100644\nindex 0000000..9dd1b22\n--- /dev/null\n+++ b/test-config.c\n@@ -0,0 +1,142 @@\n+#include \"cache.h\"\n+#include \"string-list.h\"\n+\n+/*\n+ * This program exposes the C API of the configuration mechanism\n+ * as a set of simple commands in order to facilitate testing.\n+ *\n+ * Reads stdin and prints result of command to stdout:\n+ *\n+ * get_value -> prints the value with highest priority for the entered key\n+ *\n+ * get_value_multi -> prints all values for the entered key in increasing order\n+ *\t\t     of priority\n+ *\n+ * get_int -> print integer value for the entered key or die\n+ *\n+ * get_bool -> print bool value for the entered key or die\n+ *\n+ * configset_get_value -> returns value with the highest priority for the entered key\n+ * \t\t\tfrom a config_set constructed from files entered as arguments.\n+ *\n+ * configset_get_value_multi -> returns value_list for the entered key sorted in\n+ * \t\t\t\tascending order of priority from a config_set\n+ * \t\t\t\tconstructed from files entered as arguments.\n+ *\n+ * Examples:\n+ *\n+ * To print the value with highest priority for key \"foo.bAr Baz.rock\":\n+ * \ttest-config get_value \"foo.bAr Baz.rock\"\n+ *\n+ */\n+\n+\n+int main(int argc, char **argv)\n+{\n+\tint i, val;\n+\tconst char *v;\n+\tconst struct string_list *strptr;\n+\tstruct config_set cs;\n+\tgit_configset_init(&cs);\n+\n+\tif (argc < 2) {\n+\t\tfprintf(stderr, \"Please, provide a command name on the command-line\\n\");\n+\t\tgoto exit1;\n+\t} else if (argc == 3 && !strcmp(argv[1], \"get_value\")) {\n+\t\tif (!git_config_get_value(argv[2], &v)) {\n+\t\t\tif (!v)\n+\t\t\t\tprintf(\"(NULL)\\n\");\n+\t\t\telse\n+\t\t\t\tprintf(\"%s\\n\", v);\n+\t\t\tgoto exit0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\tgoto exit1;\n+\t\t}\n+\t} else if (argc == 3 && !strcmp(argv[1], \"get_value_multi\")) {\n+\t\tstrptr = git_config_get_value_multi(argv[2]);\n+\t\tif (strptr) {\n+\t\t\tfor (i = 0; i < strptr->nr; i++) {\n+\t\t\t\tv = strptr->items[i].string;\n+\t\t\t\tif (!v)\n+\t\t\t\t\tprintf(\"(NULL)\\n\");\n+\t\t\t\telse\n+\t\t\t\t\tprintf(\"%s\\n\", v);\n+\t\t\t}\n+\t\t\tgoto exit0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\tgoto exit1;\n+\t\t}\n+\t} else if (argc == 3 && !strcmp(argv[1], \"get_int\")) {\n+\t\tif (!git_config_get_int(argv[2], &val)) {\n+\t\t\tprintf(\"%d\\n\", val);\n+\t\t\tgoto exit0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\tgoto exit1;\n+\t\t}\n+\t} else if (argc == 3 && !strcmp(argv[1], \"get_bool\")) {\n+\t\tif (!git_config_get_bool(argv[2], &val)) {\n+\t\t\tprintf(\"%d\\n\", val);\n+\t\t\tgoto exit0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\tgoto exit1;\n+\t\t}\n+\t} else if (!strcmp(argv[1], \"configset_get_value\")) {\n+\t\tfor (i = 3; i < argc; i++) {\n+\t\t\tint err;\n+\t\t\tif ((err = git_configset_add_file(&cs, argv[i]))) {\n+\t\t\t\tfprintf(stderr, \"Error (%d) reading configuration file %s.\\n\", err, argv[i]);\n+\t\t\t\tgoto exit2;\n+\t\t\t}\n+\t\t}\n+\t\tif (!git_configset_get_value(&cs, argv[2], &v)) {\n+\t\t\tif (!v)\n+\t\t\t\tprintf(\"(NULL)\\n\");\n+\t\t\telse\n+\t\t\t\tprintf(\"%s\\n\", v);\n+\t\t\tgoto exit0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\tgoto exit1;\n+\t\t}\n+\t} else if (!strcmp(argv[1], \"configset_get_value_multi\")) {\n+\t\tfor (i = 3; i < argc; i++) {\n+\t\t\tint err;\n+\t\t\tif ((err = git_configset_add_file(&cs, argv[i]))) {\n+\t\t\t\tfprintf(stderr, \"Error (%d) reading configuration file %s.\\n\", err, argv[i]);\n+\t\t\t\tgoto exit2;\n+\t\t\t}\n+\t\t}\n+\t\tstrptr = git_configset_get_value_multi(&cs, argv[2]);\n+\t\tif (strptr) {\n+\t\t\tfor (i = 0; i < strptr->nr; i++) {\n+\t\t\t\tv = strptr->items[i].string;\n+\t\t\t\tif (!v)\n+\t\t\t\t\tprintf(\"(NULL)\\n\");\n+\t\t\t\telse\n+\t\t\t\t\tprintf(\"%s\\n\", v);\n+\t\t\t}\n+\t\t\tgoto exit0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\tgoto exit1;\n+\t\t}\n+\t}\n+\n+\tdie(\"%s: Please check the syntax and the function name\", argv[0]);\n+\n+exit0:\n+\tgit_configset_clear(&cs);\n+\treturn 0;\n+\n+exit1:\n+\tgit_configset_clear(&cs);\n+\treturn 1;\n+\n+exit2:\n+\tgit_configset_clear(&cs);\n+\treturn 2;\n+}\n-- \n1.9.0.GIT\n"},{"id":"246785","messageId":"vpqegx5vivp.fsf@anie.imag.fr","threadId":"37223","inReplyTo":"1406542239-17024-1-git-send-email-tanayabh@gmail.com","subject":"Re: [PATCH v13 0/2] git config cache & special querying api utilizing the cache","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2014-07-28T11:10:34Z","receivedAt":"2014-07-28T11:10:34Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Tanay Abhra <tanayabh@gmail.com> writes:\n\n> [v13]: v12 was rejected because of redundant implementation of the new functions,\n> \thope this one is okay.\n\nIt is to me at least.\n\nThanks,\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"246948","messageId":"xmqqsilj26dk.fsf@gitster.dls.corp.google.com","threadId":"37223","inReplyTo":"vpqegx5vivp.fsf@anie.imag.fr","subject":"Re: [PATCH v13 0/2] git config cache & special querying api utilizing the cache","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-07-29T21:37:59Z","receivedAt":"2014-07-29T21:37:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:\n\n> Tanay Abhra <tanayabh@gmail.com> writes:\n>\n>> [v13]: v12 was rejected because of redundant implementation of the new functions,\n>> \thope this one is okay.\n>\n> It is to me at least.\n>\n> Thanks,\n\nOK, will replace.\n"}]}