Skip to content

Commit 452878f

Browse files
rawsun007claude
andcommitted
fix: strip inline config comments the way git does
A `#` or `;` outside quotes starts a comment in git, with or without a space before it and whether or not the value is quoted. The parser only cut a `;` that was preceded by whitespace in an unquoted value, so `name = Alice # work` read back with the comment attached, and `k = "quoted" # after` was mistaken for an unterminated multi-line quote and returned `quoted" # after`. `strip_inline_comment` cuts the comment before the quote-structure branches, using the same quote- and escape-aware scan that `is_line_continuation` already uses, so all three branches see comment-free text. Escape handling is untouched. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent cf43820 commit 452878f

2 files changed

Lines changed: 42 additions & 5 deletions

File tree

git/config.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,24 @@ 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.
516+
517+
Quoting and backslash escapes are honoured, so a ``#`` inside a quoted
518+
value is literal and an unterminated quote swallows the rest of the line.
519+
"""
520+
quoted = escaped = False
521+
for index, char in enumerate(value):
522+
if escaped:
523+
escaped = False
524+
elif char == "\\":
525+
escaped = True
526+
elif char == '"':
527+
quoted = not quoted
528+
elif char in "#;" and not quoted:
529+
return value[:index]
530+
return value
531+
514532
def parse_value(value: str) -> str:
515533
parsed: List[str] = []
516534
whitespace: List[str] = []
@@ -575,11 +593,7 @@ def parse_value(value: str) -> str:
575593
optname, vi, optval = mo.group("option", "vi", "value")
576594
optname = self.optionxform(optname.rstrip())
577595

578-
if vi in ("=", ":") and ";" in optval and not optval.strip().startswith('"'):
579-
pos = optval.find(";")
580-
if pos != -1 and optval[pos - 1].isspace():
581-
optval = optval[:pos]
582-
optval = optval.strip()
596+
optval = strip_inline_comment(optval).strip()
583597

584598
if len(optval) < 2 or optval[0] != '"':
585599
# Does not open quoting.

test/test_config.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,29 @@ def test_multi_line_config(self):
239239
)
240240
self.assertEqual(len(config.sections()), 23)
241241

242+
def test_inline_comments_are_stripped_like_git(self):
243+
"""A `#` or `;` outside quotes starts a comment, with or without a space
244+
before it, and whether or not the value is quoted. Expectations are what
245+
`git config -f <file> --get a.k` prints on git 2.50.1."""
246+
cases = [
247+
(b"[a]\n\tk = value # comment\n", "value"),
248+
(b"[a]\n\tk = value ; comment\n", "value"),
249+
(b"[a]\n\tk = value#nospace\n", "value"),
250+
(b"[a]\n\tk = value;nospace\n", "value"),
251+
(b"[a]\n\tk = a # b ; c\n", "a"),
252+
(b'[a]\n\tk = "quoted" # after\n', "quoted"),
253+
# A comment character inside quotes is literal.
254+
(b'[a]\n\tk = "has # inside"\n', "has # inside"),
255+
(b'[a]\n\tk = "has ; inside"\n', "has ; inside"),
256+
]
257+
for content, expected in cases:
258+
config_file = io.BytesIO(content)
259+
config_file.name = "inline_comment.config"
260+
config = GitConfigParser(config_file)
261+
config.read()
262+
with self.subTest(content=content):
263+
self.assertEqual(config.get_value("a", "k"), expected)
264+
242265
def test_backslash_line_continuation(self):
243266
"""An unquoted value ending in a backslash continues on the next line,
244267
exactly as git config parses it: the final backslash and the newline

0 commit comments

Comments
 (0)