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 "", newErrNoConfigEntry(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 "", newErrNoConfigEntry(key)
131		}
132		if len(section.OptionAll(optionName)) > 1 {
133			return "", newErrMultipleConfigEntry(key)
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 "", newErrNoConfigEntry(key)
141		}
142		subsection := section.Subsection(subsectionName)
143		if !subsection.HasOption(optionName) {
144			return "", newErrNoConfigEntry(key)
145		}
146		if len(subsection.OptionAll(optionName)) > 1 {
147			return "", newErrMultipleConfigEntry(key)
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		}
216	case cfg.Raw.HasSection(split[0]):
217		section := cfg.Raw.Section(split[0])
218		rest := strings.Join(split[1:], ".")
219
220		if section.HasSubsection(rest) {
221			section.RemoveSubsection(rest)
222		}
223		if section.HasOption(rest) {
224			section.RemoveOption(rest)
225		}
226	}
227
228	return cw.repo.SetConfig(cfg)
229}