]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ds/config-internal-whitespace-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 1 Apr 2024 20:21:34 +0000 (13:21 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 1 Apr 2024 20:21:34 +0000 (13:21 -0700)
"git config" corrupted literal HT characters written in the
configuration file as part of a value, which has been corrected.

* ds/config-internal-whitespace-fix:
  config.txt: describe handling of whitespace further
  t1300: add more tests for whitespace and inline comments
  config: really keep value-internal whitespace verbatim
  config: minor addition of whitespace

Documentation/config.txt
config.c
t/t1300-config.sh

index 782c2bab906cf188e3cb21fa6d9fa8c4fe78663d..70b448b132628c71ee8807bfef59c1e4e60fc823 100644 (file)
@@ -22,9 +22,10 @@ multivalued.
 Syntax
 ~~~~~~
 
-The syntax is fairly flexible and permissive; whitespaces are mostly
-ignored.  The '#' and ';' characters begin comments to the end of line,
-blank lines are ignored.
+The syntax is fairly flexible and permissive.  Whitespace characters,
+which in this context are the space character (SP) and the horizontal
+tabulation (HT), are mostly ignored.  The '#' and ';' characters begin
+comments to the end of line.  Blank lines are ignored.
 
 The file consists of sections and variables.  A section begins with
 the name of the section in square brackets and continues until the next
@@ -63,16 +64,17 @@ the variable is the boolean "true").
 The variable names are case-insensitive, allow only alphanumeric characters
 and `-`, and must start with an alphabetic character.
 
