package repository import ( "fmt" "strconv" "strings" "time" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" ) var _ Config = &goGitConfig{} type goGitConfig struct { ConfigRead ConfigWrite } func newGoGitLocalConfig(repo *gogit.Repository) *goGitConfig { return &goGitConfig{ ConfigRead: &goGitConfigReader{getConfig: repo.Config}, ConfigWrite: &goGitConfigWriter{repo: repo}, } } func newGoGitGlobalConfig() *goGitConfig { // TODO: replace that with go-git native implementation once it's supported // see: https://github.com/go-git/go-git // see: https://github.com/src-d/go-git/issues/760 return &goGitConfig{ ConfigRead: &goGitConfigReader{getConfig: func() (*config.Config, error) { return config.LoadConfig(config.GlobalScope) }}, ConfigWrite: &configPanicWriter{}, } } var _ ConfigRead = &goGitConfigReader{} type goGitConfigReader struct { getConfig func() (*config.Config, error) } func (cr *goGitConfigReader) ReadAll(keyPrefix string) (map[string]string, error) { cfg, err := cr.getConfig() if err != nil { return nil, err } split := strings.Split(keyPrefix, ".") result := make(map[string]string) switch { case keyPrefix == "": for _, section := range cfg.Raw.Sections { for _, option := range section.Options { result[fmt.Sprintf("%s.%s", section.Name, option.Key)] = option.Value } for _, subsection := range section.Subsections { for _, option := range subsection.Options { result[fmt.Sprintf("%s.%s.%s", section.Name, subsection.Name, option.Key)] = option.Value } } } case len(split) == 1: if !cfg.Raw.HasSection(split[0]) { return nil, nil } section := cfg.Raw.Section(split[0]) for _, option := range section.Options { result[fmt.Sprintf("%s.%s", section.Name, option.Key)] = option.Value } for _, subsection := range section.Subsections { for _, option := range subsection.Options { result[fmt.Sprintf("%s.%s.%s", section.Name, subsection.Name, option.Key)] = option.Value } } default: if !cfg.Raw.HasSection(split[0]) { return nil, nil } section := cfg.Raw.Section(split[0]) rest := strings.Join(split[1:], ".") rest = strings.TrimSuffix(rest, ".") for _, subsection := range section.Subsections { if strings.HasPrefix(subsection.Name, rest) { for _, option := range subsection.Options { result[fmt.Sprintf("%s.%s.%s", section.Name, subsection.Name, option.Key)] = option.Value } } } } return result, nil } func (cr *goGitConfigReader) ReadBool(key string) (bool, error) { val, err := cr.ReadString(key) if err != nil { return false, err } return strconv.ParseBool(val) } func (cr *goGitConfigReader) ReadString(key string) (string, error) { cfg, err := cr.getConfig() if err != nil { return "", err } split := strings.Split(key, ".") if len(split) <= 1 { return "", fmt.Errorf("invalid key") } sectionName := split[0] if !cfg.Raw.HasSection(sectionName) { return "", fmt.Errorf("%w: missing key %s", ErrNoConfigEntry, key) } section := cfg.Raw.Section(sectionName) switch { case len(split) == 2: optionName := split[1] if !section.HasOption(optionName) { return "", fmt.Errorf("%w: missing key %s", ErrNoConfigEntry, key) } if len(section.OptionAll(optionName)) > 1 { return "", ErrMultipleConfigEntry } return section.Option(optionName), nil default: subsectionName := strings.Join(split[1:len(split)-1], ".") optionName := split[len(split)-1] if !section.HasSubsection(subsectionName) { return "", fmt.Errorf("%w: missing key %s", ErrNoConfigEntry, key) } subsection := section.Subsection(subsectionName) if !subsection.HasOption(optionName) { return "", fmt.Errorf("%w: missing key %s", ErrNoConfigEntry, key) } if len(subsection.OptionAll(optionName)) > 1 { return "", ErrMultipleConfigEntry } return subsection.Option(optionName), nil } } func (cr *goGitConfigReader) ReadTimestamp(key string) (time.Time, error) { value, err := cr.ReadString(key) if err != nil { return time.Time{}, err } return ParseTimestamp(value) } var _ ConfigWrite = &goGitConfigWriter{} // Only works for the local config as go-git only support that type goGitConfigWriter struct { repo *gogit.Repository } func (cw *goGitConfigWriter) StoreString(key, value string) error { cfg, err := cw.repo.Config() if err != nil { return err } split := strings.Split(key, ".") switch { case len(split) <= 1: return fmt.Errorf("invalid key") case len(split) == 2: cfg.Raw.Section(split[0]).SetOption(split[1], value) default: section := split[0] subsection := strings.Join(split[1:len(split)-1], ".") option := split[len(split)-1] cfg.Raw.Section(section).Subsection(subsection).SetOption(option, value) } return cw.repo.SetConfig(cfg) } func (cw *goGitConfigWriter) StoreTimestamp(key string, value time.Time) error { return cw.StoreString(key, strconv.Itoa(int(value.Unix()))) } func (cw *goGitConfigWriter) StoreBool(key string, value bool) error { return cw.StoreString(key, strconv.FormatBool(value)) } func (cw *goGitConfigWriter) RemoveAll(keyPrefix string) error { cfg, err := cw.repo.Config() if err != nil { return err } split := strings.Split(keyPrefix, ".") switch { case keyPrefix == "": cfg.Raw.Sections = nil // warning: this does not actually remove everything as go-git config hold // some entries in multiple places (cfg.User ...) case len(split) == 1: if cfg.Raw.HasSection(split[0]) { cfg.Raw.RemoveSection(split[0]) } else { return fmt.Errorf("invalid key prefix") } default: if !cfg.Raw.HasSection(split[0]) { return fmt.Errorf("invalid key prefix") } section := cfg.Raw.Section(split[0]) rest := strings.Join(split[1:], ".") ok := false if section.HasSubsection(rest) { section.RemoveSubsection(rest) ok = true } if section.HasOption(rest) { section.RemoveOption(rest) ok = true } if !ok { return fmt.Errorf("invalid key prefix") } } return cw.repo.SetConfig(cfg) }