Skip to content

Commit 42566c6

Browse files
codexByron
authored andcommitted
fix: ignore comments after implicit boolean config keys
Bare entries such as "enabled # comment" and "enabled ; comment" retained the comment in the option name. getboolean("flag", "enabled") therefore raised NoOptionError, and an unrelated write emitted the malformed key again. An equals sign or colon inside the comment also made the parser mistake the entry for an assignment. Stop the shared option-name expression at either comment marker. Both the assignment and valueless patterns then extract only the bare name, preserving its None representation, true boolean value, and bare syntax on write-back. Quoted values continue through the existing value parser. Add a round-trip regression covering both comment markers with spaces, tabs, or no separator, including equals signs and colons in comments. All six cases failed with NoOptionError before the fix. Check the parsed name and boolean, exact output after an unrelated edit, and the boolean read back by both GitPython and git config. Git reference: checkout 1630431f326e15fcde608827b5ff38422528eb59, Documentation/config.adoc and config.c:get_value. Despite the general comment rule in the documentation, that parser and Git 2.50.1 (Apple Git-155) reject comments after bare keys. Accepting them is the requested GitPython behavior; the normalized bare-key output is accepted by Git. Validation on Python 3.12.14: 42 configuration tests and six regression subtests passed, with two existing skips. Ruff lint and formatting, mypy (46 source files), basedpyright, and git diff --check passed.
1 parent d171e34 commit 42566c6

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

git/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
310310
re_comment = re.compile(r"^\s*[#;]")
311311
# } END configuration
312312

313-
optvalueonly_source = r"\s*(?P<option>[^:=\s][^:=]*)"
313+
optvalueonly_source = r"\s*(?P<option>[^:=\s#;][^:=#;]*)"
314314

315315
OPTVALUEONLY = re.compile(optvalueonly_source)
316316

test/test_config.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,36 @@ def test_implicit_boolean_round_trip(self, rw_dir):
917917
self.assertIs(config.getboolean("flag", "empty"), False)
918918
self.assertEqual(dict(config.items_all("flag"))["multiple"], ["false", None, "", None])
919919

920+
@with_rw_directory
921+
def test_implicit_boolean_with_comments_round_trip(self, rw_dir):
922+
config_path = osp.join(rw_dir, "config")
923+
comments = (
924+
" # comment",
925+
" ; comment",
926+
"# comment = value",
927+
"; comment = value",
928+
"\t# comment: value",
929+
"\t; comment: value",
930+
)
931+
for comment in comments:
932+
with self.subTest(comment=comment):
933+
with open(config_path, "wb") as config_file:
934+
config_file.write(("[flag]\n\tenabled%s\n" % comment).encode(defenc))
935+
936+
with GitConfigParser(config_path, read_only=False) as config:
937+
self.assertIs(config.getboolean("flag", "enabled"), True)
938+
self.assertEqual(config.items("flag"), [("enabled", None)])
939+
config.set_value("other", "value", "updated")
940+
941+
with open(config_path, "rb") as config_file:
942+
self.assertEqual(config_file.read(), b"[flag]\n\tenabled\n[other]\n\tvalue = updated\n")
943+
with GitConfigParser(config_path) as config:
944+
self.assertIs(config.getboolean("flag", "enabled"), True)
945+
self.assertEqual(
946+
subprocess.check_output(["git", "config", "--file", config_path, "--bool", "flag.enabled"]),
947+
b"true\n",
948+
)
949+
920950
def test_config_with_quotes(self):
921951
cr = GitConfigParser(fixture_path("git_config_with_quotes"), read_only=True)
922952

0 commit comments

Comments
 (0)