config: arbitrary number of matches for --unset and --replace-all

git-config used a static match array to hold the matches we want to unset/replace when using --unset or --replace-all. Use a variable-sized array instead. This in particular fixes the symptoms git-svn had when storing large numbers of svn-remote.*.added-placeholder entries in the config file. While the tests are rather more paranoid than just --unset and --replace-all, the other operations already worked. Indeed git-svn's usage only breaks the first time *after* creating so many entries, when it wants to unset and re-add them all. Reported-by: Jess Hottenstein <jess.hottenstein@gmail.com> Signed-off-by: Thomas Rast <tr@thomasrast.ch> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Thomas Rast committed Nov 13, 2013 at 11:19 UTC 83786fa412662f6d7cdade9e4043882c5ad21c1a
2 files changed +77 -6
config.c
+13 -6
@@ -1160,15 +1160,14 @@ int git_config(config_fn_t fn, void *data)
1160 * Find all the stuff for git_config_set() below.
1161 */
1162
1163 -#define MAX_MATCHES 512
1164 -
1163 static struct {
1164 int baselen;
1165 char *key;
1166 int do_not_match;
1167 regex_t *value_regex;
1168 int multi_replace;
1171 - size_t offset[MAX_MATCHES];
1169 + size_t *offset;
1170 + unsigned int offset_alloc;
1171 enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
1172 int seen;
1173 } store;
@@ -1191,11 +1190,11 @@ static int store_aux(const char *key, const char *value, void *cb)
1190 if (matches(key, value)) {
1191 if (store.seen == 1 && store.multi_replace == 0) {
1192 warning("%s has multiple values", key);
1194 - } else if (store.seen >= MAX_MATCHES) {
1195 - error("too many matches for %s", key);
1196 - return 1;
1193 }
1194
1195 + ALLOC_GROW(store.offset, store.seen + 1,
1196 + store.offset_alloc);
1197 +
1198 store.offset[store.seen] = cf->do_ftell(cf);
1199 store.seen++;
1200 }
@@ -1223,11 +1222,15 @@ static int store_aux(const char *key, const char *value, void *cb)
1222 * Do not increment matches: this is no match, but we
1223 * just made sure we are in the desired section.
1224 */
1225 + ALLOC_GROW(store.offset, store.seen + 1,
1226 + store.offset_alloc);
1227 store.offset[store.seen] = cf->do_ftell(cf);
1228 /* fallthru */
1229 case SECTION_END_SEEN:
1230 case START:
1231 if (matches(key, value)) {
1232 + ALLOC_GROW(store.offset, store.seen + 1,
1233 + store.offset_alloc);
1234 store.offset[store.seen] = cf->do_ftell(cf);
1235 store.state = KEY_SEEN;
1236 store.seen++;
@@ -1235,6 +1238,9 @@ static int store_aux(const char *key, const char *value, void *cb)
1238 if (strrchr(key, '.') - key == store.baselen &&
1239 !strncmp(key, store.key, store.baselen)) {
1240 store.state = SECTION_SEEN;
1241 + ALLOC_GROW(store.offset,
1242 + store.seen + 1,
1243 + store.offset_alloc);
1244 store.offset[store.seen] = cf->do_ftell(cf);
1245 }
1246 }
@@ -1533,6 +1539,7 @@ int git_config_set_multivar_in_file(const char *config_filename,
1539 }
1540 }
1541
1542 + ALLOC_GROW(store.offset, 1, store.offset_alloc);
1543 store.offset[0] = 0;
1544 store.state = START;
1545 store.seen = 0;
t/t1303-wacky-config.sh
+64
@@ -3,17 +3,28 @@
3 test_description='Test wacky input to git config'
4 . ./test-lib.sh
5
6 +# Leaving off the newline is intentional!
7 setup() {
8 (printf "[section]\n" &&
9 printf " key = foo") >.git/config
10 }
11
12 +# 'check section.key value' verifies that the entry for section.key is
13 +# 'value'
14 check() {
15 echo "$2" >expected
16 git config --get "$1" >actual 2>&1
17 test_cmp actual expected
18 }
19
20 +# 'check section.key regex value' verifies that the entry for
21 +# section.key *that matches 'regex'* is 'value'
22 +check_regex() {
23 + echo "$3" >expected
24 + git config --get "$1" "$2" >actual 2>&1
25 + test_cmp actual expected
26 +}
27 +
28 test_expect_success 'modify same key' '
29 setup &&
30 git config section.key bar &&
@@ -47,4 +58,57 @@ test_expect_success 'do not crash on special long config line' '
58 check section.key "$LONG_VALUE"
59 '
60
61 +setup_many() {
62 + setup &&
63 + # This time we want the newline so that we can tack on more
64 + # entries.
65 + echo >>.git/config &&
66 + # Semi-efficient way of concatenating 5^5 = 3125 lines. Note
67 + # that because 'setup' already put one line, this means 3126
68 + # entries for section.key in the config file.
69 + cat >5to1 <<-\EOF &&
70 + key = foo
71 + key = foo
72 + key = foo
73 + key = foo
74 + key = foo
75 + EOF
76 + cat 5to1 5to1 5to1 5to1 5to1 >5to2 && # 25
77 + cat 5to2 5to2 5to2 5to2 5to2 >5to3 && # 125
78 + cat 5to3 5to3 5to3 5to3 5to3 >5to4 && # 635
79 + cat 5to4 5to4 5to4 5to4 5to4 >>.git/config # 3125
80 +}
81 +
82 +test_expect_success 'get many entries' '
83 + setup_many &&
84 + git config --get-all section.key >actual &&
85 + test_line_count = 3126 actual
86 +'
87 +
88 +test_expect_success 'get many entries by regex' '
89 + setup_many &&
90 + git config --get-regexp "sec.*ke." >actual &&
91 + test_line_count = 3126 actual
92 +'
93 +
94 +test_expect_success 'add and replace one of many entries' '
95 + setup_many &&
96 + git config --add section.key bar &&
97 + check_regex section.key "b.*r" bar &&
98 + git config section.key beer "b.*r" &&
99 + check_regex section.key "b.*r" beer
100 +'
101 +
102 +test_expect_success 'replace many entries' '
103 + setup_many &&
104 + git config --replace-all section.key bar &&
105 + check section.key bar
106 +'
107 +
108 +test_expect_success 'unset many entries' '
109 + setup_many &&
110 + git config --unset-all section.key &&
111 + test_must_fail git config section.key
112 +'
113 +
114 test_done