Add sync command

This commit is contained in:
Mikołaj Pęczkowski 2021-11-02 19:19:31 +01:00
commit 5e6a5e23fd
18 changed files with 1077 additions and 0 deletions

39
README.md Normal file
View File

@ -0,0 +1,39 @@
# Git repositories manager
Sync your repositories with single click
## Usage
Create a yaml file with the given structure
```yaml
# Place where your repositories should be saved
workspace: ${HOME}/workspace
# List with repositories, that you want to manage with GRM
repositories:
- src: "git@github.com:Revalus/GitRepositoryManager.git"
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
```
### Note
By default, the config file is searched for in `[HOME_DIR]./config/grm/config.yaml`.
## Commands and arguments
### 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 |
### Commands
| command | Description |
|---------|-------------|
| sync | Fetches changes from repositories or pulls a repository if one does not exist.
## Changelog
0.1 Add sync command - allow to fetch and clone repositories

89
app/app.go Normal file
View File

@ -0,0 +1,89 @@
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.1"
)
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.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)
}
}

94
app/app_test.go Normal file
View File

@ -0,0 +1,94 @@
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.1
}

61
app/console_output.go Normal file
View File

@ -0,0 +1,61 @@
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", colorGreen, msg)
}
fmt.Println(msg)
}
func (co ConsoleOutput) ChangedStatusF(format string, a ...interface{}) {
msg := fmt.Sprintf(format, a...)
if co.Color {
msg = fmt.Sprintf("%v%v", colorYellow, msg)
}
fmt.Println(msg)
}
func (co ConsoleOutput) ErrorStatusF(format string, a ...interface{}) {
msg := fmt.Sprintf(format, a...)
if co.Color {
msg = fmt.Sprintf("%v%v", colorRed, msg)
}
fmt.Println(msg)
}

25
app/utils.go Normal file
View File

@ -0,0 +1,25 @@
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
}

36
app/utils_test.go Normal file
View File

@ -0,0 +1,36 @@
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)
}
}

13
commands/command.go Normal file
View File

@ -0,0 +1,13 @@
package commands
import "gitlab.com/revalus/grm/config"
type Command interface {
Command(repoCfg config.RepositoryConfig, cmdStatus chan CommandStatus)
}
type CommandStatus struct {
Name string
Changed bool
Message string
Error bool
}

88
commands/sync_cmd.go Normal file
View File

@ -0,0 +1,88 @@
package commands
import (
"fmt"
"github.com/go-git/go-git/v5"
"gitlab.com/revalus/grm/config"
)
type Synchronizer struct {
workspace string
}
func NewSynchronizer(workspace string) Synchronizer {
return Synchronizer{
workspace: workspace,
}
}
const (
syncUpToDate = "up to date"
syncFetched = "has been fetched" // Why fetched, instead of updated? To be consistent with git commands :D
syncCloned = "has been cloned"
)
func fetchRepository(repo *git.Repository) (bool, error) {
err := repo.Fetch(&git.FetchOptions{})
if err == git.NoErrAlreadyUpToDate {
return false, nil
}
if err != nil && err != git.NoErrAlreadyUpToDate {
return false, err
}
return true, nil
}
func cloneRepository(destPath string, repoCfg *config.RepositoryConfig) (bool, error) {
_, err := git.PlainClone(destPath, false, &git.CloneOptions{
URL: repoCfg.Src,
})
if err != nil {
return false, err
}
return true, nil
}
func (s Synchronizer) Command(repoCfg config.RepositoryConfig, status chan CommandStatus) {
var err error
cmdStatus := CommandStatus{
Name: repoCfg.Name,
Changed: false,
Message: "",
Error: false,
}
destPath := fmt.Sprintf("%v/%v", s.workspace, repoCfg.Dest)
repo, err := git.PlainOpen(destPath)
if err != nil && err == git.ErrRepositoryNotExists {
cmdStatus.Changed, err = cloneRepository(destPath, &repoCfg)
cmdStatus.Message = syncCloned
} else if err == nil {
cmdStatus.Changed, err = fetchRepository(repo)
if cmdStatus.Changed {
cmdStatus.Message = syncFetched
} else {
cmdStatus.Message = syncUpToDate
}
} else {
cmdStatus.Error = true
cmdStatus.Message = err.Error()
status <- cmdStatus
}
if err != nil {
cmdStatus.Error = true
cmdStatus.Message = err.Error()
}
status <- cmdStatus
}

