* changes: ConfigTest: Add some additional comment parsing tests Config: Drop backslash in invalid escape sequences in subsections Config: Match C git behavior more closely in escaping valuestags/v4.10.0.201712302008-r
+ "[user]\n" | + "[user]\n" | ||||
+ " email = A U Thor <thor@example.com> # Just an example...\n" | + " email = A U Thor <thor@example.com> # Just an example...\n" | ||||
+ " name = \"A Thor \\\\ \\\"\\t \"\n" | + " name = \"A Thor \\\\ \\\"\\t \"\n" | ||||
+ " defaultCheckInComment = \"a many line\\ncomment\\n to test\"\n"; | |||||
+ " defaultCheckInComment = a many line\\ncomment\\n to test\n"; | |||||
assertEquals(expectedStr, new String(IO.readFully(cfg), Constants.CHARSET)); | assertEquals(expectedStr, new String(IO.readFully(cfg), Constants.CHARSET)); | ||||
} | } | ||||
} | } | ||||
@Test | @Test | ||||
public void testEscapeSpecialCharacters() throws ConfigInvalidException { | |||||
public void testNoEscapeSpecialCharacters() throws ConfigInvalidException { | |||||
assertValueRoundTrip("x\\y", "x\\\\y"); | |||||
assertValueRoundTrip("x\"y", "x\\\"y"); | |||||
assertValueRoundTrip("x\ny", "x\\ny"); | |||||
assertValueRoundTrip("x\ty", "x\\ty"); | |||||
assertValueRoundTrip("x\by", "x\\by"); | |||||
} | |||||
@Test | |||||
public void testParseLiteralBackspace() throws ConfigInvalidException { | |||||
// This is round-tripped with an escape sequence by JGit, but C git writes | |||||
// it out as a literal backslash. | |||||
assertEquals("x\by", parseEscapedValue("x\by")); | |||||
} | |||||
@Test | |||||
public void testEscapeCommentCharacters() throws ConfigInvalidException { | |||||
assertValueRoundTrip("x#y", "\"x#y\""); | assertValueRoundTrip("x#y", "\"x#y\""); | ||||
assertValueRoundTrip("x;y", "\"x;y\""); | assertValueRoundTrip("x;y", "\"x;y\""); | ||||
assertValueRoundTrip("x\\y", "\"x\\\\y\""); | |||||
assertValueRoundTrip("x\"y", "\"x\\\"y\""); | |||||
assertValueRoundTrip("x\ny", "\"x\\ny\""); | |||||
assertValueRoundTrip("x\ty", "\"x\\ty\""); | |||||
assertValueRoundTrip("x\by", "\"x\\by\""); | |||||
} | } | ||||
@Test | @Test | ||||
assertEquals("baz", parseEscapedValue("baz # comment")); | assertEquals("baz", parseEscapedValue("baz # comment")); | ||||
assertEquals("baz", parseEscapedValue("baz " + WS + " ; comment")); | assertEquals("baz", parseEscapedValue("baz " + WS + " ; comment")); | ||||
assertEquals("baz", parseEscapedValue("baz " + WS + " # comment")); | assertEquals("baz", parseEscapedValue("baz " + WS + " # comment")); | ||||
assertEquals("baz ", parseEscapedValue("\"baz \"; comment")); | |||||
assertEquals("baz ", parseEscapedValue("\"baz \"# comment")); | |||||
assertEquals("baz ", parseEscapedValue("\"baz \" ; comment")); | |||||
assertEquals("baz ", parseEscapedValue("\"baz \" # comment")); | |||||
} | } | ||||
@Test | @Test | ||||
public void testParseInvalidSubsections() { | public void testParseInvalidSubsections() { | ||||
assertInvalidSubsection( | assertInvalidSubsection( | ||||
JGitText.get().newlineInQuotesNotAllowed, "\"x\ny\""); | JGitText.get().newlineInQuotesNotAllowed, "\"x\ny\""); | ||||
assertInvalidSubsection( | |||||
MessageFormat.format(JGitText.get().badEscape, 'q'), "\"x\\q\""); | |||||
} | |||||
@Test | |||||
public void testDropBackslashFromInvalidEscapeSequenceInSubsectionName() | |||||
throws ConfigInvalidException { | |||||
assertEquals("x0", parseEscapedSubsection("\"x\\0\"")); | |||||
assertEquals("xq", parseEscapedSubsection("\"x\\q\"")); | |||||
// Unlike for values, \b, \n, and \t are not valid escape sequences. | // Unlike for values, \b, \n, and \t are not valid escape sequences. | ||||
assertInvalidSubsection( | |||||
MessageFormat.format(JGitText.get().badEscape, 'b'), "\"x\\b\""); | |||||
assertInvalidSubsection( | |||||
MessageFormat.format(JGitText.get().badEscape, 'n'), "\"x\\n\""); | |||||
assertInvalidSubsection( | |||||
MessageFormat.format(JGitText.get().badEscape, 't'), "\"x\\t\""); | |||||
assertEquals("xb", parseEscapedSubsection("\"x\\b\"")); | |||||
assertEquals("xn", parseEscapedSubsection("\"x\\n\"")); | |||||
assertEquals("xt", parseEscapedSubsection("\"x\\t\"")); | |||||
} | } | ||||
private static void assertValueRoundTrip(String value) | private static void assertValueRoundTrip(String value) |
StringBuilder r = new StringBuilder(x.length()); | StringBuilder r = new StringBuilder(x.length()); | ||||
for (int k = 0; k < x.length(); k++) { | for (int k = 0; k < x.length(); k++) { | ||||
char c = x.charAt(k); | char c = x.charAt(k); | ||||
boolean thisCharNeedsQuote = true; | |||||
// git-config(1) lists the limited set of supported escape sequences. | |||||
// git-config(1) lists the limited set of supported escape sequences, but | |||||
// the documentation is otherwise not especially normative. In particular, | |||||
// which ones of these produce and/or require escaping and/or quoting | |||||
// around them is not documented and was discovered by trial and error. | |||||
// In summary: | |||||
// | |||||
// * Quotes are only required if there is leading/trailing whitespace or a | |||||
// comment character. | |||||
// * Bytes that have a supported escape sequence are escaped, except for | |||||
// \b for some reason which isn't. | |||||
// * Needing an escape sequence is not sufficient reason to quote the | |||||
// value. | |||||
switch (c) { | switch (c) { | ||||
case '\0': | case '\0': | ||||
// Unix command line calling convention cannot pass a '\0' as an | // Unix command line calling convention cannot pass a '\0' as an | ||||
break; | break; | ||||
case '\b': | case '\b': | ||||
// Doesn't match `git config foo.bar $'x\by'`, which doesn't escape the | |||||
// \x08, but since both escaped and unescaped forms are readable, we'll | |||||
// prefer internal consistency here. | |||||
r.append('\\').append('b'); | r.append('\\').append('b'); | ||||
break; | break; | ||||
case '\\': | case '\\': | ||||
r.append('\\').append('\\'); | |||||
break; | |||||
case '"': | case '"': | ||||
r.append('\\').append(c); | |||||
r.append('\\').append('"'); | |||||
break; | break; | ||||
case '#': | case '#': | ||||
case ';': | case ';': | ||||
needQuote = true; | |||||
r.append(c); | r.append(c); | ||||
break; | break; | ||||
default: | default: | ||||
thisCharNeedsQuote = false; | |||||
r.append(c); | r.append(c); | ||||
break; | break; | ||||
} | } | ||||
needQuote |= thisCharNeedsQuote; | |||||
} | } | ||||
return needQuote ? '"' + r.toString() + '"' : r.toString(); | return needQuote ? '"' + r.toString() + '"' : r.toString(); | ||||
continue; | continue; | ||||
default: | default: | ||||
throw new ConfigInvalidException(MessageFormat.format( | |||||
JGitText.get().badEscape, | |||||
Character.valueOf(((char) c)))); | |||||
// C git simply drops backslashes if the escape sequence is not | |||||
// recognized. | |||||
r.append((char) c); | |||||
continue; | |||||
} | } | ||||
} | } | ||||
if ('"' == c) { | if ('"' == c) { |