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