Skip to content

Commit 60dd946

Browse files
Byroncodex
andcommitted
fix: ignore continuation markers in config comments
<!-- agent --> Git treats # and ; outside quotes as the start of a comment. A trailing backslash in that ignored text therefore cannot continue the value. The continuation loop counted trailing slashes without lexical context, so it consumed the next option and a later writable flush silently dropped that setting. - Track quote and escape state while checking the accumulated value. - Stop continuation scanning at an unquoted comment. - Cover preservation of the following option across a writable flush. Assisted-by: GPT 5.6 Co-authored-by: GPT 5.6 <codex@openai.com>
1 parent 075a664 commit 60dd946

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

git/config.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,19 @@ def string_decode(v: str) -> str:
467467

468468
# END string_decode
469469

470+
def is_line_continuation(value: str) -> bool:
471+
quoted = escaped = False
472+
for char in value:
473+
if escaped:
474+
escaped = False
475+
elif char == "\\":
476+
escaped = True
477+
elif char == '"':
478+
quoted = not quoted
479+
elif char in "#;" and not quoted:
480+
return False
481+
return escaped
482+
470483
while True:
471484
# We assume to read binary!
472485
line = fp.readline().decode(defenc)
@@ -520,8 +533,7 @@ def string_decode(v: str) -> str:
520533
# included). An even number means the last backslash
521534
# is escaped and the value ends there.
522535
while True:
523-
trailing = len(optval) - len(optval.rstrip("\\"))
524-
if trailing % 2 == 0:
536+
if not is_line_continuation(optval):
525537
break
526538
continuation = fp.readline()
527539
if not continuation:

test/test_config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,19 @@ def test_backslash_line_continuation(self):
163163
key = "co" if section == "alias" else "k"
164164
self.assertEqual(config.get_value(section, key), expected)
165165

166+
@with_rw_directory
167+
def test_comment_backslash_does_not_continue_value(self, rw_dir):
168+
config_path = osp.join(rw_dir, "config")
169+
with open(config_path, "wb") as config_file:
170+
config_file.write(b"[a]\n\tk = one\\\n two ; ignored \\\n\tx = two\n")
171+
172+
with GitConfigParser(config_path, read_only=False) as config:
173+
self.assertEqual(config.get_value("a", "x"), "two")
174+
config.set_value("a", "added", "three")
175+
176+
with GitConfigParser(config_path) as config:
177+
self.assertEqual(config.get_value("a", "x"), "two")
178+
166179
def test_config_value_with_trailing_new_line(self):
167180
config_content = b'[section-header]\nkey:"value\n"'
168181
config_file = io.BytesIO(config_content)

0 commit comments

Comments
 (0)