95
commands/sync_cmd_test.go Normal file
View File

@ -0,0 +1,95 @@
package commands
import (
"fmt"
"os"
"testing"
"gitlab.com/revalus/grm/config"
)
func createTempDir() 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)
return tempDir
}
func TestSyncInit(t *testing.T) {
sync := NewSynchronizer("test")
if sync.workspace != "test" {
t.Errorf("Expected to get \"test\", instead of this got %v", sync.workspace)
}
}
func TestSyncCommand(t *testing.T) {
workdir := createTempDir()
defer func() {
os.RemoveAll(workdir)
}()
sync := Synchronizer{
workspace: workdir,
}
cfg := config.RepositoryConfig{
Src: "https://github.com/avelino/awesome-go",
Dest: "awesome-go",
}
ch := make(chan CommandStatus)
// Pull part
go sync.Command(cfg, ch)
cloneStatus := <-ch
if cloneStatus.Error {
t.Errorf("Unexpected error: %v", cloneStatus.Message)
}
info, err := os.Stat(fmt.Sprintf("%v/awesome-go/.git", workdir))
if err != nil {
t.Errorf("Unexpected error: %v", err.Error())
}
if !info.IsDir() {
t.Errorf("Expected that the selected path is dir")
}
if cloneStatus.Changed != true {
t.Errorf("Expected that the status is changed")
}
if cloneStatus.Message != syncCloned {
t.Errorf("Expected to get %v, instead of this got %v", syncCloned, cloneStatus.Message)
}
// Fetch part
go sync.Command(cfg, ch)
fetchStatus := <-ch
if fetchStatus.Error {
t.Errorf("Unexpected error: %v", err.Error())
}
if fetchStatus.Changed != false {
t.Errorf("Expected that the status is not changed")
}
if fetchStatus.Message != syncUpToDate {
t.Errorf("Expected to get %v, instead of this got %v", syncUpToDate, cloneStatus.Message)
}
}

54
config/cmd.go Normal file
View File

@ -0,0 +1,54 @@
package config
import (
"errors"
"fmt"
"os"
"github.com/akamensky/argparse"
)
const (
defaultConfigPath = ".config/grm/config.yaml"
)
func getDefaultConfigDir() string {
// Only systems like Unix, Linux, and Windows systems are supported
userHomeDir, _ := os.UserHomeDir()
return fmt.Sprintf("%v/%v", userHomeDir, defaultConfigPath)
}
func ParseCliArguments(name, description string, arguments []string) (CliArguments, error) {
parser := argparse.NewParser(name, description)
syncCMD := parser.NewCommand("sync", "Synchronize repositories with remote branches, if the repository does not exist, clone it. (If pulling is not possible, the repository will be fetched)")
configFile := parser.String("c", "config-file", &argparse.Options{
Default: getDefaultConfigDir(),
Help: "Path to the configuration file",
})
version := parser.Flag("v", "version", &argparse.Options{
Default: false,
Help: "Print version",
})
color := parser.Flag("", "no-color", &argparse.Options{
Default: false,
Help: "Turn off color printing",
})
if err := parser.Parse(arguments); err != nil {
return CliArguments{}, err
}
if !syncCMD.Happened() && !(*version) {
return CliArguments{}, errors.New(errNoCommand)
}
return CliArguments{
ConfigurationFile: *configFile,
Sync: syncCMD.Happened(),
Version: *version,
Color: !(*color),
}, nil
}

51
config/cmd_test.go Normal file
View File

