Refactor git into an interface (#146)

* Refactor git into an interface
This commit is contained in:
Jay Gabriels
2021-08-25 20:34:36 -07:00
committed by GitHub
parent 43f7c06eeb
commit f5cbf68006
5 changed files with 195 additions and 77 deletions

View File

@@ -10,7 +10,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
### Added
- integration tests on windows, ubuntu, and mac for github
- GHORG_MATCH_REGEX to filter cloning repos by regex; thanks @petomalina
- fetch all if checkout fails when repo exists locally then retry
### Changed
- initial clone will try to checkout a branch if specified; thanks @dword-design
- default clone directory to $HOME/ghorg

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
@@ -14,6 +13,7 @@ import (
"github.com/gabrie30/ghorg/colorlog"
"github.com/gabrie30/ghorg/configs"
"github.com/gabrie30/ghorg/git"
"github.com/gabrie30/ghorg/scm"
"github.com/korovkin/limiter"
"github.com/spf13/cobra"
@@ -140,8 +140,34 @@ func cloneFunc(cmd *cobra.Command, argz []string) {
parseParentFolder(argz)
args = argz
targetCloneSource = argz[0]
setupRepoClone()
}
CloneAllRepos()
func setupRepoClone() {
var cloneTargets []scm.Repo
var err error
if os.Getenv("GHORG_CLONE_TYPE") == "org" {
cloneTargets, err = getAllOrgCloneUrls()
} else if os.Getenv("GHORG_CLONE_TYPE") == "user" {
cloneTargets, err = getAllUserCloneUrls()
} else {
colorlog.PrintError("GHORG_CLONE_TYPE not set or unsupported")
os.Exit(1)
}
if err != nil {
colorlog.PrintError("Encountered an error, aborting")
fmt.Println(err)
os.Exit(1)
}
if len(cloneTargets) == 0 {
colorlog.PrintInfo("No repos found for " + os.Getenv("GHORG_SCM_TYPE") + " " + os.Getenv("GHORG_CLONE_TYPE") + ": " + targetCloneSource + ", check spelling and verify clone-type (user/org) is set correctly e.g. -c=user")
os.Exit(0)
}
git := git.NewGit()
CloneAllRepos(git, cloneTargets)
}
func getAllOrgCloneUrls() ([]scm.Repo, error) {
@@ -251,32 +277,9 @@ func filterByRegex(repos []scm.Repo) []scm.Repo {
}
// CloneAllRepos clones all repos
func CloneAllRepos() {
func CloneAllRepos(git git.Gitter, cloneTargets []scm.Repo) {
// resc, errc, infoc := make(chan string), make(chan error), make(chan error)
var cloneTargets []scm.Repo
var err error
if os.Getenv("GHORG_CLONE_TYPE") == "org" {
cloneTargets, err = getAllOrgCloneUrls()
} else if os.Getenv("GHORG_CLONE_TYPE") == "user" {
cloneTargets, err = getAllUserCloneUrls()
} else {
colorlog.PrintError("GHORG_CLONE_TYPE not set or unsupported")
os.Exit(1)
}
if err != nil {
colorlog.PrintError("Encountered an error, aborting")
fmt.Println(err)
os.Exit(1)
}
if len(cloneTargets) == 0 {
colorlog.PrintInfo("No repos found for " + os.Getenv("GHORG_SCM_TYPE") + " " + os.Getenv("GHORG_CLONE_TYPE") + ": " + targetCloneSource + ", check spelling and verify clone-type (user/org) is set correctly e.g. -c=user")
os.Exit(0)
}
if os.Getenv("GHORG_MATCH_REGEX") != "" {
colorlog.PrintInfo("Filtering repos down by regex that match the provided...")
fmt.Println("")
@@ -284,7 +287,7 @@ func CloneAllRepos() {
}
// filter repos down based on ghorgignore if one exists
_, err = os.Stat(configs.GhorgIgnoreLocation())
_, err := os.Stat(configs.GhorgIgnoreLocation())
if !os.IsNotExist(err) {
// Open the file parse each line and remove cloneTargets containing
toIgnore, err := readGhorgIgnore()
@@ -340,19 +343,18 @@ func CloneAllRepos() {
path = repo.Path
}
repoDir := filepath.Join(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO"), parentFolder, configs.GetCorrectFilePathSeparator(), path)
repo.HostPath = filepath.Join(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO"), parentFolder, configs.GetCorrectFilePathSeparator(), path)
if os.Getenv("GHORG_BACKUP") == "true" {
repoDir = filepath.Join(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO"), parentFolder+"_backup", configs.GetCorrectFilePathSeparator(), path)
repo.HostPath = filepath.Join(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO"), parentFolder+"_backup", configs.GetCorrectFilePathSeparator(), path)
}
action := "cloning"
if repoExistsLocally(repoDir) == true {
if repoExistsLocally(repo.HostPath) == true {
if os.Getenv("GHORG_BACKUP") == "true" {
cmd := exec.Command("git", "remote", "update")
cmd.Dir = repoDir
err := cmd.Run()
err := git.UpdateRemote(repo)
if err != nil {
e := fmt.Sprintf("Could not update remotes in Repo: %s Error: %v", repo.URL, err)
cloneErrors = append(cloneErrors, e)
@@ -360,50 +362,34 @@ func CloneAllRepos() {
}
} else {
cmd := exec.Command("git", "checkout", branch)
cmd.Dir = repoDir
err := cmd.Run()
err := git.Checkout(repo)
if err != nil {
cmd = exec.Command("git", "fetch", "--all")
cmd.Dir = repoDir
err = cmd.Run()
cmd = exec.Command("git", "checkout", branch)
cmd.Dir = repoDir
err = cmd.Run()
if err != nil {
e := fmt.Sprintf("Could not checkout out %s, branch may not exist, no changes made Repo: %s Error: %v", branch, repo.URL, err)
cloneInfos = append(cloneInfos, e)
return
}
e := fmt.Sprintf("Could not checkout out %s, branch may not exist, no changes made Repo: %s Error: %v", repo.CloneBranch, repo.URL, err)
cloneInfos = append(cloneInfos, e)
return
}
cmd = exec.Command("git", "clean", "-f", "-d")
cmd.Dir = repoDir
err = cmd.Run()
err = git.Clean(repo)
if err != nil {
e := fmt.Sprintf("Problem running git clean: %s Error: %v", repo.URL, err)
cloneErrors = append(cloneErrors, e)
return
}
cmd = exec.Command("git", "reset", "--hard", "origin/"+branch)
cmd.Dir = repoDir
err = cmd.Run()
err = git.Reset(repo)
if err != nil {
e := fmt.Sprintf("Problem resetting %s Repo: %s Error: %v", branch, repo.URL, err)
e := fmt.Sprintf("Problem resetting %s Repo: %s Error: %v", repo.CloneBranch, repo.URL, err)
cloneErrors = append(cloneErrors, e)
return
}
// TODO: handle case where repo was removed, should not give user an error
cmd = exec.Command("git", "pull", "origin", branch)
cmd.Dir = repoDir
err = cmd.Run()
err = git.Pull(repo)
if err != nil {
e := fmt.Sprintf("Problem trying to pull %v Repo: %s Error: %v", branch, repo.URL, err)
e := fmt.Sprintf("Problem trying to pull %v Repo: %s Error: %v", repo.CloneBranch, repo.URL, err)
cloneErrors = append(cloneErrors, e)
return
}
@@ -413,13 +399,7 @@ func CloneAllRepos() {
} else {
// if https clone and github/gitlab add personal access token to url
args := []string{"clone", repo.CloneURL, repoDir}
if os.Getenv("GHORG_BACKUP") == "true" {
args = append(args, "--mirror")
}
cmd := exec.Command("git", args...)
err := cmd.Run()
err = git.Clone(repo)
if err != nil {
e := fmt.Sprintf("Problem trying to clone Repo: %s Error: %v", repo.URL, err)
@@ -428,9 +408,7 @@ func CloneAllRepos() {
}
if os.Getenv("GHORG_BRANCH") != "" {
cmd = exec.Command("git", "checkout", branch)
cmd.Dir = repoDir
err = cmd.Run()
err := git.Checkout(repo)
if err != nil {
e := fmt.Sprintf("Could not checkout out %s, branch may not exist, no changes made Repo: %s Error: %v", branch, repo.URL, err)
cloneInfos = append(cloneInfos, e)
@@ -440,10 +418,7 @@ func CloneAllRepos() {
// TODO: make configs around remote name
// we clone with api-key in clone url
args = []string{"remote", "set-url", "origin", repo.URL}
cmd = exec.Command("git", args...)
cmd.Dir = repoDir
err = cmd.Run()
err = git.SetOrigin(repo)
if err != nil {
e := fmt.Sprintf("Problem trying to set remote on Repo: %s Error: %v", repo.URL, err)

View File

@@ -1,6 +1,13 @@
package cmd
import "testing"
import (
"io/ioutil"
"log"
"os"
"testing"
"github.com/gabrie30/ghorg/scm"
)
func TestShouldLowerRegularString(t *testing.T) {
@@ -42,3 +49,67 @@ func TestShouldNotChangeNonLettersString(t *testing.T) {
t.Errorf("Wrong folder name, expected: %s, got: %s", numberName, parentFolder)
}
}
type MockGitClient struct{}
func NewMockGit() MockGitClient {
return MockGitClient{}
}
func (g MockGitClient) Clone(repo scm.Repo) error {
_, err := ioutil.TempDir(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO"), "ghorg_test_repo")
if err != nil {
log.Fatal(err)
}
return nil
}
func (g MockGitClient) SetOrigin(repo scm.Repo) error {
return nil
}
func (g MockGitClient) Checkout(repo scm.Repo) error {
return nil
}
func (g MockGitClient) Clean(repo scm.Repo) error {
return nil
}
func (g MockGitClient) UpdateRemote(repo scm.Repo) error {
return nil
}
func (g MockGitClient) Pull(repo scm.Repo) error {
return nil
}
func (g MockGitClient) Reset(repo scm.Repo) error {
return nil
}
func TestInitialClone(t *testing.T) {
dir, err := ioutil.TempDir(".", "ghorg_tests")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", dir)
os.Setenv("GHORG_CONCURRENCY", "1")
var testRepos = []scm.Repo{
{
Name: "testRepoOne",
},
{
Name: "testRepoTwo",
},
}
mockGit := NewMockGit()
CloneAllRepos(mockGit, testRepos)
got, _ := ioutil.ReadDir(dir)
expected := len(testRepos)
if len(got) != expected {
t.Errorf("Wrong number of repos in clone, expected: %v, got: %v", expected, got)
}
}

72
git/git.go Normal file
View File

@@ -0,0 +1,72 @@
package git
import (
"os"
"os/exec"
"github.com/gabrie30/ghorg/scm"
)
type Gitter interface {
Clone(scm.Repo) error
Reset(scm.Repo) error
Pull(scm.Repo) error
SetOrigin(scm.Repo) error
Clean(scm.Repo) error
Checkout(scm.Repo) error
UpdateRemote(scm.Repo) error
}
type GitClient struct{}
func NewGit() GitClient {
return GitClient{}
}
func (g GitClient) Clone(repo scm.Repo) error {
args := []string{"clone", repo.CloneURL, repo.HostPath}
if os.Getenv("GHORG_BACKUP") == "true" {
args = append(args, "--mirror")
}
cmd := exec.Command("git", args...)
err := cmd.Run()
return err
}
func (g GitClient) SetOrigin(repo scm.Repo) error {
args := []string{"remote", "set-url", "origin", repo.URL}
cmd := exec.Command("git", args...)
cmd.Dir = repo.HostPath
return cmd.Run()
}
func (g GitClient) Checkout(repo scm.Repo) error {
cmd := exec.Command("git", "checkout", repo.CloneBranch)
cmd.Dir = repo.HostPath
return cmd.Run()
}
func (g GitClient) Clean(repo scm.Repo) error {
cmd := exec.Command("git", "clean", "-f", "-d")
cmd.Dir = repo.HostPath
return cmd.Run()
}
func (g GitClient) UpdateRemote(repo scm.Repo) error {
cmd := exec.Command("git", "remote", "update")
cmd.Dir = repo.HostPath
return cmd.Run()
}
func (g GitClient) Pull(repo scm.Repo) error {
cmd := exec.Command("git", "pull", "origin", repo.CloneBranch)
cmd.Dir = repo.HostPath
return cmd.Run()
}
func (g GitClient) Reset(repo scm.Repo) error {
cmd := exec.Command("git", "reset", "--hard", "origin/"+repo.CloneBranch)
cmd.Dir = repo.HostPath
return cmd.Run()
}

View File

@@ -3,6 +3,7 @@ package scm
// Repo represents an SCM repo
type Repo struct {
Name string
HostPath string
Path string
URL string
CloneURL string