diff --git a/README.md b/README.md index 5dd195b..321b7d5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # git-synchronizer -[![build](https://github.com/insightsengineering/git-synchronizer/actions/workflows/build.yml/badge.svg)](https://github.com/insightsengineering/git-synchronizer/actions/workflows/build.yml) +[![build](https://github.com/insightsengineering/git-synchronizer/actions/workflows/test.yml/badge.svg)](https://github.com/insightsengineering/git-synchronizer/actions/workflows/test.yml) + +`git-synchronizer` allows you to mirror a collection of `git` repositories from one location to another. For each source repository, you can set a destination repository to which the source should be mirrored (see example [configuration file](#configuration-file)). + +`git-synchronizer` will: +* push all branches and tags from source to destination repository, +* remove branches and tags from the destination repository which are no longer present in source repository. ## Installing @@ -16,24 +22,60 @@ git-synchronizer --help ## Configuration file -If you'd like to set the options in a configuration file, by default `git-synchronizer` checks `~/.git-synchronizer`, `~/.git-synchronizer.yaml` and `~/.git-synchronizer.yml` files. +By default `git-synchronizer` attempts to read `~/.git-synchronizer`, `~/.git-synchronizer.yaml` and `~/.git-synchronizer.yml` configuration files. If any of these files exist, `git-synchronizer` uses options defined there, unless they are overridden by command line flags. You can also specify custom path to configuration file with `--config .yml` command line flag. -When using custom configuration file, if you specify command line flags, the latter will still take precedence. Example contents of configuration file: ```yaml -logLevel: trace -exampleParameter: exampleValue +# Default authentication methods to use for source and destination repositories (optional). +defaults: + source: + auth: + method: token + # Name of environment variable storing the Personal Access Token with permissions to read source repositories. + token_name: GITHUB_TOKEN + destination: + auth: + method: token + # Name of environment variable storing the Personal Access Token with permissions to push to destination repositories. + token_name: GITLAB_TOKEN + +# List of repository pairs to be synchronized. +repositories: + + # Repositories using default tokens. + - source: + repo: https://github.example.com/org-1/repo-1 + destination: + repo: https://gitlab.example.com/org-5/repo-1 + + - source: + repo: https://github.example.com/org-1/repo-2 + # Overriding token for source repository. + auth: + method: token + token_name: GITHUB_TOKEN_EXTRA + destination: + repo: https://gitlab.example.com/org-5/repo-2 + + - source: + repo: https://github.example.com/org-1/repo-3 + destination: + repo: https://gitlab.example.com/org-5/repo-3 + # Overriding token for destination repository. + auth: + method: token + token_name: GITLAB_TOKEN_EXTRA ``` ## Environment variables `git-synchronizer` reads environment variables with `GITSYNCHRONIZER_` prefix and tries to match them with CLI flags. For example, setting the following variables will override the respective values from configuration file: -`GITSYNCHRONIZER_LOGLEVEL`, `GITSYNCHRONIZER_EXAMPLEPARAMETER` etc. +`GITSYNCHRONIZER_LOGLEVEL` etc. The order of precedence is: @@ -41,6 +83,8 @@ CLI flag → environment variable → configuration file → default value. To check the available names of environment variables, please run `git-synchronizer --help`. +Please note that providing the list of repositories to be synchronized with a CLI flag is not supported. + ## Development This project is built with the [Go programming language](https://go.dev/). diff --git a/cmd/mirror.go b/cmd/mirror.go new file mode 100644 index 0000000..b8acc1a --- /dev/null +++ b/cmd/mirror.go @@ -0,0 +1,369 @@ +/* +Copyright 2024 F. Hoffmann-La Roche AG + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "encoding/json" + "os" + "sort" + "strings" + "time" + + git "github.com/go-git/go-git/v5" + gitconfig "github.com/go-git/go-git/v5/config" + githttp "github.com/go-git/go-git/v5/plumbing/transport/http" +) + +const refBranchPrefix = "refs/heads/" +const refTagPrefix = "refs/tags/" +const basicAuthUsername = "This can be any string." +const token = "token" + +type MirrorStatus struct { + Errors []string + LastCloneEnd time.Time + CloneDuration time.Duration + PushDuration time.Duration +} + +// SetRepositoryAuth ensures that repositories for which the authentication settings have not been +// overridden, use the default authentication settings from config file. +func SetRepositoryAuth(repositories *[]RepositoryPair, defaultSettings RepositoryPair) { + for i := 0; i < len(*repositories); i++ { + if (*repositories)[i].Source.Auth.Method == "" { + (*repositories)[i].Source.Auth.Method = defaultSettings.Source.Auth.Method + if (*repositories)[i].Source.Auth.Method == token { + (*repositories)[i].Source.Auth.TokenName = defaultSettings.Source.Auth.TokenName + } + } + if (*repositories)[i].Destination.Auth.Method == "" { + (*repositories)[i].Destination.Auth.Method = defaultSettings.Destination.Auth.Method + if (*repositories)[i].Destination.Auth.Method == token { + (*repositories)[i].Destination.Auth.TokenName = defaultSettings.Destination.Auth.TokenName + } + } + } + repositoriesJSON, err := json.MarshalIndent(*repositories, "", " ") + checkError(err) + log.Trace("repositories = ", string(repositoriesJSON)) +} + +// ValidateRepositories checks for common issues with input repository data from config file. +func ValidateRepositories(repositories []RepositoryPair) { + var allDestinationRepositories []string + for _, repo := range repositories { + if stringInSlice(repo.Destination.RepositoryURL, allDestinationRepositories) { + log.Fatal( + "Multiple repositories set to be synchronized to the same destination repository: ", + repo.Source.RepositoryURL, + ) + } + allDestinationRepositories = append(allDestinationRepositories, repo.Destination.RepositoryURL) + sourceURL := strings.Split(repo.Source.RepositoryURL, "/") + sourceProjectName := sourceURL[len(sourceURL)-1] + destinationURL := strings.Split(repo.Destination.RepositoryURL, "/") + destinationProjectName := destinationURL[len(destinationURL)-1] + if sourceProjectName != destinationProjectName { + log.Warn( + "Source project name (", sourceProjectName, + ") and destination project name (", destinationProjectName, ") differ!", + ) + } + } +} + +// GetBranchesAndTagsFromRemote returns list of branches and tags present in remoteName of repository. +func GetBranchesAndTagsFromRemote(repository *git.Repository, remoteName string, listOptions *git.ListOptions) ([]string, []string, error) { + var branchList []string + var tagList []string + remote, err := repository.Remote(remoteName) + if err != nil { + return branchList, tagList, err + } + refList, err := remote.List(listOptions) + if err != nil { + return branchList, tagList, err + } + + for _, ref := range refList { + refName := ref.Name().String() + if strings.HasPrefix(refName, refBranchPrefix) { + branchName := strings.TrimPrefix(refName, refBranchPrefix) + branchList = append(branchList, branchName) + } else if strings.HasPrefix(refName, refTagPrefix) { + tagName := strings.TrimPrefix(refName, refTagPrefix) + tagList = append(tagList, tagName) + } + } + sort.Strings(branchList) + sort.Strings(tagList) + return branchList, tagList, nil +} + +// ProcessError formats err and appends it to allErrors. +func ProcessError(err error, activity string, url string, allErrors *[]string) { + var e string + if err != nil && err != git.NoErrAlreadyUpToDate { + e = "Error while " + activity + url + ": " + err.Error() + } + if e != "" { + log.Error(e) + *allErrors = append(*allErrors, e) + } +} + +// GetCloneOptions returns clone options for source repository. +func GetCloneOptions(source string, sourceAuth Authentication) *git.CloneOptions { + var sourcePat string + if sourceAuth.Method == token { + sourcePat = os.Getenv(sourceAuth.TokenName) + } else if sourceAuth.Method != "" { + log.Error("Unknown auth method: ", sourceAuth.Method) + } + if sourcePat != "" { + gitCloneOptions := &git.CloneOptions{ + URL: source, + Auth: &githttp.BasicAuth{ + Username: basicAuthUsername, + Password: sourcePat, + }, + } + return gitCloneOptions + } + gitCloneOptions := &git.CloneOptions{URL: source} + return gitCloneOptions +} + +// GetListOptions returns list options for source repository. +func GetListOptions(sourceAuth Authentication) *git.ListOptions { + var sourcePat string + if sourceAuth.Method == token { + sourcePat = os.Getenv(sourceAuth.TokenName) + } else if sourceAuth.Method != "" { + log.Error("Unknown auth method: ", sourceAuth.Method) + } + if sourcePat != "" { + gitListOptions := &git.ListOptions{ + Auth: &githttp.BasicAuth{ + Username: basicAuthUsername, + Password: sourcePat, + }, + } + return gitListOptions + } + gitListOptions := &git.ListOptions{} + return gitListOptions +} + +// GetFetchOptions returns fetch options for source repository. +func GetFetchOptions(refSpec string, sourceAuth Authentication) *git.FetchOptions { + var sourcePat string + if sourceAuth.Method == token { + sourcePat = os.Getenv(sourceAuth.TokenName) + } else if sourceAuth.Method != "" { + log.Error("Unknown auth method: ", sourceAuth.Method) + } + if sourcePat != "" { + gitFetchOptions := &git.FetchOptions{ + RefSpecs: []gitconfig.RefSpec{gitconfig.RefSpec(refSpec)}, + Auth: &githttp.BasicAuth{ + Username: basicAuthUsername, + Password: sourcePat, + }, + } + return gitFetchOptions + } + gitFetchOptions := &git.FetchOptions{ + RefSpecs: []gitconfig.RefSpec{gitconfig.RefSpec(refSpec)}, + } + return gitFetchOptions +} + +// GetDestionationAuth returns authentication struct for destination git repository. +func GetDestinationAuth(destAuth Authentication) *githttp.BasicAuth { + var destinationPat string + if destAuth.Method == token { + destinationPat = os.Getenv(destAuth.TokenName) + } else if destAuth.Method != "" { + log.Error("Unknown auth method: ", destAuth.Method) + } + destinationAuth := &githttp.BasicAuth{ + Username: basicAuthUsername, + Password: destinationPat, + } + return destinationAuth +} + +// MirrorRepository mirrors branches and tags from source to destination. Tags and branches +// no longer present in source are removed from destination. +func MirrorRepository(messages chan MirrorStatus, source, destination string, sourceAuthentication, destinationAuthentication Authentication) { + log.Debug("Cloning ", source) + cloneStart := time.Now() + gitDirectory, err := os.MkdirTemp(localTempDirectory, "") + checkError(err) + defer os.RemoveAll(gitDirectory) + var allErrors []string + gitCloneOptions := GetCloneOptions(source, sourceAuthentication) + repository, err := git.PlainClone(gitDirectory, false, gitCloneOptions) + if err != nil { + ProcessError(err, "cloning repository from ", source, &allErrors) + messages <- MirrorStatus{allErrors, time.Now(), 0, 0} + return + } + + gitListOptions := GetListOptions(sourceAuthentication) + sourceBranchList, sourceTagList, err := GetBranchesAndTagsFromRemote(repository, "origin", gitListOptions) + if err != nil { + ProcessError(err, "getting branches and tags from ", source, &allErrors) + messages <- MirrorStatus{allErrors, time.Now(), 0, 0} + return + } + log.Debug(source, " branches = ", sourceBranchList) + log.Debug(source, " tags = ", sourceTagList) + + log.Info("Fetching all branches from ", source) + sourceRemote, err := repository.Remote("origin") + if err != nil { + ProcessError(err, "getting source remote for ", source, &allErrors) + messages <- MirrorStatus{allErrors, time.Now(), 0, 0} + return + } + + gitFetchOptions := GetFetchOptions("refs/heads/*:refs/heads/*", sourceAuthentication) + err = sourceRemote.Fetch(gitFetchOptions) + if err != nil { + ProcessError(err, "fetching branches from ", source, &allErrors) + messages <- MirrorStatus{allErrors, time.Now(), 0, 0} + return + } + + cloneDuration := time.Since(cloneStart) + cloneEnd, pushStart := time.Now(), time.Now() + + _, err = repository.CreateRemote(&gitconfig.RemoteConfig{ + Name: "destination", + URLs: []string{destination}, + }) + if err != nil { + ProcessError(err, "creating remote for ", destination, &allErrors) + messages <- MirrorStatus{allErrors, time.Now(), 0, 0} + return + } + + destinationAuth := GetDestinationAuth(destinationAuthentication) + + destinationBranchList, destinationTagList, err := GetBranchesAndTagsFromRemote(repository, "destination", &git.ListOptions{Auth: destinationAuth}) + if err != nil { + ProcessError(err, "getting branches and tags from ", destination, &allErrors) + } + log.Debug(destination, " branches = ", destinationBranchList) + log.Debug(destination, " tags = ", destinationTagList) + + log.Info("Pushing all branches from ", source, " to ", destination) + for _, branch := range sourceBranchList { + log.Debug("Pushing branch ", branch, " to ", destination) + err = repository.Push(&git.PushOptions{ + RemoteName: "destination", + RefSpecs: []gitconfig.RefSpec{gitconfig.RefSpec("+" + refBranchPrefix + branch + ":" + refBranchPrefix + branch)}, + Auth: destinationAuth, Force: true, Atomic: true}) + ProcessError(err, "pushing branch "+branch+" to ", destination, &allErrors) + } + + // Remove any branches not present in the source repository anymore. + for _, branch := range destinationBranchList { + if !stringInSlice(branch, sourceBranchList) { + log.Info("Removing branch ", branch, " from ", destination) + err = repository.Push(&git.PushOptions{ + RemoteName: "destination", + RefSpecs: []gitconfig.RefSpec{gitconfig.RefSpec(":" + refBranchPrefix + branch)}, + Auth: destinationAuth, Force: true, Atomic: true}) + ProcessError(err, "removing branch "+branch+" from ", destination, &allErrors) + } + } + + log.Info("Pushing all tags from ", source, " to ", destination) + err = repository.Push(&git.PushOptions{ + RemoteName: "destination", + RefSpecs: []gitconfig.RefSpec{gitconfig.RefSpec("+" + refTagPrefix + "*:" + refTagPrefix + "*")}, + Auth: destinationAuth, Force: true, Atomic: true}) + ProcessError(err, "pushing all tags to ", destination, &allErrors) + + // Remove any tags not present in the source repository anymore. + for _, tag := range destinationTagList { + if !stringInSlice(tag, sourceTagList) { + log.Info("Removing tag ", tag, " from ", destination) + err := repository.Push(&git.PushOptions{ + RemoteName: "destination", + RefSpecs: []gitconfig.RefSpec{gitconfig.RefSpec(":" + refTagPrefix + tag)}, + Auth: destinationAuth, Force: true, Atomic: true}) + ProcessError(err, "removing tag "+tag+" from ", destination, &allErrors) + } + } + pushDuration := time.Since(pushStart) + messages <- MirrorStatus{allErrors, cloneEnd, cloneDuration, pushDuration} +} + +// MirrorRepositories ensures that branches and tags from source repository are mirrored to +// the destination repository for each repositoryPair. +func MirrorRepositories(repos []RepositoryPair) { + messages := make(chan MirrorStatus, 100) + var allErrors []string + synchronizationStart := time.Now() + for _, repository := range repos { + log.Info("Mirroring ", repository.Source.RepositoryURL, " → ", repository.Destination.RepositoryURL) + go MirrorRepository( + messages, repository.Source.RepositoryURL, repository.Destination.RepositoryURL, + repository.Source.Auth, repository.Destination.Auth, + ) + } + receivedResults := 0 + var lastCloneEnd time.Time + var totalCloneDuration time.Duration + var totalPushDuration time.Duration +results_receiver_loop: + for { + select { + case msg := <-messages: + receivedResults++ + log.Info("Finished mirroring ", receivedResults, " out of ", len(repos), " repositories.") + allErrors = append(allErrors, msg.Errors...) + if lastCloneEnd.Before(msg.LastCloneEnd) { + lastCloneEnd = msg.LastCloneEnd + } + totalCloneDuration += msg.CloneDuration + totalPushDuration += msg.PushDuration + if receivedResults == len(repos) { + break results_receiver_loop + } + default: + time.Sleep(time.Second) + } + } + cloneDuration := lastCloneEnd.Sub(synchronizationStart) + syncDuration := time.Since(synchronizationStart) + log.Infof("Last clone finished %v after synchronization had started (%.1f%% of total synchronization time).", + cloneDuration.Round(time.Second), (float64(100)*cloneDuration.Seconds())/syncDuration.Seconds()) + log.Infof("Synchronization took %v (wall-clock time).", syncDuration.Round(time.Second)) + log.Debugf("Total clone duration: %v (goroutine time).", totalCloneDuration.Round(time.Second)) + log.Debugf("Total push duration: %v (goroutine time).", totalPushDuration.Round(time.Second)) + if len(allErrors) > 0 { + log.Error("The following errors have been encountered:") + for _, e := range allErrors { + log.Error(e) + } + os.Exit(1) + } +} diff --git a/cmd/mirror_test.go b/cmd/mirror_test.go new file mode 100644 index 0000000..22d832a --- /dev/null +++ b/cmd/mirror_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 F. Hoffmann-La Roche AG + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_SetRepositoryAuth(t *testing.T) { + repositories := []RepositoryPair{ + { + Repository{ + "https://example.com/org-1/repo-1", + Authentication{"", ""}, + }, + Repository{ + "https://example.com/org-2/repo-2", + Authentication{"", ""}, + }, + }, + { + Repository{ + "https://example.com/org-3/repo-3", + Authentication{"token", "CUSTOM_TOKEN_1"}, + }, + Repository{ + "https://example.com/org-4/repo-4", + Authentication{"token", "CUSTOM_TOKEN_2"}, + }, + }, + } + defaultSettings := RepositoryPair{ + Repository{ + "", Authentication{"token", "GITLAB_TOKEN"}, + }, + Repository{ + "", Authentication{"token", "GITHUB_TOKEN"}, + }, + } + SetRepositoryAuth(&repositories, defaultSettings) + assert.Equal(t, repositories[0].Source.Auth.Method, "token") + assert.Equal(t, repositories[0].Source.Auth.TokenName, "GITLAB_TOKEN") + assert.Equal(t, repositories[0].Destination.Auth.Method, "token") + assert.Equal(t, repositories[0].Destination.Auth.TokenName, "GITHUB_TOKEN") + assert.Equal(t, repositories[1].Source.Auth.Method, "token") + assert.Equal(t, repositories[1].Source.Auth.TokenName, "CUSTOM_TOKEN_1") + assert.Equal(t, repositories[1].Destination.Auth.Method, "token") + assert.Equal(t, repositories[1].Destination.Auth.TokenName, "CUSTOM_TOKEN_2") +} diff --git a/cmd/root.go b/cmd/root.go index 0fb3492..f22491e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,8 +16,10 @@ limitations under the License. package cmd import ( + "encoding/json" "fmt" "os" + "runtime" "github.com/jamiealquiza/envy" "github.com/sirupsen/logrus" @@ -28,7 +30,29 @@ import ( var cfgFile string var logLevel string -var exampleParameter string +var workingDirectory string + +type RepositoryPair struct { + Source Repository `mapstructure:"source"` + Destination Repository `mapstructure:"destination"` +} + +type Repository struct { + RepositoryURL string `mapstructure:"repo"` + Auth Authentication `mapstructure:"auth"` +} + +type Authentication struct { + Method string `mapstructure:"method"` + TokenName string `mapstructure:"token_name"` +} + +// Repository list provided in YAML configuration file. +var inputRepositories []RepositoryPair + +var defaultSettings RepositoryPair + +var localTempDirectory string var log = logrus.New() @@ -37,8 +61,8 @@ func setLogLevel() { customFormatter.TimestampFormat = "2006-01-02 15:04:05" customFormatter.ForceColors = true log.SetFormatter(customFormatter) - log.SetReportCaller(true) - customFormatter.FullTimestamp = true + log.SetReportCaller(false) + customFormatter.FullTimestamp = false fmt.Println(`logLevel = "` + logLevel + `"`) switch logLevel { case "trace": @@ -62,9 +86,7 @@ func newRootCommand() { rootCmd = &cobra.Command{ Use: "git-synchronizer", Short: "A tool to synchronize git repositories.", - Long: `A tool to synchronize -git -repositories.`, + Long: `A tool to synchronize git repositories from one git server to another.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { initializeConfig() }, @@ -72,15 +94,34 @@ repositories.`, setLogLevel() fmt.Println(`config = "` + cfgFile + `"`) - fmt.Println(`exampleParameter = "` + exampleParameter + `"`) + inputRepositoriesJSON, err := json.MarshalIndent(inputRepositories, "", " ") + checkError(err) + defaultSettingsJSON, err := json.MarshalIndent(defaultSettings, "", " ") + checkError(err) + log.Trace("inputRepositories = ", string(inputRepositoriesJSON)) + log.Trace("defaultSettings = ", string(defaultSettingsJSON)) + + if runtime.GOOS == "windows" { + localTempDirectory = os.Getenv("TMP") + workingDirectory + } else { + localTempDirectory = workingDirectory + } + + SetRepositoryAuth(&inputRepositories, defaultSettings) + ValidateRepositories(inputRepositories) + + err = os.MkdirAll(localTempDirectory, os.ModePerm) + checkError(err) + + MirrorRepositories(inputRepositories) }, } - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", + rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.git-synchronizer.yaml)") - rootCmd.PersistentFlags().StringVar(&logLevel, "logLevel", "info", + rootCmd.PersistentFlags().StringVarP(&logLevel, "logLevel", "l", "info", "Logging level (trace, debug, info, warn, error). ") - rootCmd.PersistentFlags().StringVar(&exampleParameter, "exampleParameter", "", - "Example parameter that does nothing.") + rootCmd.PersistentFlags().StringVarP(&workingDirectory, "workingDirectory", "w", "/tmp/git-synchronizer", + "Directory where synchronized repositories will be cloned.") // Add version command. rootCmd.AddCommand(extension.NewVersionCobraCmd()) @@ -130,7 +171,7 @@ func Execute() { func initializeConfig() { for _, v := range []string{ - "logLevel", "exampleParameter", + "logLevel", "workingDirectory", } { // If the flag has not been set in newRootCommand() and it has been set in initConfig(). // In other words: if it's not been provided in command line, but has been @@ -142,4 +183,11 @@ func initializeConfig() { checkError(err) } } + + // Check if a YAML list of input git repositories has been provided in the configuration file. + err := viper.UnmarshalKey("repositories", &inputRepositories) + checkError(err) + // Read default settings from configuration file. + err = viper.UnmarshalKey("defaults", &defaultSettings) + checkError(err) } diff --git a/cmd/utils.go b/cmd/utils.go index 38bd816..d2a2b2d 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -15,8 +15,21 @@ limitations under the License. */ package cmd +import ( + git "github.com/go-git/go-git/v5" +) + func checkError(err error) { - if err != nil { + if err != nil && err != git.NoErrAlreadyUpToDate { log.Error(err) } } + +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} diff --git a/go.mod b/go.mod index 4939c0d..2cec9ee 100644 --- a/go.mod +++ b/go.mod @@ -2,24 +2,38 @@ module github.com/insightsengineering/git-synchronizer go 1.21 +toolchain go1.21.6 + require ( + github.com/go-git/go-git/v5 v5.11.0 github.com/jamiealquiza/envy v1.1.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 + github.com/stretchr/testify v1.8.4 go.szostok.io/version v1.2.0 ) require ( + dario.cat/mergo v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.14.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/goccy/go-yaml v1.11.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.4.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -27,6 +41,8 @@ require ( github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.11 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -37,22 +53,31 @@ require ( github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/muesli/termenv v0.15.1 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sergi/go-diff v1.1.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect + github.com/skeema/knownhosts v1.2.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 227af9e..388af94 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,62 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +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/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= +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/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +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-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +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/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -42,8 +74,15 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jamiealquiza/envy v1.1.0 h1:Nwh4wqTZ28gDA8zB+wFkhnUpz3CEcO12zotjeqqRoKE= github.com/jamiealquiza/envy v1.1.0/go.mod h1:MP36BriGCLwEHhi1OU8E9569JNZrjWfCvzG7RsPnHus= +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/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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -66,26 +105,37 @@ github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/I github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -104,6 +154,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -112,6 +163,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +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= go.szostok.io/version v1.2.0 h1:8eMMdfsonjbibwZRLJ8TnrErY8bThFTQsZYV16mcXms= go.szostok.io/version v1.2.0/go.mod h1:EiU0gPxaXb6MZ+apSN0WgDO6F4JXyC99k9PIXf2k2E8= @@ -121,50 +174,83 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 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.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 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-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.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-20210423082822-04245dca01da/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-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.0.0-20220811171246-fbc7d0a398ab/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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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/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/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=