mirror of
https://github.com/grdl/git-get.git
synced 2026-02-05 01:29:42 +00:00
Remove gogit and major refactoring (#2)
* Fix typo in readme * Reimplement all git methods without go-git * Rename repo pkg to git, add gitconfig methods * Improve tests for configuration reading * Rename package file to io and move RepoFinder there * Refactor printers - Remove smart printer - Decouple printers from git repos with interfaces - Update printer functions - Remove unnecessary flags - Add better remote URL detection * Update readme and go.mod * Add author to git commit in tests Otherwise tests will fail in CI. * Install git before running tests and don't use cgo * Add better error message, revert installing git * Ensure commit message is in quotes * Set up git config before running tests
This commit is contained in:
committed by
GitHub
parent
2ef739ea49
commit
8c132cdafa
150
pkg/list.go
150
pkg/list.go
@@ -3,140 +3,72 @@ package pkg
|
||||
import (
|
||||
"fmt"
|
||||
"git-get/pkg/cfg"
|
||||
"git-get/pkg/git"
|
||||
"git-get/pkg/io"
|
||||
"git-get/pkg/print"
|
||||
"git-get/pkg/repo"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/karrick/godirwalk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// 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.
|
||||
var errSkipNode = errors.New(".git directory found, skipping this node")
|
||||
|
||||
var repos []string
|
||||
|
||||
// ListCfg provides configuration for the List command.
|
||||
type ListCfg struct {
|
||||
Fetch bool
|
||||
Output string
|
||||
PrivateKey string
|
||||
Root string
|
||||
Fetch bool
|
||||
Output string
|
||||
Root string
|
||||
}
|
||||
|
||||
// List executes the "git list" command.
|
||||
func List(c *ListCfg) error {
|
||||
paths, err := findRepos(c.Root)
|
||||
paths, err := io.NewRepoFinder(c.Root).Find()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repos, err := openAll(paths)
|
||||
if err != nil {
|
||||
return err
|
||||
// TODO: we should open, fetch and read status of each repo in separate goroutine
|
||||
var repos []git.Repo
|
||||
for _, path := range paths {
|
||||
repo, err := git.Open(path)
|
||||
if err != nil {
|
||||
// TODO: how should we handle it?
|
||||
continue
|
||||
}
|
||||
|
||||
if c.Fetch {
|
||||
err := repo.Fetch()
|
||||
if err != nil {
|
||||
// TODO: handle error
|
||||
}
|
||||
}
|
||||
|
||||
repos = append(repos, *repo)
|
||||
}
|
||||
|
||||
var printer print.Printer
|
||||
switch c.Output {
|
||||
case cfg.OutFlat:
|
||||
printer = &print.FlatPrinter{}
|
||||
printables := make([]print.Repo, len(repos))
|
||||
for i := range repos {
|
||||
printables[i] = &repos[i]
|
||||
}
|
||||
fmt.Println(print.NewFlatPrinter().Print(printables))
|
||||
|
||||
case cfg.OutTree:
|
||||
printer = &print.TreePrinter{}
|
||||
case cfg.OutSmart:
|
||||
printer = &print.SmartPrinter{}
|
||||
printables := make([]print.Repo, len(repos))
|
||||
for i := range repos {
|
||||
printables[i] = &repos[i]
|
||||
}
|
||||
fmt.Println(print.NewTreePrinter().Print(c.Root, printables))
|
||||
|
||||
case cfg.OutDump:
|
||||
printer = &print.DumpPrinter{}
|
||||
printables := make([]print.DumpRepo, len(repos))
|
||||
for i := range repos {
|
||||
printables[i] = &repos[i]
|
||||
}
|
||||
fmt.Println(print.NewDumpPrinter().Print(printables))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid --out flag; allowed values: [%s]", strings.Join(cfg.AllowedOut, ", "))
|
||||
}
|
||||
|
||||
fmt.Println(printer.Print(c.Root, repos))
|
||||
return nil
|
||||
}
|
||||
|
||||
func findRepos(root string) ([]string, error) {
|
||||
repos = []string{}
|
||||
|
||||
if _, err := os.Stat(root); err != nil {
|
||||
return nil, fmt.Errorf("repos root %s doesn't exist or can't be accessed", root)
|
||||
}
|
||||
|
||||
walkOpts := &godirwalk.Options{
|
||||
ErrorCallback: errorCb,
|
||||
Callback: walkCb,
|
||||
// Use Unsorted to improve speed because repos will be processed by goroutines in a random order anyway.
|
||||
Unsorted: true,
|
||||
}
|
||||
|
||||
err := godirwalk.Walk(root, walkOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(repos) == 0 {
|
||||
return nil, fmt.Errorf("no git repos found in root path %s", root)
|
||||
}
|
||||
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
func walkCb(path string, ent *godirwalk.Dirent) error {
|
||||
if ent.IsDir() && ent.Name() == ".git" {
|
||||
repos = append(repos, strings.TrimSuffix(path, ".git"))
|
||||
return errSkipNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func errorCb(_ string, err error) godirwalk.ErrorAction {
|
||||
// Skip .git directory and directories we don't have permissions to access
|
||||
// TODO: Will syscall.EACCES work on windows?
|
||||
if errors.Is(err, errSkipNode) || errors.Is(err, syscall.EACCES) {
|
||||
return godirwalk.SkipNode
|
||||
}
|
||||
return godirwalk.Halt
|
||||
}
|
||||
|
||||
func openAll(paths []string) ([]*repo.Repo, error) {
|
||||
var repos []*repo.Repo
|
||||
reposChan := make(chan *repo.Repo)
|
||||
|
||||
for _, path := range paths {
|
||||
go func(path string) {
|
||||
repo, err := repo.Open(path)
|
||||
|
||||
if err != nil {
|
||||
// TODO handle error
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
err = repo.LoadStatus()
|
||||
if err != nil {
|
||||
// TODO handle error
|
||||
fmt.Println(err)
|
||||
}
|
||||
// when error happened we just sent a nil
|
||||
reposChan <- repo
|
||||
}(path)
|
||||
}
|
||||
|
||||
for repo := range reposChan {
|
||||
repos = append(repos, repo)
|
||||
|
||||
// TODO: is this the right way to close the channel? What if we have non-unique paths?
|
||||
if len(repos) == len(paths) {
|
||||
close(reposChan)
|
||||
}
|
||||
}
|
||||
|
||||
// sort the final array to make printing easier
|
||||
sort.Slice(repos, func(i, j int) bool {
|
||||
return strings.Compare(repos[i].Path, repos[j].Path) < 0
|
||||
})
|
||||
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user