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:
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package path
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package path
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -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
58
pkg/get.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user