Skip to content

Commit 482bf07

Browse files
authored
Merge pull request #642 from thockin/release-3.x
V3: Allow quoted keys for --git-config
2 parents b07dba4 + 25295dd commit 482bf07

File tree

2 files changed

+113
-38
lines changed

2 files changed

+113
-38
lines changed

cmd/git-sync/main.go

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ var flAskPassURL = flag.String("askpass-url", envString("GIT_ASKPASS_URL", ""),
127127
var flGitCmd = flag.String("git", envString("GIT_SYNC_GIT", "git"),
128128
"the git command to run (subject to PATH search, mostly for testing)")
129129
var flGitConfig = flag.String("git-config", envString("GIT_SYNC_GIT_CONFIG", ""),
130-
"additional git config options in 'key1:val1,key2:val2' format")
130+
"additional git config options in 'section.var1:val1,\"section.sub.var2\":\"val2\"' format")
131131
var flGitGC = flag.String("git-gc", envString("GIT_SYNC_GIT_GC", "auto"),
132132
"git garbage collection behavior: one of 'auto', 'always', 'aggressive', or 'off'")
133133

@@ -1293,9 +1293,19 @@ func parseGitConfigs(configsFlag string) ([]keyVal, error) {
12931293
if r, ok := <-ch; !ok {
12941294
break
12951295
} else {
1296-
cur.key, err = parseGitConfigKey(r, ch)
1297-
if err != nil {
1298-
return nil, err
1296+
// This can accumulate things that git doesn't allow, but we'll
1297+
// just let git handle it, rather than try to pre-validate to their
1298+
// spec.
1299+
if r == '"' {
1300+
cur.key, err = parseGitConfigQKey(ch)
1301+
if err != nil {
1302+
return nil, err
1303+
}
1304+
} else {
1305+
cur.key, err = parseGitConfigKey(r, ch)
1306+
if err != nil {
1307+
return nil, err
1308+
}
12991309
}
13001310
}
13011311

@@ -1322,6 +1332,23 @@ func parseGitConfigs(configsFlag string) ([]keyVal, error) {
13221332
return result, nil
13231333
}
13241334

1335+
func parseGitConfigQKey(ch <-chan rune) (string, error) {
1336+
str, err := parseQString(ch)
1337+
if err != nil {
1338+
return "", err
1339+
}
1340+
1341+
// The next character must be a colon.
1342+
r, ok := <-ch
1343+
if !ok {
1344+
return "", fmt.Errorf("unexpected end of key: %q", str)
1345+
}
1346+
if r != ':' {
1347+
return "", fmt.Errorf("unexpected character after quoted key: %q%c", str, r)
1348+
}
1349+
return str, nil
1350+
}
1351+
13251352
func parseGitConfigKey(r rune, ch <-chan rune) (string, error) {
13261353
buf := make([]rune, 0, 64)
13271354
buf = append(buf, r)
@@ -1331,62 +1358,66 @@ func parseGitConfigKey(r rune, ch <-chan rune) (string, error) {
13311358
case r == ':':
13321359
return string(buf), nil
13331360
default:
1334-
// This can accumulate things that git doesn't allow, but we'll
1335-
// just let git handle it, rather than try to pre-validate to their
1336-
// spec.
13371361
buf = append(buf, r)
13381362
}
13391363
}
13401364
return "", fmt.Errorf("unexpected end of key: %q", string(buf))
13411365
}
13421366

13431367
func parseGitConfigQVal(ch <-chan rune) (string, error) {
1368+
str, err := parseQString(ch)
1369+
if err != nil {
1370+
return "", err
1371+
}
1372+
1373+
// If there is a next character, it must be a comma.
1374+
r, ok := <-ch
1375+
if ok && r != ',' {
1376+
return "", fmt.Errorf("unexpected character after quoted value %q%c", str, r)
1377+
}
1378+
return str, nil
1379+
}
1380+
1381+
func parseGitConfigVal(r rune, ch <-chan rune) (string, error) {
13441382
buf := make([]rune, 0, 64)
1383+
buf = append(buf, r)
13451384

13461385
for r := range ch {
13471386
switch r {
13481387
case '\\':
1349-
if e, err := unescape(ch); err != nil {
1388+
if r, err := unescape(ch); err != nil {
13501389
return "", err
13511390
} else {
1352-
buf = append(buf, e)
1353-
}
1354-
case '"':
1355-
// Once we have a closing quote, the next must be either a comma or
1356-
// end-of-string. This helps reset the state for the next key, if
1357-
// there is one.
1358-
r, ok := <-ch
1359-
if ok && r != ',' {
1360-
return "", fmt.Errorf("unexpected trailing character '%c'", r)
1391+
buf = append(buf, r)
13611392
}
1393+
case ',':
13621394
return string(buf), nil
13631395
default:
13641396
buf = append(buf, r)
13651397
}
13661398
}
1367-
return "", fmt.Errorf("unexpected end of value: %q", string(buf))
1399+
// We ran out of characters, but that's OK.
1400+
return string(buf), nil
13681401
}
13691402

1370-
func parseGitConfigVal(r rune, ch <-chan rune) (string, error) {
1403+
func parseQString(ch <-chan rune) (string, error) {
13711404
buf := make([]rune, 0, 64)
1372-
buf = append(buf, r)
13731405

13741406
for r := range ch {
13751407
switch r {
13761408
case '\\':
1377-
if r, err := unescape(ch); err != nil {
1409+
if e, err := unescape(ch); err != nil {
13781410
return "", err
13791411
} else {
1380-
buf = append(buf, r)
1412+
buf = append(buf, e)
13811413
}
1382-
case ',':
1414+
case '"':
13831415
return string(buf), nil
13841416
default:
13851417
buf = append(buf, r)
13861418
}
13871419
}
1388-
// We ran out of characters, but that's OK.
1389-
return string(buf), nil
1420+
return "", fmt.Errorf("unexpected end of quoted string: %q", string(buf))
13901421
}
13911422

13921423
// unescape processes most of the documented escapes that git config supports.

cmd/git-sync/main_test.go

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,26 +114,66 @@ func TestParseGitConfigs(t *testing.T) {
114114
name: "one-pair",
115115
input: `k:v`,
116116
expect: []keyVal{keyVal{"k", "v"}},
117+
}, {
118+
name: "one-pair-qkey",
119+
input: `"k":v`,
120+
expect: []keyVal{keyVal{"k", "v"}},
117121
}, {
118122
name: "one-pair-qval",
119123
input: `k:"v"`,
120124
expect: []keyVal{keyVal{"k", "v"}},
125+
}, {
126+
name: "one-pair-qkey-qval",
127+
input: `"k":"v"`,
128+
expect: []keyVal{keyVal{"k", "v"}},
129+
}, {
130+
name: "multi-pair",
131+
input: `k1:v1,"k2":v2,k3:"v3","k4":"v4"`,
132+
expect: []keyVal{{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}, {"k4", "v4"}},
121133
}, {
122134
name: "garbage",
123135
input: `abc123`,
124136
fail: true,
125137
}, {
126-
name: "invalid-val",
127-
input: `k:v\xv`,
128-
fail: true,
138+
name: "key-section-var",
139+
input: `sect.var:v`,
140+
expect: []keyVal{keyVal{"sect.var", "v"}},
129141
}, {
130-
name: "invalid-qval",
131-
input: `k:"v\xv"`,
132-
fail: true,
142+
name: "key-section-subsection-var",
143+
input: `sect.sub.var:v`,
144+
expect: []keyVal{keyVal{"sect.sub.var", "v"}},
133145
}, {
134-
name: "two-pair",
135-
input: `k1:v1,k2:v2`,
136-
expect: []keyVal{{"k1", "v1"}, {"k2", "v2"}},
146+
name: "key-subsection-with-space",
147+
input: `k.sect.sub section:v`,
148+
expect: []keyVal{keyVal{"k.sect.sub section", "v"}},
149+
}, {
150+
name: "key-subsection-with-escape",
151+
input: `k.sect.sub\tsection:v`,
152+
expect: []keyVal{keyVal{"k.sect.sub\\tsection", "v"}},
153+
}, {
154+
name: "key-subsection-with-comma",
155+
input: `k.sect.sub,section:v`,
156+
expect: []keyVal{keyVal{"k.sect.sub,section", "v"}},
157+
}, {
158+
name: "qkey-subsection-with-space",
159+
input: `"k.sect.sub section":v`,
160+
expect: []keyVal{keyVal{"k.sect.sub section", "v"}},
161+
}, {
162+
name: "qkey-subsection-with-escapes",
163+
input: `"k.sect.sub\t\n\\section":v`,
164+
expect: []keyVal{keyVal{"k.sect.sub\t\n\\section", "v"}},
165+
}, {
166+
name: "qkey-subsection-with-comma",
167+
input: `"k.sect.sub,section":v`,
168+
expect: []keyVal{keyVal{"k.sect.sub,section", "v"}},
169+
}, {
170+
name: "qkey-subsection-with-colon",
171+
input: `"k.sect.sub:section":v`,
172+
expect: []keyVal{keyVal{"k.sect.sub:section", "v"}},
173+
}, {
174+
name: "invalid-qkey",
175+
input: `"k\xk":v"`,
176+
fail: true,
137177
}, {
138178
name: "val-spaces",
139179
input: `k1:v 1,k2:v 2`,
@@ -155,17 +195,21 @@ func TestParseGitConfigs(t *testing.T) {
155195
input: `k1:"v1",k2:"v2",`,
156196
expect: []keyVal{{"k1", "v1"}, {"k2", "v2"}},
157197
}, {
158-
name: "val-escapes",
198+
name: "val-with-escapes",
159199
input: `k1:v\n\t\\\"\,1`,
160200
expect: []keyVal{{"k1", "v\n\t\\\",1"}},
161201
}, {
162-
name: "qval-escapes",
202+
name: "qval-with-escapes",
163203
input: `k1:"v\n\t\\\"\,1"`,
164204
expect: []keyVal{{"k1", "v\n\t\\\",1"}},
165205
}, {
166-
name: "qval-comma",
206+
name: "qval-with-comma",
167207
input: `k1:"v,1"`,
168208
expect: []keyVal{{"k1", "v,1"}},
209+
}, {
210+
name: "qkey-missing-close",
211+
input: `"k1:v1`,
212+
fail: true,
169213
}, {
170214
name: "qval-missing-close",
171215
input: `k1:"v1`,
@@ -182,7 +226,7 @@ func TestParseGitConfigs(t *testing.T) {
182226
t.Errorf("unexpected success")
183227
}
184228
if !reflect.DeepEqual(kvs, tc.expect) {
185-
t.Errorf("bad result: expected %v, got %v", tc.expect, kvs)
229+
t.Errorf("bad result:\n\texpected: %#v\n\t got: %#v", tc.expect, kvs)
186230
}
187231
})
188232
}

0 commit comments

Comments
 (0)