Skip to content

Commit c08d4f0

Browse files
committed
Write all per-host config entries to hosts.yml
Read from and write to the `hosts.yml` file every time `config.yml` is accessed. Everything that before went under the `hosts:` map now belongs to `hosts.yml`.
1 parent bad138e commit c08d4f0

File tree

6 files changed

+187
-125
lines changed

6 files changed

+187
-125
lines changed

command/config_test.go

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,31 @@ func TestConfigGet_not_found(t *testing.T) {
4949
func TestConfigSet(t *testing.T) {
5050
initBlankContext("", "OWNER/REPO", "master")
5151

52-
buf := bytes.NewBufferString("")
53-
defer config.StubWriteConfig(buf, nil)()
52+
mainBuf := bytes.Buffer{}
53+
hostsBuf := bytes.Buffer{}
54+
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
55+
5456
output, err := RunCommand("config set editor ed")
5557
if err != nil {
5658
t.Fatalf("error running command `config set editor ed`: %v", err)
5759
}
5860

59-
eq(t, output.String(), "")
61+
if len(output.String()) > 0 {
62+
t.Errorf("expected output to be blank: %q", output.String())
63+
}
6064

61-
expected := `hosts:
62-
github.com:
63-
user: OWNER
64-
oauth_token: 1234567890
65-
editor: ed
65+
expectedMain := "editor: ed\n"
66+
expectedHosts := `github.com:
67+
user: OWNER
68+
oauth_token: "1234567890"
6669
`
6770

68-
eq(t, buf.String(), expected)
71+
if mainBuf.String() != expectedMain {
72+
t.Errorf("expected config.yml to be %q, got %q", expectedMain, mainBuf.String())
73+
}
74+
if hostsBuf.String() != expectedHosts {
75+
t.Errorf("expected hosts.yml to be %q, got %q", expectedHosts, hostsBuf.String())
76+
}
6977
}
7078

7179
func TestConfigSet_update(t *testing.T) {
@@ -79,23 +87,31 @@ editor: ed
7987

8088
initBlankContext(cfg, "OWNER/REPO", "master")
8189

82-
buf := bytes.NewBufferString("")
83-
defer config.StubWriteConfig(buf, nil)()
90+
mainBuf := bytes.Buffer{}
91+
hostsBuf := bytes.Buffer{}
92+
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
8493

8594
output, err := RunCommand("config set editor vim")
8695
if err != nil {
8796
t.Fatalf("error running command `config get editor`: %v", err)
8897
}
8998

90-
eq(t, output.String(), "")
99+
if len(output.String()) > 0 {
100+
t.Errorf("expected output to be blank: %q", output.String())
101+
}
91102

92-
expected := `hosts:
93-
github.com:
94-
user: OWNER
95-
oauth_token: MUSTBEHIGHCUZIMATOKEN
96-
editor: vim
103+
expectedMain := "editor: vim\n"
104+
expectedHosts := `github.com:
105+
user: OWNER
106+
oauth_token: MUSTBEHIGHCUZIMATOKEN
97107
`
98-
eq(t, buf.String(), expected)
108+
109+
if mainBuf.String() != expectedMain {
110+
t.Errorf("expected config.yml to be %q, got %q", expectedMain, mainBuf.String())
111+
}
112+
if hostsBuf.String() != expectedHosts {
113+
t.Errorf("expected hosts.yml to be %q, got %q", expectedHosts, hostsBuf.String())
114+
}
99115
}
100116

101117
func TestConfigGetHost(t *testing.T) {
@@ -141,23 +157,32 @@ git_protocol: ssh
141157
func TestConfigSetHost(t *testing.T) {
142158
initBlankContext("", "OWNER/REPO", "master")
143159

144-
buf := bytes.NewBufferString("")
145-
defer config.StubWriteConfig(buf, nil)()
160+
mainBuf := bytes.Buffer{}
161+
hostsBuf := bytes.Buffer{}
162+
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
163+
146164
output, err := RunCommand("config set -hgithub.com git_protocol ssh")
147165
if err != nil {
148166
t.Fatalf("error running command `config set editor ed`: %v", err)
149167
}
150168

151-
eq(t, output.String(), "")
169+
if len(output.String()) > 0 {
170+
t.Errorf("expected output to be blank: %q", output.String())
171+
}
152172

153-
expected := `hosts:
154-
github.com:
155-
user: OWNER
156-
oauth_token: 1234567890
157-
git_protocol: ssh
173+
expectedMain := ""
174+
expectedHosts := `github.com:
175+
user: OWNER
176+
oauth_token: "1234567890"
177+
git_protocol: ssh
158178
`
159179

160-
eq(t, buf.String(), expected)
180+
if mainBuf.String() != expectedMain {
181+
t.Errorf("expected config.yml to be %q, got %q", expectedMain, mainBuf.String())
182+
}
183+
if hostsBuf.String() != expectedHosts {
184+
t.Errorf("expected hosts.yml to be %q, got %q", expectedHosts, hostsBuf.String())
185+
}
161186
}
162187

163188
func TestConfigSetHost_update(t *testing.T) {
@@ -171,21 +196,30 @@ hosts:
171196

172197
initBlankContext(cfg, "OWNER/REPO", "master")
173198

174-
buf := bytes.NewBufferString("")
175-
defer config.StubWriteConfig(buf, nil)()
199+
mainBuf := bytes.Buffer{}
200+
hostsBuf := bytes.Buffer{}
201+
defer config.StubWriteConfig(&mainBuf, &hostsBuf)()
176202

177203
output, err := RunCommand("config set -hgithub.com git_protocol https")
178204
if err != nil {
179205
t.Fatalf("error running command `config get editor`: %v", err)
180206
}
181207

182-
eq(t, output.String(), "")
208+
if len(output.String()) > 0 {
209+
t.Errorf("expected output to be blank: %q", output.String())
210+
}
183211

184-
expected := `hosts:
185-
github.com:
186-
git_protocol: https
187-
user: OWNER
188-
oauth_token: MUSTBEHIGHCUZIMATOKEN
212+
expectedMain := ""
213+
expectedHosts := `github.com:
214+
git_protocol: https
215+
user: OWNER
216+
oauth_token: MUSTBEHIGHCUZIMATOKEN
189217
`
190-
eq(t, buf.String(), expected)
218+
219+
if mainBuf.String() != expectedMain {
220+
t.Errorf("expected config.yml to be %q, got %q", expectedMain, mainBuf.String())
221+
}
222+
if hostsBuf.String() != expectedHosts {
223+
t.Errorf("expected hosts.yml to be %q, got %q", expectedHosts, hostsBuf.String())
224+
}
191225
}

command/testing.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
const defaultTestConfig = `hosts:
2020
github.com:
2121
user: OWNER
22-
oauth_token: 1234567890
22+
oauth_token: "1234567890"
2323
`
2424

2525
type askStubber struct {

internal/config/config_file.go

Lines changed: 40 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"io/ioutil"
88
"os"
99
"path"
10-
"path/filepath"
1110

1211
"github.com/mitchellh/go-homedir"
1312
"gopkg.in/yaml.v3"
@@ -22,6 +21,10 @@ func ConfigFile() string {
2221
return path.Join(ConfigDir(), "config.yml")
2322
}
2423

24+
func hostsConfigFile(fn string) string {
25+
return path.Join(path.Dir(fn), "hosts.yml")
26+
}
27+
2528
func ParseDefaultConfig() (Config, error) {
2629
return ParseConfig(ConfigFile())
2730
}
@@ -42,7 +45,7 @@ var ReadConfigFile = func(fn string) ([]byte, error) {
4245
}
4346

4447
var WriteConfigFile = func(fn string, data []byte) error {
45-
err := os.MkdirAll(filepath.Dir(fn), 0771)
48+
err := os.MkdirAll(path.Dir(fn), 0771)
4649
if err != nil {
4750
return err
4851
}
@@ -91,73 +94,44 @@ func parseConfigFile(fn string) ([]byte, *yaml.Node, error) {
9194

9295
func isLegacy(root *yaml.Node) bool {
9396
for _, v := range root.Content[0].Content {
94-
if v.Value == "hosts" {
95-
return false
97+
if v.Value == "github.com" {
98+
return true
9699
}
97100
}
98101

99-
return true
102+
return false
100103
}
101104

102-
func migrateConfig(fn string, root *yaml.Node) error {
103-
type ConfigEntry map[string]string
104-
type ConfigHash map[string]ConfigEntry
105-
106-
newConfigData := map[string]ConfigHash{}
107-
newConfigData["hosts"] = ConfigHash{}
105+
func migrateConfig(fn string) error {
106+
b, err := ReadConfigFile(fn)
107+
if err != nil {
108+
return err
109+
}
108110

109-
topLevelKeys := root.Content[0].Content
111+
var hosts map[string][]map[string]string
112+
err = yaml.Unmarshal(b, &hosts)
113+
if err != nil {
114+
return fmt.Errorf("error decoding legacy format: %w", err)
115+
}
110116

111-
for i, x := range topLevelKeys {
112-
if x.Value == "" {
117+
cfg := NewBlankConfig()
118+
for hostname, entries := range hosts {
119+
if len(entries) < 1 {
113120
continue
114121
}
115-
if i+1 == len(topLevelKeys) {
116-
break
117-
}
118-
hostname := x.Value
119-
newConfigData["hosts"][hostname] = ConfigEntry{}
120-
121-
authKeys := topLevelKeys[i+1].Content[0].Content
122-
123-
for j, y := range authKeys {
124-
if j+1 == len(authKeys) {
125-
break
126-
}
127-
switch y.Value {
128-
case "user":
129-
newConfigData["hosts"][hostname]["user"] = authKeys[j+1].Value
130-
case "oauth_token":
131-
newConfigData["hosts"][hostname]["oauth_token"] = authKeys[j+1].Value
122+
for key, value := range entries[0] {
123+
if err := cfg.Set(hostname, key, value); err != nil {
124+
return err
132125
}
133126
}
134127
}
135128

136-
if _, ok := newConfigData["hosts"][defaultHostname]; !ok {
137-
return errors.New("could not find default host configuration")
138-
}
139-
140-
defaultHostConfig := newConfigData["hosts"][defaultHostname]
141-
142-
if _, ok := defaultHostConfig["user"]; !ok {
143-
return errors.New("default host configuration missing user")
144-
}
145-
146-
if _, ok := defaultHostConfig["oauth_token"]; !ok {
147-
return errors.New("default host configuration missing oauth_token")
148-
}
149-
150-
newConfig, err := yaml.Marshal(newConfigData)
151-
if err != nil {
152-
return err
153-
}
154-
155129
err = BackupConfigFile(fn)
156130
if err != nil {
157131
return fmt.Errorf("failed to back up existing config: %w", err)
158132
}
159133

160-
return WriteConfigFile(fn, newConfig)
134+
return cfg.Write()
161135
}
162136

163137
func ParseConfig(fn string) (Config, error) {
@@ -167,15 +141,28 @@ func ParseConfig(fn string) (Config, error) {
167141
}
168142

169143
if isLegacy(root) {
170-
err = migrateConfig(fn, root)
144+
err = migrateConfig(fn)
171145
if err != nil {
172-
return nil, err
146+
return nil, fmt.Errorf("error migrating legacy config: %w", err)
173147
}
174148

175149
_, root, err = parseConfigFile(fn)
176150
if err != nil {
177151
return nil, fmt.Errorf("failed to reparse migrated config: %w", err)
178152
}
153+
} else {
154+
if _, hostsRoot, err := parseConfigFile(hostsConfigFile(fn)); err == nil {
155+
if len(hostsRoot.Content[0].Content) > 0 {
156+
newContent := []*yaml.Node{
157+
{Value: "hosts"},
158+
hostsRoot.Content[0],
159+
}
160+
restContent := root.Content[0].Content
161+
root.Content[0].Content = append(newContent, restContent...)
162+
}
163+
} else if !errors.Is(err, os.ErrNotExist) {
164+
return nil, err
165+
}
179166
}
180167

181168
return NewConfig(root), nil

internal/config/config_file_test.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ hosts:
5454
eq(t, token, "OTOKEN")
5555
}
5656

57+
func Test_parseConfig_hostsFile(t *testing.T) {
58+
defer StubConfig("", `---
59+
github.com:
60+
user: monalisa
61+
oauth_token: OTOKEN
62+
`)()
63+
config, err := ParseConfig("config.yml")
64+
eq(t, err, nil)
65+
user, err := config.Get("github.com", "user")
66+
eq(t, err, nil)
67+
eq(t, user, "monalisa")
68+
token, err := config.Get("github.com", "oauth_token")
69+
eq(t, err, nil)
70+
eq(t, token, "OTOKEN")
71+
}
72+
5773
func Test_parseConfig_notFound(t *testing.T) {
5874
defer StubConfig(`---
5975
hosts:
@@ -67,33 +83,29 @@ hosts:
6783
eq(t, err, &NotFoundError{errors.New(`could not find config entry for "github.com"`)})
6884
}
6985

70-
func Test_migrateConfig(t *testing.T) {
71-
oldStyle := `---
86+
func Test_ParseConfig_migrateConfig(t *testing.T) {
87+
defer StubConfig(`---
7288
github.com:
7389
- user: keiyuri
74-
oauth_token: 123456`
75-
76-
var root yaml.Node
77-
err := yaml.Unmarshal([]byte(oldStyle), &root)
78-
if err != nil {
79-
panic("failed to parse test yaml")
80-
}
81-
82-
buf := bytes.NewBufferString("")
83-
defer StubWriteConfig(buf, nil)()
90+
oauth_token: 123456
91+
`, "")()
8492

93+
mainBuf := bytes.Buffer{}
94+
hostsBuf := bytes.Buffer{}
95+
defer StubWriteConfig(&mainBuf, &hostsBuf)()
8596
defer StubBackupConfig()()
8697

87-
err = migrateConfig("config.yml", &root)
98+
_, err := ParseConfig("config.yml")
8899
eq(t, err, nil)
89100

90-
expected := `hosts:
91-
github.com:
92-
oauth_token: "123456"
93-
user: keiyuri
101+
expectedMain := ""
102+
expectedHosts := `github.com:
103+
user: keiyuri
104+
oauth_token: "123456"
94105
`
95106

96-
eq(t, buf.String(), expected)
107+
eq(t, mainBuf.String(), expectedMain)
108+
eq(t, hostsBuf.String(), expectedHosts)
97109
}
98110

99111
func Test_parseConfigFile(t *testing.T) {

0 commit comments

Comments
 (0)