@ -0,0 +1,51 @@
package config
import (
"fmt"
"os"
"testing"
)
func TestGetDefaultConfigDir(t *testing.T) {
ud, _ := os.UserHomeDir()
desiredPath := fmt.Sprintf("%v/%v", ud, defaultConfigPath)
if getDefaultConfigDir() != desiredPath {
t.Errorf("Expected to get %v, instead of this got %v", desiredPath, getDefaultConfigDir())
}
}
func TestParsingDefaultArguments(t *testing.T) {
// First item in os.Args is appPath, this have to be mocked
fakeOSArgs := []string{"appName", "sync"}
parseResult, err := ParseCliArguments("", "", fakeOSArgs)
if err != nil {
t.Errorf("Unexpected error %v", err.Error())
}
if parseResult.ConfigurationFile != getDefaultConfigDir() {
t.Errorf("Default value for configurationFile should be %v, instead of this got %v", getDefaultConfigDir(), parseResult.ConfigurationFile)
}
if parseResult.Sync != true {
t.Errorf("Default value for configurationFile should be %v, instead of this got %v", true, parseResult.Sync)
}
}
func TestParsingWithoutCommand(t *testing.T) {
// First item in os.Args is appPath, this have to be mocked
fakeOSArgs := []string{"appName"}
results, err := ParseCliArguments("", "", fakeOSArgs)
if err == nil {
t.Errorf("Expected error, not results: %v", results)
}
}

View File

@ -0,0 +1,66 @@
package config
import (
"errors"
"fmt"
"strings"
"gopkg.in/yaml.v3"
)
func GetRepositoryConfig(data []byte, fileExtension string) (Configuration, error) {
var config Configuration
var err error
switch fileExtension {
case "yaml":
err = yaml.Unmarshal(data, &config)
default:
return Configuration{}, errors.New(errNotSupportedType)
}
if err != nil {
return Configuration{}, err
}
if config.Workspace == "" {
return Configuration{}, errors.New(errMissingWorkspaceField)
}
// Get counters to check if name or dest values are not duplicated
nameFieldCounter := make(map[string][]int)
destFieldCounter := make(map[string][]int)
for index, repo := range config.Repositories {
if repo.Src == "" {
errorMessage := fmt.Sprintf(errMissingSrcField, index)
return Configuration{}, errors.New(errorMessage)
}
if repo.Name == "" {
splittedGit := strings.Split(repo.Src, "/")
nameWithExcention := splittedGit[len(splittedGit)-1]
name := strings.Split(nameWithExcention, ".")[0]
config.Repositories[index].Name = name
}
if repo.Dest == "" {
config.Repositories[index].Dest = config.Repositories[index].Name
}
nameFieldCounter[config.Repositories[index].Name] = append(nameFieldCounter[config.Repositories[index].Name], index)
destFieldCounter[config.Repositories[index].Dest] = append(destFieldCounter[config.Repositories[index].Dest], index)
}
for rowId, items := range nameFieldCounter {
if len(items) != 1 {
return Configuration{}, getDuplicateFieldError("name", rowId, items)
}
}
for rowId, items := range destFieldCounter {
if len(items) != 1 {
return Configuration{}, getDuplicateFieldError("dest", rowId, items)
}
}
return config, err
}

View File

