Skip to content

Commit 258a884

Browse files
Byroncodex
andcommitted
review
- [P2] Preserve balanced quote state after removing comments — git/config.py:596-596 For valid Git syntax such as `k = "foo"bar # "note"` followed by `x = keep`, stripping produces `"foo"bar`, which the subsequent last-character check misclassifies as an open multiline quote. On `main`, `x` and later sections remained separate entries; this change absorbs them into `k`, and writing an unrelated setting removes them from the config. Classify the stripped value using its actual quote state and add a regression test covering subsequent settings. Assisted-by: GPT 6.0 Co-authored-by: GPT 6.0 <codex@openai.com>
1 parent 452878f commit 258a884

2 files changed

Lines changed: 30 additions & 7 deletions

File tree

git/config.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,8 @@ def is_line_continuation(value: str) -> bool:
511511
return False
512512
return escaped
513513

514-
def strip_inline_comment(value: str) -> str:
515-
"""Cut an unquoted ``#`` or ``;`` comment, as git's ``parse_value`` does.
514+
def strip_inline_comment(value: str) -> Tuple[str, bool]:
515+
"""Cut an unquoted ``#`` or ``;`` comment and report whether a quote is open.
516516
517517
Quoting and backslash escapes are honoured, so a ``#`` inside a quoted
518518
value is literal and an unterminated quote swallows the rest of the line.
@@ -526,8 +526,8 @@ def strip_inline_comment(value: str) -> str:
526526
elif char == '"':
527527
quoted = not quoted
528528
elif char in "#;" and not quoted:
529-
return value[:index]
530-
return value
529+
return value[:index], False
530+
return value, quoted
531531

532532
def parse_value(value: str) -> str:
533533
parsed: List[str] = []
@@ -593,7 +593,8 @@ def parse_value(value: str) -> str:
593593
optname, vi, optval = mo.group("option", "vi", "value")
594594
optname = self.optionxform(optname.rstrip())
595595

596-
optval = strip_inline_comment(optval).strip()
596+
optval, quote_open = strip_inline_comment(optval)
597+
optval = optval.strip()
597598

598599
if len(optval) < 2 or optval[0] != '"':
599600
# Does not open quoting.
@@ -620,12 +621,12 @@ def parse_value(value: str) -> str:
620621
continued = True
621622
if continued:
622623
optval = parse_value(optval)
623-
elif optval[-1] != '"':
624+
elif quote_open:
624625
# Opens quoting and does not close: appears to start multi-line quoting.
625626
is_multi_line = True
626627
optval = string_decode(optval[1:])
627628
elif re.search(r'(?:^|[^\\])(?:\\\\)*"', optval[1:-1]):
628-
# Preserve malformed values containing unescaped quotes.
629+
# Preserve values containing additional unescaped quotes.
629630
pass
630631
else:
631632
# Opens and closes quoting.

test/test_config.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,28 @@ def test_inline_comments_are_stripped_like_git(self):
262262
with self.subTest(content=content):
263263
self.assertEqual(config.get_value("a", "k"), expected)
264264

265+
@with_rw_directory
266+
def test_inline_comments_preserve_balanced_quotes_and_following_settings(self, rw_dir):
267+
config_path = osp.join(rw_dir, "config")
268+
values = (b'"foo"bar', b'"foo\\"bar"baz', b'"foo#;bar"baz')
269+
for value in values:
270+
for comment in (b' # "note"', b' ; "note"'):
271+
with self.subTest(value=value, comment=comment):
272+
with open(config_path, "wb") as config_file:
273+
config_file.write(b"[a]\n\tk = " + value + comment + b"\n\tx = keep\n[b]\n\ty = stay\n")
274+
275+
with GitConfigParser(config_path, read_only=False) as config:
276+
self.assertEqual(config.get_value("a", "k"), value.decode(defenc))
277+
self.assertEqual(config.get_value("a", "x"), "keep")
278+
self.assertEqual(config.get_value("b", "y"), "stay")
279+
config.set_value("other", "value", "updated")
280+
281+
with GitConfigParser(config_path) as config:
282+
self.assertEqual(config.get_value("a", "k"), value.decode(defenc))
283+
self.assertEqual(config.get_value("a", "x"), "keep")
284+
self.assertEqual(config.get_value("b", "y"), "stay")
285+
self.assertEqual(config.get_value("other", "value"), "updated")
286+
265287
def test_backslash_line_continuation(self):
266288
"""An unquoted value ending in a backslash continues on the next line,
267289
exactly as git config parses it: the final backslash and the newline

0 commit comments

Comments
 (0)