{"thread":{"id":"2609","subject":"[PATCH] git-config-set: add more options","startedAt":"2005-11-20T05:52:22Z","lastAt":"2005-11-20T12:24:18Z","messageCount":3,"participants":["Johannes Schindelin","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"12361","messageId":"Pine.LNX.4.63.0511200650130.12832@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"2609","inReplyTo":null,"subject":"[PATCH] git-config-set: add more options","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2005-11-20T05:52:22Z","receivedAt":"2005-11-20T05:52:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"... namely\n\n--replace-all, to replace any amount of matching lines, not just 0 or 1,\n--get, to get the value of one key,\n--get-all, the multivar version of --get, and\n--unset-all, which deletes all matching lines from .git/config\n\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\n---\n\n\tThis patch is on top of my previous patches, because it is not\n\ta bug fix, but provides new features.\n\n\tAlso, it extends t1300-config-set.sh to test these, and moves it \n\tinto t/...\n\n\tThe only thing I am concerned about now is how to deal with\n\tanti value_regex's, i.e.\n\n\t\t[proxy]\n\t\t\tcommand=ssh for kernel.org\n\t\t\tcommand=rsh\n\n\tI cannot think of a POSIX regex which matches all values *not*\n\tcontaining \" for \" (like the second command), short of matching\n\tthe exact value.\n\n\tNote: we could adjust the pattern syntax so that a prefix \"!\"\n\tmeans \"no match\", and \"\\!\" means \"match literal !\", but this\n\tis ugly.\n\n Documentation/git-config-set.txt |   62 ++++++++++++++++++--\n cache.h                          |    3 -\n config-set.c                     |   92 +++++++++++++++++++++++++++++-\n config.c                         |  116 +++++++++++++++++++++++---------------\n t/t1300-config-set.sh            |   46 +++++++++++++++\n 5 files changed, 258 insertions(+), 61 deletions(-)\ndiff --git a/Documentation/git-config-set.txt b/Documentation/git-config-set.txt\nindex 8e897be..c707fbc 100644\n--- a/Documentation/git-config-set.txt\n+++ b/Documentation/git-config-set.txt\n@@ -8,12 +8,18 @@ git-config-set - Set options in .git/con\n \n SYNOPSIS\n --------\n-'git-config-set' ( name [value [value_regex]] | --unset name [value_regex] )\n+'git-config-set' name [value [value_regex]]\n+'git-config-set' --replace-all name [value [value_regex]]\n+'git-config-set' --get name [value_regex]\n+'git-config-set' --get-all name [value_regex]\n+'git-config-set' --unset name [value_regex]\n+'git-config-set' --unset-all name [value_regex]\n \n DESCRIPTION\n -----------\n-You can set/replace/unset options with this command. The name is actually\n-the section and the key separated by a dot, and the value will be escaped.\n+You can query/set/replace/unset options with this command. The name is\n+actually the section and the key separated by a dot, and the value will be\n+escaped.\n \n If you want to set/unset an option which can occor on multiple lines, you\n should provide a POSIX regex for the value.\n@@ -31,8 +37,23 @@ This command will fail if\n OPTIONS\n -------\n \n+--replace-all::\n+\tDefault behaviour is to replace at most one line. This replaces\n+\tall lines matching the key (and optionally the value_regex)\n+\n+--get::\n+\tGet the value for a given key (optionally filtered by a regex\n+\tmatching the value).\n+\n+--get-all::\n+\tLike get, but does not fail if the number of values for the key\n+\tis not exactly one.\n+\n --unset::\n-\tRemove the given option from .git/config\n+\tRemove the line matching the key from .git/config.\n+\n+--unset-all::\n+\tRemove all matching lines from .git/config.\n \n \n EXAMPLE\n@@ -84,14 +105,39 @@ To delete the entry for renames, do\n % git config-set --unset diff.renames\n ------------\n \n-or just\n+If you want to delete an entry for a multivar (like proxy.command above),\n+you have to provide a regex matching the value of exactly one line.\n+\n+To query the value for a given key, do\n \n ------------\n-% git config-set diff.renames\n+% git config-set --get core.filemode\n ------------\n \n-If you want to delete an entry for a multivar (like proxy.command above),\n-you have to provide a regex matching the value of exactly one line.\n+or\n+\n+------------\n+% git config-set core.filemode\n+------------\n+\n+or, to query a multivar:\n+\n+------------\n+% git config-set --get proxy.command \"for kernel.org$\"\n+------------\n+\n+If you want to know all the values for a multivar, do:\n+\n+------------\n+% git config-set --get-all proxy.command\n+------------\n+\n+If you like to live dangerous, you can replace *all* proxy.commands by a\n+new one with\n+\n+------------\n+% git config-set --replace-all proxy.command ssh\n+------------\n \n \n Author\ndiff --git a/cache.h b/cache.h\nindex e2be3e7..a7c1bbd 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -192,7 +192,6 @@ extern int diff_rename_limit_default;\n \n /* Return a statically allocated filename matching the sha1 signature */\n extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n-extern char *enter_repo(char *path, int strict);\n extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));\n extern char *sha1_file_name(const unsigned char *sha1);\n extern char *sha1_pack_name(const unsigned char *sha1);\n@@ -388,7 +387,7 @@ 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+extern int git_config_set_multivar(const char *, const char *, const char *, int);\n \n #define MAX_GITNAME (1000)\n extern char git_default_email[MAX_GITNAME];\ndiff --git a/config-set.c b/config-set.c\nindex 1b1547b..90a28b3 100644\n--- a/config-set.c\n+++ b/config-set.c\n@@ -1,24 +1,106 @@\n #include \"cache.h\"\n+#include <regex.h>\n \n static const char git_config_set_usage[] =\n-\"git-config-set name [value [value_regex]] | --unset name [value_regex]\";\n+\"git-config-set [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]]\";\n+\n+static char* key = NULL;\n+static char* value = NULL;\n+static regex_t* regex = NULL;\n+static int do_all = 0;\n+static int seen = 0;\n+\n+static int show_config(const char* key_, const char* value_)\n+{\n+\tif (!strcmp(key_, key) &&\n+\t\t\t(regex == NULL ||\n+\t\t\t !regexec(regex, value_, 0, NULL, 0))) {\n+\t\tif (do_all) {\n+\t\t\tprintf(\"%s\\n\", value_);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tif (seen > 0) {\n+\t\t\tfprintf(stderr, \"More than one value: %s\\n\", value);\n+\t\t\tfree(value);\n+\t\t}\n+\t\tvalue = strdup(value_);\n+\t\tseen++;\n+\t}\n+\treturn 0;\n+}\n+\n+static int get_value(const char* key_, const char* regex_)\n+{\n+\tint i;\n+\n+\tkey = malloc(strlen(key_)+1);\n+\tfor (i = 0; key_[i]; i++)\n+\t\tkey[i] = tolower(key_[i]);\n+\n+\tif (regex_) {\n+\t\tregex = (regex_t*)malloc(sizeof(regex_t));\n+\t\tif (regcomp(regex, regex_, REG_EXTENDED)) {\n+\t\t\tfprintf(stderr, \"Invalid pattern: %s\\n\", regex_);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\ti = git_config(show_config);\n+\tif (value) {\n+\t\tprintf(\"%s\\n\", value);\n+\t\tfree(value);\n+\t}\n+\tfree(key);\n+\tif (regex) {\n+\t\tregfree(regex);\n+\t\tfree(regex);\n+\t}\n+\n+\tif (do_all)\n+\t\treturn 0;\n+\n+\treturn seen == 1 ? 0 : 1;\n+}\n \n int main(int argc, const char **argv)\n {\n \tsetup_git_directory();\n \tswitch (argc) {\n \tcase 2:\n-\t\treturn git_config_set(argv[1], NULL);\n+\t\treturn get_value(argv[1], NULL);\n \tcase 3:\n \t\tif (!strcmp(argv[1], \"--unset\"))\n \t\t\treturn git_config_set(argv[2], NULL);\n-\t\telse\n+\t\telse if (!strcmp(argv[1], \"--unset-all\"))\n+\t\t\treturn git_config_set_multivar(argv[2], NULL, NULL, 1);\n+\t\telse if (!strcmp(argv[1], \"--get\"))\n+\t\t\treturn get_value(argv[2], NULL);\n+\t\telse if (!strcmp(argv[1], \"--get-all\")) {\n+\t\t\tdo_all = 1;\n+\t\t\treturn get_value(argv[2], NULL);\n+\t\t} else\n+\n \t\t\treturn git_config_set(argv[1], argv[2]);\n \tcase 4:\n \t\tif (!strcmp(argv[1], \"--unset\"))\n-\t\t\treturn git_config_set_multivar(argv[2], NULL, argv[3]);\n+\t\t\treturn git_config_set_multivar(argv[2], NULL, argv[3], 0);\n+\t\telse if (!strcmp(argv[1], \"--unset-all\"))\n+\t\t\treturn git_config_set_multivar(argv[2], NULL, argv[3], 1);\n+\t\telse if (!strcmp(argv[1], \"--get\"))\n+\t\t\treturn get_value(argv[2], argv[3]);\n+\t\telse if (!strcmp(argv[1], \"--get-all\")) {\n+\t\t\tdo_all = 1;\n+\t\t\treturn get_value(argv[2], argv[3]);\n+\t\t} else if (!strcmp(argv[1], \"--replace-all\"))\n+\n+\t\t\treturn git_config_set_multivar(argv[2], argv[3], NULL, 1);\n \t\telse\n-\t\t\treturn git_config_set_multivar(argv[1], argv[2], argv[3]);\n+\n+\t\t\treturn git_config_set_multivar(argv[1], argv[2], argv[3], 0);\n+\tcase 5:\n+\t\tif (!strcmp(argv[1], \"--replace-all\"))\n+\t\t\treturn git_config_set_multivar(argv[2], argv[3], argv[4], 1);\n+\tcase 1:\n \tdefault:\n \t\tusage(git_config_set_usage);\n \t}\ndiff --git a/config.c b/config.c\nindex bbcafff..697d79f 100644\n--- a/config.c\n+++ b/config.c\n@@ -263,11 +263,15 @@ int git_config(config_fn_t fn)\n /*\n  * Find all the stuff for git_config_set() below.\n  */\n+\n+#define MAX_MATCHES 512\n+\n static struct {\n \tint baselen;\n \tchar* key;\n \tregex_t* value_regex;\n-\toff_t offset;\n+\tint multi_replace;\n+\toff_t offset[MAX_MATCHES];\n \tenum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;\n \tint seen;\n } store;\n@@ -279,12 +283,16 @@ static int store_aux(const char* key, co\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\tif (store.seen == 1 && store.multi_replace == 0) {\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} else if (store.seen >= MAX_MATCHES) {\n+\t\t\t\tfprintf(stderr, \"Too many matches\\n\");\n+\t\t\t\treturn 1;\n \t\t\t}\n-\t\t\tstore.offset = ftell(config_file);\n+\n+\t\t\tstore.offset[store.seen] = ftell(config_file);\n \t\t\tstore.seen++;\n \t\t}\n \t\tbreak;\n@@ -293,14 +301,15 @@ static int store_aux(const char* key, co\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\t/* do not increment matches: this is no match */\n+\t\t\tstore.offset[store.seen] = 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.offset[store.seen] = 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@@ -334,14 +343,38 @@ static void store_write_pair(int fd, con\n \twrite(fd, \"\\n\", 1);\n }\n \n+static int find_beginning_of_line(const char* contents, int size,\n+\tint offset_, int* found_bracket)\n+{\n+\tint equal_offset = size, bracket_offset = size;\n+\tint offset;\n+\n+\tfor (offset = offset_-2; offset > 0 \n+\t\t\t&& contents[offset] != '\\n'; offset--)\n+\t\tswitch (contents[offset]) {\n+\t\t\tcase '=': equal_offset = offset; break;\n+\t\t\tcase ']': bracket_offset = offset; break;\n+\t\t}\n+\tif (bracket_offset < equal_offset) {\n+\t\t*found_bracket = 1;\n+\t\toffset = bracket_offset+1;\n+\t} else\n+\t\toffset++;\n+\n+\treturn offset;\n+}\n+\n int git_config_set(const char* key, const char* value)\n {\n-\treturn git_config_set_multivar(key, value, NULL);\n+\treturn git_config_set_multivar(key, value, NULL, 0);\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+ * if multi_replace==0, nothing, or only one matching key/value is replaced,\n+ *     else all matching key/values (regardless how many) are removed,\n+ *     before the new pair is written.\n  *\n  * Returns 0 on success.\n  *\n@@ -360,7 +393,7 @@ int git_config_set(const char* key, cons\n  *\n  */\n int git_config_set_multivar(const char* key, const char* value,\n-\tconst char* value_regex)\n+\tconst char* value_regex, int multi_replace)\n {\n \tint i;\n \tstruct stat st;\n@@ -368,6 +401,8 @@ int git_config_set_multivar(const char* \n \tchar* config_file = strdup(git_path(\"config\"));\n \tchar* lock_file = strdup(git_path(\"config.lock\"));\n \n+\tstore.multi_replace = multi_replace;\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@@ -431,7 +466,7 @@ int git_config_set_multivar(const char* \n \t} else{\n \t\tint in_fd;\n \t\tchar* contents;\n-\t\tint offset, new_line = 0;\n+\t\tint i, copy_begin, copy_end, new_line = 0;\n \n \t\tif (value_regex == NULL)\n \t\t\tstore.value_regex = NULL;\n@@ -446,7 +481,7 @@ int git_config_set_multivar(const char* \n \t\t\t}\n \t\t}\n \n-\t\tstore.offset = 0;\n+\t\tstore.offset[0] = 0;\n \t\tstore.state = START;\n \t\tstore.seen = 0;\n \n@@ -472,52 +507,42 @@ int git_config_set_multivar(const char* \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/* if nothing to unset, or too many matches, error out */\n+\t\tif ((store.seen == 0 && value == NULL) ||\n+\t\t\t\t(store.seen > 1 && multi_replace == 0)) {\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\tif (store.seen == 0)\n+\t\t\tstore.seen = 1;\n+\n+\t\tfor (i = 0, copy_begin = 0; i < store.seen; i++) {\n+\t\t\tif (store.offset[i] == 0) {\n+\t\t\t\tstore.offset[i] = copy_end = st.st_size;\n+\t\t\t} else if (store.state != KEY_SEEN) {\n+\t\t\t\tcopy_end = store.offset[i];\n \t\t\t} else\n-\t\t\t\toffset++;\n+\t\t\t\tcopy_end = find_beginning_of_line(\n+\t\t\t\t\tcontents, st.st_size,\n+\t\t\t\t\tstore.offset[i]-2, &new_line);\n+\n+\t\t\t/* write the first part of the config */\n+\t\t\tif (copy_end > copy_begin) {\n+\t\t\t\twrite(fd, contents + copy_begin,\n+\t\t\t\tcopy_end - copy_begin);\n+\t\t\t\tif (new_line)\n+\t\t\t\t\twrite(fd, \"\\n\", 1);\n+\t\t\t}\n+\t\t\tcopy_begin = store.offset[i];\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@@ -526,9 +551,9 @@ int git_config_set_multivar(const char* \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+\t\tif (copy_begin < st.st_size)\n+\t\t\twrite(fd, contents + copy_begin,\n+\t\t\t\tst.st_size - copy_begin);\n \n \t\tmunmap(contents, st.st_size);\n \t\tunlink(config_file);\n@@ -544,3 +569,4 @@ int git_config_set_multivar(const char* \n \treturn 0;\n }\n \n+\ndiff --git a/t1300-config-set.sh b/t/t1300-config-set.sh\nsimilarity index 100%\nrename from t1300-config-set.sh\nrename to t/t1300-config-set.sh\nindex df89216..717bf4d 100644\n--- a/t1300-config-set.sh\n+++ b/t/t1300-config-set.sh\n@@ -76,9 +76,44 @@ noIndent= sillyValue ; 'nother silly com\n # empty line\n \t\t; comment\n \t\thaha   =\"beta\" # last silly comment\n+haha = hello\n+\thaha = bello\n [nextSection] noNewline = ouch\n EOF\n \n+cp .git/config .git/config2\n+\n+test_expect_success 'multiple unset' \\\n+\t'git-config-set --unset-all beta.haha'\n+\n+cat > expect << EOF\n+[beta] ; silly comment # another comment\n+noIndent= sillyValue ; 'nother silly comment\n+\n+# empty line\n+\t\t; comment\n+[nextSection] noNewline = ouch\n+EOF\n+\n+test_expect_success 'multiple unset is correct' 'cmp .git/config expect'\n+\n+mv .git/config2 .git/config\n+\n+test_expect_success '--replace-all' \\\n+\t'git-config-set --replace-all beta.haha gamma'\n+\n+cat > expect << EOF\n+[beta] ; silly comment # another comment\n+noIndent= sillyValue ; 'nother silly comment\n+\n+# empty line\n+\t\t; comment\n+\thaha = gamma\n+[nextSection] noNewline = ouch\n+EOF\n+\n+test_expect_success 'all replaced' 'cmp .git/config expect'\n+\n git-config-set beta.haha alpha\n \n cat > expect << EOF\n@@ -108,7 +143,8 @@ EOF\n \n test_expect_success 'really really mean test' 'cmp .git/config expect'\n \n-git-config-set beta.haha\n+test_expect_success 'get value' 'test alpha = $(git-config-set beta.haha)'\n+git-config-set --unset beta.haha\n \n cat > expect << EOF\n [beta] ; silly comment # another comment\n@@ -137,6 +173,12 @@ EOF\n \n test_expect_success 'multivar' 'cmp .git/config expect'\n \n+test_expect_failure 'ambiguous get' \\\n+\t'git-config-set --get nextsection.nonewline'\n+\n+test_expect_success 'get multivar' \\\n+\t'git-config-set --get-all nextsection.nonewline'\n+\n git-config-set nextsection.nonewline \"wow3\" \"wow$\"\n \n cat > expect << EOF\n@@ -152,6 +194,8 @@ EOF\n \n test_expect_success 'multivar replace' 'cmp .git/config expect'\n \n+test_expect_failure 'ambiguous value' 'git-config-set nextsection.nonewline'\n+\n test_expect_failure 'ambiguous unset' \\\n \t'git-config-set --unset nextsection.nonewline'\n \n"},{"id":"12362","messageId":"7vr79b3i7e.fsf@assigned-by-dhcp.cox.net","threadId":"2609","inReplyTo":"Pine.LNX.4.63.0511200650130.12832@wbgn013.biozentrum.uni-wuerzburg.de","subject":"Re: [PATCH] git-config-set: add more options","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-11-20T07:13:25Z","receivedAt":"2005-11-20T07:13:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> \tThe only thing I am concerned about now is how to deal with\n> \tanti value_regex's, i.e.\n>...\n> \tNote: we could adjust the pattern syntax so that a prefix \"!\"\n> \tmeans \"no match\", and \"\\!\" means \"match literal !\", but this\n> \tis ugly.\n\nI do not think it is so bad, except that I would not even bother\ndoing the quoting of prefix '!'; the user can always say '[!]'\nto mean a positive match that begins with an exclamation point.\n"},{"id":"12372","messageId":"Pine.LNX.4.63.0511201320340.4838@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"2609","inReplyTo":"7vr79b3i7e.fsf@assigned-by-dhcp.cox.net","subject":"[PATCH] git-config-set: support selecting values by non-matching regex","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2005-11-20T12:24:18Z","receivedAt":"2005-11-20T12:24:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nExtend the regex syntax of value_regex so that prepending an exclamation\nmark means non-match:\n\n\t[core]\n\t\tquetzal = \"Dodo\" for Brainf*ck\n\t\tquetzal = \"T. Rex\" for Malbolge\n\t\tquetzal = \"cat\"\n\nYou can match the third line with\n\n\tgit-config-set --get quetzal '! for '\n\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\n---\n\n\tOn Sat, 19 Nov 2005, Junio C Hamano wrote:\n\n\t> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\t> \n\t> > \tThe only thing I am concerned about now is how to deal with\n\t> > \tanti value_regex's, i.e.\n\t> >...\n\t> > \tNote: we could adjust the pattern syntax so that a prefix \"!\"\n\t> > \tmeans \"no match\", and \"\\!\" means \"match literal !\", but this\n\t> > \tis ugly.\n\t> \n\t> I do not think it is so bad, except that I would not even bother\n\t> doing the quoting of prefix '!'; the user can always say '[!]'\n\t> to mean a positive match that begins with an exclamation point.\n\n\tThe patch is a little bit bigger, because I included also the\n\tdocumentation update and test cases proving that it works.\n\n\tAlso, the matching part got minimally refactored in config.c.\n\n\n Documentation/git-config-set.txt |   18 +++++++++++++++++-\n config-set.c                     |    9 ++++++++-\n config.c                         |   23 +++++++++++++++++------\n t/t1300-config-set.sh            |   28 ++++++++++++++++++++++++++++\n 4 files changed, 70 insertions(+), 8 deletions(-)\n\napplies-to: 648bd602281a789b2dbcecedeae32321bb09ccbf\ne2e9f798d6aa02ca54193f0c84d916d0e1a1d29b\ndiff --git a/Documentation/git-config-set.txt b/Documentation/git-config-set.txt\nindex c707fbc..bfbd421 100644\n--- a/Documentation/git-config-set.txt\n+++ b/Documentation/git-config-set.txt\n@@ -22,7 +22,9 @@ actually the section and the key separat\n escaped.\n \n If you want to set/unset an option which can occor on multiple lines, you\n-should provide a POSIX regex for the value.\n+should provide a POSIX regex for the value. If you want to handle the lines\n+*not* matching the regex, just prepend a single exlamation mark in front\n+(see EXAMPLES).\n \n This command will fail if\n \n@@ -82,6 +84,7 @@ Given a .git/config like this:\n \t\tcommand=\"ssh\" for \"ssh://kernel.org/\"\n \t\tcommand=\"proxy-command\" for kernel.org\n \t\tcommand=\"myprotocol-command\" for \"my://\"\n+\t\tcommand=default-proxy ; for all the rest\n \n you can set the filemode to true with\n \n@@ -139,6 +142,19 @@ new one with\n % git config-set --replace-all proxy.command ssh\n ------------\n \n+However, if you really only want to replace the line for the default proxy,\n+i.e. the one without a \"for ...\" postfix, do something like this:\n+\n+------------\n+% git config-set proxy.command ssh '! for '\n+------------\n+\n+To actually match only values with an exclamation mark, you have to\n+\n+------------\n+% git config-set section.key value '[!]'\n+------------\n+\n \n Author\n ------\ndiff --git a/config-set.c b/config-set.c\nindex 90a28b3..5f654f7 100644\n--- a/config-set.c\n+++ b/config-set.c\n@@ -8,13 +8,15 @@ static char* key = NULL;\n static char* value = NULL;\n static regex_t* regex = NULL;\n static int do_all = 0;\n+static int do_not_match = 0;\n static int seen = 0;\n \n static int show_config(const char* key_, const char* value_)\n {\n \tif (!strcmp(key_, key) &&\n \t\t\t(regex == NULL ||\n-\t\t\t !regexec(regex, value_, 0, NULL, 0))) {\n+\t\t\t (do_not_match ^\n+\t\t\t  !regexec(regex, value_, 0, NULL, 0)))) {\n \t\tif (do_all) {\n \t\t\tprintf(\"%s\\n\", value_);\n \t\t\treturn 0;\n@@ -38,6 +40,11 @@ static int get_value(const char* key_, c\n \t\tkey[i] = tolower(key_[i]);\n \n \tif (regex_) {\n+\t\tif (regex_[0] == '!') {\n+\t\t\tdo_not_match = 1;\n+\t\t\tregex_++;\n+\t\t}\n+\n \t\tregex = (regex_t*)malloc(sizeof(regex_t));\n \t\tif (regcomp(regex, regex_, REG_EXTENDED)) {\n \t\t\tfprintf(stderr, \"Invalid pattern: %s\\n\", regex_);\ndiff --git a/config.c b/config.c\nindex 697d79f..5d237c8 100644\n--- a/config.c\n+++ b/config.c\n@@ -269,6 +269,7 @@ int git_config(config_fn_t fn)\n static struct {\n \tint baselen;\n \tchar* key;\n+\tint do_not_match;\n \tregex_t* value_regex;\n \tint multi_replace;\n \toff_t offset[MAX_MATCHES];\n@@ -276,13 +277,19 @@ static struct {\n \tint seen;\n } store;\n \n+static int matches(const char* key, const char* value)\n+{\n+\treturn !strcmp(key, store.key) &&\n+\t\t(store.value_regex == NULL ||\n+\t\t (store.do_not_match ^\n+\t\t  !regexec(store.value_regex, value, 0, NULL, 0)));\n+}\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\tif (matches(key, value)) {\n \t\t\tif (store.seen == 1 && store.multi_replace == 0) {\n \t\t\t\tfprintf(stderr,\n \t\t\t\t\t\"Warning: %s has multiple values\\n\",\n@@ -306,9 +313,7 @@ static int store_aux(const char* key, co\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\tif (matches(key, value)) {\n \t\t\tstore.offset[store.seen] = ftell(config_file);\n \t\t\tstore.state = KEY_SEEN;\n \t\t\tstore.seen++;\n@@ -471,6 +476,12 @@ int git_config_set_multivar(const char* \n \t\tif (value_regex == NULL)\n \t\t\tstore.value_regex = NULL;\n \t\telse {\n+\t\t\tif (value_regex[0] == '!') {\n+\t\t\t\tstore.do_not_match = 1;\n+\t\t\t\tvalue_regex++;\n+\t\t\t} else\n+\t\t\t\tstore.do_not_match = 0;\n+\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)) {\ndiff --git a/t/t1300-config-set.sh b/t/t1300-config-set.sh\nindex 717bf4d..59b6c4c 100644\n--- a/t/t1300-config-set.sh\n+++ b/t/t1300-config-set.sh\n@@ -69,6 +69,28 @@ EOF\n \n test_expect_success 'similar section' 'cmp .git/config expect'\n \n+test_expect_success 'replace with non-match' \\\n+\t'git-config-set core.penguin kingpin !blue'\n+\n+test_expect_success 'replace with non-match (actually matching)' \\\n+\t'git-config-set core.penguin \"very blue\" !kingpin'\n+\n+cat > expect << EOF\n+#\n+# This is the config file\n+#\n+\n+[core]\n+\tpenguin = very blue\n+\tMovie = BadPhysics\n+\tUPPERCASE = true\n+\tpenguin = kingpin\n+[Cores]\n+\tWhatEver = Second\n+EOF\n+\n+test_expect_success 'non-match result' 'cmp .git/config expect'\n+\n cat > .git/config << EOF\n [beta] ; silly comment # another comment\n noIndent= sillyValue ; 'nother silly comment\n@@ -173,6 +195,12 @@ EOF\n \n test_expect_success 'multivar' 'cmp .git/config expect'\n \n+test_expect_success 'non-match' \\\n+\t'git-config-set --get nextsection.nonewline !for'\n+\n+test_expect_success 'non-match value' \\\n+\t'test wow = $(git-config-set --get nextsection.nonewline !for)'\n+\n test_expect_failure 'ambiguous get' \\\n \t'git-config-set --get nextsection.nonewline'\n \n---\n0.99.9.GIT\n"}]}