mirror of
https://github.com/grdl/git-get.git
synced 2026-02-05 21:07:58 +00:00
Refactor packages structure
- Isolate files into their own packages - Create new printer package and interface - Refactor Repo stuct to embed the go-git *Repository directly - Simplify cmd package
This commit is contained in:
103
path/list.go
Normal file
103
path/list.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git-get/cfg"
|
||||
"git-get/git"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/karrick/godirwalk"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// skipNode 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 skipNode = errors.New(".git directory found, skipping this node")
|
||||
|
||||
var repos []string
|
||||
|
||||
func FindRepos() ([]string, error) {
|
||||
repos = []string{}
|
||||
|
||||
root := viper.GetString(cfg.KeyReposRoot)
|
||||
|
||||
if _, err := os.Stat(root); err != nil {
|
||||
return nil, fmt.Errorf("Repos root %s does not 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 repos root %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 skipNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ErrorCb(_ string, err error) godirwalk.ErrorAction {
|
||||
if errors.Is(err, skipNode) {
|
||||
return godirwalk.SkipNode
|
||||
}
|
||||
return godirwalk.Halt
|
||||
}
|
||||
|
||||
func OpenAll(paths []string) ([]*git.Repo, error) {
|
||||
var repos []*git.Repo
|
||||
reposChan := make(chan *git.Repo)
|
||||
|
||||
for _, path := range paths {
|
||||
go func(path string) {
|
||||
repo, err := git.OpenRepo(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
|
||||
}
|
||||
73
path/url.go
Normal file
73
path/url.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"git-get/cfg"
|
||||
urlpkg "net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"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/).
|
||||
// See: https://golang.org/src/cmd/go/internal/get/vcs.go
|
||||
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
|
||||
|
||||
func ParseURL(rawURL string) (url *urlpkg.URL, err error) {
|
||||
// If rawURL matches the SCP-like syntax, convert it into a standard ssh Path.
|
||||
// eg, git@github.com:user/repo => ssh://git@github.com/user/repo
|
||||
if m := scpSyntax.FindStringSubmatch(rawURL); m != nil {
|
||||
url = &urlpkg.URL{
|
||||
Scheme: "ssh",
|
||||
User: urlpkg.User(m[1]),
|
||||
Host: m[2],
|
||||
Path: m[3],
|
||||
}
|
||||
} else {
|
||||
url, err = urlpkg.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed parsing Path")
|
||||
}
|
||||
}
|
||||
|
||||
if url.Host == "" && url.Path == "" {
|
||||
return nil, errors.New("Parsed Path is empty")
|
||||
}
|
||||
|
||||
if url.Scheme == "git+ssh" {
|
||||
url.Scheme = "ssh"
|
||||
}
|
||||
|
||||
// Default to "git" user when using ssh and no user is provided
|
||||
if url.Scheme == "ssh" && url.User == nil {
|
||||
url.User = urlpkg.User("git")
|
||||
}
|
||||
|
||||
// Default to configured defaultHost when host is empty
|
||||
if url.Host == "" {
|
||||
url.Host = viper.GetString(cfg.KeyDefaultHost)
|
||||
}
|
||||
|
||||
// Default to https when scheme is empty
|
||||
if url.Scheme == "" {
|
||||
url.Scheme = "https"
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
func URLToPath(url *urlpkg.URL) (repoPath string) {
|
||||
// Remove port numbers from host
|
||||
repoHost := strings.Split(url.Host, ":")[0]
|
||||
|
||||
// Remove trailing ".git" from repo name
|
||||
repoPath = path.Join(repoHost, url.Path)
|
||||
repoPath = strings.TrimSuffix(repoPath, ".git")
|
||||
|
||||
// Remove tilde (~) char from username
|
||||
repoPath = strings.ReplaceAll(repoPath, "~", "")
|
||||
|
||||
return repoPath
|
||||
}
|
||||
85
path/url_test.go
Normal file
85
path/url_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"git-get/cfg"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Following URLs are considered valid according to https://git-scm.com/docs/git-clone#_git_urls:
|
||||
// ssh://[user@]host.xz[:port]/path/to/repo.git
|
||||
// ssh://[user@]host.xz[:port]/~[user]/path/to/repo.git/
|
||||
// [user@]host.xz:path/to/repo.git/
|
||||
// [user@]host.xz:/~[user]/path/to/repo.git/
|
||||
// git://host.xz[:port]/path/to/repo.git/
|
||||
// git://host.xz[:port]/~[user]/path/to/repo.git/
|
||||
// http[s]://host.xz[:port]/path/to/repo.git/
|
||||
// ftp[s]://host.xz[:port]/path/to/repo.git/
|
||||
// /path/to/repo.git/
|
||||
// file:///path/to/repo.git/
|
||||
|
||||
func TestURLParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"ssh://github.com/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"ssh://user@github.com/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"ssh://user@github.com:1234/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"ssh://user@github.com/~user/grdl/git-get.git", "github.com/user/grdl/git-get"},
|
||||
{"git+ssh://github.com/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"git@github.com:grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"git@github.com:/~user/grdl/git-get.git", "github.com/user/grdl/git-get"},
|
||||
{"git://github.com/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"git://github.com/~user/grdl/git-get.git", "github.com/user/grdl/git-get"},
|
||||
{"https://github.com/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"http://github.com/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"https://github.com/grdl/git-get", "github.com/grdl/git-get"},
|
||||
{"https://github.com/git-get.git", "github.com/git-get"},
|
||||
{"https://github.com/git-get", "github.com/git-get"},
|
||||
{"https://github.com/grdl/sub/path/git-get.git", "github.com/grdl/sub/path/git-get"},
|
||||
{"https://github.com:1234/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"https://github.com/grdl/git-get.git/", "github.com/grdl/git-get"},
|
||||
{"https://github.com/grdl/git-get/", "github.com/grdl/git-get"},
|
||||
{"https://github.com/grdl/git-get/////", "github.com/grdl/git-get"},
|
||||
{"https://github.com/grdl/git-get.git/////", "github.com/grdl/git-get"},
|
||||
{"ftp://github.com/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"ftps://github.com/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"rsync://github.com/grdl/git-get.git", "github.com/grdl/git-get"},
|
||||
{"local/grdl/git-get/", "github.com/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
|
||||
cfg.InitConfig()
|
||||
|
||||
for _, test := range tests {
|
||||
url, err := ParseURL(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing Path: %+v", err)
|
||||
}
|
||||
|
||||
got := URLToPath(url)
|
||||
|
||||
if got != test.want {
|
||||
t.Errorf("Wrong result of parsing Path: %s, got: %s; want: %s", test.in, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidURLParse(t *testing.T) {
|
||||
invalidURLs := []string{
|
||||
"",
|
||||
//TODO: This Path is technically a correct scp-like syntax. Not sure how to handle it
|
||||
"github.com:grdl/git-git.get.git",
|
||||
|
||||
//TODO: Is this a valid git Path?
|
||||
//"git@github.com:1234:grdl/git-get.git",
|
||||
}
|
||||
|
||||
for _, in := range invalidURLs {
|
||||
got, err := ParseURL(in)
|
||||
if err == nil {
|
||||
t.Errorf("Wrong result of parsing invalid Path: %s, got: %s, want: error", in, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user