-A line that defines a value can be continued to the next line by
-ending it with a `\`; the backslash and the end-of-line are
-stripped.  Leading whitespaces after 'name =', the remainder of the
-line after the first comment character '#' or ';', and trailing
-whitespaces of the line are discarded unless they are enclosed in
-double quotes.  Internal whitespaces within the value are retained
-verbatim.
-
-Inside double quotes, double quote `"` and backslash `\` characters
-must be escaped: use `\"` for `"` and `\\` for `\`.
+Whitespace characters surrounding `name`, `=` and `value` are discarded.
+Internal whitespace characters within 'value' are retained verbatim.
+Comments starting with either `#` or `;` and extending to the end of line
+are discarded.  A line that defines a value can be continued to the next
+line by ending it with a backslash (`\`);  the backslash and the end-of-line
+characters are discarded.
+
+If `value` needs to contain leading or trailing whitespace characters,
+it must be enclosed in double quotation marks (`"`).  Inside double quotation
+marks, double quote (`"`) and backslash (`\`) characters must be escaped:
+use `\"` for `"` and `\\` for `\`.
 
 The following escape sequences (beside `\"` and `\\`) are recognized:
 `\n` for newline character (NL), `\t` for horizontal tabulation (HT, TAB)
index 3cfeb3d8bd99f4ca15d0f3a06cd4b1fe932f7f47..5072f12e62e46f1f7aaa6aba1307ce73330687bd 100644 (file)
--- a/config.c
+++ b/config.c
@@ -817,7 +817,8 @@ static int get_next_char(struct config_source *cs)
 
 static char *parse_value(struct config_source *cs)
 {
-       int quote = 0, comment = 0, space = 0;
+       int quote = 0, comment = 0;
+       size_t trim_len = 0;
 
        strbuf_reset(&cs->value);
        for (;;) {
@@ -827,13 +828,17 @@ static char *parse_value(struct config_source *cs)
                                cs->linenr--;
                                return NULL;
                        }
+                       if (trim_len)
+                               strbuf_setlen(&cs->value, trim_len);
                        return cs->value.buf;
                }
                if (comment)
                        continue;
                if (isspace(c) && !quote) {
+                       if (!trim_len)
+                               trim_len = cs->value.len;
                        if (cs->value.len)
-                               space++;
+                               strbuf_addch(&cs->value, c);
                        continue;
                }
                if (!quote) {
@@ -842,8 +847,8 @@ static char *parse_value(struct config_source *cs)
                                continue;
                        }
                }
-               for (; space; space--)
-                       strbuf_addch(&cs->value, ' ');
+               if (trim_len)
+                       trim_len = 0;
                if (c == '\\') {
                        c = get_next_char(cs);
                        switch (c) {
@@ -869,7 +874,7 @@ static char *parse_value(struct config_source *cs)
                        continue;
                }
                if (c == '"') {
-                       quote = 1-quote;
+                       quote = 1 - quote;
                        continue;
                }
                strbuf_addch(&cs->value, c);
index 31c38786870849e7a815f32a08933f059c9c8ffb..f4bd69512ed98bc718d516aadf6b6ba74e7a8e8e 100755 (executable)
@@ -11,6 +11,98 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
+test_expect_success 'setup whitespace config' '
+       sed -e "s/^|//" \
+           -e "s/[$]$//" \
+           -e "s/X/    /g" >.git/config <<-\EOF
+       [section]
+       |       solid = rock
+       |       sparse = big XX blue
+       |       sparseAndTail = big XX blue $
+       |       sparseAndTailQuoted = "big XX blue "
+       |       sparseAndBiggerTail = big XX blue X X
+       |       sparseAndBiggerTailQuoted = "big XX blue X X"
+       |       sparseAndBiggerTailQuotedPlus = "big XX blue X X"X $
+       |       headAndTail = Xbig blue $
+       |       headAndTailQuoted = "Xbig blue "
+       |       headAndTailQuotedPlus = "Xbig blue " $
+       |       annotated = big blueX# to be discarded
+       |       annotatedQuoted = "big blue"X# to be discarded
+       EOF
+'
+
+test_expect_success 'no internal whitespace' '
+       echo "rock" >expect &&
+       git config --get section.solid >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'internal whitespace' '
+       echo "big QQ blue" | q_to_tab >expect &&
+       git config --get section.sparse >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'internal and trailing whitespace' '
+       echo "big QQ blue" | q_to_tab >expect &&
+       git config --get section.sparseAndTail >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'internal and trailing whitespace, all quoted' '
+       echo "big QQ blue " | q_to_tab >expect &&
+       git config --get section.sparseAndTailQuoted >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'internal and more trailing whitespace' '
+       echo "big QQ blue" | q_to_tab >expect &&
+       git config --get section.sparseAndBiggerTail >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'internal and more trailing whitespace, all quoted' '
+       echo "big QQ blue Q Q" | q_to_tab >expect &&
+       git config --get section.sparseAndBiggerTailQuoted >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'internal and more trailing whitespace, not all quoted' '
+       echo "big QQ blue Q Q" | q_to_tab >expect &&
+       git config --get section.sparseAndBiggerTailQuotedPlus >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'leading and trailing whitespace' '
+       echo "big blue" >expect &&
+       git config --get section.headAndTail >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'leading and trailing whitespace, all quoted' '
+       echo "Qbig blue " | q_to_tab >expect &&
+       git config --get section.headAndTailQuoted >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'leading and trailing whitespace, not all quoted' '
+       echo "Qbig blue " | q_to_tab >expect &&
+       git config --get section.headAndTailQuotedPlus >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'inline comment' '
+       echo "big blue" >expect &&
+       git config --get section.annotated >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'inline comment, quoted' '
+       echo "big blue" >expect &&
+       git config --get section.annotatedQuoted >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'clear default config' '
        rm -f .git/config
 '
@@ -1066,9 +1158,25 @@ test_expect_success '--null --get-regexp' '
        test_cmp expect result
 '
 
-test_expect_success 'inner whitespace kept verbatim' '
-       git config section.val "foo       bar" &&
-       test_cmp_config "foo      bar" section.val
+test_expect_success 'inner whitespace kept verbatim, spaces only' '
+       echo "foo   bar" >expect &&
+       git config section.val "foo   bar" &&
+       git config --get section.val >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'inner whitespace kept verbatim, horizontal tabs only' '
+       echo "fooQQbar" | q_to_tab >expect &&
+       git config section.val "$(cat expect)" &&
+       git config --get section.val >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'inner whitespace kept verbatim, horizontal tabs and spaces' '
+       echo "foo Q  bar" | q_to_tab >expect &&
+       git config section.val "$(cat expect)" &&
+       git config --get section.val >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success SYMLINKS 'symlinked configuration' '