From 1461c35c6c6d5aea73f5526017aa797332be99a8 Mon Sep 17 00:00:00 2001 From: geek-alpha <2714239176@qq.com> Date: Tue, 22 Sep 2026 00:35:36 +0800 Subject: [PATCH] fix: skip a UTF-8 BOM when reading config files git ignores a UTF-8 BOM at the start of a config file, so a file written by a Windows editor still parses. GitConfigParser raised MissingSectionHeaderError instead, because the BOM was decoded into the first line, which then no longer matched a section header. Evidence on git 2.47.3: `git config -f bom.cfg --list` prints core.bare=true for a file starting with the three BOM bytes, while GitPython raised MissingSectionHeaderError. With this change both read the same values, also when the BOM file is pulled in through include.path. The new test fails without the change. --- git/config.py | 9 +++++++-- test/test_config.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/git/config.py b/git/config.py index bf9c3ecbb..9bb82273c 100644 --- a/git/config.py +++ b/git/config.py @@ -550,9 +550,14 @@ def parse_value(value: str) -> str: while True: # We assume to read binary! - line = fp.readline().decode(defenc) - if not line: + raw_line = fp.readline() + if not raw_line: break + if lineno == 0 and raw_line.startswith(b"\xef\xbb\xbf"): + # A UTF-8 BOM is not part of the content. git skips it, so a + # config file written by a Windows editor still parses. + raw_line = raw_line[3:] + line = raw_line.decode(defenc) lineno = lineno + 1 # Comment or blank line? if line.strip() == "" or self.re_comment.match(line): diff --git a/test/test_config.py b/test/test_config.py index e00bde183..b0f7f72eb 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -354,6 +354,17 @@ def test_comment_backslash_does_not_continue_value(self, rw_dir): with GitConfigParser(config_path) as config: self.assertEqual(config.get_value("a", "x"), "two") + def test_utf8_bom_is_skipped_like_git(self): + """git skips a UTF-8 BOM at the start of a config file, so one written by + a Windows editor still parses. Expectations are what + `git config -f --list` prints on git 2.47.3.""" + content = b"\xef\xbb\xbf[core]\n\tbare = true\n" + config_file = io.BytesIO(content) + config_file.name = "bom.config" + config = GitConfigParser(config_file) + config.read() + self.assertIs(config.get_value("core", "bare"), True) + def test_config_value_with_trailing_new_line(self): config_content = b'[section-header]\nkey:"value\n"' config_file = io.BytesIO(config_content)