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