mirror of
https://github.com/grdl/git-get.git
synced 2026-02-05 19:27:57 +00:00
Refactor pkg directory back
This commit is contained in:
66
pkg/path/bundle.go
Normal file
66
pkg/path/bundle.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"git-get/pkg/git"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidNumberOfElements = errors.New("More than two space-separated 2 elements on the line")
|
||||
)
|
||||
|
||||
// ParseBundleFile opens a given gitgetfile and parses its content into a slice of CloneOpts.
|
||||
func ParseBundleFile(path string) ([]*git.CloneOpts, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed opening gitgetfile %s", path)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
var opts []*git.CloneOpts
|
||||
var line int
|
||||
for scanner.Scan() {
|
||||
line++
|
||||
opt, err := parseLine(scanner.Text())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Failed parsing line %d", line)
|
||||
}
|
||||
|
||||
opts = append(opts, opt)
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// parseLine splits a gitgetfile line into space-separated segments.
|
||||
// First part is the URL to clone. Second, optional, is the branch (or tag) to checkout after cloning
|
||||
func parseLine(line string) (*git.CloneOpts, error) {
|
||||
parts := strings.Split(line, " ")
|
||||
|
||||
if len(parts) > 2 {
|
||||
return nil, ErrInvalidNumberOfElements
|
||||
}
|
||||
|
||||
url, err := ParseURL(parts[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
branch := ""
|
||||
if len(parts) == 2 {
|
||||
branch = parts[1]
|
||||
}
|
||||
|
||||
return &git.CloneOpts{
|
||||
URL: url,
|
||||
Branch: branch,
|
||||
// When cloning a bundle we ignore errors about already cloned repos
|
||||
IgnoreExisting: true,
|
||||
}, nil
|
||||
}
|
||||
55
pkg/path/bundle_test.go
Normal file
55
pkg/path/bundle_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParsingRefs(t *testing.T) {
|
||||
var tests = []struct {
|
||||
line string
|
||||
wantBranch string
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
line: "https://github.com/grdl/git-get",
|
||||
wantBranch: "",
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
line: "https://github.com/grdl/git-get master",
|
||||
wantBranch: "master",
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
line: "https://github.com/grdl/git-get refs/tags/v1.0.0",
|
||||
wantBranch: "refs/tags/v1.0.0",
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
line: "https://github.com/grdl/git-get master branch",
|
||||
wantBranch: "",
|
||||
wantErr: ErrInvalidNumberOfElements,
|
||||
},
|
||||
{
|
||||
line: "https://github.com",
|
||||
wantBranch: "",
|
||||
wantErr: ErrEmptyURLPath,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
got, err := parseLine(test.line)
|
||||
if err != nil && test.wantErr == nil {
|
||||
t.Fatalf("Test case %d should not return an error", i)
|
||||
}
|
||||
|
||||
if err != nil && test.wantErr != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if got.Branch != test.wantBranch {
|
||||
t.Errorf("Failed test case %d, got: %s; wantBranch: %s", i, got.Branch, test.wantBranch)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
106
pkg/path/list.go
Normal file
106
pkg/path/list.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git-get/pkg/cfg"
|
||||
"git-get/pkg/git"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"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 {
|
||||
// Skip .git directory and directories we don't have permissions to access
|
||||
// TODO: Will syscall.EACCES work on windows?
|
||||
if errors.Is(err, skipNode) || errors.Is(err, syscall.EACCES) {
|
||||
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
|
||||
}
|
||||
77
pkg/path/url.go
Normal file
77
pkg/path/url.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"git-get/pkg/cfg"
|
||||
urlpkg "net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
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/).
|
||||
// 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 URL")
|
||||
}
|
||||
}
|
||||
|
||||
if url.Host == "" && url.Path == "" {
|
||||
return nil, ErrEmptyURLPath
|
||||
}
|
||||
|
||||
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
pkg/path/url_test.go
Normal file
85
pkg/path/url_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"git-get/pkg/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; wantBranch: %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, wantBranch: error", in, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user