diff --git a/dulwich/config.py b/dulwich/config.py index 3e911524fc..6e9b4830bd 100644 --- a/dulwich/config.py +++ b/dulwich/config.py @@ -793,7 +793,10 @@ def _parse_string(value: bytes) -> bytes: # the rest of the line is a comment break elif c in _WHITESPACE_CHARS: - whitespace.append(c) + if in_quotes: + ret.append(c) + else: + whitespace.append(c) else: if whitespace: ret.extend(whitespace) diff --git a/property_tests/test_config.py b/property_tests/test_config.py index 66f863742c..b10540047a 100644 --- a/property_tests/test_config.py +++ b/property_tests/test_config.py @@ -76,19 +76,15 @@ max_size=12, ).map(lambda value: value.encode("ascii")) - values = ( - st.text( - alphabet=( - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789" - ' -_./:#\t\n\\"' - ), - max_size=32, - ) - .map(lambda value: value.encode("ascii")) - .filter(lambda value: not value.endswith((b" ", b"\t"))) - ) + values = st.text( + alphabet=( + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + ' -_./:#\t\n\\"' + ), + max_size=32, + ).map(lambda value: value.encode("ascii")) sections = st.tuples( section_names, diff --git a/tests/test_config.py b/tests/test_config.py index 58a1a7383d..a0e9bbea5a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -218,6 +218,17 @@ def test_write_to_file_subsection(self) -> None: c.write_to_file(f) self.assertEqual(b'[branch "blie"]\n\tfoo = bar\n', f.getvalue()) + def test_write_to_file_preserves_quoted_trailing_whitespace(self) -> None: + c = ConfigFile() + c.set((b"core",), b"foo", b" ") + c.set((b"core",), b"bar", b"\t") + + f = BytesIO() + c.write_to_file(f) + reparsed = self.from_file(f.getvalue()) + + self.assertEqual(c, reparsed) + def test_same_line(self) -> None: cf = self.from_file(b"[branch.foo] foo = bar\n") self.assertEqual(b"bar", cf.get((b"branch", b"foo"), b"foo")) @@ -1001,7 +1012,9 @@ def test_not_quoted(self) -> None: class ParseStringTests(TestCase): def test_quoted(self) -> None: self.assertEqual(b" foo", _parse_string(b'" foo"')) + self.assertEqual(b"foo ", _parse_string(b'"foo "')) self.assertEqual(b"\tfoo", _parse_string(b'"\\tfoo"')) + self.assertEqual(b"foo\t", _parse_string(b'"foo\\t"')) def test_not_quoted(self) -> None: self.assertEqual(b"foo", _parse_string(b"foo"))