feat: use go-git instead of "system git"
This commit is contained in:
parent
a4f4663749
commit
d980126473
9 changed files with 275 additions and 155 deletions
|
|
@ -2,11 +2,12 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-git/go-git/v6"
|
||||
"github.com/go-git/go-git/v6/plumbing/transport/http"
|
||||
"github.com/scornet256/go-logger"
|
||||
)
|
||||
|
||||
|
|
@ -31,7 +32,6 @@ type GitStats struct {
|
|||
|
||||
// increment counters
|
||||
func (stats *GitStats) IncrementCounter(operation string, repoPath string) {
|
||||
|
||||
stats.mu.Lock()
|
||||
defer stats.mu.Unlock()
|
||||
|
||||
|
|
@ -54,9 +54,8 @@ func (stats *GitStats) IncrementCounter(operation string, repoPath string) {
|
|||
|
||||
// concurrent git operations
|
||||
func CheckoutRepositories(repositories []Repository, stats *GitStats) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
semaphore := make(chan struct{}, config.Concurrency)
|
||||
semaphore := make(chan struct{}, globalConfig.Concurrency)
|
||||
|
||||
for _, repo := range repositories {
|
||||
wg.Add(1)
|
||||
|
|
@ -79,67 +78,67 @@ func CheckoutRepositories(repositories []Repository, stats *GitStats) {
|
|||
// manage single repo
|
||||
func processRepository(repo Repository) GitOperationResult {
|
||||
repoName := string(repo.PathWithNamespace)
|
||||
repoDestination := filepath.Join(config.Destination, repoName)
|
||||
repoDestination := filepath.Join(globalConfig.Destination, repoName)
|
||||
|
||||
logger.Print("Starting on repository: "+repoName, nil)
|
||||
|
||||
// check repo status
|
||||
status, err := checkRepositoryStatus(repoDestination)
|
||||
// check if repo exists
|
||||
_, err := git.PlainOpen(repoDestination)
|
||||
if err != nil {
|
||||
return GitOperationResult{
|
||||
RepoName: repoName,
|
||||
Operation: "error",
|
||||
Error: fmt.Errorf("checking repository status: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
if err == git.ErrRepositoryNotExists {
|
||||
// repo doesn't exist, clone it
|
||||
gitURL := buildGitURL(repoName)
|
||||
|
||||
switch {
|
||||
case strings.Contains(status, "No such file or directory"):
|
||||
return cloneRepository(repoName, repoDestination, gitURL)
|
||||
case strings.Contains(status, gitURL):
|
||||
return pullRepository(repoName, repoDestination)
|
||||
default:
|
||||
}
|
||||
return GitOperationResult{
|
||||
RepoName: repoName,
|
||||
Operation: "error",
|
||||
Error: fmt.Errorf("unexpected repository status: %s", status),
|
||||
Error: fmt.Errorf("opening repository: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
// repo exists, pull it
|
||||
return pullRepository(repoName, repoDestination)
|
||||
}
|
||||
|
||||
// check repo status
|
||||
func checkRepositoryStatus(repoDestination string) (string, error) {
|
||||
cmd := exec.Command("git", "-C", repoDestination, "remote", "-v")
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
// If directory doesn't exist, that's expected for new clones
|
||||
if err != nil && strings.Contains(string(output), "No such file or directory") {
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
// craft git url with auth
|
||||
// craft git url without auth (auth handled separately)
|
||||
func buildGitURL(repoName string) string {
|
||||
return fmt.Sprintf("https://%s-token:%s@%s/%s.git",
|
||||
config.GitBackend, config.GitToken, config.GitHost, repoName)
|
||||
return fmt.Sprintf("https://%s/%s.git", globalConfig.GitHost, repoName)
|
||||
}
|
||||
|
||||
// create auth method
|
||||
func getAuth() *http.BasicAuth {
|
||||
return &http.BasicAuth{
|
||||
Username: globalConfig.GitBackend + "-token",
|
||||
Password: globalConfig.GitToken,
|
||||
}
|
||||
}
|
||||
|
||||
// clone new repository
|
||||
func cloneRepository(repoName, repoDestination, gitURL string) GitOperationResult {
|
||||
logger.Print("Cloning repository: "+repoName, nil)
|
||||
|
||||
cmd := exec.Command("git", "clone", gitURL, repoDestination)
|
||||
output, err := cmd.CombinedOutput()
|
||||
// ensure parent directory exists
|
||||
parentDir := filepath.Dir(repoDestination)
|
||||
if err := os.MkdirAll(parentDir, 0755); err != nil {
|
||||
return GitOperationResult{
|
||||
RepoName: repoName,
|
||||
Operation: "error",
|
||||
Error: fmt.Errorf("creating parent directory: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
_, err := git.PlainClone(repoDestination, &git.CloneOptions{
|
||||
URL: gitURL,
|
||||
Auth: getAuth(),
|
||||
Progress: nil,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return GitOperationResult{
|
||||
RepoName: repoName,
|
||||
Operation: "error",
|
||||
Error: fmt.Errorf("cloning repository: %w, output: %s", err, string(output)),
|
||||
Error: fmt.Errorf("cloning repository: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,27 +157,77 @@ func cloneRepository(repoName, repoDestination, gitURL string) GitOperationResul
|
|||
func pullRepository(repoName, repoDestination string) GitOperationResult {
|
||||
logger.Print("Pulling repository: "+repoName, nil)
|
||||
|
||||
// Find remote
|
||||
remote, err := findRemote(repoDestination)
|
||||
// open repository
|
||||
repo, err := git.PlainOpen(repoDestination)
|
||||
if err != nil {
|
||||
return GitOperationResult{
|
||||
RepoName: repoName,
|
||||
Operation: "error",
|
||||
Error: fmt.Errorf("finding remote: %w", err),
|
||||
Error: fmt.Errorf("opening repository: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
// update remote URL with current token (in case token changed)
|
||||
gitURL := buildGitURL(repoName)
|
||||
if err := updateRemoteURL(repoDestination, gitURL); err != nil {
|
||||
logger.Print("WARNING: failed to update remote URL: "+err.Error(), nil)
|
||||
}
|
||||
|
||||
// get worktree
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return GitOperationResult{
|
||||
RepoName: repoName,
|
||||
Operation: "error",
|
||||
Error: fmt.Errorf("getting worktree: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
// check for uncommitted/unstaged changes
|
||||
status, err := worktree.Status()
|
||||
if err != nil {
|
||||
return GitOperationResult{
|
||||
RepoName: repoName,
|
||||
Operation: "error",
|
||||
Error: fmt.Errorf("checking status: %w", err),
|
||||
}
|
||||
}
|
||||
|
||||
if !status.IsClean() {
|
||||
// determine error type
|
||||
errorType := "unstaged"
|
||||
for _, s := range status {
|
||||
if s.Staging != git.Unmodified {
|
||||
errorType = "uncommitted"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return GitOperationResult{
|
||||
RepoName: repoName,
|
||||
Operation: "error",
|
||||
Error: fmt.Errorf("repository has local changes"),
|
||||
ErrorType: errorType,
|
||||
}
|
||||
}
|
||||
|
||||
// pull changes
|
||||
cmd := exec.Command("git", "-C", repoDestination, "pull", remote)
|
||||
output, err := cmd.CombinedOutput()
|
||||
err = worktree.Pull(&git.PullOptions{
|
||||
Auth: getAuth(),
|
||||
Progress: nil,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
errorType := classifyPullError(string(output))
|
||||
if err == git.NoErrAlreadyUpToDate {
|
||||
// not an error, just already up to date
|
||||
logger.Print("Repository already up to date: "+repoName, nil)
|
||||
} else {
|
||||
return GitOperationResult{
|
||||
RepoName: repoName,
|
||||
Operation: "error",
|
||||
Error: fmt.Errorf("pulling repository: %w, output: %s", err, string(output)),
|
||||
ErrorType: errorType,
|
||||
Error: fmt.Errorf("pulling repository: %w", err),
|
||||
ErrorType: "other",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,73 +242,51 @@ func pullRepository(repoName, repoDestination string) GitOperationResult {
|
|||
}
|
||||
}
|
||||
|
||||
// find remote for repo
|
||||
func findRemote(repoDestination string) (string, error) {
|
||||
cmd := exec.Command("git", "-C", repoDestination, "remote", "show")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting remote: %w", err)
|
||||
}
|
||||
|
||||
remotes := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
if len(remotes) == 0 {
|
||||
return "", fmt.Errorf("no remotes found")
|
||||
}
|
||||
|
||||
return remotes[0], nil
|
||||
}
|
||||
|
||||
// manage pull error
|
||||
func classifyPullError(output string) string {
|
||||
switch {
|
||||
case strings.Contains(output, "You have unstaged changes"):
|
||||
return "unstaged"
|
||||
case strings.Contains(output, "Your index contains uncommitted changes"):
|
||||
return "uncommitted"
|
||||
default:
|
||||
return "other"
|
||||
}
|
||||
}
|
||||
|
||||
// set git user config
|
||||
func setGitUserConfig(repoName, repoDestination string) error {
|
||||
|
||||
// git user name
|
||||
if err := setGitUserName(repoName, repoDestination); err != nil {
|
||||
return fmt.Errorf("setting username: %w", err)
|
||||
repo, err := git.PlainOpen(repoDestination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening repository: %w", err)
|
||||
}
|
||||
|
||||
// git user mail
|
||||
if err := setGitUserEmail(repoName, repoDestination); err != nil {
|
||||
return fmt.Errorf("setting email: %w", err)
|
||||
cfg, err := repo.Config()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting config: %w", err)
|
||||
}
|
||||
|
||||
cfg.User.Name = globalConfig.GitUserName
|
||||
cfg.User.Email = globalConfig.GitUserMail
|
||||
|
||||
if err := repo.SetConfig(cfg); err != nil {
|
||||
return fmt.Errorf("setting config: %w", err)
|
||||
}
|
||||
|
||||
logger.Print("Set git user config for: "+repoName, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// set git user name
|
||||
func setGitUserName(repoName, repoDestination string) error {
|
||||
|
||||
cmd := exec.Command("git", "-C", repoDestination, "config", "user.name", config.GitUserName)
|
||||
output, err := cmd.CombinedOutput()
|
||||
// update remote URL with current token
|
||||
func updateRemoteURL(repoDestination, gitURL string) error {
|
||||
repo, err := git.PlainOpen(repoDestination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting git username: %w, output: %s", err, string(output))
|
||||
return fmt.Errorf("opening repository: %w", err)
|
||||
}
|
||||
|
||||
logger.Print("Set git username for: "+repoName, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// set git user mail
|
||||
func setGitUserEmail(repoName, repoDestination string) error {
|
||||
|
||||
cmd := exec.Command("git", "-C", repoDestination, "config", "user.email", config.GitUserMail)
|
||||
output, err := cmd.CombinedOutput()
|
||||
cfg, err := repo.Config()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting git email: %w, output: %s", err, string(output))
|
||||
return fmt.Errorf("getting config: %w", err)
|
||||
}
|
||||
|
||||
// update first remote's URL
|
||||
for name := range cfg.Remotes {
|
||||
cfg.Remotes[name].URLs = []string{gitURL}
|
||||
break
|
||||
}
|
||||
|
||||
if err := repo.SetConfig(cfg); err != nil {
|
||||
return fmt.Errorf("setting config: %w", err)
|
||||
}
|
||||
|
||||
logger.Print("Set git email for: "+repoName, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -291,7 +318,7 @@ func handleResult(result GitOperationResult, stats *GitStats) {
|
|||
}
|
||||
|
||||
// update progress bar
|
||||
if !config.Debug {
|
||||
if !globalConfig.Debug {
|
||||
_ = bar.Add(1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,11 +47,10 @@ func NewGiteaClient(baseURL, token string) *GiteaClient {
|
|||
|
||||
// fetch gitea repos
|
||||
func FetchRepositoriesGitea() ([]Repository, error) {
|
||||
client := NewGiteaClient(config.GitHost, config.GitToken)
|
||||
|
||||
client := NewGiteaClient(globalConfig.GitHost, globalConfig.GitToken)
|
||||
options := GiteaAPIOptions{
|
||||
Visibility: "all",
|
||||
IncludeArchived: config.IncludeArchived,
|
||||
IncludeArchived: globalConfig.IncludeArchived,
|
||||
Sort: "alpha",
|
||||
Limit: 100,
|
||||
Page: 1,
|
||||
|
|
|
|||
|
|
@ -63,11 +63,10 @@ func NewGitLabClient(baseURL, token string) *GitLabClient {
|
|||
|
||||
// fetch gitlab repos
|
||||
func FetchRepositoriesGitLab() ([]Repository, error) {
|
||||
client := NewGitLabClient(config.GitHost, config.GitToken)
|
||||
|
||||
client := NewGitLabClient(globalConfig.GitHost, globalConfig.GitToken)
|
||||
options := GitLabAPIOptions{
|
||||
Membership: true,
|
||||
IncludeArchived: config.IncludeArchived,
|
||||
IncludeArchived: globalConfig.IncludeArchived,
|
||||
OrderBy: "name",
|
||||
Sort: "asc",
|
||||
PerPage: 100,
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ func expandPath(path string) string {
|
|||
|
||||
// loadconfig from yaml file
|
||||
func loadConfig(configPath string) (*Config, error) {
|
||||
config := &Config{}
|
||||
config.setDefaults()
|
||||
cfg := &Config{}
|
||||
cfg.setDefaults()
|
||||
|
||||
// check if config file exists
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
|
|
@ -78,11 +78,11 @@ func loadConfig(configPath string) (*Config, error) {
|
|||
}
|
||||
|
||||
// parse yaml
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
if err := yaml.Unmarshal(data, cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// validateConfig validates the configuration values
|
||||
|
|
@ -147,11 +147,6 @@ func manageArguments() *Config {
|
|||
|
||||
flag.Parse()
|
||||
|
||||
// override debug setting if flag is set
|
||||
if *debugFlag {
|
||||
config.Debug = true
|
||||
}
|
||||
|
||||
if *versionFlag {
|
||||
fmt.Println(version)
|
||||
os.Exit(0)
|
||||
|
|
@ -160,23 +155,28 @@ func manageArguments() *Config {
|
|||
configPath := *configFileFlag
|
||||
|
||||
// Load configuration from YAML file
|
||||
config, err := loadConfig(configPath)
|
||||
cfg, err := loadConfig(configPath)
|
||||
if err != nil {
|
||||
flag.Usage()
|
||||
logger.Fatal("Configuration error: "+err.Error(), nil)
|
||||
}
|
||||
|
||||
// override debug setting if flag is set
|
||||
if *debugFlag {
|
||||
cfg.Debug = true
|
||||
}
|
||||
|
||||
// Process configuration
|
||||
config.processConfig()
|
||||
cfg.processConfig()
|
||||
|
||||
// Validate configuration
|
||||
if err := config.validateConfig(); err != nil {
|
||||
if err := cfg.validateConfig(); err != nil {
|
||||
flag.Usage()
|
||||
logger.Fatal("Configuration validation error: "+err.Error(), nil)
|
||||
}
|
||||
|
||||
// Log configuration
|
||||
config.logConfig(configPath)
|
||||
cfg.logConfig(configPath)
|
||||
|
||||
return config
|
||||
return cfg
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
// version
|
||||
var version string
|
||||
var config *Config
|
||||
var globalConfig *Config
|
||||
|
||||
// repository data
|
||||
type Repository struct {
|
||||
|
|
@ -19,37 +19,31 @@ type Repository struct {
|
|||
func main() {
|
||||
|
||||
// set app version
|
||||
version = "2.2.2"
|
||||
version = "3.0.0"
|
||||
|
||||
// set appname for logger
|
||||
logger.SetAppName("gogitlabber")
|
||||
|
||||
// manage all argument magic and load configuration
|
||||
config = manageArguments()
|
||||
globalConfig = manageArguments()
|
||||
|
||||
// set debugging
|
||||
logger.SetDebug(config.Debug)
|
||||
|
||||
// check for git
|
||||
err := verifyGitAvailable()
|
||||
if err != nil {
|
||||
logger.Fatal("VALIDATION: git not found in path", err)
|
||||
}
|
||||
logger.Print("VALIDATION: git found in path", nil)
|
||||
logger.SetDebug(globalConfig.Debug)
|
||||
|
||||
// validate git backend is set
|
||||
if config.GitBackend == "" {
|
||||
if globalConfig.GitBackend == "" {
|
||||
logger.Fatal("Configuration error: git_backend is required (gitlab|gitea)", nil)
|
||||
}
|
||||
|
||||
// make initial progressbar
|
||||
if !config.Debug {
|
||||
if !globalConfig.Debug {
|
||||
progressBar()
|
||||
}
|
||||
|
||||
// fetch repository information
|
||||
var repositories []Repository
|
||||
switch config.GitBackend {
|
||||
var err error
|
||||
switch globalConfig.GitBackend {
|
||||
case "gitea":
|
||||
repositories, err = FetchRepositoriesGitea()
|
||||
if err != nil {
|
||||
|
|
@ -61,7 +55,7 @@ func main() {
|
|||
logger.Fatal("Fetching repositories failed", err)
|
||||
}
|
||||
default:
|
||||
logger.Fatal(fmt.Sprintf("Unsupported git backend: %s (supported: gitlab|gitea)", config.GitBackend), nil)
|
||||
logger.Fatal(fmt.Sprintf("Unsupported git backend: %s (supported: gitlab|gitea)", globalConfig.GitBackend), nil)
|
||||
}
|
||||
|
||||
// manage found repositories
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func progressBar() {
|
|||
|
||||
// update progressbar
|
||||
func updateProgressBar(repoCount int) error {
|
||||
if config.Debug {
|
||||
if globalConfig.Debug {
|
||||
return nil // Skip progress bar in debug mode
|
||||
}
|
||||
logger.Print("Resetting progress bar", nil)
|
||||
|
|
@ -68,7 +68,7 @@ func printPullErrorUnstaged(stats *GitStats) {
|
|||
if len(stats.pullErrorMsgUnstaged) > 0 {
|
||||
fmt.Println("Repositories with unstaged changes:")
|
||||
for _, repo := range stats.pullErrorMsgUnstaged {
|
||||
fmt.Printf("❕ %s has unstaged changes.\n", repo)
|
||||
fmt.Printf("• %s has unstaged changes.\n", repo)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ func printPullErrorUncommitted(stats *GitStats) {
|
|||
if len(stats.pullErrorMsgUncommitted) > 0 {
|
||||
fmt.Println("Repositories with uncommitted changes:")
|
||||
for _, repo := range stats.pullErrorMsgUncommitted {
|
||||
fmt.Printf("❕ %s has uncommitted changes.\n", repo)
|
||||
fmt.Printf("• %s has uncommitted changes.\n", repo)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ func printGeneralErrors(stats *GitStats) {
|
|||
if len(stats.generalErrors) > 0 {
|
||||
fmt.Println("Repositories with errors:")
|
||||
for _, repo := range stats.generalErrors {
|
||||
fmt.Printf("❌ %s failed to process.\n", repo)
|
||||
fmt.Printf("✗ %s failed to process.\n", repo)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// verify if git is available
|
||||
func verifyGitAvailable() error {
|
||||
_, err := exec.LookPath("git")
|
||||
if err != nil {
|
||||
return fmt.Errorf("git is not installed or not in PATH: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
27
go.mod
27
go.mod
|
|
@ -9,10 +9,33 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.16.4 // indirect
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251210072406-9b5f6428e1da // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/term v0.30.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/term v0.38.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
93
go.sum
93
go.sum
|
|
@ -1,15 +1,63 @@
|
|||
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.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
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.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
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/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
||||
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0 h1:eY5aB2GXiVdgTueBcqsBt53WuJTRZAuCdIS/86Pcq5c=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0/go.mod h1:0NjwVNrwtVFZBReAp5OoGklGJIgJFEbVyHneAr4lc8k=
|
||||
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
|
||||
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251210072406-9b5f6428e1da h1:ch21RnknyB1dYlWSpomdW3pXNcQZJtrtDi8wz5up31s=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251210072406-9b5f6428e1da/go.mod h1:XY/p4VJq0DwOVAAs+58NpHcQrqwHDEzMv4g8MBK7ZVA=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
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/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
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/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||
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/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
|
|
@ -20,13 +68,58 @@ github.com/scornet256/go-logger v0.0.0-20250306113004-0062c214ab34 h1:icrxu4GofO
|
|||
github.com/scornet256/go-logger v0.0.0-20250306113004-0062c214ab34/go.mod h1:GptTzXTPlyNj2mZjhRyWfmP4EDb1Ca2osDpooBy6MmI=
|
||||
github.com/scornet256/go-logger v0.0.2 h1:8k+PciBU7kZjRPAdb03zTQjnLnDaM+GDrlIOa/ur6/Y=
|
||||
github.com/scornet256/go-logger v0.0.2/go.mod h1:GptTzXTPlyNj2mZjhRyWfmP4EDb1Ca2osDpooBy6MmI=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/stretchr/objx v0.1.0/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
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=
|
||||
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/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.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue