From c17a0e3d040759f71840c265c91e37a0b7ef910c Mon Sep 17 00:00:00 2001 From: Guillaume Marty Date: Thu, 8 Oct 2026 22:01:42 +0200 Subject: [PATCH] Fix panic when git config contains a key without a value --- main.go | 31 +++++++++++++++++++++---------- main_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 91aaf84d4..47c607bb8 100644 --- a/main.go +++ b/main.go @@ -838,16 +838,7 @@ func main() { log.V(0).Info("unexpected stderr reading git config", "stdout", stdout, "stderr", stderr) os.Exit(1) } else { - cfgs := strings.Split(stdout, string(rune(0))) - kvs := funcr.PseudoStruct{} // like a map but ordered - for _, cfg := range cfgs { - if cfg == "" { - continue - } - parts := strings.SplitN(cfg, "\n", 2) // any additional newlines are part of the value - kvs = append(kvs, parts[0], parts[1]) - } - log.V(0).Info("git config", "configs", kvs) + log.V(0).Info("git config", "configs", parseGitConfigList(stdout)) } // The scope of the initialization context ends here, so we call cancel to release resources associated with it. @@ -2324,6 +2315,26 @@ type keyVal struct { val string } +// parseGitConfigList parses the output of `git config list -z`. A key with no +// value (e.g. a bare `key` line, which git treats as boolean true) is printed +// without a newline, and is reported with a nil value. +func parseGitConfigList(stdout string) funcr.PseudoStruct { + cfgs := strings.Split(stdout, string(rune(0))) + kvs := funcr.PseudoStruct{} // like a map but ordered + for _, cfg := range cfgs { + if cfg == "" { + continue + } + parts := strings.SplitN(cfg, "\n", 2) // any additional newlines are part of the value + if len(parts) == 1 { + kvs = append(kvs, parts[0], nil) + continue + } + kvs = append(kvs, parts[0], parts[1]) + } + return kvs +} + func parseGitConfigs(configsFlag string) ([]keyVal, error) { // Use a channel as a FIFO. We don't expect the input strings to be very // large, so this simple model should suffice. diff --git a/main_test.go b/main_test.go index 13de2a4e9..38eec7bf8 100644 --- a/main_test.go +++ b/main_test.go @@ -24,6 +24,7 @@ import ( "testing" "time" + "github.com/go-logr/logr/funcr" "go.uber.org/goleak" ) @@ -248,6 +249,53 @@ func TestParseGitConfigs(t *testing.T) { } } +func TestParseGitConfigList(t *testing.T) { + cases := []struct { + name string + input string + expect funcr.PseudoStruct + }{{ + name: "empty", + input: "", + expect: funcr.PseudoStruct{}, + }, { + name: "one-pair", + input: "k\nv\x00", + expect: funcr.PseudoStruct{"k", "v"}, + }, { + name: "no-trailing-nul", + input: "k\nv", + expect: funcr.PseudoStruct{"k", "v"}, + }, { + name: "empty-value", + input: "k\n\x00", + expect: funcr.PseudoStruct{"k", ""}, + }, { + name: "multi-line-value", + input: "k\nline1\nline2\x00", + expect: funcr.PseudoStruct{"k", "line1\nline2"}, + }, { + // `git config list -z` prints a key with no value (e.g. a bare `key` + // line, which git treats as boolean true) without a newline. + name: "no-value", + input: "k\x00", + expect: funcr.PseudoStruct{"k", nil}, + }, { + name: "multiple", + input: "core.bare\nfalse\x00demo.bare\x00demo.empty\n\x00demo.multi\nline1\nline2\x00", + expect: funcr.PseudoStruct{"core.bare", "false", "demo.bare", nil, "demo.empty", "", "demo.multi", "line1\nline2"}, + }} + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + kvs := parseGitConfigList(tc.input) + if !reflect.DeepEqual(kvs, tc.expect) { + t.Errorf("bad result:\n\texpected: %#v\n\t got: %#v", tc.expect, kvs) + } + }) + } +} + func TestDirIsEmpty(t *testing.T) { root := absPath(t.TempDir())