@ -0,0 +1,150 @@
package config
import (
"fmt"
"reflect"
"testing"
)
var exampleYamlConfig = []byte(`
workspace: /tmp
repositories:
- src: "https://github.com/example/example.git"
dest: "example/path"
name: "custom_example"
- src: https://github.com/example/example2.git
`)
var destinationConfiguration = Configuration{
Workspace: "/tmp",
Repositories: []RepositoryConfig{
{
Name: "custom_example",
Dest: "example/path",
Src: "https://github.com/example/example.git",
},
{
Name: "example2",
Src: "https://github.com/example/example2.git",
Dest: "example2",
},
},
}
func TestNotSupportedFileExcension(t *testing.T) {
_, err := GetRepositoryConfig(exampleYamlConfig, "custom")
if err == nil {
t.Error("Expected to get error")
}
if err.Error() != errNotSupportedType {
t.Errorf("Expected to get %v, instead of this got %v", errNotSupportedType, err.Error())
}
}
func TestGetRepositoryConfigFromYaml(t *testing.T) {
result, err := GetRepositoryConfig(exampleYamlConfig, "yaml")
if err != nil {
t.Errorf("Unexpected error %v", err.Error())
}
if !reflect.DeepEqual(result.Repositories, destinationConfiguration.Repositories) {
t.Errorf("Default value for configurationFile should be:\n %v \ninstead of this got:\n %v", result, destinationConfiguration)
}
}
func TestWrongYamlFormat(t *testing.T) {
exampleWrongYamlConfig := []byte(`---
workspace: "/test"
repositories:
- src: "https://github.com/example/example.git"
dest: "example/path"
name: "custom_example"
`)
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
expectedError := "yaml: line 2: found character that cannot start any token"
if err.Error() != expectedError {
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError, err.Error())
}
}
func TestMissingWorkspaceRequiredField(t *testing.T) {
exampleWrongYamlConfig := []byte(`---
repositories:
- src: "https://github.com/example/example.git"
dest: "example/path"
name: "custom_example"
`)
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
if err.Error() != errMissingWorkspaceField {
t.Errorf("Expected to get error with value %v, instead of this got: %v", errMissingWorkspaceField, err.Error())
}
}
func TestMissingSourceRequiredField(t *testing.T) {
exampleWrongYamlConfig := []byte(`---
workspace: /tmp
repositories:
- dest: "example/path"
name: "custom_example"
`)
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
expectedError := fmt.Sprintf(errMissingSrcField, 0)
if err.Error() != expectedError {
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError, err.Error())
}
}
func TestDuplicatedNameField(t *testing.T) {
exampleWrongYamlConfig := []byte(`
workspace: "/tmp"
repositories:
- src: "https://github.com/example/example1.git"
dest: "example/path"
name: "custom_example"
- src: "https://github.com/example/example2.git"
name: "example2"
- src: "https://github.com/example/example2.git"
`)
result, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
if err == nil {
t.Errorf("Unexpected result: %v", result)
}
expectedError := getDuplicateFieldError("name", "example2", []int{1, 2})
if err.Error() != expectedError.Error() {
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError.Error(), err.Error())
}
}
func TestDuplicatedDestField(t *testing.T) {
exampleWrongYamlConfig := []byte(`
workspace: "/tmp"
repositories:
- src: "https://github.com/example/example1.git"
dest: "example/path"
- src: "https://github.com/example/example2.git"
dest: "example"
- src: "https://github.com/example/example3.git"
dest: "example"
`)
result, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
if err == nil {
t.Errorf("Unexpected result: %v", result)
}
expectedError := getDuplicateFieldError("dest", "example", []int{1, 2})
if err.Error() != expectedError.Error() {
t.Errorf("Expected to get error with value \"%v\", instead of this got: \"%v\"", expectedError, err)
}
}

27
config/errors.go Normal file
View File

@ -0,0 +1,27 @@
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"
)
func getDuplicateFieldError(field string, name string, rows []int) error {
var rowsInString []string
for _, row := range rows {
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)
}

19
config/structures.go Normal file
View File

@ -0,0 +1,19 @@
package config
type Configuration struct {
Workspace string
Repositories []RepositoryConfig
}
type RepositoryConfig struct {
Name string `yaml:",omitempty"`
Src string `yaml:",omitempty"`
Dest string `yaml:",omitempty"`
}
type CliArguments struct {
ConfigurationFile string
Sync bool
Version bool
Color bool
}

30
go.mod Normal file
View File

@ -0,0 +1,30 @@
module gitlab.com/revalus/grm
go 1.17
require (
github.com/akamensky/argparse v1.3.1
github.com/go-git/go-git/v5 v5.4.2
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
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/go-git/go-billy/v5 v5.3.1 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/imdario/mergo v0.3.12 // 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/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
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

124
go.sum Normal file
View File

@ -0,0 +1,124 @@
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=
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/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/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/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/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/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/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/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=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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/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=
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=

16
main.go Normal file
View File

@ -0,0 +1,16 @@
package main
import (
"os"
"gitlab.com/revalus/grm/app"
)
const ()
func main() {
app := app.GitRepositoryManager{}
app.Parse(os.Args)
app.Run()
}