Allow quoted keys for --git-config

This allows keys to contain literal ':' which would previously confuse
the parser.
This commit is contained in:
Tim Hockin 2022-11-19 13:50:34 -08:00
parent b07dba4026
commit 25295dd0de
No known key found for this signature in database
2 changed files with 118 additions and 43 deletions

View File

@ -127,7 +127,7 @@ var flAskPassURL = flag.String("askpass-url", envString("GIT_ASKPASS_URL", ""),
var flGitCmd = flag.String("git", envString("GIT_SYNC_GIT", "git"),
"the git command to run (subject to PATH search, mostly for testing)")
var flGitConfig = flag.String("git-config", envString("GIT_SYNC_GIT_CONFIG", ""),
"additional git config options in 'key1:val1,key2:val2' format")
"additional git config options in 'section.var1:val1,\"section.sub.var2\":\"val2\"' format")
var flGitGC = flag.String("git-gc", envString("GIT_SYNC_GIT_GC", "auto"),
"git garbage collection behavior: one of 'auto', 'always', 'aggressive', or 'off'")
@ -1292,12 +1292,22 @@ func parseGitConfigs(configsFlag string) ([]keyVal, error) {
// Peek and see if we have a key.
if r, ok := <-ch; !ok {
break
} else {
// This can accumulate things that git doesn't allow, but we'll
// just let git handle it, rather than try to pre-validate to their
// spec.
if r == '"' {
cur.key, err = parseGitConfigQKey(ch)
if err != nil {
return nil, err
}
} else {
cur.key, err = parseGitConfigKey(r, ch)
if err != nil {
return nil, err
}
}
}
// Peek and see if we have a value.
if r, ok := <-ch; !ok {
@ -1322,6 +1332,23 @@ func parseGitConfigs(configsFlag string) ([]keyVal, error) {
return result, nil
}
func parseGitConfigQKey(ch <-chan rune) (string, error) {
str, err := parseQString(ch)
if err != nil {
return "", err
}
// The next character must be a colon.
r, ok := <-ch
if !ok {
return "", fmt.Errorf("unexpected end of key: %q", str)
}
if r != ':' {
return "", fmt.Errorf("unexpected character after quoted key: %q%c", str, r)
}
return str, nil
}
func parseGitConfigKey(r rune, ch <-chan rune) (string, error) {
buf := make([]rune, 0, 64)
buf = append(buf, r)
@ -1331,9 +1358,6 @@ func parseGitConfigKey(r rune, ch <-chan rune) (string, error) {
case r == ':':
return string(buf), nil
default:
// This can accumulate things that git doesn't allow, but we'll
// just let git handle it, rather than try to pre-validate to their
// spec.
buf = append(buf, r)
}
}
@ -1341,30 +1365,17 @@ func parseGitConfigKey(r rune, ch <-chan rune) (string, error) {
}
func parseGitConfigQVal(ch <-chan rune) (string, error) {
buf := make([]rune, 0, 64)
for r := range ch {
switch r {
case '\\':
if e, err := unescape(ch); err != nil {
str, err := parseQString(ch)
if err != nil {
return "", err
} else {
buf = append(buf, e)
}
case '"':
// Once we have a closing quote, the next must be either a comma or
// end-of-string. This helps reset the state for the next key, if
// there is one.
// If there is a next character, it must be a comma.
r, ok := <-ch
if ok && r != ',' {
return "", fmt.Errorf("unexpected trailing character '%c'", r)
return "", fmt.Errorf("unexpected character after quoted value %q%c", str, r)
}
return string(buf), nil
default:
buf = append(buf, r)
}
}
return "", fmt.Errorf("unexpected end of value: %q", string(buf))
return str, nil
}
func parseGitConfigVal(r rune, ch <-chan rune) (string, error) {
@ -1389,6 +1400,26 @@ func parseGitConfigVal(r rune, ch <-chan rune) (string, error) {
return string(buf), nil
}
func parseQString(ch <-chan rune) (string, error) {
buf := make([]rune, 0, 64)
for r := range ch {
switch r {
case '\\':
if e, err := unescape(ch); err != nil {
return "", err
} else {
buf = append(buf, e)
}
case '"':
return string(buf), nil
default:
buf = append(buf, r)
}
}
return "", fmt.Errorf("unexpected end of quoted string: %q", string(buf))
}
// unescape processes most of the documented escapes that git config supports.
func unescape(ch <-chan rune) (rune, error) {
r, ok := <-ch

View File

@ -114,26 +114,66 @@ func TestParseGitConfigs(t *testing.T) {
name: "one-pair",
input: `k:v`,
expect: []keyVal{keyVal{"k", "v"}},
}, {
name: "one-pair-qkey",
input: `"k":v`,
expect: []keyVal{keyVal{"k", "v"}},
}, {
name: "one-pair-qval",
input: `k:"v"`,
expect: []keyVal{keyVal{"k", "v"}},
}, {
name: "one-pair-qkey-qval",
input: `"k":"v"`,
expect: []keyVal{keyVal{"k", "v"}},
}, {
name: "multi-pair",
input: `k1:v1,"k2":v2,k3:"v3","k4":"v4"`,
expect: []keyVal{{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}, {"k4", "v4"}},
}, {
name: "garbage",
input: `abc123`,
fail: true,
}, {
name: "invalid-val",
input: `k:v\xv`,
fail: true,
name: "key-section-var",
input: `sect.var:v`,
expect: []keyVal{keyVal{"sect.var", "v"}},
}, {
name: "invalid-qval",
input: `k:"v\xv"`,
fail: true,
name: "key-section-subsection-var",
input: `sect.sub.var:v`,
expect: []keyVal{keyVal{"sect.sub.var", "v"}},
}, {
name: "two-pair",
input: `k1:v1,k2:v2`,
expect: []keyVal{{"k1", "v1"}, {"k2", "v2"}},
name: "key-subsection-with-space",
input: `k.sect.sub section:v`,
expect: []keyVal{keyVal{"k.sect.sub section", "v"}},
}, {
name: "key-subsection-with-escape",
input: `k.sect.sub\tsection:v`,
expect: []keyVal{keyVal{"k.sect.sub\\tsection", "v"}},
}, {
name: "key-subsection-with-comma",
input: `k.sect.sub,section:v`,
expect: []keyVal{keyVal{"k.sect.sub,section", "v"}},
}, {
name: "qkey-subsection-with-space",
input: `"k.sect.sub section":v`,
expect: []keyVal{keyVal{"k.sect.sub section", "v"}},
}, {
name: "qkey-subsection-with-escapes",
input: `"k.sect.sub\t\n\\section":v`,
expect: []keyVal{keyVal{"k.sect.sub\t\n\\section", "v"}},
}, {
name: "qkey-subsection-with-comma",
input: `"k.sect.sub,section":v`,
expect: []keyVal{keyVal{"k.sect.sub,section", "v"}},
}, {
name: "qkey-subsection-with-colon",
input: `"k.sect.sub:section":v`,
expect: []keyVal{keyVal{"k.sect.sub:section", "v"}},
}, {
name: "invalid-qkey",
input: `"k\xk":v"`,
fail: true,
}, {
name: "val-spaces",
input: `k1:v 1,k2:v 2`,
@ -155,17 +195,21 @@ func TestParseGitConfigs(t *testing.T) {
input: `k1:"v1",k2:"v2",`,
expect: []keyVal{{"k1", "v1"}, {"k2", "v2"}},
}, {
name: "val-escapes",
name: "val-with-escapes",
input: `k1:v\n\t\\\"\,1`,
expect: []keyVal{{"k1", "v\n\t\\\",1"}},
}, {
name: "qval-escapes",
name: "qval-with-escapes",
input: `k1:"v\n\t\\\"\,1"`,
expect: []keyVal{{"k1", "v\n\t\\\",1"}},
}, {
name: "qval-comma",
name: "qval-with-comma",
input: `k1:"v,1"`,
expect: []keyVal{{"k1", "v,1"}},
}, {
name: "qkey-missing-close",
input: `"k1:v1`,
fail: true,
}, {
name: "qval-missing-close",
input: `k1:"v1`,
@ -182,7 +226,7 @@ func TestParseGitConfigs(t *testing.T) {
t.Errorf("unexpected success")
}
if !reflect.DeepEqual(kvs, tc.expect) {
t.Errorf("bad result: expected %v, got %v", tc.expect, kvs)
t.Errorf("bad result:\n\texpected: %#v\n\t got: %#v", tc.expect, kvs)
}
})
}