6
0
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:
Grzegorz Dlugoszewski
2020-06-08 12:07:03 +02:00
parent 29c21cb78d
commit f3d0df1bfd
14 changed files with 318 additions and 261 deletions

103
path/list.go Normal file
View 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
View 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
View 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)
}
}
}