Skip to content

Commit 5ddb888

Browse files
committed
config: allow multiple environments when calling config.Unmarshal
1 parent bf36df4 commit 5ddb888

File tree

2 files changed

+99
-37
lines changed

2 files changed

+99
-37
lines changed

config/config.go

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
package config
44

55
import (
6-
"errors"
76
"fmt"
87
"reflect"
8+
"regexp"
9+
"strconv"
10+
"strings"
911
"sync"
1012

1113
"github.com/git-lfs/git-lfs/tools"
@@ -120,32 +122,27 @@ func (c *Configuration) Unmarshal(v interface{}) error {
120122
field := into.Field(i)
121123
sfield := into.Type().Field(i)
122124

123-
key, env, err := c.parseTag(sfield.Tag)
124-
if err != nil {
125-
return err
126-
}
127-
128-
if env == nil {
129-
continue
130-
}
131-
132125
var val interface{}
133-
switch sfield.Type.Kind() {
134-
case reflect.String:
135-
var ok bool
126+
for _, lookup := range c.parseTag(sfield.Tag) {
127+
if _, ok := lookup.Get(); !ok {
128+
continue
129+
}
136130

137-
val, ok = env.Get(key)
138-
if !ok {
139-
val = field.String()
131+
switch sfield.Type.Kind() {
132+
case reflect.String:
133+
val, _ = lookup.Get()
134+
case reflect.Int:
135+
val = lookup.Int(int(field.Int()))
136+
case reflect.Bool:
137+
val = lookup.Bool(field.Bool())
138+
default:
139+
return fmt.Errorf("lfs/config: unsupported target type for field %q: %v",
140+
sfield.Name, sfield.Type.String())
141+
}
142+
143+
if val != nil {
144+
break
140145
}
141-
case reflect.Int:
142-
val = env.Int(key, int(field.Int()))
143-
case reflect.Bool:
144-
val = env.Bool(key, field.Bool())
145-
default:
146-
return fmt.Errorf(
147-
"lfs/config: unsupported target type for field %q: %v",
148-
sfield.Name, sfield.Type.String())
149146
}
150147

151148
if val != nil {
@@ -156,27 +153,59 @@ func (c *Configuration) Unmarshal(v interface{}) error {
156153
return nil
157154
}
158155

156+
var (
157+
tagRe = regexp.MustCompile("((\\w+:\"[^\"]*\")\\b?)+")
158+
emptyEnv = EnvironmentOf(MapFetcher(nil))
159+
)
160+
161+
type lookup struct {
162+
key string
163+
env Environment
164+
}
165+
166+
func (l *lookup) Get() (interface{}, bool) { return l.env.Get(l.key) }
167+
func (l *lookup) Int(or int) int { return l.env.Int(l.key, or) }
168+
func (l *lookup) Bool(or bool) bool { return l.env.Bool(l.key, or) }
169+
159170
// parseTag returns the key, environment, and optional error assosciated with a
160171
// given tag. It will return the XOR of either the `git` or `os` tag. That is to
161172
// say, a field tagged with EITHER `git` OR `os` is valid, but pone tagged with
162173
// both is not.
163174
//
164175
// If neither field was found, then a nil environment will be returned.
165-
func (c *Configuration) parseTag(tag reflect.StructTag) (key string, env Environment, err error) {
166-
git, os := tag.Get("git"), tag.Get("os")
176+
func (c *Configuration) parseTag(tag reflect.StructTag) []*lookup {
177+
var lookups []*lookup
178+
179+
parts := tagRe.FindAllString(string(tag), -1)
180+
for _, part := range parts {
181+
sep := strings.SplitN(part, ":", 2)
182+
if len(sep) != 2 {
183+
panic(fmt.Sprintf("config: invalid struct tag %q", tag))
184+
}
167185

168-
if len(git) != 0 && len(os) != 0 {
169-
return "", nil, errors.New("lfs/config: ambiguous tags")
170-
}
186+
var env Environment
187+
switch strings.ToLower(sep[0]) {
188+
case "git":
189+
env = c.Git
190+
case "os":
191+
env = c.Os
192+
default:
193+
// ignore other struct tags, like `json:""`, etc.
194+
env = emptyEnv
195+
}
171196

172-
if len(git) != 0 {
173-
return git, c.Git, nil
174-
}
175-
if len(os) != 0 {
176-
return os, c.Os, nil
197+
uq, err := strconv.Unquote(sep[1])
198+
if err != nil {
199+
panic(err.Error())
200+
}
201+
202+
lookups = append(lookups, &lookup{
203+
key: uq,
204+
env: env,
205+
})
177206
}
178207

179-
return
208+
return lookups
180209
}
181210

182211
// BasicTransfersOnly returns whether to only allow "basic" HTTP transfers.

config/config_test.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,16 +228,49 @@ func TestUnmarshalOverridesNonZeroValuesWhenValuesPresent(t *testing.T) {
228228
assert.Equal(t, false, v.Bool)
229229
}
230230

231-
func TestUnmarshalDoesNotAllowBothOsAndGitTags(t *testing.T) {
231+
func TestUnmarshalAllowsBothOsAndGitTags(t *testing.T) {
232232
v := &struct {
233233
String string `git:"string" os:"STRING"`
234234
}{}
235235

236+
cfg := NewFrom(Values{
237+
Git: map[string][]string{"string": []string{"foo"}},
238+
Os: map[string][]string{"STRING": []string{"bar"}},
239+
})
240+
241+
err := cfg.Unmarshal(v)
242+
243+
assert.Nil(t, err)
244+
assert.Equal(t, "foo", v.String)
245+
}
246+
247+
func TestUnmarshalYieldsToDefaultIfBothEnvsMissing(t *testing.T) {
248+
v := &struct {
249+
String string `git:"string" os:"STRING"`
250+
}{"foo"}
251+
236252
cfg := NewFrom(Values{})
237253

238254
err := cfg.Unmarshal(v)
239255

240-
assert.Equal(t, "lfs/config: ambiguous tags", err.Error())
256+
assert.Nil(t, err)
257+
assert.Equal(t, "foo", v.String)
258+
}
259+
260+
func TestUnmarshalOverridesDefaultIfAnyEnvPresent(t *testing.T) {
261+
v := &struct {
262+
String string `git:"string" os:"STRING"`
263+
}{"foo"}
264+
265+
cfg := NewFrom(Values{
266+
Git: map[string][]string{"string": []string{"bar"}},
267+
Os: map[string][]string{"STRING": []string{"baz"}},
268+
})
269+
270+
err := cfg.Unmarshal(v)
271+
272+
assert.Nil(t, err)
273+
assert.Equal(t, "bar", v.String)
241274
}
242275

243276
func TestUnmarshalIgnoresUnknownEnvironments(t *testing.T) {

0 commit comments

Comments
 (0)