{"thread":{"id":"2578","subject":"[PATCH] Add functions git_config_set() and git_config_set_multivar()","startedAt":"2005-11-17T21:32:36Z","lastAt":"2005-11-17T21:32:36Z","messageCount":1,"participants":["Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"12144","messageId":"Pine.LNX.4.63.0511172230460.18177@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"2578","inReplyTo":null,"subject":"[PATCH] Add functions git_config_set() and git_config_set_multivar()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2005-11-17T21:32:36Z","receivedAt":"2005-11-17T21:32:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"    \nThe function git_config_set() does exactly what you think it does.\nGiven a key (in the form \"core.filemode\") and a value, it sets the\nkey to the value. Example:\n\n\tgit_config_set(\"core.filemode\", \"true\");\n\nThe function git_config_set_multivar() is meant for setting variables which\ncan have several values for the same key. Example:\n\n\t[diff]\n\t\ttwohead = resolve\n\t\ttwohead = recarsive\n\nthe typo in the second line can be replaced by\n\n\tgit_config_set_multivar(\"diff.twohead\", \"recursive\", \"^recar\");\n\nThe third argument of the function is a POSIX extended regex which has to\nmatch the value. If there is no key/value pair with a matching value, a new\nkey/value pair is added.\n\nThese commands are also capable of unsetting (deleting) entries:\n\n\tgit_config_set_multivar(\"diff.twohead\", NULL, \"sol\");\n\nwill delete the entry\n\n\t\ttwohead = resolve\n\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\n---\n\n cache.h  |    2 \n config.c |  294 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 295 insertions(+), 1 deletions(-)\ndiff --git a/cache.h b/cache.h\nindex 99afa2c..f93f516 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -386,6 +386,8 @@ extern int git_default_config(const char\n extern int git_config(config_fn_t fn);\n extern int git_config_int(const char *, const char *);\n extern int git_config_bool(const char *, const char *);\n+extern int git_config_set(const char *, const char *);\n+extern int git_config_set_multivar(const char *, const char *, const char *);\n \n #define MAX_GITNAME (1000)\n extern char git_default_email[MAX_GITNAME];\ndiff --git a/config.c b/config.c\nindex 915bb97..bbcafff 100644\n--- a/config.c\n+++ b/config.c\n@@ -1,5 +1,12 @@\n-\n+/*\n+ * GIT - The information manager from hell\n+ *\n+ * Copyright (C) Linus Torvalds, 2005\n+ * Copyright (C) Johannes Schindelin, 2005\n+ *\n+ */\n #include \"cache.h\"\n+#include <regex.h>\n \n #define MAXNAME (256)\n \n@@ -252,3 +259,288 @@ int git_config(config_fn_t fn)\n \t}\n \treturn ret;\n }\n+\n+/*\n+ * Find all the stuff for git_config_set() below.\n+ */\n+static struct {\n+\tint baselen;\n+\tchar* key;\n+\tregex_t* value_regex;\n+\toff_t offset;\n+\tenum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;\n+\tint seen;\n+} store;\n+\n+static int store_aux(const char* key, const char* value)\n+{\n+\tswitch (store.state) {\n+\tcase KEY_SEEN:\n+\t\tif (!strcmp(key, store.key) &&\n+\t\t\t\t(store.value_regex == NULL ||\n+\t\t\t\t!regexec(store.value_regex, value, 0, NULL, 0))) {\n+\t\t\tif (store.seen == 1) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\"Warning: %s has multiple values\\n\",\n+\t\t\t\t\tkey);\n+\t\t\t}\n+\t\t\tstore.offset = ftell(config_file);\n+\t\t\tstore.seen++;\n+\t\t}\n+\t\tbreak;\n+\tcase SECTION_SEEN:\n+\t\tif (strncmp(key, store.key, store.baselen+1)) {\n+\t\t\tstore.state = SECTION_END_SEEN;\n+\t\t\tbreak;\n+\t\t} else\n+\t\t\tstore.offset = ftell(config_file);\n+\t\t/* fallthru */\n+\tcase SECTION_END_SEEN:\n+\tcase START:\n+\t\tif (!strcmp(key, store.key) &&\n+\t\t\t\t(store.value_regex == NULL ||\n+\t\t\t\t!regexec(store.value_regex, value, 0, NULL, 0))) {\n+\t\t\tstore.offset = ftell(config_file);\n+\t\t\tstore.state = KEY_SEEN;\n+\t\t\tstore.seen++;\n+\t\t} else if(!strncmp(key, store.key, store.baselen))\n+\t\t\tstore.state = SECTION_SEEN;\n+\t}\n+\treturn 0;\n+}\n+\n+static void store_write_section(int fd, const char* key)\n+{\n+\twrite(fd, \"[\", 1);\n+\twrite(fd, key, store.baselen);\n+\twrite(fd, \"]\\n\", 2);\n+}\n+\n+static void store_write_pair(int fd, const char* key, const char* value)\n+{\n+\tint i;\n+\n+\twrite(fd, \"\\t\", 1);\n+\twrite(fd, key+store.baselen+1,\n+\t\tstrlen(key+store.baselen+1));\n+\twrite(fd, \" = \", 3);\n+\tfor (i = 0; value[i]; i++)\n+\t\tswitch (value[i]) {\n+\t\tcase '\\n': write(fd, \"\\\\n\", 2); break;\n+\t\tcase '\\t': write(fd, \"\\\\t\", 2); break;\n+\t\tcase '\"': case '\\\\': write(fd, \"\\\\\", 1);\n+\t\tdefault: write(fd, value+i, 1);\n+\t}\n+\twrite(fd, \"\\n\", 1);\n+}\n+\n+int git_config_set(const char* key, const char* value)\n+{\n+\treturn git_config_set_multivar(key, value, NULL);\n+}\n+\n+/*\n+ * If value==NULL, unset in (remove from) config,\n+ * if value_regex!=NULL, disregard key/value pairs where value does not match.\n+ *\n+ * Returns 0 on success.\n+ *\n+ * This function does this:\n+ *\n+ * - it locks the config file by creating \".git/config.lock\"\n+ *\n+ * - it then parses the config using store_aux() as validator to find\n+ *   the position on the key/value pair to replace. If it is to be unset,\n+ *   it must be found exactly once.\n+ *\n+ * - the config file is mmap()ed and the part before the match (if any) is\n+ *   written to the lock file, then the changed part and the rest.\n+ *\n+ * - the config file is removed and the lock file rename()d to it.\n+ *\n+ */\n+int git_config_set_multivar(const char* key, const char* value,\n+\tconst char* value_regex)\n+{\n+\tint i;\n+\tstruct stat st;\n+\tint fd;\n+\tchar* config_file = strdup(git_path(\"config\"));\n+\tchar* lock_file = strdup(git_path(\"config.lock\"));\n+\n+\t/*\n+\t * Since \"key\" actually contains the section name and the real\n+\t * key name separated by a dot, we have to know where the dot is.\n+\t */\n+\tfor (store.baselen = 0;\n+\t\t\tkey[store.baselen] != '.' && key[store.baselen];\n+\t\t\tstore.baselen++);\n+\tif (!key[store.baselen] || !key[store.baselen+1]) {\n+\t\tfprintf(stderr, \"key does not contain a section: %s\\n\", key);\n+\t\treturn 2;\n+\t}\n+\n+\t/*\n+\t * Validate the key and while at it, lower case it for matching.\n+\t */\n+\tstore.key = (char*)malloc(strlen(key)+1);\n+\tfor (i = 0; key[i]; i++)\n+\t\tif (i != store.baselen && (!isalnum(key[i]) ||\n+\t\t\t\t(i == store.baselen+1 && !isalpha(key[i])))) {\n+\t\t\tfprintf(stderr, \"invalid key: %s\\n\", key);\n+\t\t\tfree(store.key);\n+\t\t\treturn 1;\n+\t\t} else\n+\t\t\tstore.key[i] = tolower(key[i]);\n+\n+\t/*\n+\t * The lock_file serves a purpose in addition to locking: the new\n+\t * contents of .git/config will be written into it.\n+\t */\n+\tfd = open(lock_file, O_WRONLY | O_CREAT | O_EXCL, 0666);\n+\tif (fd < 0) {\n+\t\tfprintf(stderr, \"could not lock config file\\n\");\n+\t\tfree(store.key);\n+\t\treturn -1;\n+\t}\n+\n+\t/*\n+\t * If .git/config does not exist yet, write a minimal version.\n+\t */\n+\tif (stat(config_file, &st)) {\n+\t\tstatic const char contents[] =\n+\t\t\t\"#\\n\"\n+\t\t\t\"# This is the config file\\n\"\n+\t\t\t\"#\\n\"\n+\t\t\t\"\\n\";\n+\n+\t\tfree(store.key);\n+\n+\t\t/* if nothing to unset, error out */\n+\t\tif (value == NULL) {\n+\t\t\tclose(fd);\n+\t\t\tunlink(lock_file);\n+\t\t\treturn 5;\n+\t\t}\n+\n+\t\tstore.key = (char*)key;\n+\n+\t\twrite(fd, contents, sizeof(contents)-1);\n+\t\tstore_write_section(fd, key);\n+\t\tstore_write_pair(fd, key, value);\n+\t} else{\n+\t\tint in_fd;\n+\t\tchar* contents;\n+\t\tint offset, new_line = 0;\n+\n+\t\tif (value_regex == NULL)\n+\t\t\tstore.value_regex = NULL;\n+\t\telse {\n+\t\t\tstore.value_regex = (regex_t*)malloc(sizeof(regex_t));\n+\t\t\tif (regcomp(store.value_regex, value_regex,\n+\t\t\t\t\tREG_EXTENDED)) {\n+\t\t\t\tfprintf(stderr, \"Invalid pattern: %s\",\n+\t\t\t\t\tvalue_regex);\n+\t\t\t\tfree(store.value_regex);\n+\t\t\t\treturn 6;\n+\t\t\t}\n+\t\t}\n+\n+\t\tstore.offset = 0;\n+\t\tstore.state = START;\n+\t\tstore.seen = 0;\n+\n+\t\t/*\n+\t\t * After this, store.offset will contain the *end* offset\n+\t\t * of the last match, or remain at 0 if no match was found.\n+\t\t * As a side effect, we make sure to transform only a valid\n+\t\t * existing config file.\n+\t\t */\n+\t\tif (git_config(store_aux)) {\n+\t\t\tfprintf(stderr, \"invalid config file\\n\");\n+\t\t\tfree(store.key);\n+\t\t\tif (store.value_regex != NULL) {\n+\t\t\t\tregfree(store.value_regex);\n+\t\t\t\tfree(store.value_regex);\n+\t\t\t}\n+\t\t\treturn 3;\n+\t\t}\n+\n+\t\tfree(store.key);\n+\t\tif (store.value_regex != NULL) {\n+\t\t\tregfree(store.value_regex);\n+\t\t\tfree(store.value_regex);\n+\t\t}\n+\n+\t\t/* if nothing to unset, error out */\n+\t\tif (store.seen == 0 && value == NULL) {\n+\t\t\tclose(fd);\n+\t\t\tunlink(lock_file);\n+\t\t\treturn 5;\n+\t\t}\n+\n+\t\tstore.key = (char*)key;\n+\n+\t\tin_fd = open(config_file, O_RDONLY, 0666);\n+\t\tcontents = mmap(NULL, st.st_size, PROT_READ,\n+\t\t\tMAP_PRIVATE, in_fd, 0);\n+\t\tclose(in_fd);\n+\n+\t\tif (store.offset == 0) {\n+\t\t\tstore.offset = offset = st.st_size;\n+\t\t} else if (store.state != KEY_SEEN) {\n+\t\t\toffset = store.offset;\n+\t\t} else {\n+\t\t\tint equal_offset = st.st_size,\n+\t\t\t\tbracket_offset = st.st_size;\n+\n+\t\t\tif (value == NULL && store.seen > 1) {\n+\t\t\t\tfprintf(stderr, \"Cannot remove multivar (%s has %d values\\n\", key, store.seen);\n+\t\t\t\tclose(fd);\n+\t\t\t\tunlink(lock_file);\n+\t\t\t\treturn 7;\n+\t\t\t}\n+\t\t\tfor (offset = store.offset-2; offset > 0 \n+\t\t\t\t\t&& contents[offset] != '\\n'; offset--)\n+\t\t\t\tswitch (contents[offset]) {\n+\t\t\t\tcase '=': equal_offset = offset; break;\n+\t\t\t\tcase ']': bracket_offset = offset; break;\n+\t\t\t\t}\n+\t\t\tif (bracket_offset < equal_offset) {\n+\t\t\t\tnew_line = 1;\n+\t\t\t\toffset = bracket_offset+1;\n+\t\t\t} else\n+\t\t\t\toffset++;\n+\t\t}\n+\n+\t\t/* write the first part of the config */\n+\t\twrite(fd, contents, offset);\n+\t\tif (new_line)\n+\t\t\twrite(fd, \"\\n\", 1);\n+\n+\t\t/* write the pair (value == NULL means unset) */\n+\t\tif (value != NULL) {\n+\t\t\tif (store.state == START)\n+\t\t\t\tstore_write_section(fd, key);\n+\t\t\tstore_write_pair(fd, key, value);\n+\t\t}\n+\n+\t\t/* write the rest of the config */\n+\t\tif (store.offset < st.st_size)\n+\t\t\twrite(fd, contents + store.offset,\n+\t\t\t\tst.st_size - store.offset);\n+\n+\t\tmunmap(contents, st.st_size);\n+\t\tunlink(config_file);\n+\t}\n+\n+\tclose(fd);\n+\n+\tif (rename(lock_file, config_file) < 0) {\n+\t\tfprintf(stderr, \"Could not rename the lock file?\\n\");\n+\t\treturn 4;\n+\t}\n+\n+\treturn 0;\n+}\n+\n"}]}