Skip to content

fix(config): read an option on the same line as its section header - #2307

Closed
Keerthana-64 wants to merge 1 commit into
gitpython-developers:mainfrom
Keerthana-64:config-header-same-line-option
Closed

Keerthana-64 wants to merge 1 commit into
gitpython-developers:mainfrom
Keerthana-64:config-header-same-line-option

Conversation

@Keerthana-64

Copy link
Copy Markdown
Contributor

.gitmodules with an option on a section header line, git 2.54.0:

[submodule "lib"]
	path = lib
	url = https://good.example/lib
[submodule "lib"] url = https://evil.example/lib

$ git submodule init
Submodule 'lib' (https://evil.example/lib) registered for path 'lib'
>>> [(s.name, s.url) for s in git.Repo(".").submodules]
[('lib', 'https://good.example/lib')]

GitConfigParser._read matches headers with configparser's greedy \[(?P<header>.+)\] and drops the rest of the line, while git ends a header at its first unquoted ] and parses what follows as a normal config line. a tool that checks submodule URLs with GitPython therefore approves a different URL than the one git clones, and the same match turns [c] k = v ] into a section named c] k = v. the header now ends at the first unquoted ] and the remainder goes back through the parse loop as its own line, which matches git for options, comments and a second header ([f] [g] k = v sets g.k); a header with unbalanced quotes keeps the previous reading. test_option_after_section_header_is_read_like_git fails before and passes after, and the three full-suite failures on this machine (test_diff_with_staged_file, test_empty_repo, test_rebasing) fail the same way without the change.

`GitConfigParser._read` recognized a section header with the inherited
`configparser` pattern `\[(?P<header>.+)\]`, matched against the
stripped line, and discarded everything after the match. git ends a
header at its first `]` outside a quoted subsection name and parses the
rest of the line as an ordinary config line, so `[core] bare = true`
sets `core.bare` in git and nothing in GitPython.

`.gitmodules` is read with this parser and is controlled by whoever
authored the superproject. For

    [submodule "lib"]
    	path = lib
    	url = https://good.example/lib
    [submodule "lib"] url = https://evil.example/lib

`git config -f .gitmodules --list` shows both URLs, and
`git submodule init` registers and later clones
`https://evil.example/lib`, while `Repo.submodules` reports
`https://good.example/lib`. A tool that uses GitPython to check which
URLs a repository's submodules point at approves a different URL than
the one git fetches. The greedy match also misread other lines:
`[c] k = v ]` became a section named `c] k = v` with no options, and
`[d] k = "multi\` continued on the next line produced an option named
`line"` instead of `d.k = multi  line`.

The header now ends at the first unquoted `]`, with backslash escapes
honored inside the quoted subsection name, and the text after it is fed
back through the parse loop as a line of its own. That reuses the
existing comment, option, continuation and header handling, so
`[e] ; comment` adds nothing, `[b]flag` is an implicit boolean, and
`[f] [g] k = v` sets `g.k`, all as git reads them. A header with
unbalanced quotes keeps the previous reading up to the last `]`, and
files whose headers stand alone on their lines parse exactly as before.

`test_option_after_section_header_is_read_like_git` in
`test/test_config.py` checks each case above against what
`git config -f <file> --list` prints on git 2.54.0. It fails before this
change and passes after. `ruff check`, `ruff format --check` and `mypy`
pass. In a full clone prepared with `init-tests-after-clone.sh`, the
pytest suite passes except `test_diff_with_staged_file`,
`test_empty_repo` and `test_rebasing`, which fail the same way without
this change.
Byron pushed a commit that referenced this pull request Oct 24, 2026
Cover the configuration regressions from PRs #2305 and #2307 against actual
`git config` output. The current configuration facade already delegates
these operations to Git, so the valid cases pass without a runtime change.

Use scripted fixtures for a real home-relative conditional include and the
complete inline-header sample. Preserve native `HOME` and `USERPROFILE`
separators so the same test exercises the Windows path case on Windows.
Check both matching and nonmatching repositories and file/stream readers.
Also check adjacent headers, an escaped quote before `]`, and rejection of
unbalanced quotes, missing/stray brackets, malformed keys and sections.

Git accepts multiple section headers on one line but omits empty sections
from `git config list`. Reflect that output and the facade's documented
empty-section limitation instead of restoring a separate Python parser.
Document the existing `GIX-12` fallback without claiming native coverage.

Validation on macOS, Git 2.54.0, Python 3.12 and GixPython 0.2.0:

- All 11 added cases passed against the unchanged runtime.
- `test/test_config.py`: 62 passed with each of the CLI and Gix backends.
- `ruff check`, `ruff format --check`, `bash -n` and `git diff --check` passed.

Windows and Linux execution was not available for this local check.
Byron pushed a commit that referenced this pull request Oct 24, 2026
Cover the configuration regressions from PRs #2305 and #2307 against actual
`git config` output. The current configuration facade already delegates
these operations to Git, so the valid cases pass without a runtime change.

Use scripted fixtures for a real home-relative conditional include and the
complete inline-header sample. Preserve native `HOME` and `USERPROFILE`
separators so the same test exercises the Windows path case on Windows.
Check both matching and nonmatching repositories and file/stream readers.
Also check adjacent headers, an escaped quote before `]`, and rejection of
unbalanced quotes, missing/stray brackets, malformed keys and sections.

Git accepts multiple section headers on one line but omits empty sections
from `git config list`. Reflect that output and the facade's documented
empty-section limitation instead of restoring a separate Python parser.
Document the existing `GIX-12` fallback without claiming native coverage.

Validation on macOS, Git 2.54.0, Python 3.12 and GixPython 0.2.0:

- All 11 added cases passed against the unchanged runtime.
- `test/test_config.py`: 62 passed with each of the CLI and Gix backends.
- `ruff check`, `ruff format --check`, `bash -n` and `git diff --check` passed.

Windows and Linux execution was not available for this local check.
@Byron

Byron commented Oct 24, 2026

Copy link
Copy Markdown
Member

Codex, operating on behalf of Byron: This PR has been superseded by #2274. Its regression coverage has been integrated there, with assertions checked against Git’s behavior and fixes included where needed.

@Byron Byron closed this Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants