1package repository
2
3import (
4 "fmt"
5 "strconv"
6 "strings"
7 "time"
8
9 gogit "github.com/go-git/go-git/v5"
10 "github.com/go-git/go-git/v5/config"
11)
12
13var _ Config = &goGitConfig{}
14
15type goGitConfig struct {
16 ConfigRead
17 ConfigWrite
18}
19
20func newGoGitLocalConfig(repo *gogit.Repository) *goGitConfig {
21 return &goGitConfig{
22 ConfigRead: &goGitConfigReader{getConfig: repo.Config},
23 ConfigWrite: &goGitConfigWriter{repo: repo},
24 }
25}
26
27func newGoGitGlobalConfig() *goGitConfig {
28 // TODO: replace that with go-git native implementation once it's supported
29 // see: https://github.com/go-git/go-git
30 // see: https://github.com/src-d/go-git/issues/760
31
32 return &goGitConfig{
33 ConfigRead: &goGitConfigReader{getConfig: func() (*config.Config, error) {
34 return config.LoadConfig(config.GlobalScope)
35 }},
36 ConfigWrite: &configPanicWriter{},
37 }
38}
39
40var _ ConfigRead = &goGitConfigReader{}
41
42type goGitConfigReader struct {
43 getConfig func() (*config.Config, error)
44}
45
46func (cr *goGitConfigReader) ReadAll(keyPrefix string) (map[string]string, error) {
47 cfg, err := cr.getConfig()
48 if err != nil {
49 return nil, err
50 }
51
52 split := strings.Split(keyPrefix, ".")
53 result := make(map[string]string)
54
55 switch {
56 case keyPrefix == "":
57 for _, section := range cfg.Raw.Sections {
58 for _, option := range section.Options {
59 result[fmt.Sprintf("%s.%s", section.Name, option.Key)] = option.Value
60 }
61 for _, subsection := range section.Subsections {
62 for _, option := range subsection.Options {
63 result[fmt.Sprintf("%s.%s.%s", section.Name, subsection.Name, option.Key)] = option.Value
64 }
65 }
66 }
67 case len(split) == 1:
68 if !cfg.Raw.HasSection(split[0]) {
69 return nil, nil
70 }
71 section := cfg.Raw.Section(split[0])
72 for _, option := range section.Options {
73 result[fmt.Sprintf("%s.%s", section.Name, option.Key)] = option.Value
74 }
75 for _, subsection := range section.Subsections {
76 for _, option := range subsection.Options {
77 result[fmt.Sprintf("%s.%s.%s", section.Name, subsection.Name, option.Key)] = option.Value
78 }
79 }
80 default:
81 if !cfg.Raw.HasSection(split[0]) {
82 return nil, nil
83 }
84 section := cfg.Raw.Section(split[0])
85 rest := strings.Join(split[1:], ".")
86 rest = strings.TrimSuffix(rest, ".")
87 for _, subsection := range section.Subsections {
88 if strings.HasPrefix(subsection.Name, rest) {
89 for _, option := range subsection.Options {
90 result[fmt.Sprintf("%s.%s.%s", section.Name, subsection.Name, option.Key)] = option.Value
91 }
92 }
93 }
94 }
95
96 return result, nil
97}
98
99func (cr *goGitConfigReader) ReadBool(key string) (bool, error) {
100 val, err := cr.ReadString(key)
101 if err != nil {
102 return false, err
103 }
104
105 return strconv.ParseBool(val)
106}
107
108func (cr *goGitConfigReader) ReadString(key string) (string, error) {
109 cfg, err := cr.getConfig()
110 if err != nil {
111 return "", err
112 }
113
114 split := strings.Split(key, ".")
115
116 if len(split) <= 1 {
117 return "", fmt.Errorf("invalid key")
118 }
119
120 sectionName := split[0]
121 if !cfg.Raw.HasSection(sectionName) {
122 return "", fmt.Errorf("%w: missing key %s", ErrNoConfigEntry, key)
123 }
124 section := cfg.Raw.Section(sectionName)
125
126 switch {
127 case len(split) == 2:
128 optionName := split[1]
129 if !section.HasOption(optionName) {
130 return "", fmt.Errorf("%w: missing key %s", ErrNoConfigEntry, key)
131 }
132 if len(section.OptionAll(optionName)) > 1 {
133 return "", ErrMultipleConfigEntry
134 }
135 return section.Option(optionName), nil
136 default:
137 subsectionName := strings.Join(split[1:len(split)-1], ".")
138 optionName := split[len(split)-1]
139 if !section.HasSubsection(subsectionName) {
140 return "", fmt.Errorf("%w: missing key %s", ErrNoConfigEntry, key)
141 }
142 subsection := section.Subsection(subsectionName)
143 if !subsection.HasOption(optionName) {
144 return "", fmt.Errorf("%w: missing key %s", ErrNoConfigEntry, key)
145 }
146 if len(subsection.OptionAll(optionName)) > 1 {
147 return "", ErrMultipleConfigEntry
148 }
149 return subsection.Option(optionName), nil
150 }
151}
152
153func (cr *goGitConfigReader) ReadTimestamp(key string) (time.Time, error) {
154 value, err := cr.ReadString(key)
155 if err != nil {
156 return time.Time{}, err
157 }
158 return ParseTimestamp(value)
159}
160
161var _ ConfigWrite = &goGitConfigWriter{}
162
163// Only works for the local config as go-git only support that
164type goGitConfigWriter struct {
165 repo *gogit.Repository
166}
167
168func (cw *goGitConfigWriter) StoreString(key, value string) error {
169 cfg, err := cw.repo.Config()
170 if err != nil {
171 return err
172 }
173
174 split := strings.Split(key, ".")
175
176 switch {
177 case len(split) <= 1:
178 return fmt.Errorf("invalid key")
179 case len(split) == 2:
180 cfg.Raw.Section(split[0]).SetOption(split[1], value)
181 default:
182 section := split[0]
183 subsection := strings.Join(split[1:len(split)-1], ".")
184 option := split[len(split)-1]
185 cfg.Raw.Section(section).Subsection(subsection).SetOption(option, value)
186 }
187
188 return cw.repo.SetConfig(cfg)
189}
190
191func (cw *goGitConfigWriter) StoreTimestamp(key string, value time.Time) error {
192 return cw.StoreString(key, strconv.Itoa(int(value.Unix())))
193}
194
195func (cw *goGitConfigWriter) StoreBool(key string, value bool) error {
196 return cw.StoreString(key, strconv.FormatBool(value))
197}
198
199func (cw *goGitConfigWriter) RemoveAll(keyPrefix string) error {
200 cfg, err := cw.repo.Config()
201 if err != nil {
202 return err
203 }
204
205 split := strings.Split(keyPrefix, ".")
206
207 switch {
208 case keyPrefix == "":
209 cfg.Raw.Sections = nil
210 // warning: this does not actually remove everything as go-git config hold
211 // some entries in multiple places (cfg.User ...)
212 case len(split) == 1:
213 if cfg.Raw.HasSection(split[0]) {
214 cfg.Raw.RemoveSection(split[0])
215 } else {
216 return fmt.Errorf("invalid key prefix")
217 }
218 default:
219 if !cfg.Raw.HasSection(split[0]) {
220 return fmt.Errorf("invalid key prefix")
221 }
222 section := cfg.Raw.Section(split[0])
223 rest := strings.Join(split[1:], ".")
224
225 ok := false
226 if section.HasSubsection(rest) {
227 section.RemoveSubsection(rest)
228 ok = true
229 }
230 if section.HasOption(rest) {
231 section.RemoveOption(rest)
232 ok = true
233 }
234 if !ok {
235 return fmt.Errorf("invalid key prefix")
236 }
237 }
238
239 return cw.repo.SetConfig(cfg)
240}