6
0
mirror of https://github.com/grdl/git-get.git synced 2026-02-10 06:09:22 +00:00

Refactor implementations of get and list commands

- Simplify the main functions by moving actual implementation to "pkg" package
- Remove the "path" package and move files into the root "pkg" package
- Replace viper with explicit Cfg structs
This commit is contained in:
Grzegorz Dlugoszewski
2020-06-18 15:44:10 +02:00
parent 8511cd6c97
commit 5a588f22d1
9 changed files with 151 additions and 118 deletions

View File

@@ -1,30 +1,26 @@
package main package main
import ( import (
"fmt" "git-get/pkg"
"git-get/pkg/cfg" "git-get/pkg/cfg"
"git-get/pkg/path"
"git-get/pkg/repo"
"os"
pathpkg "path"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var cmd = &cobra.Command{ var cmd = &cobra.Command{
Use: "git-get <repo>", Use: "git-get <repo>",
Short: "git get", Short: "git get",
Run: Run, RunE: run,
Args: cobra.MaximumNArgs(1), // TODO: add custom validator Args: cobra.MaximumNArgs(1), // TODO: add custom validator
Version: cfg.Version(), Version: cfg.Version(),
SilenceUsage: true,
} }
func init() { func init() {
cmd.PersistentFlags().StringP(cfg.KeyReposRoot, "r", "", "repos root") cmd.PersistentFlags().StringP(cfg.KeyReposRoot, "r", "", "repos root")
cmd.PersistentFlags().StringP(cfg.KeyPrivateKey, "p", "", "SSH private key path") cmd.PersistentFlags().StringP(cfg.KeyPrivateKey, "p", "", "SSH private key path")
cmd.PersistentFlags().StringP(cfg.KeyDump, "d", "", "Dump file path") cmd.PersistentFlags().StringP(cfg.KeyDump, "d", "", "Dump file path")
cmd.PersistentFlags().StringP(cfg.KeyBranch, "b", cfg.DefBranch, "Branch (or tag) to checkout after cloning") cmd.PersistentFlags().StringP(cfg.KeyBranch, "b", cfg.DefBranch, "Branch (or tag) to checkout after cloning")
viper.BindPFlag(cfg.KeyReposRoot, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot)) viper.BindPFlag(cfg.KeyReposRoot, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot))
@@ -33,49 +29,23 @@ func init() {
viper.BindPFlag(cfg.KeyBranch, cmd.PersistentFlags().Lookup(cfg.KeyBranch)) viper.BindPFlag(cfg.KeyBranch, cmd.PersistentFlags().Lookup(cfg.KeyBranch))
} }
func Run(cmd *cobra.Command, args []string) { func run(cmd *cobra.Command, args []string) error {
cfg.InitConfig() cfg.Init()
root := viper.GetString(cfg.KeyReposRoot) var url string
if len(args) > 0 {
if bundle := viper.GetString(cfg.KeyDump); bundle != "" { url = args[0]
opts, err := path.ParseBundleFile(bundle)
exitIfError(err)
for _, opt := range opts {
path := pathpkg.Join(root, path.URLToPath(opt.URL))
opt.Path = path
_, _ = repo.Clone(opt)
}
os.Exit(0)
} }
url, err := path.ParseURL(args[0]) config := &pkg.GetCfg{
exitIfError(err) Branch: viper.GetString(cfg.KeyBranch),
Dump: viper.GetString(cfg.KeyDump),
branch := viper.GetString(cfg.KeyBranch) Root: viper.GetString(cfg.KeyReposRoot),
path := pathpkg.Join(root, path.URLToPath(url))
cloneOpts := &repo.CloneOpts{
URL: url, URL: url,
Path: path,
Branch: branch,
} }
return pkg.Get(config)
_, err = repo.Clone(cloneOpts)
exitIfError(err)
} }
func main() { func main() {
if err := cmd.Execute(); err != nil { cmd.Execute()
fmt.Println(err)
os.Exit(1)
}
}
func exitIfError(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
} }

View File

@@ -1,28 +1,25 @@
package main package main
import ( import (
"fmt" "git-get/pkg"
"git-get/pkg/cfg" "git-get/pkg/cfg"
"git-get/pkg/path"
"git-get/pkg/print"
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var cmd = &cobra.Command{ var cmd = &cobra.Command{
Use: "git-list", Use: "git-list",
Short: "git list", Short: "git list",
Run: Run, RunE: run,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Version: cfg.Version(), Version: cfg.Version(),
SilenceUsage: true,
} }
func init() { func init() {
cmd.PersistentFlags().StringP(cfg.KeyReposRoot, "r", "", "repos root") cmd.PersistentFlags().StringP(cfg.KeyReposRoot, "r", "", "repos root")
cmd.PersistentFlags().StringP(cfg.KeyPrivateKey, "p", "", "SSH private key path") cmd.PersistentFlags().StringP(cfg.KeyPrivateKey, "p", "", "SSH private key path")
cmd.PersistentFlags().BoolP(cfg.KeyFetch, "f", false, "Fetch from remotes when listing repositories") cmd.PersistentFlags().BoolP(cfg.KeyFetch, "f", false, "Fetch from remotes when listing repositories")
cmd.PersistentFlags().StringP(cfg.KeyOutput, "o", cfg.DefOutput, "output format.") cmd.PersistentFlags().StringP(cfg.KeyOutput, "o", cfg.DefOutput, "output format.")
@@ -32,46 +29,19 @@ func init() {
viper.BindPFlag(cfg.KeyOutput, cmd.PersistentFlags().Lookup(cfg.KeyOutput)) viper.BindPFlag(cfg.KeyOutput, cmd.PersistentFlags().Lookup(cfg.KeyOutput))
} }
func Run(cmd *cobra.Command, args []string) { func run(cmd *cobra.Command, args []string) error {
cfg.InitConfig() cfg.Init()
root := viper.GetString(cfg.KeyReposRoot) config := &pkg.ListCfg{
Fetch: viper.GetBool(cfg.KeyFetch),
// TODO: move it to OpenAll and don't export Output: viper.GetString(cfg.KeyOutput),
paths, err := path.FindRepos() PrivateKey: viper.GetString(cfg.KeyPrivateKey),
exitIfError(err) Root: viper.GetString(cfg.KeyReposRoot),
repos, err := path.OpenAll(paths)
exitIfError(err)
var printer print.Printer
switch viper.GetString(cfg.KeyOutput) {
case cfg.OutFlat:
printer = &print.FlatPrinter{}
case cfg.OutTree:
printer = &print.SimpleTreePrinter{}
case cfg.OutSmart:
printer = &print.SmartTreePrinter{}
case cfg.OutDump:
printer = &print.DumpPrinter{}
default:
err = fmt.Errorf("invalid --out flag; allowed values: %v", []string{cfg.OutFlat, cfg.OutTree, cfg.OutSmart})
} }
exitIfError(err)
fmt.Println(printer.Print(root, repos)) return pkg.List(config)
} }
func main() { func main() {
if err := cmd.Execute(); err != nil { cmd.Execute()
fmt.Println(err)
os.Exit(1)
}
}
func exitIfError(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
} }

View File

@@ -1,4 +1,4 @@
package path package pkg
import ( import (
"bufio" "bufio"

View File

@@ -1,4 +1,4 @@
package path package pkg
import ( import (
"testing" "testing"

View File

@@ -60,11 +60,11 @@ type gitconfig struct {
*config.Config *config.Config
} }
// InitConfig initializes viper config registry. Values are looked up in the following order: cli flag, env variable, gitconfig file, default value // Init initializes viper config registry. Values are looked up in the following order: cli flag, env variable, gitconfig file, default value
// Viper doesn't support gitconfig file format so it can't find missing values there automatically. They need to be specified in setMissingValues func. // Viper doesn't support gitconfig file format so it can't find missing values there automatically. They need to be specified in setMissingValues func.
// //
// Because it reads the cli flags it needs to be called after the cmd.Execute(). // Because it reads the cli flags it needs to be called after the cmd.Execute().
func InitConfig() { func Init() {
viper.SetEnvPrefix(strings.ToUpper(GitgetPrefix)) viper.SetEnvPrefix(strings.ToUpper(GitgetPrefix))
viper.AutomaticEnv() viper.AutomaticEnv()

58
pkg/get.go Normal file
View File

@@ -0,0 +1,58 @@
package pkg
import (
"git-get/pkg/repo"
"path"
)
// GetCfg provides configuration for the Get command.
type GetCfg struct {
Branch string
Dump string
Root string
URL string
}
// Get executes the "git get" command.
func Get(c *GetCfg) error {
if c.Dump != "" {
return cloneDumpFile(c)
}
if c.URL != "" {
return cloneSingleRepo(c)
}
return nil
}
func cloneSingleRepo(c *GetCfg) error {
url, err := ParseURL(c.URL)
if err != nil {
return err
}
cloneOpts := &repo.CloneOpts{
URL: url,
Path: path.Join(c.Root, URLToPath(url)),
Branch: c.Branch,
}
_, err = repo.Clone(cloneOpts)
return err
}
func cloneDumpFile(c *GetCfg) error {
opts, err := ParseBundleFile(c.Dump)
if err != nil {
return err
}
for _, opt := range opts {
path := path.Join(c.Root, URLToPath(opt.URL))
opt.Path = path
_, _ = repo.Clone(opt)
}
return nil
}

View File

@@ -1,8 +1,9 @@
package path package pkg
import ( import (
"fmt" "fmt"
"git-get/pkg/cfg" "git-get/pkg/cfg"
"git-get/pkg/print"
"git-get/pkg/repo" "git-get/pkg/repo"
"os" "os"
"sort" "sort"
@@ -11,27 +12,62 @@ import (
"github.com/karrick/godirwalk" "github.com/karrick/godirwalk"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/viper"
) )
// skipNode is used as an error indicating that .git directory has been found. // errSkipNode is used as an error indicating that .git directory has been found.
// It's handled by ErrorsCallback to tell the WalkCallback to skip this dir. // It's handled by ErrorsCallback to tell the WalkCallback to skip this dir.
var skipNode = errors.New(".git directory found, skipping this node") var errSkipNode = errors.New(".git directory found, skipping this node")
var repos []string var repos []string
func FindRepos() ([]string, error) { // ListCfg provides configuration for the List command.
repos = []string{} type ListCfg struct {
Fetch bool
Output string
PrivateKey string
Root string
}
root := viper.GetString(cfg.KeyReposRoot) // List executes the "git list" command.
func List(c *ListCfg) error {
paths, err := findRepos(c.Root)
if err != nil {
return err
}
repos, err := openAll(paths)
if err != nil {
return err
}
var printer print.Printer
switch c.Output {
case cfg.OutFlat:
printer = &print.FlatPrinter{}
case cfg.OutTree:
printer = &print.SimpleTreePrinter{}
case cfg.OutSmart:
printer = &print.SmartTreePrinter{}
case cfg.OutDump:
printer = &print.DumpPrinter{}
default:
return fmt.Errorf("invalid --out flag; allowed values: %v", []string{cfg.OutFlat, cfg.OutTree, cfg.OutSmart})
}
fmt.Println(printer.Print(c.Root, repos))
return nil
}
func findRepos(root string) ([]string, error) {
repos = []string{}
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)
} }
walkOpts := &godirwalk.Options{ walkOpts := &godirwalk.Options{
ErrorCallback: ErrorCb, ErrorCallback: errorCb,
Callback: WalkCb, Callback: walkCb,
// Use Unsorted to improve speed because repos will be processed by goroutines in a random order anyway. // Use Unsorted to improve speed because repos will be processed by goroutines in a random order anyway.
Unsorted: true, Unsorted: true,
} }
@@ -48,24 +84,24 @@ func FindRepos() ([]string, error) {
return repos, nil return repos, nil
} }
func WalkCb(path string, ent *godirwalk.Dirent) error { func walkCb(path string, ent *godirwalk.Dirent) error {
if ent.IsDir() && ent.Name() == ".git" { if ent.IsDir() && ent.Name() == ".git" {
repos = append(repos, strings.TrimSuffix(path, ".git")) repos = append(repos, strings.TrimSuffix(path, ".git"))
return skipNode return errSkipNode
} }
return nil return nil
} }
func ErrorCb(_ string, err error) godirwalk.ErrorAction { func errorCb(_ string, err error) godirwalk.ErrorAction {
// Skip .git directory and directories we don't have permissions to access // Skip .git directory and directories we don't have permissions to access
// TODO: Will syscall.EACCES work on windows? // TODO: Will syscall.EACCES work on windows?
if errors.Is(err, skipNode) || errors.Is(err, syscall.EACCES) { if errors.Is(err, errSkipNode) || errors.Is(err, syscall.EACCES) {
return godirwalk.SkipNode return godirwalk.SkipNode
} }
return godirwalk.Halt return godirwalk.Halt
} }
func OpenAll(paths []string) ([]*repo.Repo, error) { func openAll(paths []string) ([]*repo.Repo, error) {
var repos []*repo.Repo var repos []*repo.Repo
reposChan := make(chan *repo.Repo) reposChan := make(chan *repo.Repo)

View File

@@ -1,4 +1,4 @@
package path package pkg
import ( import (
"git-get/pkg/cfg" "git-get/pkg/cfg"
@@ -11,9 +11,8 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var ( // ErrEmptyURLPath is an error indicating that the path part of URL is empty.
ErrEmptyURLPath = errors.New("Parsed URL path is empty") var ErrEmptyURLPath = errors.New("Parsed URL path is empty")
)
// 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/).
// See: https://golang.org/src/cmd/go/internal/get/vcs.go // See: https://golang.org/src/cmd/go/internal/get/vcs.go

View File

@@ -1,4 +1,4 @@
package path package pkg
import ( import (
"git-get/pkg/cfg" "git-get/pkg/cfg"
@@ -50,7 +50,7 @@ func TestURLParse(t *testing.T) {
} }
// We need to init config first so the default values are correctly loaded // We need to init config first so the default values are correctly loaded
cfg.InitConfig() cfg.Init()
for _, test := range tests { for _, test := range tests {
url, err := ParseURL(test.in) url, err := ParseURL(test.in)