diff --git a/cmd/git-get/main.go b/cmd/git-get/main.go index 521b303..ea32324 100644 --- a/cmd/git-get/main.go +++ b/cmd/git-get/main.go @@ -8,8 +8,6 @@ import ( "github.com/spf13/cobra" ) -const ReposRoot = "/tmp/gitget" - var cmd = &cobra.Command{ Use: "git-get ", Short: "git get", @@ -19,14 +17,14 @@ var cmd = &cobra.Command{ } func init() { - //cmd.PersistentFlags(). + pkg.LoadConf() } func Run(cmd *cobra.Command, args []string) { url, err := pkg.ParseURL(args[0]) exitIfError(err) - _, err = pkg.CloneRepo(url, ReposRoot, false) + _, err = pkg.CloneRepo(url, pkg.Cfg.ReposRoot(), false) exitIfError(err) } diff --git a/go.mod b/go.mod index 9155d00..340c0f5 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( github.com/go-git/go-git/v5 v5.1.0 + github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.0.0 ) diff --git a/go.sum b/go.sum index 28239a1..939db20 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= diff --git a/pkg/config.go b/pkg/config.go new file mode 100644 index 0000000..16a0166 --- /dev/null +++ b/pkg/config.go @@ -0,0 +1,113 @@ +package pkg + +import ( + "os" + "path" + "strings" + + "github.com/go-git/go-git/v5/config" + plumbing "github.com/go-git/go-git/v5/plumbing/format/config" + "github.com/mitchellh/go-homedir" +) + +const ( + CfgSection = "gitget" + CfgReposRoot = "reposRoot" + CfgDefaultHost = "defaultHost" + + EnvReposRoot = "GITGET_REPOSROOT" + EnvDefaultHost = "GITGET_DEFAULTHOST" + + DefaultReposRootSubpath = "repositories" + DefaultHost = "github.com" +) + +var Cfg *Conf + +// Conf provides methods for accessing configuration values +// Values are looked up in the following order: env variable, gitignore file, default value +type Conf struct { + gitconfig *config.Config +} + +func LoadConf() { + // We don't care if loading gitconfig file throws an error + // When gitconfig is nil, getters will just return the default values + gitconfig, _ := config.LoadConfig(config.GlobalScope) + + Cfg = &Conf{ + gitconfig: gitconfig, + } +} + +func (c *Conf) ReposRoot() string { + defReposRoot := path.Join(home(), DefaultReposRootSubpath) + + reposRoot := os.Getenv(EnvReposRoot) + if reposRoot != "" { + return reposRoot + } + + if c.gitconfig == nil { + return defReposRoot + } + + gitget := c.findConfigSection(CfgSection) + if gitget == nil { + return defReposRoot + } + + reposRoot = gitget.Option(CfgReposRoot) + reposRoot = strings.TrimSpace(reposRoot) + if reposRoot == "" { + return defReposRoot + } + + return reposRoot +} + +func (c *Conf) DefaultHost() string { + defaultHost := os.Getenv(EnvDefaultHost) + if defaultHost != "" { + return defaultHost + } + + if 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 nil +} + +// 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 +// and there's no reposRoot specified in the global config, the current dir will be used as repos root. +func home() string { + home, err := homedir.Dir() + if err != nil { + return "" + } + + return home +} diff --git a/pkg/config_test.go b/pkg/config_test.go new file mode 100644 index 0000000..c985d50 --- /dev/null +++ b/pkg/config_test.go @@ -0,0 +1,146 @@ +package pkg + +import ( + "os" + "path" + "testing" + + "github.com/go-git/go-git/v5/config" +) + +func newConfigWithFullGitconfig() *Conf { + gitconfig := config.NewConfig() + + gitget := gitconfig.Raw.Section(CfgSection) + gitget.AddOption(CfgReposRoot, "file.root") + gitget.AddOption(CfgDefaultHost, "file.host") + + return &Conf{ + gitconfig: gitconfig, + } +} + +func newConfigWithEmptyGitgetSection() *Conf { + gitconfig := config.NewConfig() + + _ = gitconfig.Raw.Section(CfgSection) + + return &Conf{ + gitconfig: gitconfig, + } +} + +func newConfigWithEmptyValues() *Conf { + gitconfig := config.NewConfig() + + gitget := gitconfig.Raw.Section(CfgSection) + gitget.AddOption(CfgReposRoot, "") + gitget.AddOption(CfgDefaultHost, " ") + + return &Conf{ + gitconfig: gitconfig, + } +} + +func newConfigWithoutGitgetSection() *Conf { + gitconfig := config.NewConfig() + + return &Conf{ + gitconfig: gitconfig, + } +} + +func newConfigWithEmptyGitconfig() *Conf { + return &Conf{ + gitconfig: nil, + } +} + +func newConfigWithEnvVars() *Conf { + _ = os.Setenv(EnvDefaultHost, "env.host") + _ = os.Setenv(EnvReposRoot, "env.root") + + return &Conf{ + gitconfig: nil, + } +} + +func newConfigWithGitconfigAndEnvVars() *Conf { + gitconfig := config.NewConfig() + + gitget := gitconfig.Raw.Section(CfgSection) + gitget.AddOption(CfgReposRoot, "file.root") + gitget.AddOption(CfgDefaultHost, "file.host") + + _ = os.Setenv(EnvDefaultHost, "env.host") + _ = os.Setenv(EnvReposRoot, "env.root") + + return &Conf{ + gitconfig: gitconfig, + } +} + +func newConfigWithEmptySectionAndEnvVars() *Conf { + gitconfig := config.NewConfig() + + _ = gitconfig.Raw.Section(CfgSection) + + _ = os.Setenv(EnvDefaultHost, "env.host") + _ = os.Setenv(EnvReposRoot, "env.root") + + return &Conf{ + gitconfig: gitconfig, + } +} + +func newConfigWithMixed() *Conf { + gitconfig := config.NewConfig() + + gitget := gitconfig.Raw.Section(CfgSection) + gitget.AddOption(CfgReposRoot, "file.root") + gitget.AddOption(CfgDefaultHost, "file.host") + + _ = os.Setenv(EnvDefaultHost, "env.host") + + return &Conf{ + gitconfig: gitconfig, + } +} + +func TestConfig(t *testing.T) { + defReposRoot := path.Join(home(), DefaultReposRootSubpath) + + var tests = []struct { + makeConfig func() *Conf + wantReposRoot string + wantDefaultHost string + }{ + {newConfigWithFullGitconfig, "file.root", "file.host"}, + {newConfigWithoutGitgetSection, defReposRoot, DefaultHost}, + {newConfigWithEmptyGitconfig, defReposRoot, DefaultHost}, + {newConfigWithEnvVars, "env.root", "env.host"}, + {newConfigWithGitconfigAndEnvVars, "env.root", "env.host"}, + {newConfigWithEmptySectionAndEnvVars, "env.root", "env.host"}, + {newConfigWithEmptyGitgetSection, defReposRoot, DefaultHost}, + {newConfigWithEmptyValues, defReposRoot, DefaultHost}, + {newConfigWithMixed, "file.root", "env.host"}, + } + + for _, test := range tests { + cfg := test.makeConfig() + + if cfg.ReposRoot() != test.wantReposRoot { + t.Errorf("Wrong reposRoot value, got: %+v; want: %+v", cfg.ReposRoot(), test.wantReposRoot) + } + + if cfg.DefaultHost() != test.wantDefaultHost { + t.Errorf("Wrong defaultHost value, got: %+v; want: %+v", cfg.DefaultHost(), test.wantDefaultHost) + } + + // Unset env variables after each test so they don't affect other tests + err := os.Unsetenv(EnvDefaultHost) + checkFatal(t, err) + err = os.Unsetenv(EnvReposRoot) + checkFatal(t, err) + } +} diff --git a/pkg/helpers_test.go b/pkg/helpers_test.go index a7a0460..1ed4a41 100644 --- a/pkg/helpers_test.go +++ b/pkg/helpers_test.go @@ -14,6 +14,11 @@ import ( "github.com/pkg/errors" ) +const ( + testUser = "Test User" + testEmail = "testuser@example.com" +) + func newRepoEmpty(t *testing.T) *Repo { dir := newTempDir(t) @@ -162,8 +167,8 @@ func (r *Repo) newCommit(t *testing.T, msg string) { opts := &git.CommitOptions{ Author: &object.Signature{ - Name: "Some Guy", - Email: "someguy@example.com", + Name: testUser, + Email: testEmail, When: time.Date(2000, 01, 01, 16, 00, 00, 0, time.UTC), }, }