Repository navigation
Conversation
pjbgf
left a comment
There was a problem hiding this comment.
Add a test to ensure that cfg.Marshal() is called before ConfigWriter(), so trying to save an invalid section don't leave a repository with an empty config file. Instead it should error and retain the existing config file.
… encoding.
git-config(1) states that subsection names "can contain any characters except
newline and the null byte". The encoder escaped only `"` and `\`, so either
character was written raw into the section header and produced a file that
could not be read back: go-git reports "string not terminated" / "illegal
character NUL", and git reports "bad config line" or silently truncates the
name at the NUL.
Escaping them is not an option, because git drops a backslash before any
character other than `"` and `\` — `\n` reads back as the letter "n", which
would quietly rename the subsection. git refuses to create such a key at all
("invalid key (newline)"), so do the same and return an error rather than
write a config that nothing can parse.
Reachable from .gitmodules, whose submodule names come from the repository
itself and are re-encoded by config.Modules.Marshal.
Assisted-by: Claude Opus 5 <[email protected]>
Signed-off-by: Rohit Behera <[email protected]>
SetConfig opened the config writer, which truncates the file, before calling Marshal. Now that Marshal can fail on a subsection name git cannot represent, a failed save left the repository with an empty config. Marshal first, so the error is returned and the existing file is kept. Assisted-by: Claude Opus 5.5 <[email protected]> Signed-off-by: Rohit Behera <[email protected]>
6a14949 to
cb4c1ed
Compare
|
Good catch, thanks. Added |
|
The Windows failure looks unrelated: |
Problem
git-config(1): subsection names "can contain any characters except newline and the null byte."The encoder escapes only
"and\, so either of those two characters is written raw into the section header and the result cannot be read back — by go-git or by git:git config --file … --liston that same output:fatal: bad config line 1. With a NUL instead of the newline, go-git reportsillegal character NULand git silently truncates the name toa.This is reachable from
.gitmodules: submodule names come from the repository being operated on, andconfig.Modules.Marshalre-encodes them.Fix
Return an error instead of writing a file nothing can parse.
SetConfignow marshals before it opens the config writer. The writer truncates the file, so with the old order that error left the repository with an empty config.Escaping is not an option here. git drops a backslash before any character other than
"and\, so\nreads back as the letternand would quietly rename the subsection. git refuses to create such a key at all (error: invalid key (newline)), so the encoder does the same.Tests
Two regression tests in
encoder_test.go. Withdrawing the fix while keeping them reproduces the failure onmain:TestSetConfigKeepsExistingConfigWhenEncodingFailscovers the save path: with the old order the config is read back as"".Rebased on main.
go test ./...passes apart from the knownplumbing/format/gitignoreTestConformanceSuitefailure (#2317). The root package andplumbing/transport/httphit network timeouts on my machine when the whole suite runs at once, and both pass when run on their own.golangci-lintreports 0 issues for both packages. Behaviour checked against git 2.50.1.Scope
Section names and option keys have the same gap — they are written with no escaping or validation at all, so a space, tab, newline,
],"or\in either also yields an unreadable file. Those aren't reachable from repository data the way subsection names are, so I left them out to keep this a focused fix; happy to extend it if you'd prefer them covered here.Found with a decode/encode/decode round-trip fuzz target, not included in this PR.
AI disclosure (per
AI_POLICY.md): this change was produced with an AI agent, Claude Opus 5 and Opus 5.5 via Claude Code, and both commits carry theAssisted-by:trailer. The reproduction, the git 2.50.1 comparisons, the baseline/after suite runs and the withdraw-the-fix check above were all actually executed, and the output quoted is real.