6
0
mirror of https://github.com/grdl/git-get.git synced 2026-02-11 07:09:16 +00:00

Refactor config provider using viper

This commit is contained in:
Grzegorz Dlugoszewski
2020-06-04 11:38:01 +02:00
parent 205eecc073
commit f2d8496136
7 changed files with 142 additions and 130 deletions

View File

@@ -6,6 +6,7 @@ import (
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
var ( var (
@@ -23,11 +24,17 @@ var cmd = &cobra.Command{
} }
var list bool var list bool
var reposRoot string
func init() { func init() {
pkg.LoadConf() // pkg.LoadConf()
cmd.PersistentFlags().BoolVarP(&list, "list", "l", false, "Lists all repositories inside git-get root") cmd.PersistentFlags().BoolVarP(&list, "list", "l", false, "Lists all repositories inside git-get root")
cmd.PersistentFlags().StringVarP(&reposRoot, "reposRoot", "r", "", "repos root")
viper.BindPFlag("reposRoot", cmd.PersistentFlags().Lookup("reposRoot"))
pkg.InitConfig()
} }
func Run(cmd *cobra.Command, args []string) { func Run(cmd *cobra.Command, args []string) {
@@ -45,7 +52,7 @@ func Run(cmd *cobra.Command, args []string) {
url, err := pkg.ParseURL(args[0]) url, err := pkg.ParseURL(args[0])
exitIfError(err) exitIfError(err)
_, err = pkg.CloneRepo(url, pkg.Cfg.ReposRoot(), false) _, err = pkg.CloneRepo(url, viper.GetString(pkg.KeyReposRoot), false)
exitIfError(err) exitIfError(err)
} }

1
go.mod
View File

@@ -9,5 +9,6 @@ require (
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.0.0 github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.4.0
github.com/xlab/treeprint v1.0.0 github.com/xlab/treeprint v1.0.0
) )

View File

@@ -1,98 +1,85 @@
package pkg package pkg
import ( import (
"os"
"path" "path"
"strings" "strings"
"github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/config"
plumbing "github.com/go-git/go-git/v5/plumbing/format/config" plumbing "github.com/go-git/go-git/v5/plumbing/format/config"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
) )
const ( const (
CfgSection = "gitget" GitgetPrefix = "gitget"
CfgReposRoot = "reposRoot" KeyReposRoot = "reposRoot"
CfgDefaultHost = "defaultHost" DefReposRoot = "repositories"
KeyDefaultHost = "defaultHost"
EnvReposRoot = "GITGET_REPOSROOT" DefDefaultHost = "github.com"
EnvDefaultHost = "GITGET_DEFAULTHOST"
DefaultReposRootSubpath = "repositories"
DefaultHost = "github.com"
) )
var Cfg *Conf // gitconfig provides methods for looking up configiration values inside .gitconfig file
type gitconfig struct {
// Conf provides methods for accessing configuration values *config.Config
// Values are looked up in the following order: env variable, gitignore file, default value
type Conf struct {
gitconfig *config.Config
} }
func LoadConf() { // InitConfig initializes viper config registry. Values are looked up in the following order: cli flag, env variable, gitconfig file, default value
// We don't care if loading gitconfig file throws an error // Viper doesn't support gitconfig file format so it can't find missing values there automatically. They need to be specified in setMissingValues func.
// When gitconfig is nil, getters will just return the default values func InitConfig() {
gitconfig, _ := config.LoadConfig(config.GlobalScope) viper.SetEnvPrefix(strings.ToUpper(GitgetPrefix))
viper.AutomaticEnv()
Cfg = &Conf{ cfg := loadGitconfig()
gitconfig: gitconfig, setMissingValues(cfg)
}
// loadGitconfig loads configuration from a gitconfig file.
// We ignore errors when gitconfig file can't be found, opened or parsed. In those cases viper will provide default config values.
func loadGitconfig() *gitconfig {
// TODO: load system scope
cfg, _ := config.LoadConfig(config.GlobalScope)
return &gitconfig{
Config: cfg,
} }
} }
func (c *Conf) ReposRoot() string { // setMissingValues checks if config values are provided by flags or env vars. If not, it tries loading them from gitconfig file.
defReposRoot := path.Join(home(), DefaultReposRootSubpath) // If that fails, the default values are used.
func setMissingValues(cfg *gitconfig) {
reposRoot := os.Getenv(EnvReposRoot) if !viper.IsSet(KeyReposRoot) {
if reposRoot != "" { viper.Set(KeyReposRoot, cfg.get(KeyReposRoot, path.Join(home(), DefReposRoot)))
return reposRoot
} }
if c == nil || c.gitconfig == nil { if !viper.IsSet(KeyDefaultHost) {
return defReposRoot viper.Set(KeyDefaultHost, cfg.get(KeyDefaultHost, DefDefaultHost))
}
}
// get looks up the value for a given key in gitconfig file.
// It returns the default value when gitconfig is missing, or it doesn't contain a gitget section,
// or if the section is empty, or if it doesn't contain a valid value for the key.
func (c *gitconfig) get(key string, def string) string {
if c == nil || c.Config == nil {
return def
} }
gitget := c.findConfigSection(CfgSection) gitget := c.findGitconfigSection(GitgetPrefix)
if gitget == nil { if gitget == nil {
return defReposRoot return def
} }
reposRoot = gitget.Option(CfgReposRoot) opt := gitget.Option(key)
reposRoot = strings.TrimSpace(reposRoot) if strings.TrimSpace(opt) == "" {
if reposRoot == "" { return def
return defReposRoot
} }
return reposRoot return opt
} }
func (c *Conf) DefaultHost() string { func (c *gitconfig) findGitconfigSection(name string) *plumbing.Section {
defaultHost := os.Getenv(EnvDefaultHost) for _, s := range c.Raw.Sections {
if defaultHost != "" { if strings.ToLower(s.Name) == strings.ToLower(name) {
return defaultHost
}
if c == nil || c.gitconfig == nil {
return DefaultHost
}
gitget := c.findConfigSection(CfgSection)
if gitget == nil {
return DefaultHost
}
defaultHost = gitget.Option(CfgDefaultHost)
defaultHost = strings.TrimSpace(defaultHost)
if defaultHost == "" {
return DefaultHost
}
return defaultHost
}
func (c *Conf) findConfigSection(name string) *plumbing.Section {
for _, s := range c.gitconfig.Raw.Sections {
if s.Name == name {
return s return s
} }
} }
@@ -100,9 +87,9 @@ func (c *Conf) findConfigSection(name string) *plumbing.Section {
return nil return nil
} }
// home returns path to a home directory or empty string if can't be found // home returns path to a home directory or empty string if can't be found.
// Using empty string means that in the unlikely situation where home dir can't be found // Using empty string means that in the unlikely situation where home dir can't be found
// and there's no reposRoot specified in the global config, the current dir will be used as repos root. // and there's no reposRoot specified by any of the config methods, the current dir will be used as repos root.
func home() string { func home() string {
home, err := homedir.Dir() home, err := homedir.Dir()
if err != nil { if err != nil {

View File

@@ -3,141 +3,153 @@ package pkg
import ( import (
"os" "os"
"path" "path"
"strings"
"testing" "testing"
"github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/config"
"github.com/spf13/viper"
) )
func newConfigWithFullGitconfig() *Conf { const (
gitconfig := config.NewConfig() EnvDefaultHost = "GITGET_DEFAULTHOST"
EnvReposRoot = "GITGET_REPOSROOT"
)
gitget := gitconfig.Raw.Section(CfgSection) func newConfigWithFullGitconfig() *gitconfig {
gitget.AddOption(CfgReposRoot, "file.root") cfg := config.NewConfig()
gitget.AddOption(CfgDefaultHost, "file.host")
return &Conf{ gitget := cfg.Raw.Section(GitgetPrefix)
gitconfig: gitconfig, gitget.AddOption(KeyReposRoot, "file.root")
gitget.AddOption(KeyDefaultHost, "file.host")
return &gitconfig{
Config: cfg,
} }
} }
func newConfigWithEmptyGitgetSection() *Conf { func newConfigWithEmptyGitgetSection() *gitconfig {
gitconfig := config.NewConfig() cfg := config.NewConfig()
_ = gitconfig.Raw.Section(CfgSection) _ = cfg.Raw.Section(GitgetPrefix)
return &Conf{ return &gitconfig{
gitconfig: gitconfig, Config: cfg,
} }
} }
func newConfigWithEmptyValues() *Conf { func newConfigWithEmptyValues() *gitconfig {
gitconfig := config.NewConfig() cfg := config.NewConfig()
gitget := gitconfig.Raw.Section(CfgSection) gitget := cfg.Raw.Section(GitgetPrefix)
gitget.AddOption(CfgReposRoot, "") gitget.AddOption(KeyReposRoot, "")
gitget.AddOption(CfgDefaultHost, " ") gitget.AddOption(KeyDefaultHost, " ")
return &Conf{ return &gitconfig{
gitconfig: gitconfig, Config: cfg,
} }
} }
func newConfigWithoutGitgetSection() *Conf { func newConfigWithoutGitgetSection() *gitconfig {
gitconfig := config.NewConfig() cfg := config.NewConfig()
return &Conf{ return &gitconfig{
gitconfig: gitconfig, Config: cfg,
} }
} }
func newConfigWithEmptyGitconfig() *Conf { func newConfigWithEmptyGitconfig() *gitconfig {
return &Conf{ return &gitconfig{
gitconfig: nil, Config: nil,
} }
} }
func newConfigWithEnvVars() *Conf { func newConfigWithEnvVars() *gitconfig {
_ = os.Setenv(EnvDefaultHost, "env.host") _ = os.Setenv(EnvDefaultHost, "env.host")
_ = os.Setenv(EnvReposRoot, "env.root") _ = os.Setenv(EnvReposRoot, "env.root")
return &Conf{ return &gitconfig{
gitconfig: nil, Config: nil,
} }
} }
func newConfigWithGitconfigAndEnvVars() *Conf { func newConfigWithGitconfigAndEnvVars() *gitconfig {
gitconfig := config.NewConfig() cfg := config.NewConfig()
gitget := gitconfig.Raw.Section(CfgSection) gitget := cfg.Raw.Section(GitgetPrefix)
gitget.AddOption(CfgReposRoot, "file.root") gitget.AddOption(KeyReposRoot, "file.root")
gitget.AddOption(CfgDefaultHost, "file.host") gitget.AddOption(KeyDefaultHost, "file.host")
_ = os.Setenv(EnvDefaultHost, "env.host") _ = os.Setenv(EnvDefaultHost, "env.host")
_ = os.Setenv(EnvReposRoot, "env.root") _ = os.Setenv(EnvReposRoot, "env.root")
return &Conf{ return &gitconfig{
gitconfig: gitconfig, Config: cfg,
} }
} }
func newConfigWithEmptySectionAndEnvVars() *Conf { func newConfigWithEmptySectionAndEnvVars() *gitconfig {
gitconfig := config.NewConfig() cfg := config.NewConfig()
_ = gitconfig.Raw.Section(CfgSection) _ = cfg.Raw.Section(GitgetPrefix)
_ = os.Setenv(EnvDefaultHost, "env.host") _ = os.Setenv(EnvDefaultHost, "env.host")
_ = os.Setenv(EnvReposRoot, "env.root") _ = os.Setenv(EnvReposRoot, "env.root")
return &Conf{ return &gitconfig{
gitconfig: gitconfig, Config: cfg,
} }
} }
func newConfigWithMixed() *Conf { func newConfigWithMixed() *gitconfig {
gitconfig := config.NewConfig() cfg := config.NewConfig()
gitget := gitconfig.Raw.Section(CfgSection) gitget := cfg.Raw.Section(GitgetPrefix)
gitget.AddOption(CfgReposRoot, "file.root") gitget.AddOption(KeyReposRoot, "file.root")
gitget.AddOption(CfgDefaultHost, "file.host") gitget.AddOption(KeyDefaultHost, "file.host")
_ = os.Setenv(EnvDefaultHost, "env.host") _ = os.Setenv(EnvDefaultHost, "env.host")
return &Conf{ return &gitconfig{
gitconfig: gitconfig, Config: cfg,
} }
} }
func TestConfig(t *testing.T) { func TestConfig(t *testing.T) {
defReposRoot := path.Join(home(), DefaultReposRootSubpath) defReposRoot := path.Join(home(), DefReposRoot)
var tests = []struct { var tests = []struct {
makeConfig func() *Conf makeConfig func() *gitconfig
wantReposRoot string wantReposRoot string
wantDefaultHost string wantDefaultHost string
}{ }{
{newConfigWithFullGitconfig, "file.root", "file.host"}, {newConfigWithFullGitconfig, "file.root", "file.host"},
{newConfigWithoutGitgetSection, defReposRoot, DefaultHost}, {newConfigWithoutGitgetSection, defReposRoot, DefDefaultHost},
{newConfigWithEmptyGitconfig, defReposRoot, DefaultHost}, {newConfigWithEmptyGitconfig, defReposRoot, DefDefaultHost},
{newConfigWithEnvVars, "env.root", "env.host"}, {newConfigWithEnvVars, "env.root", "env.host"},
{newConfigWithGitconfigAndEnvVars, "env.root", "env.host"}, {newConfigWithGitconfigAndEnvVars, "env.root", "env.host"},
{newConfigWithEmptySectionAndEnvVars, "env.root", "env.host"}, {newConfigWithEmptySectionAndEnvVars, "env.root", "env.host"},
{newConfigWithEmptyGitgetSection, defReposRoot, DefaultHost}, {newConfigWithEmptyGitgetSection, defReposRoot, DefDefaultHost},
{newConfigWithEmptyValues, defReposRoot, DefaultHost}, {newConfigWithEmptyValues, defReposRoot, DefDefaultHost},
{newConfigWithMixed, "file.root", "env.host"}, {newConfigWithMixed, "file.root", "env.host"},
} }
for _, test := range tests { for _, test := range tests {
viper.SetEnvPrefix(strings.ToUpper(GitgetPrefix))
viper.AutomaticEnv()
cfg := test.makeConfig() cfg := test.makeConfig()
setMissingValues(cfg)
if cfg.ReposRoot() != test.wantReposRoot { if viper.GetString(KeyDefaultHost) != test.wantDefaultHost {
t.Errorf("Wrong reposRoot value, got: %+v; want: %+v", cfg.ReposRoot(), test.wantReposRoot) t.Errorf("Wrong %s value, got: %s; want: %s", KeyDefaultHost, viper.GetString(KeyDefaultHost), test.wantDefaultHost)
} }
if cfg.DefaultHost() != test.wantDefaultHost { if viper.GetString(KeyReposRoot) != test.wantReposRoot {
t.Errorf("Wrong defaultHost value, got: %+v; want: %+v", cfg.DefaultHost(), test.wantDefaultHost) t.Errorf("Wrong %s value, got: %s; want: %s", KeyReposRoot, viper.GetString(KeyReposRoot), test.wantReposRoot)
} }
// Unset env variables after each test so they don't affect other tests // Unset env variables and reset viper registry after each test
viper.Reset()
err := os.Unsetenv(EnvDefaultHost) err := os.Unsetenv(EnvDefaultHost)
checkFatal(t, err) checkFatal(t, err)
err = os.Unsetenv(EnvReposRoot) err = os.Unsetenv(EnvReposRoot)

View File

@@ -8,6 +8,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/spf13/viper"
"github.com/xlab/treeprint" "github.com/xlab/treeprint"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
@@ -24,7 +25,7 @@ var repos []string
func FindRepos() ([]string, error) { func FindRepos() ([]string, error) {
repos = []string{} repos = []string{}
root := Cfg.ReposRoot() root := viper.GetString(KeyReposRoot)
if _, err := os.Stat(root); err != nil { if _, err := os.Stat(root); err != nil {
return nil, fmt.Errorf("Repos root %s does not exist or can't be accessed", root) return nil, fmt.Errorf("Repos root %s does not exist or can't be accessed", root)
@@ -105,7 +106,7 @@ func OpenAll(paths []string) ([]*Repo, error) {
} }
func PrintRepos(repos []*Repo) { func PrintRepos(repos []*Repo) {
root := Cfg.ReposRoot() root := viper.GetString(KeyReposRoot)
seg := make([][]string, len(repos)) seg := make([][]string, len(repos))

View File

@@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/viper"
) )
// scpSyntax matches the SCP-like addresses used by the ssh protocol (eg, [user@]host.xz:path/to/repo.git/). // scpSyntax matches the SCP-like addresses used by the ssh protocol (eg, [user@]host.xz:path/to/repo.git/).
@@ -45,7 +46,7 @@ func ParseURL(rawURL string) (url *urlpkg.URL, err error) {
// Default to configured defaultHost when host is empty // Default to configured defaultHost when host is empty
if url.Host == "" { if url.Host == "" {
url.Host = Cfg.DefaultHost() url.Host = viper.GetString(KeyDefaultHost)
} }
// Default to https when scheme is empty // Default to https when scheme is empty

View File

@@ -48,6 +48,9 @@ func TestURLParse(t *testing.T) {
{"file://local/grdl/git-get", "local/grdl/git-get"}, {"file://local/grdl/git-get", "local/grdl/git-get"},
} }
// We need to init config first so the default values are correctly loaded
InitConfig()
for _, test := range tests { for _, test := range tests {
url, err := ParseURL(test.in) url, err := ParseURL(test.in)
if err != nil { if err != nil {