{"thread":{"id":"37085","subject":"[PATCH v6 0/3] git config cache & special querying api utilizing the cache","startedAt":"2014-07-09T10:57:32Z","lastAt":"2014-07-10T16:46:14Z","messageCount":14,"participants":["Tanay Abhra","Matthieu Moy","Junio C Hamano"],"isPatch":true,"patchVersion":6,"patchTotal":3},"messages":[{"id":"245618","messageId":"1404903454-10154-1-git-send-email-tanayabh@gmail.com","threadId":"37085","inReplyTo":null,"subject":"[PATCH v6 0/3] git config cache & special querying api utilizing the cache","fromName":"Tanay Abhra","fromEmail":"tanayabh@gmail.com","sentAt":"2014-07-09T10:57:32Z","receivedAt":"2014-07-09T10:57:32Z","isPatch":true,"sender":{"key":"tanayabh@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2097229?v=4"},"body":"Hi,\n\n[PATCH V7]: Style nits and a broken && chain corrected in `t/t1308-config-set.sh`. See\n\t[9] for the nits.\n\n[PATCH V6]: Style nits and mistakes corrected. Diff between v6 and v5[8] is at the bottom.\n\t    Thanks to Matthieu, Ramsay and Ram for their suggestions.\n\n[PATCH V5]: `config_set` now uses a single hashmap. Corrected style nits raised in\n\t\t\tthe thread[7]. Thanks to Junio and Matthieu for their suggestions.\n\n[PATCH v4]: Introduced `config_set` construct which points to a ordered set of\n\tconfig-files cached as hashmaps. Added relevant API functions. For more\n\tdetails see the documentation. Rewrote the git_config_get* family to use\n\t`config_set` internally. Added tests for both config_set API and git_config_get\n\tfamily. Added type specific API functions which parses the found value and\n\tconverts it into a specific type.\n\tMost of the changes implemented are the result of discussion in [6].\n\tThanks to Eric, Ramsay, Junio, Matthieu & Karsten for their suggestions\n\tand review.\n\n[PATCH v3]: Added flag for NULL values that were causing segfaults in some cases.\n\tAdded test-config for usage examples.\n\tMinor changes and corrections. Refer to discussion thread[5] for more details.\n\tThanks to Matthieu, Jeff and Junio for their valuable suggestions.\n\n[PATCH v2]:Changed the string_list to a struct instead of pointer to a struct.\n\tAdded string-list initilization functions.\n\tMinor mistakes corrected acoording to review comments[4]. Thanks to\n\tEric and Matthieu for their review.\n\n[PATCH V1]:Most of the invaluable suggestions by Eric Sunshine, Torsten Bogershausen and\n\tJeff King has been implemented[1]. Complete rewrite of config_cache*() family\n\tusing git_config() as hook as suggested by Jeff. Thanks for the review.\n\n[RFC V2]: Improved according to the suggestions by Eric Sunshine and Torsten Bogershausen.\n\tAdded cache invalidation when config file is changed.[2]\n\tI am using git_config_set_multivar_in_file() as an update hook.\n\nThis is patch is for this year's GSoC. My project is\n\"Git Config API improvements\". The link of my proposal is appended below [3].\n\nThe aim of this patch series is to generate a cache for querying values from\nthe config files in a non-callback manner as the current method reads and\nparses the config files every time a value is queried for.\n\nThe cache is generated from hooking the update_cache function to the current\nparsing and callback mechanism in config.c. It is implemented as an hashmap\nusing the hashmap-api with variables and its corresponding values list as\nits members. The values in the list are sorted in order of increasing priority.\nThe cache is initialised the first time when any of the new query functions is\ncalled. It is invalidated by using git_config_set_multivar_in_file() as an\nupdate hook.\n\nWe add two new functions to the config-api, git_config_get_string() and\ngit_config_get_string_multi() for querying in a non callback manner from\nthe cache.\n\n[1] http://marc.info/?t=140172066200006&r=1&w=2\n[2] http://git.661346.n2.nabble.com/RFC-PATCH-0-2-Git-config-cache-amp-special-querying-api-utilizing-the-cache-td7611691.html\n[3] https://drive.google.com/file/d/0B4suZ-aHqDcnSUZJRXVTTnZUN1E/edit?usp=sharing\n[4] http://thread.gmane.org/gmane.comp.version-control.git/251073/focus=251369\n[5] http://thread.gmane.org/gmane.comp.version-control.git/251704/\n[6] http://thread.gmane.org/gmane.comp.version-control.git/252329/\n[7] http://marc.info/?t=140428115200001&r=1&w=2\n[8] http://article.gmane.org/gmane.comp.version-control.git/252942/\n[9] http://thread.gmane.org/gmane.comp.version-control.git/252959/\n\nTanay Abhra (2):\n  config-hash.c\n  test-config\n\n .gitignore                             |   1 +\n Documentation/technical/api-config.txt | 134 +++++++++++++++\n Makefile                               |   2 +\n cache.h                                |  34 ++++\n config-hash.c                          | 295 +++++++++++++++++++++++++++++++++\n config.c                               |   3 +\n t/t1308-config-set.sh                  | 168 +++++++++++++++++++\n test-config.c                          | 125 ++++++++++++++\n 8 files changed, 762 insertions(+)\n create mode 100644 config-hash.c\n create mode 100755 t/t1308-config-set.sh\n create mode 100644 test-config.c\n\n-- \n1.9.0.GIT\n"},{"id":"245620","messageId":"1404903454-10154-2-git-send-email-tanayabh@gmail.com","threadId":"37085","inReplyTo":"1404903454-10154-1-git-send-email-tanayabh@gmail.com","subject":"[PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Tanay Abhra","fromEmail":"tanayabh@gmail.com","sentAt":"2014-07-09T10:57:33Z","receivedAt":"2014-07-09T10:57:33Z","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: Tanay Abhra <tanayabh@gmail.com>\n---\n Documentation/technical/api-config.txt | 134 +++++++++++++++\n Makefile                               |   1 +\n cache.h                                |  34 ++++\n config-hash.c                          | 295 +++++++++++++++++++++++++++++++++\n config.c                               |   3 +\n 5 files changed, 467 insertions(+)\n create mode 100644 config-hash.c\n\ndiff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt\nindex 230b3a0..65a6717 100644\n--- a/Documentation/technical/api-config.txt\n+++ b/Documentation/technical/api-config.txt\n@@ -77,6 +77,81 @@ 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 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_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 +209,65 @@ 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.\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/Makefile b/Makefile\nindex 07ea105..d503f78 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -777,6 +777,7 @@ LIB_OBJS += commit.o\n LIB_OBJS += compat/obstack.o\n LIB_OBJS += compat/terminal.o\n LIB_OBJS += config.o\n+LIB_OBJS += config-hash.o\n LIB_OBJS += connect.o\n LIB_OBJS += connected.o\n LIB_OBJS += convert.o\ndiff --git a/cache.h b/cache.h\nindex df65231..35bd71e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1325,6 +1325,40 @@ 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+/* config-hash.c */\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 void git_configset_iter(struct config_set *cs, config_fn_t fn, void *data);\n+extern int git_configset_get_string(struct config_set *cs, const char *key, const 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 char *key, const 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+\n extern int committer_ident_sufficiently_given(void);\n extern int author_ident_sufficiently_given(void);\n \ndiff --git a/config-hash.c b/config-hash.c\nnew file mode 100644\nindex 0000000..7cf6a96\n--- /dev/null\n+++ b/config-hash.c\n@@ -0,0 +1,295 @@\n+#include \"cache.h\"\n+#include \"hashmap.h\"\n+#include \"string-list.h\"\n+\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+struct config_hash_entry {\n+\tstruct hashmap_entry ent;\n+\tchar *key;\n+\tstruct string_list value_list;\n+};\n+\n+static int config_hash_add_value(const char *, const char *, struct hashmap *);\n+\n+static int config_hash_entry_cmp(const struct config_hash_entry *e1,\n+\t\t\t\t const struct config_hash_entry *e2, const void *unused)\n+{\n+\treturn strcmp(e1->key, e2->key);\n+}\n+\n+static void configset_init(struct config_set *cs)\n+{\n+\tif (!cs->hash_initialized) {\n+\t\thashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_hash_entry_cmp, 0);\n+\t\tcs->hash_initialized = 1;\n+\t}\n+}\n+\n+static int config_hash_callback(const char *key, const char *value, void *cb)\n+{\n+\tstruct config_set *cs = cb;\n+\tconfig_hash_add_value(key, value, &cs->config_hash);\n+\treturn 0;\n+}\n+\n+int git_configset_add_file(struct config_set *cs, const char *filename)\n+{\n+\tint ret = 0;\n+\tconfigset_init(cs);\n+\tret = git_config_from_file(config_hash_callback, filename, cs);\n+\tif (!ret)\n+\t\treturn 0;\n+\telse {\n+\t\thashmap_free(&cs->config_hash, 1);\n+\t\tcs->hash_initialized = 0;\n+\t\treturn -1;\n+\t}\n+}\n+\n+static struct config_hash_entry *config_hash_find_entry(const char *key,\n+\t\t\t\t\t\t\tstruct hashmap *config_hash)\n+{\n+\tstruct config_hash_entry k;\n+\tstruct config_hash_entry *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(config_hash, &k, NULL);\n+\tfree(normalized_key);\n+\treturn found_entry;\n+}\n+\n+static struct string_list *configset_get_list(const char *key, struct config_set *cs)\n+{\n+\tstruct config_hash_entry *e = config_hash_find_entry(key, &cs->config_hash);\n+\treturn e ? &e->value_list : NULL;\n+}\n+\n+static int config_hash_add_value(const char *key, const char *value, struct hashmap *config_hash)\n+{\n+\tstruct config_hash_entry *e;\n+\te = config_hash_find_entry(key, config_hash);\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\tmemset(&e->value_list, 0, sizeof(e->value_list));\n+\t\te->value_list.strdup_strings = 1;\n+\t\thashmap_add(config_hash, e);\n+\t}\n+\tstring_list_append_nodup(&e->value_list, value ? xstrdup(value) : NULL);\n+\n+\treturn 0;\n+}\n+\n+void git_configset_init(struct config_set *cs)\n+{\n+\tcs->hash_initialized = 0;\n+}\n+\n+int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n+{\n+\tstruct 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 = configset_get_list(key, cs);\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+\treturn configset_get_list(key, cs);\n+}\n+\n+void git_configset_clear(struct config_set *cs)\n+{\n+\tstruct config_hash_entry *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+}\n+\n+int git_configset_get_string(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_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 int git_config_check_init(void)\n+{\n+\tint ret = 0;\n+\tif (the_config_set.hash_initialized)\n+\t\treturn 0;\n+\tconfigset_init(&the_config_set);\n+\tret = git_config(config_hash_callback, &the_config_set);\n+\tif (ret >= 0)\n+\t\treturn 0;\n+\telse {\n+\t\thashmap_free(&the_config_set.config_hash, 1);\n+\t\tthe_config_set.hash_initialized = 0;\n+\t\treturn -1;\n+\t}\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+void git_config_clear(void)\n+{\n+\tif (!the_config_set.hash_initialized)\n+\t\treturn;\n+\tgit_configset_clear(&the_config_set);\n+\tthe_config_set.hash_initialized = 0;\n+}\n+\n+int git_config_get_string(const char *key, const char **dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_string(&the_config_set, key, 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+}\ndiff --git a/config.c b/config.c\nindex a1aef1c..6cffec7 100644\n--- a/config.c\n+++ b/config.c\n@@ -1708,6 +1708,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);\n-- \n1.9.0.GIT\n"},{"id":"245619","messageId":"1404903454-10154-3-git-send-email-tanayabh@gmail.com","threadId":"37085","inReplyTo":"1404903454-10154-1-git-send-email-tanayabh@gmail.com","subject":"[PATCH v7 2/2] test-config: Add tests for the config_set API","fromName":"Tanay Abhra","fromEmail":"tanayabh@gmail.com","sentAt":"2014-07-09T10:57:34Z","receivedAt":"2014-07-09T10:57:34Z","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: Tanay Abhra <tanayabh@gmail.com>\n---\n .gitignore            |   1 +\n Makefile              |   1 +\n t/t1308-config-set.sh | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++\n test-config.c         | 125 +++++++++++++++++++++++++++++++++++++\n 4 files changed, 295 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 42294e5..eeb66e2 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -176,6 +176,7 @@\n /gitweb/static/gitweb.js\n /gitweb/static/gitweb.min.*\n /test-chmtime\n+/test-config\n /test-ctype\n /test-date\n /test-delta\ndiff --git a/Makefile b/Makefile\nindex d503f78..e875070 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -548,6 +548,7 @@ X =\n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n TEST_PROGRAMS_NEED_X += test-chmtime\n+TEST_PROGRAMS_NEED_X += test-config\n TEST_PROGRAMS_NEED_X += test-ctype\n TEST_PROGRAMS_NEED_X += test-date\n TEST_PROGRAMS_NEED_X += test-delta\ndiff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh\nnew file mode 100755\nindex 0000000..27bce1e\n--- /dev/null\n+++ b/t/t1308-config-set.sh\n@@ -0,0 +1,168 @@\n+#!/bin/sh\n+\n+test_description='Test git config-set API in different settings'\n+\n+. ./test-lib.sh\n+\n+# 'check section.key value' verifies that the entry for section.key is\n+# 'value'\n+check() {\n+\techo \"$2\" >expected &&\n+\ttest-config get_value \"$1\" >actual 2>&1 &&\n+\ttest_cmp expected actual\n+}\n+\n+test_expect_success 'setup default config' '\n+\tcat >.git/config << EOF\n+\t[core]\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[core]\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 core.penguin \"very blue\"\n+'\n+\n+test_expect_success 'get value for a key with value as an empty string' '\n+\tcheck core.my \"\"\n+'\n+\n+test_expect_success 'get value for a key with value as NULL' '\n+\tcheck core.foo \"(NULL)\"\n+'\n+\n+test_expect_success 'upper case key' '\n+\tcheck core.UPPERCASE \"true\"\n+'\n+\n+test_expect_success 'mixed case key' '\n+\tcheck core.MixedCase \"true\"\n+'\n+\n+test_expect_success 'key and value with mixed case' '\n+\tcheck core.Movie \"BadPhysics\"\n+'\n+\n+test_expect_success 'key with case sensitive subsection' '\n+\tcheck \"my.Foo bAr.hi\" \"mixed-case\" &&\n+\tcheck \"my.FOO BAR.hi\" \"upper-case\" &&\n+\tcheck \"my.foo bar.hi\" \"lower-case\"\n+'\n+\n+test_expect_success 'key with case insensitive section header' '\n+\tcheck cores.baz \"ball\" &&\n+\tcheck Cores.baz \"ball\" &&\n+\tcheck CORES.baz \"ball\" &&\n+\tcheck coreS.baz \"ball\"\n+'\n+\n+test_expect_success 'find value with misspelled key' '\n+\ttest_must_fail check \"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 core.baz \"hask\"\n+'\n+\n+test_expect_success 'find integer value for a key' '\n+\techo 65 >expect &&\n+\ttest-config get_int lamb.chop >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'find integer if value is non parse-able' '\n+\techo 65 >expect &&\n+\ttest_must_fail test-config get_int lamb.head >actual &&\n+\ttest_must_fail test_cmp expect actual\n+'\n+\n+test_expect_success 'find bool value for the entered key' '\n+\tcat >expect <<-\\EOF &&\n+\t1\n+\t0\n+\t1\n+\t1\n+\t1\n+\tEOF\n+\ttest-config get_bool goat.head >actual &&\n+\ttest-config get_bool goat.skin >>actual &&\n+\ttest-config get_bool goat.nose >>actual &&\n+\ttest-config get_bool goat.horns >>actual &&\n+\ttest-config get_bool goat.legs >>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'find multiple values' '\n+\tcat >expect <<-\\EOF &&\n+\tsam\n+\tbat\n+\thask\n+\tEOF\n+\ttest-config get_value_multi \"core.baz\">actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'find value from a configset' '\n+\tcat >config2 <<-\\EOF &&\n+\t[core]\n+\t\tbaz = lama\n+\t[my]\n+\t\tnew = silk\n+\t[core]\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 core.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 core.baz config2 .git/config  >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..dc313c2\n--- /dev/null\n+++ b/test-config.c\n@@ -0,0 +1,125 @@\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\treturn 1;\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\treturn 0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\treturn 1;\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\treturn 0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\treturn 1;\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\treturn 0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\treturn 1;\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\treturn 0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\treturn 1;\n+\t\t}\n+\t} else if (!strcmp(argv[1], \"configset_get_value\")) {\n+\t\tfor (i = 3; i < argc; i++) {\n+\t\t\tif (git_configset_add_file(&cs, argv[i]))\n+\t\t\t\treturn 2;\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\treturn 0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\treturn 1;\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\tif (git_configset_add_file(&cs, argv[i]))\n+\t\t\t\treturn 2;\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\treturn 0;\n+\t\t} else {\n+\t\t\tprintf(\"Value not found for \\\"%s\\\"\\n\", argv[2]);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\tfprintf(stderr, \"%s: Please check the syntax and the function name\\n\", argv[0]);\n+\treturn 1;\n+}\n-- \n1.9.0.GIT\n"},{"id":"245626","messageId":"vpq61j6d92z.fsf@anie.imag.fr","threadId":"37085","inReplyTo":"1404903454-10154-2-git-send-email-tanayabh@gmail.com","subject":"Re: [PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2014-07-09T12:12:52Z","receivedAt":"2014-07-09T12:12:52Z","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> diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt\n> index 230b3a0..65a6717 100644\n> --- a/Documentation/technical/api-config.txt\n> +++ b/Documentation/technical/api-config.txt\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\nMissing space after ` => badly formatted in HTML.\n\nI didn't find any other formatting error, but you may double-check with\n\ncd Documentation/ && make technical/api-config.html && firefox technical/api-config.html\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"245627","messageId":"vpq4myqd92r.fsf@anie.imag.fr","threadId":"37085","inReplyTo":"1404903454-10154-3-git-send-email-tanayabh@gmail.com","subject":"Re: [PATCH v7 2/2] test-config: Add tests for the config_set API","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2014-07-09T12:13:00Z","receivedAt":"2014-07-09T12:13:00Z","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> +test_expect_success 'find value with misspelled key' '\n> +\ttest_must_fail check \"my.fOo Bar.hi\" \"Value not found for \\\"my.fOo Bar.hi\\\"\"\n> +'\n\nSorry, this is still not right. You're checking that either test-config\nOR test_cmp fails. You want to check both.\n\nBasically, you can't use the \"check\" helper here. Your v5 was right for\nthis test:\n\ntest_expect_success 'find value with misspelled key' '\n\techo \"Value not found for \\\"my.fOo Bar.hi\\\"\" >expect &&\n\ttest_must_fail test-config get_value \"my.fOo Bar.hi\" >actual &&\n\ttest_cmp expect actual\n'\n\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"245628","messageId":"53BD3805.40504@gmail.com","threadId":"37085","inReplyTo":"vpq61j6d92z.fsf@anie.imag.fr","subject":"Re: [PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Tanay Abhra","fromEmail":"tanayabh@gmail.com","sentAt":"2014-07-09T12:39:33Z","receivedAt":"2014-07-09T12:39:33Z","isPatch":true,"sender":{"key":"tanayabh@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2097229?v=4"},"body":"\n\nOn 7/9/2014 5:42 PM, Matthieu Moy wrote:\n> Tanay Abhra <tanayabh@gmail.com> writes:\n> \n>> diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt\n>> index 230b3a0..65a6717 100644\n>> --- a/Documentation/technical/api-config.txt\n>> +++ b/Documentation/technical/api-config.txt\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> \n> Missing space after ` => badly formatted in HTML.\n> \n> I didn't find any other formatting error, but you may double-check with\n> \n> cd Documentation/ && make technical/api-config.html && firefox technical/api-config.html\n> \n\nSorry for the wrong version number on the cover letter.\nThanks, for the review, I will check it ASAP.\n\nAlso, I tried implementing Junio's request about saving the line number and the file name for each\nkey-value pair. I had some success, but with some changes,\n\n1. config-hash.c had to be shifted to config.c entirely.\n2. string_list util pointer had to be used for each value.\n\nI am appending the diff below, the only changes from version 7 is that, a new struct 'key_source'\nhas been added and `config_hash_add_value` has been altered a little.\n\nFor debugging `git_configset_get_value` prints the line number and file name for each value.\nWhat are your views about it, too late for inclusion? too hackey or contrived?\nI has started on a fresh page for it, but I soon saw that I was reimplementing what was already\navailable in git_config(), so I took this path.\n\nOne more thing, Karsten's string-intern API can be used for saving file names as they are repeated a\nlot.\n\n-- 8< --\ndiff --git a/config.c b/config.c\nindex a1aef1c..697ec1c 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 \"string-list.h\"\n+#include \"hashmap.h\"\n\n struct config_source {\n \tstruct config_source *prev;\n@@ -33,10 +35,314 @@ struct config_source {\n \tlong (*do_ftell)(struct config_source *c);\n };\n\n+struct config_hash_entry {\n+\tstruct hashmap_entry ent;\n+\tchar *key;\n+\tstruct string_list value_list;\n+};\n+\n+struct key_source {\n+\tconst char *filename;\n+\tint linenr;\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_hash_add_value(const char *, const char *, struct hashmap *);\n+\n+static int config_hash_entry_cmp(const struct config_hash_entry *e1,\n+\t\t\t\t const struct config_hash_entry *e2, const void *unused)\n+{\n+\treturn strcmp(e1->key, e2->key);\n+}\n+\n+static void configset_init(struct config_set *cs)\n+{\n+\tif (!cs->hash_initialized) {\n+\t\thashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_hash_entry_cmp, 0);\n+\t\tcs->hash_initialized = 1;\n+\t}\n+}\n+\n+static int config_hash_callback(const char *key, const char *value, void *cb)\n+{\n+\tstruct config_set *cs = cb;\n+\tconfig_hash_add_value(key, value, &cs->config_hash);\n+\treturn 0;\n+}\n+\n+int git_configset_add_file(struct config_set *cs, const char *filename)\n+{\n+\tint ret = 0;\n+\tconfigset_init(cs);\n+\tret = git_config_from_file(config_hash_callback, filename, cs);\n+\tif (!ret)\n+\t\treturn 0;\n+\telse {\n+\t\thashmap_free(&cs->config_hash, 1);\n+\t\tcs->hash_initialized = 0;\n+\t\treturn -1;\n+\t}\n+}\n+\n+static struct config_hash_entry *config_hash_find_entry(const char *key,\n+\t\t\t\t\t\t\tstruct hashmap *config_hash)\n+{\n+\tstruct config_hash_entry k;\n+\tstruct config_hash_entry *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(config_hash, &k, NULL);\n+\tfree(normalized_key);\n+\treturn found_entry;\n+}\n+\n+static struct string_list *configset_get_list(const char *key, struct config_set *cs)\n+{\n+\tstruct config_hash_entry *e = config_hash_find_entry(key, &cs->config_hash);\n+\treturn e ? &e->value_list : NULL;\n+}\n+\n+static int config_hash_add_value(const char *key, const char *value, struct hashmap *config_hash)\n+{\n+\tstruct config_hash_entry *e;\n+\tstruct string_list_item *si;\n+\tstruct key_source *ks = xmalloc(sizeof(*e));\n+\te = config_hash_find_entry(key, config_hash);\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\tmemset(&e->value_list, 0, sizeof(e->value_list));\n+\t\te->value_list.strdup_strings = 1;\n+\t\thashmap_add(config_hash, e);\n+\t}\n+\tsi = string_list_append_nodup(&e->value_list, value ? xstrdup(value) : NULL);\n+\tks->filename = xstrdup(cf->name);\n+\tks->linenr = cf->linenr - 1;\n+\tsi->util = ks;\n+\n+\treturn 0;\n+}\n+\n+void git_configset_init(struct config_set *cs)\n+{\n+\tcs->hash_initialized = 0;\n+}\n+\n+int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n+{\n+\tstruct string_list *values = NULL;\n+\tstruct key_source *ks;\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 = configset_get_list(key, cs);\n+\n+\tif (!values)\n+\t\treturn 1;\n+\tassert(values->nr > 0);\n+\t*value = values->items[values->nr - 1].string;\n+\tks = values->items[values->nr - 1].util;\n+\tprintf(\"the value is in line %d of %s \\n\", ks->linenr, ks->filename);\n+\treturn 0;\n+}\n+\n+const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)\n+{\n+\treturn configset_get_list(key, cs);\n+}\n+\n+void git_configset_clear(struct config_set *cs)\n+{\n+\tstruct config_hash_entry *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+}\n+\n+int git_configset_get_string(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_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 int git_config_check_init(void)\n+{\n+\tint ret = 0;\n+\tif (the_config_set.hash_initialized)\n+\t\treturn 0;\n+\tconfigset_init(&the_config_set);\n+\tret = git_config(config_hash_callback, &the_config_set);\n+\tif (ret >= 0)\n+\t\treturn 0;\n+\telse {\n+\t\thashmap_free(&the_config_set.config_hash, 1);\n+\t\tthe_config_set.hash_initialized = 0;\n+\t\treturn -1;\n+\t}\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+void git_config_clear(void)\n+{\n+\tif (!the_config_set.hash_initialized)\n+\t\treturn;\n+\tgit_configset_clear(&the_config_set);\n+\tthe_config_set.hash_initialized = 0;\n+}\n+\n+int git_config_get_string(const char *key, const char **dest)\n+{\n+\tgit_config_check_init();\n+\treturn git_configset_get_string(&the_config_set, key, 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 static int config_file_fgetc(struct config_source *conf)\n {\n \treturn fgetc(conf->u.file);\n@@ -1708,6 +2014,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);\n-- 8< --\n"},{"id":"245629","messageId":"53BD38AA.9000409@gmail.com","threadId":"37085","inReplyTo":"vpq4myqd92r.fsf@anie.imag.fr","subject":"Re: [PATCH v7 2/2] test-config: Add tests for the config_set API","fromName":"Tanay Abhra","fromEmail":"tanayabh@gmail.com","sentAt":"2014-07-09T12:42:18Z","receivedAt":"2014-07-09T12:42:18Z","isPatch":true,"sender":{"key":"tanayabh@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2097229?v=4"},"body":"\n\nOn 7/9/2014 5:43 PM, Matthieu Moy wrote:\n> Tanay Abhra <tanayabh@gmail.com> writes:\n> \n>> +test_expect_success 'find value with misspelled key' '\n>> +\ttest_must_fail check \"my.fOo Bar.hi\" \"Value not found for \\\"my.fOo Bar.hi\\\"\"\n>> +'\n> \n> Sorry, this is still not right. You're checking that either test-config\n> OR test_cmp fails. You want to check both.\n> \n> Basically, you can't use the \"check\" helper here. Your v5 was right for\n> this test:\n> \n> test_expect_success 'find value with misspelled key' '\n> \techo \"Value not found for \\\"my.fOo Bar.hi\\\"\" >expect &&\n> \ttest_must_fail test-config get_value \"my.fOo Bar.hi\" >actual &&\n> \ttest_cmp expect actual\n> '\n> \n> \n\nYup, I had thought so too, that was the reason I left the && chain in check()\nin version 6. I will revert back to v5 for it.\n\nThanks.\n"},{"id":"245630","messageId":"vpqion68viq.fsf@anie.imag.fr","threadId":"37085","inReplyTo":"53BD3805.40504@gmail.com","subject":"Re: [PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2014-07-09T14:19:25Z","receivedAt":"2014-07-09T14:19:25Z","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> Also, I tried implementing Junio's request about saving the line\n> number and the file name for each\n> key-value pair. I had some success, but with some changes,\n\nMy opinion on this:\n\n* It's low priority. I think the order of priority should be (high to\n  low)\n\n  1) Finish and get the current series into pu (we're almost there, it's\n     not time to back off and restart something new).\n\n  2) Work on the other series that uses the new API. We don't need to\n     change _all_ the uses, but we do need a few tens of them to\n     validate the fact that the new API is nice and convenient to use.\n\n  3) Get new actual features for the user (tidy up config files, give\n     error messages based on numbers).\n\n  You probably won't finish everything, so just think: if the GSoC ends\n  between 1) and 2), how serious is it? if it ends between 2) and 3),\n  how serious? If reverse the order, then the risk of having nothing\n  finished and mergeable at the end is high. If it happens, the\n  community may or may not take over and finish it ...\n\n* Still, making sure that the (file, line) is doable later without too\n  much change is good. So, if indeed, moving all code to another file is\n  required, then it may make sense to do it now to avoid code move\n  later.\n\n> 1. config-hash.c had to be shifted to config.c entirely.\n\nWhy? I guess one reason is the use of struct cf (are there others?), but\nmoving just\n\nconfig_hash_callback\nconfig_hash_add_value\ngit_configset_add_file\n\nto config.c should do it. Then, config.c could use config-hash.c.\n\nBut a cleaner way would be to allow the callback to receive the\nconfig_file struct without accessing it as a global variable (currently,\nwe have no way to parse two config files in parallel for example).\n\nIn practice, it should be possible to pass a 4th pointer argument to the\ncallback, and keep compatibility with 3 arguments callback (having too\nmany arguments in not a problem will all ABI I know), but I'm don't\nthink this is allowed in theory.\n\nOn overall, I'm not convinced we should move the code. When the argument\n\"I need to merge these two things otherwise it doesn't compile\" comes in\na discussion, it usually means there's an architecture issue\nsomewhere ;-).\n\n> One more thing, Karsten's string-intern API can be used for saving\n> file names as they are repeated a\n> lot.\n\nThis, or storing the list of filenames in struct config_set (more or\nless as you did in previous patch), and store pointers to the same\nstring in each config_hash_entry.\n\nBut strdup-ing all filenames seems a bit heavy to me. Even though we're\nnot talking about performance-critical part of Git, I don't like the\nidea of wasting too much ;-).\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"245634","messageId":"xmqqmwci7e9p.fsf@gitster.dls.corp.google.com","threadId":"37085","inReplyTo":"vpqion68viq.fsf@anie.imag.fr","subject":"Re: [PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-07-09T15:17:22Z","receivedAt":"2014-07-09T15:17:22Z","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> My opinion on this:\n>\n> * It's low priority. I think the order of priority should be (high to\n>   low)\n>\n>   1) Finish and get the current series into pu (we're almost there, it's\n>      not time to back off and restart something new).\n>\n>   2) Work on the other series that uses the new API. We don't need to\n>      change _all_ the uses, but we do need a few tens of them to\n>      validate the fact that the new API is nice and convenient to use.\n>\n>   3) Get new actual features for the user (tidy up config files, give\n>      error messages based on numbers).\n>\n>   You probably won't finish everything, so just think: if the GSoC ends\n>   between 1) and 2), how serious is it? if it ends between 2) and 3),\n>   how serious? If reverse the order, then the risk of having nothing\n>   finished and mergeable at the end is high. If it happens, the\n>   community may or may not take over and finish it ...\n>\n> * Still, making sure that the (file, line) is doable later without too\n>   much change is good. So, if indeed, moving all code to another file is\n>   required, then it may make sense to do it now to avoid code move\n>   later.\n\nGood thinking.  As long as the code is prepared, it is a good idea\nto start small and bite off only we can chew at once, do things\nincrementally.\n\n>> 1. config-hash.c had to be shifted to config.c entirely.\n>\n> Why? I guess one reason is the use of struct cf (are there others?), but\n> moving just\n>\n> config_hash_callback\n> config_hash_add_value\n> git_configset_add_file\n>\n> to config.c should do it. Then, config.c could use config-hash.c.\n\nI am not sure why you guys needed a new file config-hash.c to begin\nwith, actually.  Besides, \"hash\" in its name is an implementation\ndetail (what it gives us is a way to grab values for configuration\nvariables from a config set) which we would rather not want to see.\n"},{"id":"245637","messageId":"53BD68D5.7000503@gmail.com","threadId":"37085","inReplyTo":"vpqion68viq.fsf@anie.imag.fr","subject":"Re: [PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Tanay Abhra","fromEmail":"tanayabh@gmail.com","sentAt":"2014-07-09T16:07:49Z","receivedAt":"2014-07-09T16:07:49Z","isPatch":true,"sender":{"key":"tanayabh@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2097229?v=4"},"body":"On 7/9/2014 7:49 PM, Matthieu Moy wrote:\n> Tanay Abhra <tanayabh@gmail.com> writes:\n> \n> \n> My opinion on this:\n> \n> * It's low priority. I think the order of priority should be (high to\n>   low)\n> \n>   1) Finish and get the current series into pu (we're almost there, it's\n>      not time to back off and restart something new).\n\nNoted. I will send the revised series tomorrow ASAP.\n\n> \n>   2) Work on the other series that uses the new API. We don't need to\n>      change _all_ the uses, but we do need a few tens of them to\n>      validate the fact that the new API is nice and convenient to use.\n> \n\nOkay, I have updated the series and will send the new one by friday.\nStill, I am aiming to rewrite at least half of total calls next weeks\nwhen (1) is in pu.\n\n>   3) Get new actual features for the user (tidy up config files, give\n>      error messages based on numbers).\n> \n>   You probably won't finish everything, so just think: if the GSoC ends\n>   between 1) and 2), how serious is it? if it ends between 2) and 3),\n>   how serious? If reverse the order, then the risk of having nothing\n>   finished and mergeable at the end is high. If it happens, the\n>   community may or may not take over and finish it ...\n>\n\nNoted, still I want to add that even when GSoC finishes, I won't leave the\nwork unfinished. I had said that I wanted to continue being part of the Git\ncommunity and I mean that. :)\n\n> * Still, making sure that the (file, line) is doable later without too\n>   much change is good. So, if indeed, moving all code to another file is\n>   required, then it may make sense to do it now to avoid code move\n>   later.\n>\n\nYes, the only problem I see is that the future readers of config.c might\nread two versions of the git_config_type helpers and may get confused,\nas they have similar names as git_config_*() & git_config_get_*().\nThat was the reason in the first place that I moved the code into a new file.\n\n>> 1. config-hash.c had to be shifted to config.c entirely.\n> \n> Why? I guess one reason is the use of struct cf (are there others?), but\n\nNope, just for using struct cf. All old API functions raise error by\naccessing \"cf\" globally for the file name and line number.\n\n> moving just\n> \n> config_hash_callback\n> config_hash_add_value\n> git_configset_add_file\n> \n> to config.c should do it. Then, config.c could use config-hash.c.\n> \n> But a cleaner way would be to allow the callback to receive the\n> config_file struct without accessing it as a global variable (currently,\n> we have no way to parse two config files in parallel for example).\n> \n> In practice, it should be possible to pass a 4th pointer argument to the\n> callback, and keep compatibility with 3 arguments callback (having too\n> many arguments in not a problem will all ABI I know), but I'm don't\n> think this is allowed in theory.\n> \n> On overall, I'm not convinced we should move the code. When the argument\n> \"I need to merge these two things otherwise it doesn't compile\" comes in\n> a discussion, it usually means there's an architecture issue\n> somewhere ;-).\n>\n\nI have to decide on what to do next on moving the contents to config.c or not.\nSeeing Junio's comments on the topic seems that he wasn't convinced in the\nfirst place that we needed a new file. What should we do, as I am sending the\nrevised patch tomorrow? The move will be trivial, just cutting and pasting the\ncontents. Other approaches you mentioned are also doable but, after a certain\namount of retooling. I am open to any method you think would be best.\n\nCheers,\nTanay Abhra.\n"},{"id":"245641","messageId":"xmqq38ea77gt.fsf@gitster.dls.corp.google.com","threadId":"37085","inReplyTo":"xmqqmwci7e9p.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-07-09T17:44:18Z","receivedAt":"2014-07-09T17:44:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> * Still, making sure that the (file, line) is doable later without too\n>>   much change is good. So, if indeed, moving all code to another file is\n>>   required, then it may make sense to do it now to avoid code move\n>>   later.\n>\n> Good thinking.  As long as the code is prepared, it is a good idea\n> to start small and bite off only we can chew at once, do things\n> incrementally.\n\nAfter thinking about it a bit more, I think <file, line> support\nneeds to be done not as a mere client of the generic, uncached\ngit_config() API that we have had for forever, like the current\niteration, but needs to know a bit more about the state the caller\nof the callback (namely, git_parse_source()), and we obviously do\nnot want to expose that machinery to anybody other than the\nimplementation of the config subsystem (to which the new facility\nthis GSoC project adds belongs to), so in that sense you have to\nhave your code in the same config.c file anyway.\n\nIt is somewhat dissapointing that this initial implementation was\ndone as a client of the traditional git_config(), by the way.  I\nwould have expected that it would be the other way around, in that\nthe traditional callers of git_config() would behefit automatically\nby having the cache layer below it.\n\nBut we have to start from somewhere.  Perhaps the round after this\none can rename the exisiting implementation of git_config() to\nsomething else internal to the caching layer and give the existing\ncallers a compatible interface that is backed by this new caching\nlayer in a transparent way.\n"},{"id":"245685","messageId":"vpqa98hwnxp.fsf@anie.imag.fr","threadId":"37085","inReplyTo":"xmqq38ea77gt.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2014-07-10T09:41:38Z","receivedAt":"2014-07-10T09:41:38Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> After thinking about it a bit more, I think <file, line> support\n> needs to be done not as a mere client of the generic, uncached\n> git_config() API that we have had for forever, like the current\n> iteration, but needs to know a bit more about the state the caller\n> of the callback (namely, git_parse_source()), and we obviously do\n> not want to expose that machinery to anybody other than the\n> implementation of the config subsystem (to which the new facility\n> this GSoC project adds belongs to), so in that sense you have to\n> have your code in the same config.c file anyway.\n\nI do not buy the argument. Why would callers of the callback-style API\nnot be allowed to give <file, line> errors?\n\nTo me, it is a weakness of the API that <file, line> information is not\navailable to callback functions, regardless of the config-cache.\n\n> It is somewhat dissapointing that this initial implementation was\n> done as a client of the traditional git_config(), by the way.  I\n> would have expected that it would be the other way around, in that\n> the traditional callers of git_config() would behefit automatically\n> by having the cache layer below it.\n\nI disagree, and I agree ;-).\n\nI disagree that it is disapointing to use git_config(), and I think the\nbeauty of the current implementation is to allow this cache mechanism\nwithout changing the parsing code.\n\nI agree that the callers of git_config() could benefit from the cache\nmechanism, i.e. use the in-memory pre-parsed config instead of\nre-parsing the file each time.\n\n> But we have to start from somewhere.  Perhaps the round after this\n> one can rename the exisiting implementation of git_config() to\n> something else internal to the caching layer and give the existing\n> callers a compatible interface that is backed by this new caching\n> layer in a transparent way.\n\nIn PATCH v4, there was a git_config_iter function that did exactly that.\nI didn't notice it was gone for v5, but could be rather easily\nresurected.\n\nI suggest, on top of the current series:\n\nPATCH 3 : (re)introduce git_config_iter, compatible with git_config\n  (one variant with a configset argument, another working on the_config_set).\n\nPATCH 4 : rename git_config to e.g. git_config_parse, and\n  git_config_iter to git_config.\n\nThen, check that tests still pass (PATCH 4 automatically uses the new\ncode essentially in every place where Git deals with config, so existing\ntests will start to stress the code a lot more), and check with e.g.\n\"strace -e open git ...\" that config files are now parsed only once\nwhere they used to be parsed multiple times.\n\nI'd do that as a separate series, to let the current one finally reach\npu.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"245689","messageId":"vpq1tttwj8i.fsf@anie.imag.fr","threadId":"37085","inReplyTo":"53BD68D5.7000503@gmail.com","subject":"Re: [PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2014-07-10T11:23:09Z","receivedAt":"2014-07-10T11:23:09Z","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> Noted, still I want to add that even when GSoC finishes, I won't leave the\n> work unfinished. I had said that I wanted to continue being part of the Git\n> community and I mean that. :)\n\nThis is a good thing, but you shouldn't rely on it for your GSoC. After\nthe GSoC finishes, you will have much less time for Git.\n\n> I have to decide on what to do next on moving the contents to config.c or not.\n> Seeing Junio's comments on the topic seems that he wasn't convinced in the\n> first place that we needed a new file. What should we do, as I am sending the\n> revised patch tomorrow? The move will be trivial, just cutting and pasting the\n> contents. Other approaches you mentioned are also doable but, after a certain\n> amount of retooling. I am open to any method you think would be best.\n\nNo strong opinion from me here. I like splitting stuff into relatively\nsmall files, and to me it makes sense to keep the parsing code and the\ncaching code separate (although config-hash.c is no longer a good name,\nconfig-set.c or config-cache.c would be better). But I can for sure live\nwith both in the same file.\n\nI guess you'll have to make the decision if others don't give better\nargument ;-).\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"245693","messageId":"xmqqvbr588mh.fsf@gitster.dls.corp.google.com","threadId":"37085","inReplyTo":"vpqa98hwnxp.fsf@anie.imag.fr","subject":"Re: [PATCH v7 1/2] add `config_set` API for caching config-like files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-07-10T16:46:14Z","receivedAt":"2014-07-10T16:46:14Z","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> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> After thinking about it a bit more, I think <file, line> support\n>> needs to be done not as a mere client of the generic, uncached\n>> git_config() API that we have had for forever, like the current\n>> iteration, but needs to know a bit more about the state the caller\n>> of the callback (namely, git_parse_source()), and we obviously do\n>> not want to expose that machinery to anybody other than the\n>> implementation of the config subsystem (to which the new facility\n>> this GSoC project adds belongs to), so in that sense you have to\n>> have your code in the same config.c file anyway.\n>\n> I do not buy the argument. Why would callers of the callback-style API\n> not be allowed to give <file, line> errors?\n>\n> To me, it is a weakness of the API that <file, line> information is not\n> available to callback functions, regardless of the config-cache.\n\nI agree that it is good to allow scan-all-config-items-with-callback\ncallers to learn <file, line>, but that is irrelevant.  Perhaps you\nmisread what I envision as the endgame of this thing to be and that\nis where our differences come from.  One thing I think would be good\nto see in the endgame will be to give the benefit of the caching\nlayer to the callback callers without having them change a single\nline of their code.\n\nOne possible sequence of changes to make it happen would go like\nthis, roughly in this order:\n\n - rename the current git_config() to git_config_raw(), and\n   make it static to the config.c.\n\n - write a thin wrapper git_config() around git_config_raw() in\n   config.c, until caching layer learns the iterator.\n\n - write caching layer to read from git_config_raw().\n\n - teach git_config_raw() feed its callback functions to learn the\n   <file, line> information.  git_configset_get_<type> can then\n   start using this in their error reporting.\n\n - implement iterator in the caching layer.\n\n - rewrite git_config() that was a thin wrapper around\n   git_config_raw() by using the iterator over the cached values.\n\n - (optional) think about ways to give <file, line> information to\n   the callback style callers.  Perhaps we need git_config_2().\n   Perhaps we can rewrite all callers of git_config().  We do not\n   have to decide it now.\n\nBetween git_parse_source() and git_config_raw() we would need to\npass the line-number information, but there is no reason for us to\nmake public (all of these will be implementation details of the\nconfig system, including the config caching).\n\nMy answer to \"why shouldn't the callbacks have <file, line>\ninformation?\" is \"there is no reason why they shouldn't.  It is a\ngood addition in the long run\".  But the optionality of the last\nstep in the above list makes it an irrelevant question.\n"}]}