Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
9d7a64a371 | |||
29d40969c2 | |||
68636d5e64 | |||
8cd3ed2127 | |||
9f4d85e707 | |||
806ff9b4c1 | |||
d7615a90ba | |||
14dcb9a66e | |||
5445ce1ccf | |||
741f18efd1 | |||
2d06e8e09c | |||
19e68df732 | |||
62dadc53bf | |||
5fd9bc851b | |||
23e4547e52 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
build
|
||||
.idea
|
25
README.md
25
README.md
@ -11,9 +11,10 @@ Create a yaml file with the given structure
|
||||
workspace: ${HOME}/workspace
|
||||
# List with repositories, that you want to manage with GRM
|
||||
repositories:
|
||||
- src: "git@github.com:Revalus/GitRepositoryManager.git"
|
||||
- src: "git@github.com:Revalus/GitRepositoryManager.git" # Required - specified repository to clone/fetch data
|
||||
name: GRM # Optional, Uniq - if no name is specified, the repository will be treated as a name
|
||||
dest: manager # Optional, Uniq - if no value is specified, name will be taken as destination
|
||||
tags: ['companyX', 'departmentY'] # Optional - tags to specify to limit/exclude actions on the repository
|
||||
```
|
||||
|
||||
### Note
|
||||
@ -24,19 +25,27 @@ By default, the config file is searched for in `[HOME_DIR]./config/grm/config.ya
|
||||
|
||||
### Global args
|
||||
|
||||
| argument | type | default | Description |
|
||||
|-------------------|--------|--------------------------------------|----------------------------------------------------------------------|
|
||||
| -c, --config-file | string | `[HOME_DIR]./config/grm/config.yaml` | Path to configuration file, where the repositories must be specified |
|
||||
| argument | type | default | Description |
|
||||
|------------------------------|----------|--------------------------------------|------------------------------------------------------------------------|
|
||||
| **-c**, **--config-file** | *string* | `[HOME_DIR]./config/grm/config.yaml` | Path to configuration file, where the repositories must be specified |
|
||||
| **-v**, **--version** | *bool* | `false` | Display current version |
|
||||
| **--no-color** | *bool* | `false` | Turning off the display of output in color |
|
||||
| **-n** **--name** | *string* | `empty` | Limit action to the specified repository name |
|
||||
| **-t** **--tag** | *string* | `empty` | Limit action to the specified repository tag (may be more than one tag)|
|
||||
| **max-concurrent-process** | *string* | `empty` | Determine how many tasks can run simultaneously |
|
||||
|
||||
### Commands
|
||||
|
||||
| command | Description |
|
||||
|---------|-------------|
|
||||
| sync | Fetches changes from repositories or pulls a repository if one does not exist.
|
||||
| status | Get repository information - what is the current branch, how many commits are above and behind it for each remote.
|
||||
| command | Description |
|
||||
|---------|--------------------------------------------------------------------------------------------------------------------|
|
||||
| sync | Fetches changes from repositories or pulls a repository if one does not exist. |
|
||||
| status | Get repository information - what is the current branch, how many commits are above and behind it for each remote. |
|
||||
|
||||
## Changelog
|
||||
|
||||
- 0.3.2 3rd party security libs update
|
||||
- 0.3.1 Upgrade to Go 1.19
|
||||
- 0.3.0 Adding the ability to limit actions to repositories containing a given name or tags
|
||||
- 0.2.0 Add status command - get information about the current status in the repository
|
||||
- 0.1.1 Allow to use env vars in config
|
||||
- 0.1.0 Add sync command - allow to fetch and clone repositories
|
||||
|
26
Taskfile.yml
Normal file
26
Taskfile.yml
Normal file
@ -0,0 +1,26 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
pull:
|
||||
cmds:
|
||||
- go mod download
|
||||
silent: true
|
||||
|
||||
test:
|
||||
cmds:
|
||||
- go test $(find './internal' -name "*.go" -exec dirname {} \; | uniq | grep '/') -cover
|
||||
|
||||
build:
|
||||
dir: "cmd"
|
||||
deps:
|
||||
- pull
|
||||
cmds:
|
||||
- mkdir -p ../build
|
||||
- go build -o ../build/grm
|
||||
|
||||
install:
|
||||
dir: "build"
|
||||
deps:
|
||||
- build
|
||||
cmds:
|
||||
- cp grm ${GOPATH}/bin/grm
|
93
app/app.go
93
app/app.go
@ -1,93 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gitlab.com/revalus/grm/commands"
|
||||
"gitlab.com/revalus/grm/config"
|
||||
)
|
||||
|
||||
const (
|
||||
APP_NAME = "Git repository manager"
|
||||
APP_DESCRIPTION = "Manage your repository with simple app"
|
||||
VERSION = "0.2.0"
|
||||
)
|
||||
|
||||
type GitRepositoryManager struct {
|
||||
cliArguments config.CliArguments
|
||||
configuration config.Configuration
|
||||
console ConsoleOutput
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) Parse(args []string) {
|
||||
co := ConsoleOutput{}
|
||||
|
||||
checkCriticalError := func(err error) {
|
||||
if err != nil {
|
||||
co.ErrorfMsg("%v", err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
arguments, err := config.ParseCliArguments(APP_NAME, APP_DESCRIPTION, args)
|
||||
checkCriticalError(err)
|
||||
|
||||
configFileContent, err := getFileContent(arguments.ConfigurationFile)
|
||||
checkCriticalError(err)
|
||||
|
||||
fileExcension, err := getFileExcension(arguments.ConfigurationFile)
|
||||
checkCriticalError(err)
|
||||
|
||||
configuration, err := config.GetRepositoryConfig(configFileContent, fileExcension)
|
||||
checkCriticalError(err)
|
||||
co.Color = arguments.Color
|
||||
|
||||
g.console = co
|
||||
g.cliArguments = arguments
|
||||
g.configuration = configuration
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) Run() {
|
||||
if g.cliArguments.Sync {
|
||||
g.console.InfoFMsg("Synchronizing repositories")
|
||||
println()
|
||||
sync := commands.NewSynchronizer(g.configuration.Workspace)
|
||||
g.runCommand(sync)
|
||||
println()
|
||||
g.console.InfoFMsg("All repositories are synced")
|
||||
}
|
||||
|
||||
if g.cliArguments.Status {
|
||||
status := commands.NewStatusChecker(g.configuration.Workspace)
|
||||
g.runCommand(status)
|
||||
}
|
||||
|
||||
if g.cliArguments.Version {
|
||||
g.console.InfoFMsg("Current version: %v", VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
func (g GitRepositoryManager) describeStatus(status commands.CommandStatus) {
|
||||
if status.Error {
|
||||
g.console.ErrorStatusF("Repository \"%v\": an error occurred: %v", status.Name, status.Message)
|
||||
return
|
||||
}
|
||||
|
||||
if status.Changed {
|
||||
g.console.ChangedStatusF("Repository \"%v\": %v", status.Name, status.Message)
|
||||
} else {
|
||||
g.console.UnchangedStatusF("Repository \"%v\": %v", status.Name, status.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) runCommand(cmd commands.Command) {
|
||||
statusChan := make(chan commands.CommandStatus)
|
||||
|
||||
for _, repo := range g.configuration.Repositories {
|
||||
go cmd.Command(repo, statusChan)
|
||||
}
|
||||
|
||||
for range g.configuration.Repositories {
|
||||
g.describeStatus(<-statusChan)
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitlab.com/revalus/grm/config"
|
||||
)
|
||||
|
||||
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"`, 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-app", "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",
|
||||
}
|
||||
|
||||
if expectedRepo != grm.configuration.Repositories[0] {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", expectedRepo, grm.configuration.Repositories[0])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Example_test_sync_output() {
|
||||
grm := GitRepositoryManager{
|
||||
configuration: config.Configuration{
|
||||
Workspace: "/tmp",
|
||||
},
|
||||
cliArguments: config.CliArguments{
|
||||
Sync: true,
|
||||
Version: true,
|
||||
},
|
||||
console: ConsoleOutput{
|
||||
Color: false,
|
||||
},
|
||||
}
|
||||
grm.Run()
|
||||
// Output:
|
||||
// Info: Synchronizing repositories
|
||||
// Info: All repositories are synced
|
||||
// Info: Current version: 0.2.0
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
colorReset = "\033[0m"
|
||||
colorRed = "\033[31m"
|
||||
colorGreen = "\033[32m"
|
||||
colorYellow = "\033[33m"
|
||||
colorBlue = "\033[34m"
|
||||
)
|
||||
|
||||
type ConsoleOutput struct {
|
||||
Color bool
|
||||
}
|
||||
|
||||
func (co ConsoleOutput) ErrorfMsg(format string, a ...interface{}) {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
if co.Color {
|
||||
msg = fmt.Sprintf("%vError:%v %v", colorRed, colorReset, msg)
|
||||
} else {
|
||||
msg = fmt.Sprintf("Error: %v", msg)
|
||||
}
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
func (co ConsoleOutput) InfoFMsg(format string, a ...interface{}) {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
if co.Color {
|
||||
msg = fmt.Sprintf("%vInfo:%v %v", colorBlue, colorReset, msg)
|
||||
} else {
|
||||
msg = fmt.Sprintf("Info: %v", msg)
|
||||
}
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
func (co ConsoleOutput) UnchangedStatusF(format string, a ...interface{}) {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
if co.Color {
|
||||
msg = fmt.Sprintf("%v%v%v", colorGreen, msg, colorReset)
|
||||
}
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
func (co ConsoleOutput) ChangedStatusF(format string, a ...interface{}) {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
if co.Color {
|
||||
msg = fmt.Sprintf("%v%v%v", colorYellow, msg, colorReset)
|
||||
}
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
func (co ConsoleOutput) ErrorStatusF(format string, a ...interface{}) {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
if co.Color {
|
||||
msg = fmt.Sprintf("%v%v%v", colorRed, msg, colorReset)
|
||||
}
|
||||
fmt.Println(msg)
|
||||
}
|
25
app/utils.go
25
app/utils.go
@ -1,25 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getFileContent(pathToFile string) ([]byte, error) {
|
||||
return ioutil.ReadFile(pathToFile)
|
||||
}
|
||||
|
||||
func getFileExcension(pathToFile string) (string, error) {
|
||||
splittedFileName := strings.Split(pathToFile, ".")
|
||||
|
||||
if len(splittedFileName) == 1 {
|
||||
msg := fmt.Sprintf("excension for file \"%v\", not found", splittedFileName)
|
||||
return "", errors.New(msg)
|
||||
}
|
||||
|
||||
fileExcension := splittedFileName[len(splittedFileName)-1]
|
||||
|
||||
return fileExcension, nil
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package app
|
||||
|
||||
import "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 := getFileExcension(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 := getFileExcension("test")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected to get error, instead of this got result %v", result)
|
||||
}
|
||||
|
||||
}
|
18
cmd/main.go
Normal file
18
cmd/main.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitlab.com/revalus/grm/internal/grm"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app := grm.GitRepositoryManager{}
|
||||
err := app.Parse(os.Args)
|
||||
if err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
exitCode := app.Run(os.Stdout)
|
||||
os.Exit(exitCode)
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
"gitlab.com/revalus/grm/config"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
41
go.mod
41
go.mod
@ -1,30 +1,33 @@
|
||||
module gitlab.com/revalus/grm
|
||||
|
||||
go 1.17
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/akamensky/argparse v1.3.1
|
||||
github.com/go-git/go-billy/v5 v5.3.1
|
||||
github.com/go-git/go-git/v5 v5.4.2
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
github.com/akamensky/argparse v1.4.0
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.11.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210920160938-87db9fbc61c7 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
||||
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 // indirect
|
||||
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c // indirect
|
||||
github.com/skeema/knownhosts v1.2.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
180
go.sum
180
go.sum
@ -1,124 +1,132 @@
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210920160938-87db9fbc61c7 h1:DSqTh6nEes/uO8BlNcGk8PzZsxY2sN9ZL//veWBdTRI=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210920160938-87db9fbc61c7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
||||
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/akamensky/argparse v1.3.1 h1:kP6+OyvR0fuBH6UhbE6yh/nskrDEIQgEA1SUXDPjx4g=
|
||||
github.com/akamensky/argparse v1.3.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
|
||||
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
|
||||
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
|
||||
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
|
||||
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
|
||||
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
|
||||
github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
|
||||
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
||||
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU=
|
||||
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c h1:QOfDMdrf/UwlVR0UBq2Mpr58UzNtvgJRXA4BgPfFACs=
|
||||
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -1,9 +1,11 @@
|
||||
package commands
|
||||
|
||||
import "gitlab.com/revalus/grm/config"
|
||||
import (
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
)
|
||||
|
||||
type Command interface {
|
||||
Command(repoCfg config.RepositoryConfig, cmdStatus chan CommandStatus)
|
||||
Command(repoCfg config.RepositoryConfig) CommandStatus
|
||||
}
|
||||
type CommandStatus struct {
|
||||
Name string
|
118
internal/commands/common_utils_test.go
Normal file
118
internal/commands/common_utils_test.go
Normal file
@ -0,0 +1,118 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"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 TestSetupPaths struct {
|
||||
baseTestDirectory string
|
||||
baseTestRepository string
|
||||
}
|
||||
|
||||
func checkErrorDuringPreparation(err error) {
|
||||
if err != nil {
|
||||
fmt.Printf("Cannot prepare a temporary directory for testing! %v ", err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func createRepositoryForTest() string {
|
||||
systemTMPDirectoryWithTestPath := fmt.Sprintf("%v/grmTest", os.TempDir())
|
||||
if _, ok := os.Stat(systemTMPDirectoryWithTestPath); ok != nil {
|
||||
err := os.Mkdir(systemTMPDirectoryWithTestPath, 0777)
|
||||
checkErrorDuringPreparation(err)
|
||||
}
|
||||
temporaryDirPath, err := os.MkdirTemp(systemTMPDirectoryWithTestPath, "*")
|
||||
checkErrorDuringPreparation(err)
|
||||
return temporaryDirPath
|
||||
}
|
||||
|
||||
// prepareRepositoryDirectories - prepare directories for file (rootRepositoryDirectory) and git metadata (gitMetadataDirectory)
|
||||
func prepareRepositoryDirectories(dirName string) (billy.Filesystem, billy.Filesystem) {
|
||||
rootRepositoryDirectory := osfs.New(dirName)
|
||||
gitMetadataDirectory, err := rootRepositoryDirectory.Chroot(".git")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
return rootRepositoryDirectory, gitMetadataDirectory
|
||||
}
|
||||
|
||||
func prepareBasicRepository() TestSetupPaths {
|
||||
|
||||
temporaryDirPath := createRepositoryForTest()
|
||||
|
||||
// Create an interface of abstraction over filesystem to provide tests over multiple systems
|
||||
// baseTestsDirectory - provides to main directory where new directories might be created
|
||||
baseTestsDirectory := osfs.New(temporaryDirPath)
|
||||
rootRepositoryDirectory, gitMetadataDirectory := prepareRepositoryDirectories(baseTestsDirectory.Root() + "/base_repository")
|
||||
|
||||
repository, err := git.Init(filesystem.NewStorage(
|
||||
gitMetadataDirectory,
|
||||
cache.NewObjectLRUDefault()),
|
||||
rootRepositoryDirectory,
|
||||
)
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
testFile, err := rootRepositoryDirectory.Create("TestFile.txt")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
_, err = testFile.Write([]byte("foo-conent"))
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
repositoryWorkTree, err := repository.Worktree()
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
_, err = repositoryWorkTree.Add(testFile.Name())
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
_, err = repositoryWorkTree.Commit("First commit", &git.CommitOptions{})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
return TestSetupPaths{
|
||||
baseTestDirectory: baseTestsDirectory.Root(),
|
||||
baseTestRepository: rootRepositoryDirectory.Root(),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func makeCommit(wk *git.Worktree, commitMessage string) {
|
||||
_, err := wk.Commit(commitMessage, &git.CommitOptions{})
|
||||
checkErrorDuringPreparation(err)
|
||||
}
|
||||
|
||||
// createAndCloneRepository - create sub-repository with cloned base repository required to verify sync command
|
||||
func createAndCloneRepository(repositoryName string, paths TestSetupPaths) *git.Repository {
|
||||
|
||||
baseGitRepository, gitMetadataDirectory := prepareRepositoryDirectories(
|
||||
path.Join(paths.baseTestDirectory, repositoryName),
|
||||
)
|
||||
|
||||
storageForSubRepository := filesystem.NewStorage(gitMetadataDirectory, cache.NewObjectLRUDefault())
|
||||
fakeLocalRepository, err := git.Clone(storageForSubRepository, baseGitRepository, &git.CloneOptions{
|
||||
URL: paths.baseTestRepository,
|
||||
})
|
||||
checkErrorDuringPreparation(err)
|
||||
return fakeLocalRepository
|
||||
}
|
||||
|
||||
func addLocalRepositoryAsAFakeRemoteRepository(repository *git.Repository, baseTestRepositoryPath string) error {
|
||||
|
||||
_, err := repository.CreateRemote(&gitcfg.RemoteConfig{
|
||||
Name: "subremote",
|
||||
URLs: []string{baseTestRepositoryPath},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return repository.Fetch(&git.FetchOptions{
|
||||
RemoteName: "subremote",
|
||||
})
|
||||
}
|
@ -2,11 +2,12 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"gitlab.com/revalus/grm/config"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
"path"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type StatusChecker struct {
|
||||
@ -20,7 +21,7 @@ func NewStatusChecker(workspace string) StatusChecker {
|
||||
}
|
||||
|
||||
func findNumberOfCommitDiffs(srcCommit *object.Commit, dstCommit *object.Commit) int {
|
||||
|
||||
// This function is a helper function to get only five latest items, based on given commit
|
||||
getFiveElementsFromHashes := func(commit *object.Commit, hashedSlice *[]string) *object.Commit {
|
||||
|
||||
var err error
|
||||
@ -36,6 +37,7 @@ func findNumberOfCommitDiffs(srcCommit *object.Commit, dstCommit *object.Commit)
|
||||
return commit
|
||||
}
|
||||
|
||||
// Compare diff between sources by hash list (the same hash list must be present to assume the end of changes)
|
||||
getRangeDiff := func(listFist, listSecond []string) (int, bool) {
|
||||
diffRange := 0
|
||||
|
||||
@ -52,8 +54,10 @@ func findNumberOfCommitDiffs(srcCommit *object.Commit, dstCommit *object.Commit)
|
||||
return diffRange, false
|
||||
}
|
||||
|
||||
baseCommitHashes := []string{}
|
||||
destCommitHashes := []string{}
|
||||
var baseCommitHashes []string
|
||||
var destCommitHashes []string
|
||||
|
||||
// Try to find all differences, limit only to five last changes to avoid reading whole repository at once
|
||||
for {
|
||||
|
||||
if srcCommit != nil {
|
||||
@ -71,7 +75,7 @@ func findNumberOfCommitDiffs(srcCommit *object.Commit, dstCommit *object.Commit)
|
||||
}
|
||||
}
|
||||
|
||||
func (sc StatusChecker) Command(repoCfg config.RepositoryConfig, status chan CommandStatus) {
|
||||
func (sc StatusChecker) Command(repoCfg config.RepositoryConfig) CommandStatus {
|
||||
|
||||
cmdStatus := CommandStatus{
|
||||
Name: repoCfg.Name,
|
||||
@ -80,38 +84,34 @@ func (sc StatusChecker) Command(repoCfg config.RepositoryConfig, status chan Com
|
||||
Error: false,
|
||||
}
|
||||
|
||||
destPath := fmt.Sprintf("%v/%v", sc.workspace, repoCfg.Dest)
|
||||
repo, err := git.PlainOpen(destPath)
|
||||
repositoryPath := path.Join(sc.workspace, repoCfg.Dest)
|
||||
repo, err := git.PlainOpen(repositoryPath)
|
||||
|
||||
if err != nil {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
status <- cmdStatus
|
||||
return
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
headReference, err := repo.Head()
|
||||
if err != nil {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
status <- cmdStatus
|
||||
return
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
remotes, err := repo.Remotes()
|
||||
if err != nil || len(remotes) == 0 {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = "cannot find remote branches"
|
||||
status <- cmdStatus
|
||||
return
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
currentBranchCommit, err := repo.CommitObject(headReference.Hash())
|
||||
if err != nil {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
status <- cmdStatus
|
||||
return
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
type remoteStatus struct {
|
||||
@ -120,14 +120,14 @@ func (sc StatusChecker) Command(repoCfg config.RepositoryConfig, status chan Com
|
||||
err error
|
||||
}
|
||||
|
||||
remotesStatus := make(map[string]remoteStatus)
|
||||
var remoteNames []string
|
||||
remoteStatues := 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{
|
||||
remoteStatues[remoteName] = remoteStatus{
|
||||
err: err,
|
||||
}
|
||||
continue
|
||||
@ -135,7 +135,7 @@ func (sc StatusChecker) Command(repoCfg config.RepositoryConfig, status chan Com
|
||||
|
||||
remoteBranchCommit, err := repo.CommitObject(*remoteRevision)
|
||||
if err != nil {
|
||||
remotesStatus[remoteName] = remoteStatus{
|
||||
remoteStatues[remoteName] = remoteStatus{
|
||||
err: err,
|
||||
}
|
||||
continue
|
||||
@ -148,11 +148,14 @@ func (sc StatusChecker) Command(repoCfg config.RepositoryConfig, status chan Com
|
||||
if status.ahead > 0 || status.behind > 0 {
|
||||
cmdStatus.Changed = true
|
||||
}
|
||||
remotesStatus[remoteName] = status
|
||||
remoteNames = append(remoteNames, remoteName)
|
||||
remoteStatues[remoteName] = status
|
||||
|
||||
}
|
||||
sort.Strings(remoteNames)
|
||||
cmdStatus.Message = fmt.Sprintf("branch %v", headReference.Name().Short())
|
||||
for remoteName, status := range remotesStatus {
|
||||
for _, remoteName := range remoteNames {
|
||||
status := remoteStatues[remoteName]
|
||||
if status.err != nil {
|
||||
cmdStatus.Message = fmt.Sprintf("%v - ( | %v | problem: %v )", cmdStatus.Message, remoteName, status.err.Error())
|
||||
continue
|
||||
@ -160,5 +163,5 @@ func (sc StatusChecker) Command(repoCfg config.RepositoryConfig, status chan Com
|
||||
cmdStatus.Message = fmt.Sprintf("%v - ( | %v | \u2191%v \u2193%v )", cmdStatus.Message, remoteName, status.ahead, status.behind)
|
||||
}
|
||||
|
||||
status <- cmdStatus
|
||||
return cmdStatus
|
||||
}
|
@ -2,20 +2,23 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-git/go-git/v5/plumbing/cache"
|
||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
"path"
|
||||
"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"
|
||||
"gitlab.com/revalus/grm/config"
|
||||
)
|
||||
|
||||
func TestIfBranchesAreEqual(t *testing.T) {
|
||||
tmpDirWithInitialRepository := getTestSetup()
|
||||
pathsToTest := prepareBasicRepository()
|
||||
|
||||
fakeLocalRepo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
|
||||
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
URL: pathsToTest.baseTestRepository,
|
||||
})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
@ -42,9 +45,9 @@ func TestIfBranchesAreEqual(t *testing.T) {
|
||||
|
||||
func TestIfCurrentBranchIsDifferent(t *testing.T) {
|
||||
|
||||
tmpDirWithInitialRepository := getTestSetup()
|
||||
pathsToTest := prepareBasicRepository()
|
||||
fakeLocalRepo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
|
||||
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
URL: pathsToTest.baseTestRepository,
|
||||
})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
@ -85,33 +88,24 @@ func TestIfCurrentBranchIsDifferent(t *testing.T) {
|
||||
|
||||
result = findNumberOfCommitDiffs(currentBranchCommit, remoteBranchCommit)
|
||||
if result != 15 {
|
||||
t.Errorf("Expected to get 5 changes, instead of this got %v", result)
|
||||
t.Errorf("Expected to get 15 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)
|
||||
|
||||
pathsToTest := prepareBasicRepository()
|
||||
sc := StatusChecker{
|
||||
workspace: tmpDirWithInitialRepository.rootFS.Root(),
|
||||
workspace: pathsToTest.baseTestDirectory,
|
||||
}
|
||||
|
||||
repoCfg := config.RepositoryConfig{
|
||||
Name: "test",
|
||||
Src: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
Dest: tmpDirWithInitialRepository.rootFS.Root(),
|
||||
Src: pathsToTest.baseTestRepository,
|
||||
Dest: pathsToTest.baseTestDirectory,
|
||||
}
|
||||
|
||||
ch := make(chan CommandStatus)
|
||||
go sc.Command(repoCfg, ch)
|
||||
repoStatus := <-ch
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "repository does not exist"
|
||||
|
||||
if !repoStatus.Error {
|
||||
@ -127,12 +121,15 @@ func TestCommandRepositoryDoesNotExists(t *testing.T) {
|
||||
|
||||
func TestCommandRepositoryNoRemoteBranch(t *testing.T) {
|
||||
|
||||
tmpDirWithInitialRepository := getTestSetup()
|
||||
dirNameForLocalRepository := "testRepo"
|
||||
fsForLocalRepo, storageForTestRepo := getFSForLocalRepo(dirNameForLocalRepository, tmpDirWithInitialRepository.rootFS)
|
||||
pathsToTest := prepareBasicRepository()
|
||||
dirNameForLocalRepository := "sub-repository"
|
||||
fsForLocalRepo, gitMetadataDirectory := prepareRepositoryDirectories(
|
||||
path.Join(pathsToTest.baseTestDirectory, dirNameForLocalRepository),
|
||||
)
|
||||
storageForTestRepo := filesystem.NewStorage(gitMetadataDirectory, cache.NewObjectLRUDefault())
|
||||
|
||||
fakeLocalRepository, err := git.Clone(storageForTestRepo, fsForLocalRepo, &git.CloneOptions{
|
||||
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
URL: pathsToTest.baseTestRepository,
|
||||
})
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
@ -140,19 +137,16 @@ func TestCommandRepositoryNoRemoteBranch(t *testing.T) {
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
sc := StatusChecker{
|
||||
workspace: tmpDirWithInitialRepository.rootFS.Root(),
|
||||
workspace: pathsToTest.baseTestDirectory,
|
||||
}
|
||||
|
||||
repoCfg := config.RepositoryConfig{
|
||||
Name: "test",
|
||||
Src: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
|
||||
Src: pathsToTest.baseTestRepository,
|
||||
Dest: dirNameForLocalRepository,
|
||||
}
|
||||
|
||||
ch := make(chan CommandStatus)
|
||||
|
||||
go sc.Command(repoCfg, ch)
|
||||
repoStatus := <-ch
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "cannot find remote branches"
|
||||
|
||||
if !repoStatus.Error {
|
||||
@ -169,11 +163,23 @@ func TestCommandRepositoryNoRemoteBranch(t *testing.T) {
|
||||
|
||||
func TestCommandAllCorrectWithoutChanges(t *testing.T) {
|
||||
|
||||
sc, _, repoCfg, _ := getBaseForTestingSyncCommand()
|
||||
pathsToTest := prepareBasicRepository()
|
||||
subRepositoryDirectoryName := "sub-repository"
|
||||
|
||||
ch := make(chan CommandStatus)
|
||||
go sc.Command(repoCfg, ch)
|
||||
repoStatus := <-ch
|
||||
// Get new empty repository to compare with base repository
|
||||
createAndCloneRepository(subRepositoryDirectoryName, pathsToTest)
|
||||
|
||||
sc := StatusChecker{
|
||||
workspace: pathsToTest.baseTestDirectory,
|
||||
}
|
||||
|
||||
repoCfg := config.RepositoryConfig{
|
||||
Name: "test",
|
||||
Src: pathsToTest.baseTestRepository,
|
||||
Dest: subRepositoryDirectoryName,
|
||||
}
|
||||
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "branch master - ( | origin | \u21910 \u21930 )"
|
||||
|
||||
if repoStatus.Error {
|
||||
@ -189,17 +195,26 @@ func TestCommandAllCorrectWithoutChanges(t *testing.T) {
|
||||
}
|
||||
func TestCommandAllCorrectWithOneChange(t *testing.T) {
|
||||
|
||||
sc, fakeLocalRepository, repoCfg, _ := getBaseForTestingSyncCommand()
|
||||
pathsToTest := prepareBasicRepository()
|
||||
subRepositoryDirectoryName := "sub-repository"
|
||||
fakeLocalRepository := createAndCloneRepository(subRepositoryDirectoryName, pathsToTest)
|
||||
|
||||
ch := make(chan CommandStatus)
|
||||
sc := StatusChecker{
|
||||
workspace: pathsToTest.baseTestDirectory,
|
||||
}
|
||||
|
||||
repoCfg := config.RepositoryConfig{
|
||||
Name: "test",
|
||||
Src: pathsToTest.baseTestRepository,
|
||||
Dest: subRepositoryDirectoryName,
|
||||
}
|
||||
|
||||
fakeLocalWorkTree, err := fakeLocalRepository.Worktree()
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
makeCommit(fakeLocalWorkTree, "commit 1")
|
||||
|
||||
go sc.Command(repoCfg, ch)
|
||||
repoStatus := <-ch
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "branch master - ( | origin | \u21911 \u21930 )"
|
||||
|
||||
if repoStatus.Message != expectedMessage {
|
||||
@ -217,12 +232,27 @@ func TestCommandAllCorrectWithOneChange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandMultiRemoteNoChanges(t *testing.T) {
|
||||
func TestCommandMultiRemotesNoChanges(t *testing.T) {
|
||||
pathsToTest := prepareBasicRepository()
|
||||
subRepositoryDirectoryName := "sub-repository"
|
||||
fakeLocalRepository := createAndCloneRepository(subRepositoryDirectoryName, pathsToTest)
|
||||
|
||||
sc, _, repoCfg := getBaseForTestingSyncMultipleRemote()
|
||||
ch := make(chan CommandStatus)
|
||||
go sc.Command(repoCfg, ch)
|
||||
repoStatus := <-ch
|
||||
sc := StatusChecker{
|
||||
workspace: pathsToTest.baseTestDirectory,
|
||||
}
|
||||
|
||||
repoCfg := config.RepositoryConfig{
|
||||
Name: "test",
|
||||
Src: pathsToTest.baseTestRepository,
|
||||
Dest: subRepositoryDirectoryName,
|
||||
}
|
||||
|
||||
err := addLocalRepositoryAsAFakeRemoteRepository(fakeLocalRepository, pathsToTest.baseTestRepository)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "branch master - ( | origin | \u21910 \u21930 ) - ( | subremote | \u21910 \u21930 )"
|
||||
|
||||
if repoStatus.Error {
|
||||
@ -237,8 +267,25 @@ func TestCommandMultiRemoteNoChanges(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandMultiRemoteWithOneChange(t *testing.T) {
|
||||
sc, fakeLocalRepository, repoCfg := getBaseForTestingSyncMultipleRemote()
|
||||
func TestCommandMultiRemotesWithOneChange(t *testing.T) {
|
||||
pathsToTest := prepareBasicRepository()
|
||||
subRepositoryDirectoryName := "sub-repository"
|
||||
fakeLocalRepository := createAndCloneRepository(subRepositoryDirectoryName, pathsToTest)
|
||||
|
||||
sc := StatusChecker{
|
||||
workspace: pathsToTest.baseTestDirectory,
|
||||
}
|
||||
|
||||
repoCfg := config.RepositoryConfig{
|
||||
Name: "test",
|
||||
Src: pathsToTest.baseTestRepository,
|
||||
Dest: subRepositoryDirectoryName,
|
||||
}
|
||||
|
||||
err := addLocalRepositoryAsAFakeRemoteRepository(fakeLocalRepository, pathsToTest.baseTestRepository)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
|
||||
fakeLocalWorkTree, err := fakeLocalRepository.Worktree()
|
||||
checkErrorDuringPreparation(err)
|
||||
@ -246,9 +293,7 @@ func TestCommandMultiRemoteWithOneChange(t *testing.T) {
|
||||
makeCommit(fakeLocalWorkTree, "commit 1")
|
||||
checkErrorDuringPreparation(err)
|
||||
|
||||
ch := make(chan CommandStatus)
|
||||
go sc.Command(repoCfg, ch)
|
||||
repoStatus := <-ch
|
||||
repoStatus := sc.Command(repoCfg)
|
||||
expectedMessage := "branch master - ( | origin | \u21911 \u21930 ) - ( | subremote | \u21911 \u21930 )"
|
||||
|
||||
if repoStatus.Error {
|
@ -1,10 +1,11 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"gitlab.com/revalus/grm/config"
|
||||
)
|
||||
|
||||
type Synchronizer struct {
|
||||
@ -26,15 +27,14 @@ const (
|
||||
func fetchRepository(repo *git.Repository) (bool, error) {
|
||||
err := repo.Fetch(&git.FetchOptions{})
|
||||
|
||||
if err == git.NoErrAlreadyUpToDate {
|
||||
switch {
|
||||
case errors.Is(err, git.NoErrAlreadyUpToDate):
|
||||
return false, nil
|
||||
case errors.Is(err, git.NoErrAlreadyUpToDate):
|
||||
return false, nil
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func cloneRepository(destPath string, repoCfg *config.RepositoryConfig) (bool, error) {
|
||||
@ -49,7 +49,7 @@ func cloneRepository(destPath string, repoCfg *config.RepositoryConfig) (bool, e
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s Synchronizer) Command(repoCfg config.RepositoryConfig, status chan CommandStatus) {
|
||||
func (s Synchronizer) Command(repoCfg config.RepositoryConfig) CommandStatus {
|
||||
var err error
|
||||
|
||||
cmdStatus := CommandStatus{
|
||||
@ -62,20 +62,22 @@ func (s Synchronizer) Command(repoCfg config.RepositoryConfig, status chan Comma
|
||||
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
|
||||
if err != nil {
|
||||
if errors.Is(err, git.ErrRepositoryNotExists) {
|
||||
cmdStatus.Changed, err = cloneRepository(destPath, &repoCfg)
|
||||
cmdStatus.Message = syncCloned
|
||||
} else {
|
||||
cmdStatus.Message = syncUpToDate
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
}
|
||||
return cmdStatus
|
||||
}
|
||||
|
||||
cmdStatus.Changed, err = fetchRepository(repo)
|
||||
if cmdStatus.Changed {
|
||||
cmdStatus.Message = syncFetched
|
||||
} else {
|
||||
cmdStatus.Error = true
|
||||
cmdStatus.Message = err.Error()
|
||||
status <- cmdStatus
|
||||
cmdStatus.Message = syncUpToDate
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -83,6 +85,6 @@ func (s Synchronizer) Command(repoCfg config.RepositoryConfig, status chan Comma
|
||||
cmdStatus.Message = err.Error()
|
||||
}
|
||||
|
||||
status <- cmdStatus
|
||||
return cmdStatus
|
||||
|
||||
}
|
@ -2,10 +2,9 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitlab.com/revalus/grm/internal/config"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitlab.com/revalus/grm/config"
|
||||
)
|
||||
|
||||
func TestSyncInit(t *testing.T) {
|
||||
@ -17,31 +16,27 @@ func TestSyncInit(t *testing.T) {
|
||||
|
||||
func TestSyncCommand(t *testing.T) {
|
||||
|
||||
testSetup := getTestSetup()
|
||||
pathsToTest := prepareBasicRepository()
|
||||
|
||||
sync := Synchronizer{
|
||||
workspace: testSetup.rootFS.Root(),
|
||||
workspace: pathsToTest.baseTestDirectory,
|
||||
}
|
||||
|
||||
cfg := config.RepositoryConfig{
|
||||
Src: fmt.Sprintf("file://%v", testSetup.baseRepository.fileSystem.Root()),
|
||||
Src: fmt.Sprintf("file://%v", pathsToTest.baseTestRepository),
|
||||
Dest: "awesome-go",
|
||||
}
|
||||
|
||||
ch := make(chan CommandStatus)
|
||||
|
||||
// Pull part
|
||||
go sync.Command(cfg, ch)
|
||||
|
||||
cloneStatus := <-ch
|
||||
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()))
|
||||
info, err := os.Stat(fmt.Sprintf("%v/awesome-go/.git", pathsToTest.baseTestDirectory))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err.Error())
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
t.Errorf("Expected that the selected path is dir")
|
||||
}
|
||||
@ -54,11 +49,7 @@ func TestSyncCommand(t *testing.T) {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", syncCloned, cloneStatus.Message)
|
||||
}
|
||||
|
||||
// Fetch part
|
||||
go sync.Command(cfg, ch)
|
||||
|
||||
fetchStatus := <-ch
|
||||
|
||||
fetchStatus := sync.Command(cfg)
|
||||
if fetchStatus.Error {
|
||||
t.Errorf("Unexpected error: %v", err.Error())
|
||||
}
|
@ -40,12 +40,29 @@ func ParseCliArguments(name, description string, arguments []string) (CliArgumen
|
||||
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{
|
||||
@ -54,5 +71,9 @@ func ParseCliArguments(name, description string, arguments []string) (CliArgumen
|
||||
Status: statusCMD.Happened(),
|
||||
Version: *version,
|
||||
Color: !(*color),
|
||||
LimitToName: *limitName,
|
||||
LimitToTags: *limitTags,
|
||||
Routines: *limitRoutines,
|
||||
IgnoreSkipped: *ignoreSkipped,
|
||||
}, nil
|
||||
}
|
@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -39,6 +40,22 @@ func TestParsingDefaultArguments(t *testing.T) {
|
||||
|
||||
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 !strings.Contains(err.Error(), "Please follow this help") {
|
||||
t.Errorf("Expected the error contains \"Please follow this help\", but as a result received: \"%v\"", err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParsingNothingProvided(t *testing.T) {
|
||||
|
||||
// First item in os.Args is appPath, this have to be mocked
|
||||
fakeOSArgs := []string{"appName"}
|
||||
|
||||
@ -49,3 +66,19 @@ func TestParsingWithoutCommand(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
}
|
@ -41,9 +41,9 @@ func GetRepositoryConfig(data []byte, fileExtension string) (Configuration, erro
|
||||
return Configuration{}, errors.New(errorMessage)
|
||||
}
|
||||
if repo.Name == "" {
|
||||
splittedGit := strings.Split(repo.Src, "/")
|
||||
nameWithExcention := splittedGit[len(splittedGit)-1]
|
||||
name := strings.Split(nameWithExcention, ".")[0]
|
||||
splitGit := strings.Split(repo.Src, "/")
|
||||
nameWithExtension := splitGit[len(splitGit)-1]
|
||||
name := strings.Split(nameWithExtension, ".")[0]
|
||||
config.Repositories[index].Name = name
|
||||
}
|
||||
if repo.Dest == "" {
|
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
@ -27,6 +28,8 @@ repositories:
|
||||
dest: "example/path"
|
||||
name: "custom_example"
|
||||
- src: https://github.com/example/example2.git
|
||||
tags:
|
||||
- "example"
|
||||
`)
|
||||
|
||||
homedir, _ := os.UserHomeDir()
|
||||
@ -43,6 +46,7 @@ repositories:
|
||||
Name: "example2",
|
||||
Src: "https://github.com/example/example2.git",
|
||||
Dest: "example2",
|
||||
Tags: []string{"example"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -98,9 +102,9 @@ repositories:
|
||||
`)
|
||||
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
|
||||
expectedError := fmt.Sprintf(errMissingSrcField, 0)
|
||||
expectedError := errors.New(fmt.Sprintf(errMissingSrcField, 0))
|
||||
|
||||
if err.Error() != expectedError {
|
||||
if errors.Is(err, expectedError) {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
@ -124,7 +128,7 @@ repositories:
|
||||
}
|
||||
expectedError := getDuplicateFieldError("name", "example2", []int{1, 2})
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
if errors.Is(err, expectedError) {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError.Error(), err.Error())
|
||||
}
|
||||
}
|
||||
@ -148,7 +152,7 @@ repositories:
|
||||
|
||||
expectedError := getDuplicateFieldError("dest", "example", []int{1, 2})
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
if errors.Is(err, expectedError) {
|
||||
t.Errorf("Expected to get error with value \"%v\", instead of this got: \"%v\"", expectedError, err)
|
||||
}
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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 {
|
||||
@ -21,7 +20,5 @@ func getDuplicateFieldError(field string, name string, rows []int) error {
|
||||
rowsInString = append(rowsInString, strconv.Itoa(row))
|
||||
}
|
||||
|
||||
errorMessage := fmt.Sprintf("The %v \"%v\" is duplicated in rows: %v", field, name, strings.Join(rowsInString, ","))
|
||||
|
||||
return errors.New(errorMessage)
|
||||
return fmt.Errorf("the %v \"%v\" is duplicated in rows: %v", field, name, strings.Join(rowsInString, ","))
|
||||
}
|
@ -7,8 +7,10 @@ type Configuration struct {
|
||||
|
||||
type RepositoryConfig struct {
|
||||
Name string `yaml:",omitempty"`
|
||||
Src string `yaml:",omitempty"`
|
||||
Dest string `yaml:",omitempty"`
|
||||
Src string
|
||||
Dest string `yaml:",omitempty"`
|
||||
Tags []string `yaml:",omitempty"`
|
||||
Skip bool
|
||||
}
|
||||
|
||||
type CliArguments struct {
|
||||
@ -17,4 +19,8 @@ type CliArguments struct {
|
||||
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"
|
||||
"gitlab.com/revalus/grm/internal/commands"
|
||||
"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.2"
|
||||
errNotFoundTags = "no repository was found with the specified tags"
|
||||
errNotFoundName = "no repository was found with the specified name"
|
||||
)
|
||||
|
||||
type GitRepositoryManager struct {
|
||||
cliArguments config.CliArguments
|
||||
configuration config.Configuration
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) Parse(args []string) error {
|
||||
arguments, err := config.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 := config.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.limitRepositoriesToTags()
|
||||
if err != nil {
|
||||
echo.ErrorfMsg(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if g.cliArguments.LimitToName != "" {
|
||||
err := g.limitRepositoryToName()
|
||||
if err != nil {
|
||||
echo.ErrorfMsg(err.Error())
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if g.cliArguments.Sync && exitCode == 0 {
|
||||
echo.InfoFMsg("Synchronizing repositories")
|
||||
sync := commands.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 := commands.NewStatusChecker(g.configuration.Workspace)
|
||||
g.runCommand(status)
|
||||
}
|
||||
|
||||
if g.cliArguments.Version {
|
||||
echo.InfoFMsg("Current version: %v", VERSION)
|
||||
}
|
||||
return exitCode
|
||||
}
|
||||
|
||||
func describeStatus(status commands.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) limitRepositoriesToTags() error {
|
||||
limitedTagsTmp := []config.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) limitRepositoryToName() error {
|
||||
for _, item := range g.configuration.Repositories {
|
||||
if g.cliArguments.LimitToName == item.Name {
|
||||
g.configuration.Repositories = []config.RepositoryConfig{item}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New(errNotFoundName)
|
||||
}
|
||||
|
||||
func (g *GitRepositoryManager) runCommand(cmd commands.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 config.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.limitRepositoriesToTags()
|
||||
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.limitRepositoryToName()
|
||||
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 TestErrorInGetExtensionFile(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…
Reference in New Issue
Block a user