Restructure project directories
Follow the standard of all go projects
This commit is contained in:
parent
741f18efd1
commit
5445ce1ccf
20 changed files with 39 additions and 42 deletions
15
internal/commands/command.go
Normal file
15
internal/commands/command.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
)
|
||||
|
||||
type Command interface {
|
||||
Command(repoCfg config.RepositoryConfig) CommandStatus
|
||||
}
|
||||
type CommandStatus struct {
|
||||
Name string
|
||||
Changed bool
|
||||
Message string
|
||||
Error bool
|
||||
}
|
||||
138
internal/commands/common_utils_test.go
Normal file
138
internal/commands/common_utils_test.go
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
"os"
|
||||
|
||||
"github.com/go-git/go-billy/v5"
|
||||
"github.com/go-git/go-billy/v5/osfs"
|
||||
"github.com/go-git/go-git/v5"
|
||||
gitcfg "github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing/cache"
|
||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
)
|
||||
|
||||
type TestSetup struct {
|
||||
rootFS billy.Filesystem
|
||||
baseRepository struct {
|
||||
fileSystem billy.Filesystem
|
||||
repo *git.Repository
|
||||
}
|
||||
}
|
||||
|
||||
func checkErrorDuringPreparation(err error) {
|
||||
if err != nil {
|
||||
fmt.Printf("Cannot prepare a temporary directory for testing! %v ", err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func createTmpDir() string {
|
||||
|
||||
baseForTMPDir := fmt.Sprintf("%v/grmTest", os.TempDir())
|
||||
if _, ok := os.Stat(baseForTMPDir); ok != nil {
|
||||
err := os.Mkdir(baseForTMPDir, 0777)
|
||||
checkErrorDuringPreparation(err)
|
||||
}
|
||||
|
||||
tempDir, err := os.MkdirTemp(baseForTMPDir, "*")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
return tempDir
|
||||
}
|
||||
|
||||
func getTestSetup() TestSetup {
|
||||
|
||||
tmpDir := createTmpDir()
|
||||
|
||||
baseFileSystem := osfs.New(tmpDir)
|
||||
|
||||
initRepositoryFileSystem, err := baseFileSystem.Chroot("worktree")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
directoryForGitMetadata, err := initRepositoryFileSystem.Chroot(".git")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
repository, err := git.Init(filesystem.NewStorage(directoryForGitMetadata, cache.NewObjectLRUDefault()), initRepositoryFileSystem)
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
fileForFirstCommit, err := initRepositoryFileSystem.Create("TestFile.txt")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
_, err = fileForFirstCommit.Write([]byte("foo-conent"))
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
repositoryWorkTree, err := repository.Worktree()
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
repositoryWorkTree.Add(fileForFirstCommit.Name())
|
||||
_, err = repositoryWorkTree.Commit("First commit", &git.CommitOptions{})
|
||||
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
return TestSetup{
|
||||
baseRepository: struct {
|
||||
fileSystem billy.Filesystem
|
||||
repo *git.Repository
|
||||
}{
|
||||
fileSystem: initRepositoryFileSystem,
|
||||
repo: repository,
|
||||
},
|
||||
rootFS: baseFileSystem,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func makeCommit(wk *git.Worktree, commitMessage string) {
|
||||
|
||||
_, err := wk.Commit(commitMessage, &git.CommitOptions{})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
}
|
||||
|
||||
func getFSForLocalRepo(dirName string, baseFileSystem billy.Filesystem) (billy.Filesystem, *filesystem.Storage) {
|
||||
fsForLocalRepo, err := baseFileSystem.Chroot(dirName)
|
||||
checkErrorDuringPreparation(err)
|
||||
fsForMetadata, err := fsForLocalRepo.Chroot(".git")
|
||||
checkErrorDuringPreparation(err)
|
||||
storageForTestRepo := filesystem.NewStorage(fsForMetadata, cache.NewObjectLRUDefault())
|
||||
return fsForLocalRepo, storageForTestRepo
|
||||
}
|
||||
|
||||
func getBaseForTestingSyncCommand() (StatusChecker, *git.Repository, config.RepositoryConfig, TestSetup) {
|
||||
tmpDirWithInitialRepository := getTestSetup()
|
||||
dirNameForLocalRepository := "testRepo"
|
||||
fsForLocalRepo, storageForTestRepo := getFSForLocalRepo(dirNameForLocalRepository, tmpDirWithInitialRepository.rootFS)
|
||||
|
||||
fakeLocalRepository, err := git.Clone(storageForTestRepo, fsForLocalRepo, &git.CloneOptions{
|
||||
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
sc := StatusChecker{
|
||||
workspace: tmpDirWithInitialRepository.rootFS.Root(),
|
||||
}
|
||||
|
||||
repoCfg := config.RepositoryConfig{
|
||||
Name: "test",
|
||||
Src: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
Dest: dirNameForLocalRepository,
|
||||
}
|
||||
|
||||
return sc, fakeLocalRepository, repoCfg, tmpDirWithInitialRepository
|
||||
}
|
||||
|
||||
func getBaseForTestingSyncMultipleRemote() (StatusChecker, *git.Repository, config.RepositoryConfig) {
|
||||
sc, fakeLocalRepository, repoCfg, tmpDirWithInitialRepository := getBaseForTestingSyncCommand()
|
||||
|
||||
fakeLocalRepository.CreateRemote(&gitcfg.RemoteConfig{
|
||||
Name: "subremote",
|
||||
URLs: []string{tmpDirWithInitialRepository.baseRepository.fileSystem.Root()},
|
||||
})
|
||||
|
||||
fakeLocalRepository.Fetch(&git.FetchOptions{
|
||||
RemoteName: "subremote",
|
||||
})
|
||||
return sc, fakeLocalRepository, repoCfg
|
||||
}
|
||||
160
internal/commands/status_cmd.go
Normal file
160
internal/commands/status_cmd.go
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
type StatusChecker struct {
|
||||
workspace string
|
||||
}
|
||||
|
||||
func NewStatusChecker(workspace string) StatusChecker {
|
||||
return StatusChecker{
|
||||
workspace: workspace,
|
||||
}
|
||||
}
|
||||
|
||||
func findNumberOfCommitDiffs(srcCommit *object.Commit, dstCommit *object.Commit) int {
|
||||
|
||||
getFiveElementsFromHashes := func(commit *object.Commit, hashedSlice *[]string) *object.Commit {
|
||||
|
||||
var err error
|
||||
for iter := 0; iter <= 5; iter++ {
|
||||
|
||||
*hashedSlice = append(*hashedSlice, commit.Hash.String())
|
||||
commit, err = commit.Parents().Next()
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return commit
|
||||
}
|
||||
|
||||
getRangeDiff := func(listFist, listSecond []string) (int, bool) {
|
||||
diffRange := 0
|
||||
|
||||
for _, itemFirst := range listFist {
|
||||
for _, itemSecond := range listSecond {
|
||||
if itemFirst == itemSecond {
|
||||
return diffRange, true
|
||||
}
|
||||
|
||||
}
|
||||
diffRange++
|
||||
}
|
||||
|
||||
return diffRange, false
|
||||
}
|
||||
|
||||
baseCommitHashes := []string{}
|
||||
destCommitHashes := []string{}
|
||||
for {
|
||||
|
||||
if srcCommit != nil {
|
||||
srcCommit = getFiveElementsFromHashes(srcCommit, &baseCommitHashes)
|
||||
}
|
||||
if dstCommit != nil {
|
||||
dstCommit = getFiveElementsFromHashes(dstCommit, &destCommitHashes)
|
||||
}
|
||||
|
||||
diff, finished := getRangeDiff(baseCommitHashes, destCommitHashes)
|
||||
|
||||
if finished {
|
||||
return diff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sc StatusChecker) Command(repoCfg config.RepositoryConfig) CommandStatus {
|
||||
|
||||
cmdStatus := CommandStatus{
|
||||
Name: repoCfg.Name,
|
||||
Changed: false,
|
||||
Message: "",
|
||||
Error: false,
|
||||
}
|
||||
|
||||
destPath := fmt.Sprintf("%v/%v", sc.workspace, repoCfg.Dest)
|
||||
repo, err := git.PlainOpen(destPath)
|
||||
|
||||
if err != nil {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
headReference, err := repo.Head()
|
||||
if err != nil {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
remotes, err := repo.Remotes()
|
||||
if err != nil || len(remotes) == 0 {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = "cannot find remote branches"
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
currentBranchCommit, err := repo.CommitObject(headReference.Hash())
|
||||
if err != nil {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
type remoteStatus struct {
|
||||
ahead int
|
||||
behind int
|
||||
err error
|
||||
}
|
||||
|
||||
remotesStatus := make(map[string]remoteStatus)
|
||||
|
||||
for _, remote := range remotes {
|
||||
remoteName := remote.Config().Name
|
||||
|
||||
remoteRevision, err := repo.ResolveRevision(plumbing.Revision(fmt.Sprintf("%v/%v", remoteName, headReference.Name().Short())))
|
||||
if err != nil {
|
||||
remotesStatus[remoteName] = remoteStatus{
|
||||
err: err,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
remoteBranchCommit, err := repo.CommitObject(*remoteRevision)
|
||||
if err != nil {
|
||||
remotesStatus[remoteName] = remoteStatus{
|
||||
err: err,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
status := remoteStatus{
|
||||
ahead: findNumberOfCommitDiffs(currentBranchCommit, remoteBranchCommit),
|
||||
behind: findNumberOfCommitDiffs(remoteBranchCommit, currentBranchCommit),
|
||||
}
|
||||
if status.ahead > 0 || status.behind > 0 {
|
||||
cmdStatus.Changed = true
|
||||
}
|
||||
remotesStatus[remoteName] = status
|
||||
|
||||
}
|
||||
cmdStatus.Message = fmt.Sprintf("branch %v", headReference.Name().Short())
|
||||
for remoteName, status := range remotesStatus {
|
||||
if status.err != nil {
|
||||
cmdStatus.Message = fmt.Sprintf("%v - ( | %v | problem: %v )", cmdStatus.Message, remoteName, status.err.Error())
|
||||
continue
|
||||
}
|
||||
cmdStatus.Message = fmt.Sprintf("%v - ( | %v | \u2191%v \u2193%v )", cmdStatus.Message, remoteName, status.ahead, status.behind)
|
||||
}
|
||||
|
||||
return cmdStatus
|
||||
}
|
||||
250
internal/commands/status_cmd_test.go
Normal file
250
internal/commands/status_cmd_test.go
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
)
|
||||
|
||||
func TestIfBranchesAreEqual(t *testing.T) {
|
||||
tmpDirWithInitialRepository := getTestSetup()
|
||||
|
||||
fakeLocalRepo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
|
||||
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
currentReference, err := fakeLocalRepo.Head()
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
remote, err := fakeLocalRepo.Remote("origin")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
remoteRevision, err := fakeLocalRepo.ResolveRevision(plumbing.Revision(fmt.Sprintf("%v/%v", remote.Config().Name, currentReference.Name().Short())))
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
currentBranchCommit, err := fakeLocalRepo.CommitObject(currentReference.Hash())
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
remoteBranchCommit, err := fakeLocalRepo.CommitObject(*remoteRevision)
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
result := findNumberOfCommitDiffs(currentBranchCommit, remoteBranchCommit)
|
||||
if result != 0 {
|
||||
t.Errorf("Expected to get 0 changes, instead of this got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfCurrentBranchIsDifferent(t *testing.T) {
|
||||
|
||||
tmpDirWithInitialRepository := getTestSetup()
|
||||
fakeLocalRepo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
|
||||
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
localWorktree, err := fakeLocalRepo.Worktree()
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
makeCommit(localWorktree, "commit 1")
|
||||
|
||||
localReference, err := fakeLocalRepo.Head()
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
remoteConnection, err := fakeLocalRepo.Remote("origin")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
remoteRevision, err := fakeLocalRepo.ResolveRevision(plumbing.Revision(fmt.Sprintf("%v/%v", remoteConnection.Config().Name, localReference.Name().Short())))
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
currentBranchCommit, err := fakeLocalRepo.CommitObject(localReference.Hash())
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
remoteBranchCommit, err := fakeLocalRepo.CommitObject(*remoteRevision)
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
result := findNumberOfCommitDiffs(currentBranchCommit, remoteBranchCommit)
|
||||
if result != 1 {
|
||||
t.Errorf("Expected to get 1 changes, instead of this got %v", result)
|
||||
}
|
||||
|
||||
for i := 1; i < 15; i++ {
|
||||
makeCommit(localWorktree, fmt.Sprintf("Commit +%v", i))
|
||||
}
|
||||
|
||||
localReference, err = fakeLocalRepo.Head()
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
currentBranchCommit, err = fakeLocalRepo.CommitObject(localReference.Hash())
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
result = findNumberOfCommitDiffs(currentBranchCommit, remoteBranchCommit)
|
||||
if result != 15 {
|
||||
t.Errorf("Expected to get 5 changes, instead of this got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandRepositoryDoesNotExists(t *testing.T) {
|
||||
|
||||
tmpDirWithInitialRepository := getTestSetup()
|
||||
fsForLocalRepo, storageForTestRepo := getFSForLocalRepo("noMatterValue", tmpDirWithInitialRepository.rootFS)
|
||||
|
||||
_, err := git.Clone(storageForTestRepo, fsForLocalRepo, &git.CloneOptions{
|
||||
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
sc := StatusChecker{
|
||||
workspace: tmpDirWithInitialRepository.rootFS.Root(),
|
||||
}
|
||||
|
||||
repoCfg := config.RepositoryConfig{
|
||||
Name: "test",
|
||||
Src: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
Dest: tmpDirWithInitialRepository.rootFS.Root(),
|
||||
}
|
||||
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "repository does not exist"
|
||||
|
||||
if !repoStatus.Error {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
if repoStatus.Changed {
|
||||
t.Errorf("Unexpected change value")
|
||||
}
|
||||
if repoStatus.Message != expectedMessage {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandRepositoryNoRemoteBranch(t *testing.T) {
|
||||
|
||||
tmpDirWithInitialRepository := getTestSetup()
|
||||
dirNameForLocalRepository := "testRepo"
|
||||
fsForLocalRepo, storageForTestRepo := getFSForLocalRepo(dirNameForLocalRepository, tmpDirWithInitialRepository.rootFS)
|
||||
|
||||
fakeLocalRepository, err := git.Clone(storageForTestRepo, fsForLocalRepo, &git.CloneOptions{
|
||||
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
err = fakeLocalRepository.DeleteRemote("origin")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
sc := StatusChecker{
|
||||
workspace: tmpDirWithInitialRepository.rootFS.Root(),
|
||||
}
|
||||
|
||||
repoCfg := config.RepositoryConfig{
|
||||
Name: "test",
|
||||
Src: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
Dest: dirNameForLocalRepository,
|
||||
}
|
||||
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "cannot find remote branches"
|
||||
|
||||
if !repoStatus.Error {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
if repoStatus.Changed {
|
||||
t.Errorf("Unexpected change value")
|
||||
}
|
||||
|
||||
if repoStatus.Message != expectedMessage {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandAllCorrectWithoutChanges(t *testing.T) {
|
||||
|
||||
sc, _, repoCfg, _ := getBaseForTestingSyncCommand()
|
||||
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "branch master - ( | origin | \u21910 \u21930 )"
|
||||
|
||||
if repoStatus.Error {
|
||||
t.Errorf("Unexpected error")
|
||||
t.Errorf("Message %v", repoStatus.Message)
|
||||
}
|
||||
if repoStatus.Changed {
|
||||
t.Errorf("Expeected that changed will be true")
|
||||
}
|
||||
if repoStatus.Message != expectedMessage {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
|
||||
}
|
||||
}
|
||||
func TestCommandAllCorrectWithOneChange(t *testing.T) {
|
||||
|
||||
sc, fakeLocalRepository, repoCfg, _ := getBaseForTestingSyncCommand()
|
||||
|
||||
fakeLocalWorkTree, err := fakeLocalRepository.Worktree()
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
makeCommit(fakeLocalWorkTree, "commit 1")
|
||||
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "branch master - ( | origin | \u21911 \u21930 )"
|
||||
|
||||
if repoStatus.Message != expectedMessage {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
|
||||
}
|
||||
if repoStatus.Error {
|
||||
t.Errorf("Unexpected error")
|
||||
t.Errorf("Message %v", repoStatus.Message)
|
||||
}
|
||||
if !repoStatus.Changed {
|
||||
t.Errorf("Expeected that changed will be true")
|
||||
}
|
||||
if repoStatus.Message != expectedMessage {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandMultiRemoteNoChanges(t *testing.T) {
|
||||
|
||||
sc, _, repoCfg := getBaseForTestingSyncMultipleRemote()
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "branch master - ( | origin | \u21910 \u21930 ) - ( | subremote | \u21910 \u21930 )"
|
||||
|
||||
if repoStatus.Error {
|
||||
t.Errorf("Unexpected error")
|
||||
t.Errorf("Message %v", repoStatus.Message)
|
||||
}
|
||||
if repoStatus.Changed {
|
||||
t.Errorf("Expeected that changed will be true")
|
||||
}
|
||||
if repoStatus.Message != expectedMessage {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandMultiRemoteWithOneChange(t *testing.T) {
|
||||
sc, fakeLocalRepository, repoCfg := getBaseForTestingSyncMultipleRemote()
|
||||
|
||||
fakeLocalWorkTree, err := fakeLocalRepository.Worktree()
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
makeCommit(fakeLocalWorkTree, "commit 1")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "branch master - ( | origin | \u21911 \u21930 ) - ( | subremote | \u21911 \u21930 )"
|
||||
|
||||
if repoStatus.Error {
|
||||
t.Errorf("Unexpected error")
|
||||
t.Errorf("Message %v", repoStatus.Message)
|
||||
}
|
||||
if !repoStatus.Changed {
|
||||
t.Errorf("Expeected that changed will be true")
|
||||
}
|
||||
if repoStatus.Message != expectedMessage {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
|
||||
}
|
||||
}
|
||||
88
internal/commands/sync_cmd.go
Normal file
88
internal/commands/sync_cmd.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
)
|
||||
|
||||
type Synchronizer struct {
|
||||
workspace string
|
||||
}
|
||||
|
||||
func NewSynchronizer(workspace string) Synchronizer {
|
||||
return Synchronizer{
|
||||
workspace: workspace,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
syncUpToDate = "up to date"
|
||||
syncFetched = "has been fetched" // Why fetched, instead of updated? To be consistent with git commands :D
|
||||
syncCloned = "has been cloned"
|
||||
)
|
||||
|
||||
func fetchRepository(repo *git.Repository) (bool, error) {
|
||||
err := repo.Fetch(&git.FetchOptions{})
|
||||
|
||||
if err == git.NoErrAlreadyUpToDate {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func cloneRepository(destPath string, repoCfg *config.RepositoryConfig) (bool, error) {
|
||||
_, err := git.PlainClone(destPath, false, &git.CloneOptions{
|
||||
URL: repoCfg.Src,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s Synchronizer) Command(repoCfg config.RepositoryConfig) CommandStatus {
|
||||
var err error
|
||||
|
||||
cmdStatus := CommandStatus{
|
||||
Name: repoCfg.Name,
|
||||
Changed: false,
|
||||
Message: "",
|
||||
Error: false,
|
||||
}
|
||||
|
||||
destPath := fmt.Sprintf("%v/%v", s.workspace, repoCfg.Dest)
|
||||
repo, err := git.PlainOpen(destPath)
|
||||
|
||||
if err != nil && err == git.ErrRepositoryNotExists {
|
||||
cmdStatus.Changed, err = cloneRepository(destPath, &repoCfg)
|
||||
cmdStatus.Message = syncCloned
|
||||
} else if err == nil {
|
||||
cmdStatus.Changed, err = fetchRepository(repo)
|
||||
if cmdStatus.Changed {
|
||||
cmdStatus.Message = syncFetched
|
||||
} else {
|
||||
cmdStatus.Message = syncUpToDate
|
||||
}
|
||||
} else {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
}
|
||||
|
||||
return cmdStatus
|
||||
|
||||
}
|
||||
63
internal/commands/sync_cmd_test.go
Normal file
63
internal/commands/sync_cmd_test.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSyncInit(t *testing.T) {
|
||||
sync := NewSynchronizer("test")
|
||||
if sync.workspace != "test" {
|
||||
t.Errorf("Expected to get \"test\", instead of this got %v", sync.workspace)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncCommand(t *testing.T) {
|
||||
|
||||
testSetup := getTestSetup()
|
||||
|
||||
sync := Synchronizer{
|
||||
workspace: testSetup.rootFS.Root(),
|
||||
}
|
||||
|
||||
cfg := config.RepositoryConfig{
|
||||
Src: fmt.Sprintf("file://%v", testSetup.baseRepository.fileSystem.Root()),
|
||||
Dest: "awesome-go",
|
||||
}
|
||||
|
||||
cloneStatus := sync.Command(cfg)
|
||||
if cloneStatus.Error {
|
||||
t.Errorf("Unexpected error: %v", cloneStatus.Message)
|
||||
}
|
||||
|
||||
info, err := os.Stat(fmt.Sprintf("%v/awesome-go/.git", testSetup.rootFS.Root()))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err.Error())
|
||||
}
|
||||
if !info.IsDir() {
|
||||
t.Errorf("Expected that the selected path is dir")
|
||||
}
|
||||
|
||||
if cloneStatus.Changed != true {
|
||||
t.Errorf("Expected that the status is changed")
|
||||
}
|
||||
|
||||
if cloneStatus.Message != syncCloned {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", syncCloned, cloneStatus.Message)
|
||||
}
|
||||
|
||||
fetchStatus := sync.Command(cfg)
|
||||
if fetchStatus.Error {
|
||||
t.Errorf("Unexpected error: %v", err.Error())
|
||||
}
|
||||
|
||||
if fetchStatus.Changed != false {
|
||||
t.Errorf("Expected that the status is not changed")
|
||||
}
|
||||
|
||||
if fetchStatus.Message != syncUpToDate {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", syncUpToDate, cloneStatus.Message)
|
||||
}
|
||||
}
|
||||
83
internal/config/cmd.go
Normal file
83
internal/config/cmd.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/akamensky/argparse"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfigPath = ".config/grm/config.yaml"
|
||||
)
|
||||
|
||||
func getDefaultConfigDir() string {
|
||||
// Only systems like Unix, Linux, and Windows systems are supported
|
||||
userHomeDir, _ := os.UserHomeDir()
|
||||
return fmt.Sprintf("%v/%v", userHomeDir, defaultConfigPath)
|
||||
}
|
||||
|
||||
func ParseCliArguments(name, description string, arguments []string) (CliArguments, error) {
|
||||
|
||||
parser := argparse.NewParser(name, description)
|
||||
|
||||
syncCMD := parser.NewCommand("sync", "Synchronize repositories with remote branches, if the repository does not exist, clone it. (If pulling is not possible, the repository will be fetched)")
|
||||
statusCMD := parser.NewCommand("status", "Get information about repositories")
|
||||
|
||||
configFile := parser.String("c", "config-file", &argparse.Options{
|
||||
Default: getDefaultConfigDir(),
|
||||
Help: "Path to the configuration file",
|
||||
})
|
||||
|
||||
version := parser.Flag("v", "version", &argparse.Options{
|
||||
Default: false,
|
||||
Help: "Print version",
|
||||
})
|
||||
|
||||
color := parser.Flag("", "no-color", &argparse.Options{
|
||||
Default: false,
|
||||
Help: "Turn off color printing",
|
||||
})
|
||||
|
||||
limitName := parser.String("n", "name", &argparse.Options{
|
||||
Help: "Limit action to the specified repository name",
|
||||
})
|
||||
|
||||
limitTags := parser.StringList("t", "tag", &argparse.Options{
|
||||
Help: "Limit actions to repositories that contain specific tags",
|
||||
})
|
||||
|
||||
limitRoutines := parser.Int("", "max-concurrent-process", &argparse.Options{
|
||||
Default: 10,
|
||||
Help: "Determine how many tasks can run simultaneously",
|
||||
})
|
||||
|
||||
ignoreSkipped := parser.Flag("", "ignore-skip-flag", &argparse.Options{
|
||||
Help: "Run selected command for all repositories with ignoring the skip flag",
|
||||
})
|
||||
|
||||
if err := parser.Parse(arguments); err != nil {
|
||||
return CliArguments{}, errors.New(parser.Usage("Please follow this help"))
|
||||
}
|
||||
|
||||
if !syncCMD.Happened() && !(*version) && !statusCMD.Happened() {
|
||||
return CliArguments{}, errors.New(errNoCommand)
|
||||
}
|
||||
|
||||
if *limitName != "" && len(*limitTags) != 0 {
|
||||
return CliArguments{}, errors.New(errNameAndTagsTogether)
|
||||
}
|
||||
|
||||
return CliArguments{
|
||||
ConfigurationFile: *configFile,
|
||||
Sync: syncCMD.Happened(),
|
||||
Status: statusCMD.Happened(),
|
||||
Version: *version,
|
||||
Color: !(*color),
|
||||
LimitToName: *limitName,
|
||||
LimitToTags: *limitTags,
|
||||
Routines: *limitRoutines,
|
||||
IgnoreSkipped: *ignoreSkipped,
|
||||
}, nil
|
||||
}
|
||||
83
internal/config/cmd_test.go
Normal file
83
internal/config/cmd_test.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetDefaultConfigDir(t *testing.T) {
|
||||
ud, _ := os.UserHomeDir()
|
||||
|
||||
desiredPath := fmt.Sprintf("%v/%v", ud, defaultConfigPath)
|
||||
|
||||
if getDefaultConfigDir() != desiredPath {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", desiredPath, getDefaultConfigDir())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParsingDefaultArguments(t *testing.T) {
|
||||
|
||||
// First item in os.Args is appPath, this have to be mocked
|
||||
fakeOSArgs := []string{"appName", "sync"}
|
||||
|
||||
parseResult, err := ParseCliArguments("", "", fakeOSArgs)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err.Error())
|
||||
}
|
||||
|
||||
if parseResult.ConfigurationFile != getDefaultConfigDir() {
|
||||
t.Errorf("Default value for configurationFile should be %v, instead of this got %v", getDefaultConfigDir(), parseResult.ConfigurationFile)
|
||||
}
|
||||
|
||||
if parseResult.Sync != true {
|
||||
t.Errorf("Default value for configurationFile should be %v, instead of this got %v", true, parseResult.Sync)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsingWithoutCommand(t *testing.T) {
|
||||
|
||||
// First item in os.Args is appPath, this have to be mocked
|
||||
fakeOSArgs := []string{"appName", "--name", "test"}
|
||||
|
||||
results, err := ParseCliArguments("", "", fakeOSArgs)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, not results: %v", results)
|
||||
}
|
||||
if err.Error() != errNoCommand {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", errNoCommand, err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParsingNothingProvided(t *testing.T) {
|
||||
|
||||
// First item in os.Args is appPath, this have to be mocked
|
||||
fakeOSArgs := []string{"appName"}
|
||||
|
||||
results, err := ParseCliArguments("", "", fakeOSArgs)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, not results: %v", results)
|
||||
}
|
||||
|
||||
}
|
||||
func TestParsingNameAndTags(t *testing.T) {
|
||||
|
||||
// First item in os.Args is appPath, this have to be mocked
|
||||
fakeOSArgs := []string{"appName", "status", "--tag", "example", "--name", "example"}
|
||||
|
||||
results, err := ParseCliArguments("", "", fakeOSArgs)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, not results: %v", results)
|
||||
}
|
||||
|
||||
if err.Error() != errNameAndTagsTogether {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", errNameAndTagsTogether, err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
69
internal/config/config_file_parse.go
Normal file
69
internal/config/config_file_parse.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func GetRepositoryConfig(data []byte, fileExtension string) (Configuration, error) {
|
||||
|
||||
var config Configuration
|
||||
var err error
|
||||
|
||||
data = []byte(os.ExpandEnv(string(data)))
|
||||
|
||||
switch fileExtension {
|
||||
case "yaml":
|
||||
err = yaml.Unmarshal(data, &config)
|
||||
default:
|
||||
return Configuration{}, errors.New(errNotSupportedType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Configuration{}, err
|
||||
}
|
||||
|
||||
if config.Workspace == "" {
|
||||
|
||||
return Configuration{}, errors.New(errMissingWorkspaceField)
|
||||
}
|
||||
// Get counters to check if name or dest values are not duplicated
|
||||
nameFieldCounter := make(map[string][]int)
|
||||
destFieldCounter := make(map[string][]int)
|
||||
|
||||
for index, repo := range config.Repositories {
|
||||
if repo.Src == "" {
|
||||
errorMessage := fmt.Sprintf(errMissingSrcField, index)
|
||||
return Configuration{}, errors.New(errorMessage)
|
||||
}
|
||||
if repo.Name == "" {
|
||||
splittedGit := strings.Split(repo.Src, "/")
|
||||
nameWithExcention := splittedGit[len(splittedGit)-1]
|
||||
name := strings.Split(nameWithExcention, ".")[0]
|
||||
config.Repositories[index].Name = name
|
||||
}
|
||||
if repo.Dest == "" {
|
||||
config.Repositories[index].Dest = config.Repositories[index].Name
|
||||
}
|
||||
nameFieldCounter[config.Repositories[index].Name] = append(nameFieldCounter[config.Repositories[index].Name], index)
|
||||
destFieldCounter[config.Repositories[index].Dest] = append(destFieldCounter[config.Repositories[index].Dest], index)
|
||||
}
|
||||
|
||||
for rowId, items := range nameFieldCounter {
|
||||
if len(items) != 1 {
|
||||
return Configuration{}, getDuplicateFieldError("name", rowId, items)
|
||||
}
|
||||
}
|
||||
|
||||
for rowId, items := range destFieldCounter {
|
||||
if len(items) != 1 {
|
||||
return Configuration{}, getDuplicateFieldError("dest", rowId, items)
|
||||
}
|
||||
}
|
||||
|
||||
return config, err
|
||||
}
|
||||
157
internal/config/config_file_parse_test.go
Normal file
157
internal/config/config_file_parse_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNotSupportedFileExcension(t *testing.T) {
|
||||
|
||||
_, err := GetRepositoryConfig([]byte("test"), "custom")
|
||||
if err == nil {
|
||||
t.Error("Expected to get error")
|
||||
}
|
||||
if err.Error() != errNotSupportedType {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", errNotSupportedType, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRepositoryConfigFromYaml(t *testing.T) {
|
||||
|
||||
var exampleYamlConfig = []byte(`
|
||||
workspace: ${HOME}
|
||||
repositories:
|
||||
- src: "https://github.com/example/example.git"
|
||||
dest: "example/path"
|
||||
name: "custom_example"
|
||||
- src: https://github.com/example/example2.git
|
||||
tags:
|
||||
- "example"
|
||||
`)
|
||||
|
||||
homedir, _ := os.UserHomeDir()
|
||||
|
||||
var destinationConfiguration = Configuration{
|
||||
Workspace: homedir,
|
||||
Repositories: []RepositoryConfig{
|
||||
{
|
||||
Name: "custom_example",
|
||||
Dest: "example/path",
|
||||
Src: "https://github.com/example/example.git",
|
||||
},
|
||||
{
|
||||
Name: "example2",
|
||||
Src: "https://github.com/example/example2.git",
|
||||
Dest: "example2",
|
||||
Tags: []string{"example"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, err := GetRepositoryConfig(exampleYamlConfig, "yaml")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result.Repositories, destinationConfiguration.Repositories) {
|
||||
t.Errorf("Default value for configurationFile should be:\n %v \ninstead of this got:\n %v", result, destinationConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrongYamlFormat(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`---
|
||||
workspace: "/test"
|
||||
repositories:
|
||||
- src: "https://github.com/example/example.git"
|
||||
dest: "example/path"
|
||||
name: "custom_example"
|
||||
`)
|
||||
|
||||
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
expectedError := "yaml: line 2: found character that cannot start any token"
|
||||
if err.Error() != expectedError {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingWorkspaceRequiredField(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`---
|
||||
repositories:
|
||||
- src: "https://github.com/example/example.git"
|
||||
dest: "example/path"
|
||||
name: "custom_example"
|
||||
`)
|
||||
|
||||
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
|
||||
if err.Error() != errMissingWorkspaceField {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", errMissingWorkspaceField, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingSourceRequiredField(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`---
|
||||
workspace: /tmp
|
||||
repositories:
|
||||
- dest: "example/path"
|
||||
name: "custom_example"
|
||||
`)
|
||||
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
|
||||
expectedError := fmt.Sprintf(errMissingSrcField, 0)
|
||||
|
||||
if err.Error() != expectedError {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicatedNameField(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`
|
||||
workspace: "/tmp"
|
||||
repositories:
|
||||
- src: "https://github.com/example/example1.git"
|
||||
dest: "example/path"
|
||||
name: "custom_example"
|
||||
- src: "https://github.com/example/example2.git"
|
||||
name: "example2"
|
||||
- src: "https://github.com/example/example2.git"
|
||||
`)
|
||||
result, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected result: %v", result)
|
||||
|
||||
}
|
||||
expectedError := getDuplicateFieldError("name", "example2", []int{1, 2})
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError.Error(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicatedDestField(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`
|
||||
workspace: "/tmp"
|
||||
repositories:
|
||||
- src: "https://github.com/example/example1.git"
|
||||
dest: "example/path"
|
||||
- src: "https://github.com/example/example2.git"
|
||||
dest: "example"
|
||||
- src: "https://github.com/example/example3.git"
|
||||
dest: "example"
|
||||
`)
|
||||
result, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected result: %v", result)
|
||||
}
|
||||
|
||||
expectedError := getDuplicateFieldError("dest", "example", []int{1, 2})
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
t.Errorf("Expected to get error with value \"%v\", instead of this got: \"%v\"", expectedError, err)
|
||||
}
|
||||
}
|
||||
25
internal/config/errors.go
Normal file
25
internal/config/errors.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
errNoCommand = "at least one command must be specified, use help to get commands"
|
||||
errNotSupportedType = "not supported configuration type"
|
||||
errMissingWorkspaceField = "missing required \"workspace\" field"
|
||||
errMissingSrcField = "missing required field the \"src\" in row %v"
|
||||
errNameAndTagsTogether = "name and tags arguments connot be used together"
|
||||
)
|
||||
|
||||
func getDuplicateFieldError(field string, name string, rows []int) error {
|
||||
|
||||
var rowsInString []string
|
||||
for _, row := range rows {
|
||||
rowsInString = append(rowsInString, strconv.Itoa(row))
|
||||
}
|
||||
|
||||
return fmt.Errorf("the %v \"%v\" is duplicated in rows: %v", field, name, strings.Join(rowsInString, ","))
|
||||
}
|
||||
26
internal/config/structures.go
Normal file
26
internal/config/structures.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package config
|
||||
|
||||
type Configuration struct {
|
||||
Workspace string
|
||||
Repositories []RepositoryConfig
|
||||
}
|
||||
|
||||
type RepositoryConfig struct {
|
||||
Name string `yaml:",omitempty"`
|
||||
Src string
|
||||
Dest string `yaml:",omitempty"`
|
||||
Tags []string `yaml:",omitempty"`
|
||||
Skip bool
|
||||
}
|
||||
|
||||
type CliArguments struct {
|
||||
ConfigurationFile string
|
||||
Sync bool
|
||||
Status bool
|
||||
Version bool
|
||||
Color bool
|
||||
LimitToName string
|
||||
LimitToTags []string
|
||||
Routines int
|
||||
IgnoreSkipped bool
|
||||
}
|
||||
72
internal/echo/echo.go
Normal file
72
internal/echo/echo.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package echo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
ColorReset = "\033[0m"
|
||||
ColorRed = "\033[31m"
|
||||
ColorGreen = "\033[32m"
|
||||
ColorYellow = "\033[33m"
|
||||
ColorBlue = "\033[34m"
|
||||
)
|
||||
|
||||
var (
|
||||
useColor bool = false
|
||||
output io.Writer = os.Stdout
|
||||
)
|
||||
|
||||
func Color(enabled bool) {
|
||||
useColor = enabled
|
||||
}
|
||||
|
||||
func Output(writer io.Writer) {
|
||||
output = writer
|
||||
}
|
||||
|
||||
func ErrorfMsg(format string, a ...interface{}) error {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
if useColor {
|
||||
msg = fmt.Sprintf("%vError:%v %v", ColorRed, ColorReset, msg)
|
||||
} else {
|
||||
msg = fmt.Sprintf("Error: %v", msg)
|
||||
}
|
||||
return write(msg)
|
||||
}
|
||||
|
||||
func InfoFMsg(format string, a ...interface{}) error {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
if useColor {
|
||||
msg = fmt.Sprintf("%vInfo:%v %v", ColorBlue, ColorReset, msg)
|
||||
} else {
|
||||
msg = fmt.Sprintf("Info: %v", msg)
|
||||
}
|
||||
return write(msg)
|
||||
}
|
||||
|
||||
func GreenMessageF(format string, a ...interface{}) error {
|
||||
return writeWithColor(fmt.Sprintf(format, a...), ColorGreen)
|
||||
}
|
||||
|
||||
func YellowMessageF(format string, a ...interface{}) error {
|
||||
return writeWithColor(fmt.Sprintf(format, a...), ColorYellow)
|
||||
}
|
||||
func RedMessageF(format string, a ...interface{}) error {
|
||||
return writeWithColor(fmt.Sprintf(format, a...), ColorRed)
|
||||
}
|
||||
|
||||
func writeWithColor(msg string, color string) error {
|
||||
if useColor {
|
||||
return write(fmt.Sprintf("%v%v%v", color, msg, ColorReset))
|
||||
}
|
||||
return write(msg)
|
||||
|
||||
}
|
||||
|
||||
func write(msg string) error {
|
||||
_, err := fmt.Fprintln(output, msg)
|
||||
return err
|
||||
}
|
||||
151
internal/echo/echo_test.go
Normal file
151
internal/echo/echo_test.go
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
package echo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type ExpectedMessageTester struct {
|
||||
expectedMessage string
|
||||
}
|
||||
|
||||
func (emt ExpectedMessageTester) Write(p []byte) (n int, err error) {
|
||||
msg := string(p)
|
||||
if msg != emt.expectedMessage {
|
||||
return 0, fmt.Errorf("expected to get \"%v\", instead of this got \"%v\"", msg, emt.expectedMessage)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func TestOverwriteColor(t *testing.T) {
|
||||
Color(false)
|
||||
if useColor {
|
||||
t.Error("Expected that \"useColor\" will be false")
|
||||
}
|
||||
Color(true)
|
||||
if !useColor {
|
||||
t.Error("Expected that \"useColor\" will be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverwriteWriter(t *testing.T) {
|
||||
Output(os.Stderr)
|
||||
if output != os.Stderr {
|
||||
t.Error("Expected to receive addresses on os.Stderr")
|
||||
}
|
||||
Output(os.Stdout)
|
||||
if output != os.Stdout {
|
||||
t.Error("Expected to receive addresses on os.Stdout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorfMsgWithoutColor(t *testing.T) {
|
||||
useColor = false
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "Error: test message\n",
|
||||
}
|
||||
err := ErrorfMsg("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorfMsgWithColor(t *testing.T) {
|
||||
useColor = true
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "\033[31mError:\033[0m test message\n",
|
||||
}
|
||||
err := ErrorfMsg("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoMsgFWithoutColor(t *testing.T) {
|
||||
useColor = false
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "Info: test message\n",
|
||||
}
|
||||
err := InfoFMsg("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoMsgFWithColor(t *testing.T) {
|
||||
useColor = true
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "\033[34mInfo:\033[0m test message\n",
|
||||
}
|
||||
err := InfoFMsg("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGreenMessageWithoutColor(t *testing.T) {
|
||||
useColor = false
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "test message\n",
|
||||
}
|
||||
err := GreenMessageF("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGreenMessageWithColor(t *testing.T) {
|
||||
useColor = true
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "\033[32mtest message\033[0m\n",
|
||||
}
|
||||
err := GreenMessageF("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestYellowMessageWithout(t *testing.T) {
|
||||
useColor = false
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "test message\n",
|
||||
}
|
||||
err := YellowMessageF("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestYellowMessageWithColor(t *testing.T) {
|
||||
useColor = true
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "\033[33mtest message\033[0m\n",
|
||||
}
|
||||
err := YellowMessageF("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedMessageWithout(t *testing.T) {
|
||||
useColor = false
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "test message\n",
|
||||
}
|
||||
err := RedMessageF("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
func TestRedMessageWithColor(t *testing.T) {
|
||||
useColor = true
|
||||
output = ExpectedMessageTester{
|
||||
expectedMessage: "\033[31mtest message\033[0m\n",
|
||||
}
|
||||
err := RedMessageF("test message")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
157
internal/grm/app.go
Normal file
157
internal/grm/app.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package grm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
commands2 "gitlab.com/revalus/grm/internal/commands"
|
||||
config2 "gitlab.com/revalus/grm/internal/config"
|
||||
"gitlab.com/revalus/grm/internal/echo"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
AppName = "Git repository manager"
|
||||
AppDescription = "Manage your repository with simple grm"
|
||||
VERSION = "0.3.0"
|
||||
errNotFoundTags = "no repository was found with the specified tags"
|
||||
errNotFoundName = "no repository was found with the specified name"
|
||||
)
|
||||
|
||||
type GitRepositoryManager struct {
|
||||
cliArguments config2.CliArguments
|
||||
configuration config2.Configuration
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) Parse(args []string) error {
|
||||
arguments, err := config2.ParseCliArguments(AppName, AppDescription, args)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
configFileContent, err := getFileContent(arguments.ConfigurationFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
fileExtension, err := getFileExtension(arguments.ConfigurationFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
configuration, err := config2.GetRepositoryConfig(configFileContent, fileExtension)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
g.cliArguments = arguments
|
||||
g.configuration = configuration
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) Run(w io.Writer) int {
|
||||
|
||||
echo.Color(g.cliArguments.Color)
|
||||
echo.Output(w)
|
||||
|
||||
exitCode := 0
|
||||
|
||||
if len(g.cliArguments.LimitToTags) != 0 {
|
||||
err := g.limitTags()
|
||||
if err != nil {
|
||||
echo.ErrorfMsg(err.Error())
|
||||
exitCode = 1
|
||||
}
|
||||
}
|
||||
|
||||
if g.cliArguments.LimitToName != "" {
|
||||
err := g.limitName()
|
||||
if err != nil {
|
||||
echo.ErrorfMsg(err.Error())
|
||||
exitCode = 1
|
||||
}
|
||||
}
|
||||
|
||||
if g.cliArguments.Sync && exitCode == 0 {
|
||||
echo.InfoFMsg("Synchronizing repositories")
|
||||
sync := commands2.NewSynchronizer(g.configuration.Workspace)
|
||||
g.runCommand(sync)
|
||||
echo.InfoFMsg("All repositories are synced")
|
||||
}
|
||||
|
||||
if g.cliArguments.Status && exitCode == 0 {
|
||||
echo.InfoFMsg("Current status of repositories")
|
||||
status := commands2.NewStatusChecker(g.configuration.Workspace)
|
||||
g.runCommand(status)
|
||||
}
|
||||
|
||||
if g.cliArguments.Version {
|
||||
echo.InfoFMsg("Current version: %v", VERSION)
|
||||
}
|
||||
return exitCode
|
||||
}
|
||||
|
||||
func describeStatus(status commands2.CommandStatus) {
|
||||
if status.Error {
|
||||
echo.RedMessageF("Repository \"%v\": an error occurred: %v", status.Name, status.Message)
|
||||
return
|
||||
}
|
||||
|
||||
if status.Changed {
|
||||
echo.YellowMessageF("Repository \"%v\": %v", status.Name, status.Message)
|
||||
} else {
|
||||
echo.GreenMessageF("Repository \"%v\": %v", status.Name, status.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) limitTags() error {
|
||||
limitedTagsTmp := []config2.RepositoryConfig{}
|
||||
|
||||
for _, item := range g.configuration.Repositories {
|
||||
if checkAnyOfItemInSlice(item.Tags, g.cliArguments.LimitToTags) {
|
||||
limitedTagsTmp = append(limitedTagsTmp, item)
|
||||
}
|
||||
}
|
||||
if len(limitedTagsTmp) == 0 {
|
||||
return errors.New(errNotFoundTags)
|
||||
}
|
||||
g.configuration.Repositories = reverseRepositoryConfigs(limitedTagsTmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) limitName() error {
|
||||
for _, item := range g.configuration.Repositories {
|
||||
if g.cliArguments.LimitToName == item.Name {
|
||||
g.configuration.Repositories = []config2.RepositoryConfig{item}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New(errNotFoundName)
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) runCommand(cmd commands2.Command) {
|
||||
routines := make(chan struct{}, g.cliArguments.Routines)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, repo := range g.configuration.Repositories {
|
||||
|
||||
if repo.Skip && !g.cliArguments.IgnoreSkipped {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
go func(r config2.RepositoryConfig) {
|
||||
defer wg.Done()
|
||||
routines <- struct{}{}
|
||||
describeStatus(cmd.Command(r))
|
||||
|
||||
<-routines
|
||||
}(repo)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
432
internal/grm/app_test.go
Normal file
432
internal/grm/app_test.go
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
package grm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitlab.com/revalus/grm/internal/commands"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
"gitlab.com/revalus/grm/internal/echo"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type FakeCommandToTest struct {
|
||||
triggerError bool
|
||||
triggerChanged bool
|
||||
}
|
||||
|
||||
type ExpectedMessageTester struct {
|
||||
expectedMessages []string
|
||||
}
|
||||
|
||||
func (emt ExpectedMessageTester) Write(p []byte) (n int, err error) {
|
||||
msg := string(p)
|
||||
if !checkIsItemInSlice(msg, emt.expectedMessages) {
|
||||
panic(fmt.Sprintf("the message \"%v\"does not match any of the given patterns: %#v", msg, emt.expectedMessages))
|
||||
} else {
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (fk FakeCommandToTest) Command(repoCfg config.RepositoryConfig) commands.CommandStatus {
|
||||
status := commands.CommandStatus{
|
||||
Name: repoCfg.Name,
|
||||
Changed: false,
|
||||
Message: "response from fake command",
|
||||
Error: false,
|
||||
}
|
||||
|
||||
if fk.triggerError {
|
||||
status.Error = true
|
||||
}
|
||||
if fk.triggerChanged {
|
||||
status.Changed = true
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func prepareConfigContent() (string, string) {
|
||||
checkErrorDuringPreparation := func(err error) {
|
||||
if err != nil {
|
||||
fmt.Printf("Cannot prepare a temporary directory for testing! %v ", err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
baseTmp := fmt.Sprintf("%v/grmTest", os.TempDir())
|
||||
if _, ok := os.Stat(baseTmp); ok != nil {
|
||||
err := os.Mkdir(baseTmp, 0777)
|
||||
checkErrorDuringPreparation(err)
|
||||
}
|
||||
|
||||
tempDir, err := os.MkdirTemp(baseTmp, "*")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
configFilePath := fmt.Sprintf("%v/config-file.yaml", tempDir)
|
||||
|
||||
file, err := os.Create(configFilePath)
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
defer file.Close()
|
||||
|
||||
yamlConfig := fmt.Sprintf(`
|
||||
workspace: %v
|
||||
repositories:
|
||||
- src: "https://github.com/golang/example.git"
|
||||
tags: ['example']
|
||||
`, tempDir)
|
||||
|
||||
_, err = file.WriteString(yamlConfig)
|
||||
|
||||
checkErrorDuringPreparation(err)
|
||||
return tempDir, configFilePath
|
||||
}
|
||||
|
||||
func TestParseApplication(t *testing.T) {
|
||||
workdir, configFile := prepareConfigContent()
|
||||
t.Cleanup(func() {
|
||||
os.Remove(workdir)
|
||||
})
|
||||
|
||||
args := []string{"custom-grm", "sync", "-c", configFile}
|
||||
grm := GitRepositoryManager{}
|
||||
grm.Parse(args)
|
||||
|
||||
if workdir != grm.configuration.Workspace {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", workdir, grm.configuration.Repositories)
|
||||
}
|
||||
|
||||
if !grm.cliArguments.Sync {
|
||||
t.Error("The value of \"sync\" is expected to be true")
|
||||
}
|
||||
|
||||
expectedRepo := config.RepositoryConfig{
|
||||
Name: "example",
|
||||
Src: "https://github.com/golang/example.git",
|
||||
Dest: "example",
|
||||
Tags: []string{"example"},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectedRepo, grm.configuration.Repositories[0]) {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", expectedRepo, grm.configuration.Repositories[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputFromSync(t *testing.T) {
|
||||
grm := GitRepositoryManager{
|
||||
configuration: config.Configuration{
|
||||
Workspace: "/tmp",
|
||||
},
|
||||
cliArguments: config.CliArguments{
|
||||
Sync: true,
|
||||
Version: true,
|
||||
Color: false,
|
||||
Routines: 10,
|
||||
},
|
||||
}
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Info: Synchronizing repositories\n",
|
||||
"Info: All repositories are synced\n",
|
||||
fmt.Sprintf("Info: Current version: %v\n", VERSION),
|
||||
},
|
||||
}
|
||||
grm.Run(emt)
|
||||
}
|
||||
|
||||
func TestLimitTags(t *testing.T) {
|
||||
grm := GitRepositoryManager{
|
||||
cliArguments: config.CliArguments{
|
||||
LimitToTags: []string{"example"},
|
||||
Routines: 10,
|
||||
},
|
||||
configuration: config.Configuration{
|
||||
Repositories: []config.RepositoryConfig{
|
||||
{Name: "example1", Tags: []string{"example"}},
|
||||
{Name: "example2", Tags: []string{"example"}},
|
||||
{Name: "notExample"},
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeCommand := FakeCommandToTest{
|
||||
triggerError: false,
|
||||
triggerChanged: false,
|
||||
}
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Repository \"example1\": response from fake command\n",
|
||||
"Repository \"example2\": response from fake command\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
echo.Output(emt)
|
||||
grm.limitTags()
|
||||
grm.runCommand(fakeCommand)
|
||||
}
|
||||
|
||||
func TestLimitName(t *testing.T) {
|
||||
grm := GitRepositoryManager{
|
||||
cliArguments: config.CliArguments{
|
||||
LimitToName: "notExample",
|
||||
Routines: 10,
|
||||
},
|
||||
configuration: config.Configuration{
|
||||
Repositories: []config.RepositoryConfig{
|
||||
{Name: "example1", Tags: []string{"example"}},
|
||||
{Name: "example2", Tags: []string{"example"}},
|
||||
{Name: "notExample"},
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeCommand := FakeCommandToTest{
|
||||
triggerError: false,
|
||||
triggerChanged: false,
|
||||
}
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Repository \"notExample\": response from fake command\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
echo.Output(emt)
|
||||
grm.limitName()
|
||||
grm.runCommand(fakeCommand)
|
||||
}
|
||||
func TestRunWithNotExistingNameInLimit(t *testing.T) {
|
||||
grm := GitRepositoryManager{
|
||||
cliArguments: config.CliArguments{
|
||||
LimitToName: "not-existing-name",
|
||||
Routines: 10,
|
||||
},
|
||||
configuration: config.Configuration{
|
||||
Repositories: []config.RepositoryConfig{
|
||||
{Name: "example1", Tags: []string{"example"}},
|
||||
{Name: "example2", Tags: []string{"example"}},
|
||||
{Name: "notExample"},
|
||||
},
|
||||
},
|
||||
}
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Error: no repository was found with the specified name\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
status := grm.Run(emt)
|
||||
if status != 1 {
|
||||
t.Errorf("Expected to get status %v, instead o this got %v", 1, status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWithNotExistingTagsInLimit(t *testing.T) {
|
||||
grm := GitRepositoryManager{
|
||||
cliArguments: config.CliArguments{
|
||||
LimitToTags: []string{"not-existing-tag"},
|
||||
Routines: 10,
|
||||
},
|
||||
configuration: config.Configuration{
|
||||
Repositories: []config.RepositoryConfig{
|
||||
{Name: "example1", Tags: []string{"example"}},
|
||||
{Name: "example2", Tags: []string{"example"}},
|
||||
{Name: "notExample"},
|
||||
},
|
||||
},
|
||||
}
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Error: no repository was found with the specified tags\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
status := grm.Run(emt)
|
||||
if status != 1 {
|
||||
t.Errorf("Expected to get status %v, instead o this got %v", 1, status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStatusOutput(t *testing.T) {
|
||||
grm := GitRepositoryManager{
|
||||
configuration: config.Configuration{
|
||||
Workspace: "/tmp",
|
||||
},
|
||||
cliArguments: config.CliArguments{
|
||||
Status: true,
|
||||
Routines: 10,
|
||||
},
|
||||
}
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Info: Current status of repositories\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
status := grm.Run(emt)
|
||||
if status != 0 {
|
||||
t.Errorf("Expected to get status %v, instead o this got %v", 1, status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeStatusErrorNoColor(t *testing.T) {
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Repository \"Test\": an error occurred: test\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
echo.Output(emt)
|
||||
status := commands.CommandStatus{
|
||||
Name: "Test",
|
||||
Message: "test",
|
||||
Error: true,
|
||||
}
|
||||
|
||||
describeStatus(status)
|
||||
}
|
||||
|
||||
func TestDescribeStatusErrorColor(t *testing.T) {
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
fmt.Sprintf("%vRepository \"Test\": an error occurred: test%v\n", echo.ColorRed, echo.ColorReset),
|
||||
},
|
||||
}
|
||||
echo.Color(true)
|
||||
echo.Output(emt)
|
||||
status := commands.CommandStatus{
|
||||
Name: "Test",
|
||||
Message: "test",
|
||||
Error: true,
|
||||
}
|
||||
|
||||
describeStatus(status)
|
||||
}
|
||||
|
||||
func TestDescribeStatusChangedNoColor(t *testing.T) {
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Repository \"Test\": test\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
echo.Output(emt)
|
||||
status := commands.CommandStatus{
|
||||
Name: "Test",
|
||||
Message: "test",
|
||||
Changed: true,
|
||||
}
|
||||
|
||||
describeStatus(status)
|
||||
}
|
||||
|
||||
func TestDescribeStatusChangedColor(t *testing.T) {
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
fmt.Sprintf("%vRepository \"Test\": test%v\n", echo.ColorYellow, echo.ColorReset),
|
||||
},
|
||||
}
|
||||
echo.Color(true)
|
||||
echo.Output(emt)
|
||||
status := commands.CommandStatus{
|
||||
Name: "Test",
|
||||
Message: "test",
|
||||
Changed: true,
|
||||
}
|
||||
|
||||
describeStatus(status)
|
||||
}
|
||||
|
||||
func TestDescribeStatusNoChangeNoColor(t *testing.T) {
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Repository \"Test\": test\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
echo.Output(emt)
|
||||
status := commands.CommandStatus{
|
||||
Name: "Test",
|
||||
Message: "test",
|
||||
Changed: false,
|
||||
}
|
||||
|
||||
describeStatus(status)
|
||||
}
|
||||
|
||||
func TestDescribeStatusNoChangeColor(t *testing.T) {
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
fmt.Sprintf("%vRepository \"Test\": test%v\n", echo.ColorGreen, echo.ColorReset),
|
||||
},
|
||||
}
|
||||
echo.Color(true)
|
||||
echo.Output(emt)
|
||||
status := commands.CommandStatus{
|
||||
Name: "Test",
|
||||
Message: "test",
|
||||
Changed: false,
|
||||
}
|
||||
|
||||
describeStatus(status)
|
||||
}
|
||||
|
||||
func TestSkipRepository(t *testing.T) {
|
||||
grm := GitRepositoryManager{
|
||||
cliArguments: config.CliArguments{
|
||||
LimitToTags: []string{"example"},
|
||||
Routines: 10,
|
||||
},
|
||||
configuration: config.Configuration{
|
||||
Repositories: []config.RepositoryConfig{
|
||||
{Name: "example1"},
|
||||
{Name: "example2", Skip: true},
|
||||
{Name: "example3"},
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeCommand := FakeCommandToTest{
|
||||
triggerError: false,
|
||||
triggerChanged: false,
|
||||
}
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Repository \"example1\": response from fake command\n",
|
||||
"Repository \"example3\": response from fake command\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
echo.Output(emt)
|
||||
grm.runCommand(fakeCommand)
|
||||
}
|
||||
|
||||
func TestSkipRepositoryWithIgnore(t *testing.T) {
|
||||
grm := GitRepositoryManager{
|
||||
cliArguments: config.CliArguments{
|
||||
LimitToTags: []string{"example"},
|
||||
Routines: 10,
|
||||
IgnoreSkipped: true,
|
||||
},
|
||||
configuration: config.Configuration{
|
||||
Repositories: []config.RepositoryConfig{
|
||||
{Name: "example1"},
|
||||
{Name: "example2", Skip: true},
|
||||
{Name: "example3"},
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeCommand := FakeCommandToTest{
|
||||
triggerError: false,
|
||||
triggerChanged: false,
|
||||
}
|
||||
emt := ExpectedMessageTester{
|
||||
expectedMessages: []string{
|
||||
"Repository \"example1\": response from fake command\n",
|
||||
"Repository \"example2\": response from fake command\n",
|
||||
"Repository \"example3\": response from fake command\n",
|
||||
},
|
||||
}
|
||||
echo.Color(false)
|
||||
echo.Output(emt)
|
||||
grm.runCommand(fakeCommand)
|
||||
}
|
||||
54
internal/grm/utils.go
Normal file
54
internal/grm/utils.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package grm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getFileContent(pathToFile string) ([]byte, error) {
|
||||
return os.ReadFile(pathToFile)
|
||||
}
|
||||
|
||||
func getFileExtension(pathToFile string) (string, error) {
|
||||
splitFileName := strings.Split(pathToFile, ".")
|
||||
|
||||
if len(splitFileName) == 1 {
|
||||
msg := fmt.Sprintf("excension for file \"%v\", not found", splitFileName)
|
||||
return "", errors.New(msg)
|
||||
}
|
||||
|
||||
fileExtension := splitFileName[len(splitFileName)-1]
|
||||
|
||||
return fileExtension, nil
|
||||
}
|
||||
|
||||
func checkIsItemInSlice(check string, sliceToCheck []string) bool {
|
||||
for _, item := range sliceToCheck {
|
||||
if item == check {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkAnyOfItemInSlice(check []string, sliceToCheck []string) bool {
|
||||
|
||||
for _, item := range check {
|
||||
if checkIsItemInSlice(item, sliceToCheck) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func reverseRepositoryConfigs(repositories []config.RepositoryConfig) []config.RepositoryConfig {
|
||||
for i, j := 0, len(repositories)-1; i < j; i, j = i+1, j-1 {
|
||||
repositories[i], repositories[j] = repositories[j], repositories[i]
|
||||
}
|
||||
return repositories
|
||||
}
|
||||
84
internal/grm/utils_test.go
Normal file
84
internal/grm/utils_test.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package grm
|
||||
|
||||
import (
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetFileExtension(t *testing.T) {
|
||||
|
||||
toTest := map[string]string{
|
||||
"myYamlFile.yaml": "yaml",
|
||||
"myTxtFile.txt": "txt",
|
||||
"myJsonFile.json": "json",
|
||||
}
|
||||
|
||||
for key, value := range toTest {
|
||||
result, err := getFileExtension(key)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if result != value {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", value, result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestErrorInGetExcensionFile(t *testing.T) {
|
||||
|
||||
result, err := getFileExtension("test")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected to get error, instead of this got result %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsItemInSlice(t *testing.T) {
|
||||
testedSlice := []string{"1", "2", "3", "4", "5"}
|
||||
|
||||
result := checkIsItemInSlice("0", testedSlice)
|
||||
if result {
|
||||
t.Error("Expected to get false as result")
|
||||
}
|
||||
|
||||
result = checkIsItemInSlice("1", testedSlice)
|
||||
if !result {
|
||||
t.Error("Expected to get true as result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyInSlice(t *testing.T) {
|
||||
testedSlice := []string{"1", "2", "3", "4", "5"}
|
||||
|
||||
result := checkAnyOfItemInSlice([]string{"0", "10"}, testedSlice)
|
||||
if result {
|
||||
t.Error("Expected to get false as result")
|
||||
}
|
||||
|
||||
result = checkAnyOfItemInSlice([]string{"0", "5"}, testedSlice)
|
||||
if !result {
|
||||
t.Error("Expected to get true as result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverseStringsSlice(t *testing.T) {
|
||||
testedSlice := []config.RepositoryConfig{
|
||||
{Name: "test1"},
|
||||
{Name: "test2"},
|
||||
{Name: "test3"},
|
||||
}
|
||||
expectedResult := []config.RepositoryConfig{
|
||||
{Name: "test3"},
|
||||
{Name: "test2"},
|
||||
{Name: "test1"},
|
||||
}
|
||||
result := reverseRepositoryConfigs(testedSlice)
|
||||
if !reflect.DeepEqual(result, expectedResult) {
|
||||
t.Errorf("Expected to get \"%#v\", instead of this got \"%#v\"", expectedResult, result)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue