feat: support mirrors
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Pre-commit / pre-commit (pull_request) Successful in 6m8s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Pre-commit / pre-commit (pull_request) Successful in 6m8s
				
			This commit is contained in:
		| @@ -11,7 +11,7 @@ | ||||
|     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||
|         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> | ||||
|         <text x="33.5" y="14">coverage</text> | ||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">19.4%</text> | ||||
|         <text x="86" y="14">19.4%</text> | ||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">18.8%</text> | ||||
|         <text x="86" y="14">18.8%</text> | ||||
|     </g> | ||||
| </svg> | ||||
|   | ||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
							
								
								
									
										53
									
								
								e2e-tests/issue_78_mirrors_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								e2e-tests/issue_78_mirrors_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| //go:build e2e | ||||
|  | ||||
| package e2etests_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/efficientgo/e2e" | ||||
| ) | ||||
|  | ||||
| func TestE2EIssue78Mirrors(t *testing.T) { | ||||
| 	dockerMultipleRun( | ||||
| 		t, | ||||
| 		"issue-78-mirrors", | ||||
| 		COMMON_SYSTEMS, | ||||
| 		func(t *testing.T, r e2e.Runnable) { | ||||
| 			defaultPrepare(t, r) | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-url", REPO_NAME_FOR_E2E_TESTS, "https://example.com") | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "ref") | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "clear", REPO_NAME_FOR_E2E_TESTS) | ||||
| 			execShouldError(t, r, "sudo", "alr", "ref") | ||||
|  | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", "--partial", REPO_NAME_FOR_E2E_TESTS, "gitea.plemya-x.ru/Maks1mS") | ||||
| 			execShouldError(t, r, "sudo", "alr", "ref") | ||||
|  | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||
| 			execShouldError(t, r, "sudo", "alr", "ref") | ||||
|  | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||
| 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||
| 			execShouldError(t, r, "sudo", "alr", "ref") | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
| @@ -31,7 +31,6 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/go-git/go-billy/v5" | ||||
| 	"github.com/go-git/go-billy/v5/osfs" | ||||
| 	"github.com/go-git/go-git/v5" | ||||
| 	gitConfig "github.com/go-git/go-git/v5/config" | ||||
| 	"github.com/go-git/go-git/v5/plumbing" | ||||
| @@ -69,159 +68,212 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { | ||||
| 	} | ||||
|  | ||||
| 	for _, repo := range repos { | ||||
| 		repoURL, err := url.Parse(repo.URL) | ||||
| 		urls := []string{repo.URL} | ||||
| 		urls = append(urls, repo.Mirrors...) | ||||
|  | ||||
| 		var lastErr error | ||||
|  | ||||
| 		for i, repoURL := range urls { | ||||
| 			if i > 0 { | ||||
| 				slog.Info(gotext.Get("Trying mirror"), "repo", repo.Name, "mirror", repoURL) | ||||
| 			} | ||||
|  | ||||
| 			err := rs.pullRepoFromURL(ctx, repoURL, repo) | ||||
| 			if err != nil { | ||||
| 				lastErr = err | ||||
| 				slog.Warn(gotext.Get("Failed to pull from URL"), "repo", repo.Name, "url", repoURL, "error", err) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			// Success | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		return fmt.Errorf("failed to pull repository %s from any URL: %w", repo.Name, lastErr) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func readGitRepo(repoDir, repoUrl string) (*git.Repository, bool, error) { | ||||
| 	gitDir := filepath.Join(repoDir, ".git") | ||||
| 	if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() { | ||||
| 		r, err := git.PlainOpen(repoDir) | ||||
| 		if err == nil { | ||||
| 			err = updateRemoteURL(r, repoUrl) | ||||
| 			if err == nil { | ||||
| 				_, err := r.Head() | ||||
| 				if err == nil { | ||||
| 					return r, false, nil | ||||
| 				} | ||||
|  | ||||
| 				if errors.Is(err, plumbing.ErrReferenceNotFound) { | ||||
| 					return r, true, nil | ||||
| 				} | ||||
|  | ||||
| 				slog.Debug("error getting HEAD, reinitializing...", "err", err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		slog.Debug("error while reading repo, reinitializing...", "err", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.RemoveAll(repoDir); err != nil { | ||||
| 		return nil, false, fmt.Errorf("failed to remove repo directory: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.MkdirAll(repoDir, 0o755); err != nil { | ||||
| 		return nil, false, fmt.Errorf("failed to create repo directory: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	r, err := git.PlainInit(repoDir, false) | ||||
| 	if err != nil { | ||||
| 		return nil, false, fmt.Errorf("failed to initialize git repo: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = r.CreateRemote(&gitConfig.RemoteConfig{ | ||||
| 		Name: git.DefaultRemoteName, | ||||
| 		URLs: []string{repoUrl}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, false, err | ||||
| 	} | ||||
|  | ||||
| 	return r, true, nil | ||||
| } | ||||
|  | ||||
| func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo types.Repo) error { | ||||
| 	repoURL, err := url.Parse(rawRepoUrl) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("invalid URL %s: %w", rawRepoUrl, err) | ||||
| 	} | ||||
|  | ||||
| 	slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) | ||||
| 	repoDir := filepath.Join(rs.cfg.GetPaths().RepoDir, repo.Name) | ||||
|  | ||||
| 	var repoFS billy.Filesystem | ||||
|  | ||||
| 	r, freshGit, err := readGitRepo(repoDir, repoURL.String()) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to open repo") | ||||
| 	} | ||||
|  | ||||
| 	err = r.FetchContext(ctx, &git.FetchOptions{ | ||||
| 		Progress: os.Stderr, | ||||
| 		Force:    true, | ||||
| 	}) | ||||
| 	if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var old *plumbing.Reference | ||||
|  | ||||
| 	w, err := r.Worktree() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	revHash, err := resolveHash(r, repo.Ref) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error resolving hash: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	if !freshGit { | ||||
| 		old, err = r.Head() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) | ||||
| 		repoDir := filepath.Join(rs.cfg.GetPaths().RepoDir, repo.Name) | ||||
|  | ||||
| 		var repoFS billy.Filesystem | ||||
| 		gitDir := filepath.Join(repoDir, ".git") | ||||
| 		// Only pull repos that contain valid git repos | ||||
| 		if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() { | ||||
| 			r, err := git.PlainOpen(repoDir) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = r.FetchContext(ctx, &git.FetchOptions{ | ||||
| 				Progress: os.Stderr, | ||||
| 				Force:    true, | ||||
| 			}) | ||||
| 			if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			w, err := r.Worktree() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			old, err := r.Head() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			revHash, err := resolveHash(r, repo.Ref) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("error resolving hash: %w", err) | ||||
| 			} | ||||
|  | ||||
| 			if old.Hash() == *revHash { | ||||
| 				slog.Info(gotext.Get("Repository up to date"), "name", repo.Name) | ||||
| 			} | ||||
|  | ||||
| 			err = w.Checkout(&git.CheckoutOptions{ | ||||
| 				Hash:  plumbing.NewHash(revHash.String()), | ||||
| 				Force: true, | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			repoFS = w.Filesystem | ||||
|  | ||||
| 			new, err := r.Head() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			// If the DB was not present at startup, that means it's | ||||
| 			// empty. In this case, we need to update the DB fully | ||||
| 			// rather than just incrementally. | ||||
| 			if rs.db.IsEmpty() { | ||||
| 				err = rs.processRepoFull(ctx, repo, repoDir) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} else { | ||||
| 				err = rs.processRepoChanges(ctx, repo, r, w, old, new) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			err = os.RemoveAll(repoDir) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = os.MkdirAll(repoDir, 0o755) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			r, err := git.PlainInit(repoDir, false) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			_, err = r.CreateRemote(&gitConfig.RemoteConfig{ | ||||
| 				Name: git.DefaultRemoteName, | ||||
| 				URLs: []string{repoURL.String()}, | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = r.FetchContext(ctx, &git.FetchOptions{ | ||||
| 				Progress: os.Stderr, | ||||
| 				Force:    true, | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			w, err := r.Worktree() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			revHash, err := resolveHash(r, repo.Ref) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("error resolving hash: %w", err) | ||||
| 			} | ||||
|  | ||||
| 			err = w.Checkout(&git.CheckoutOptions{ | ||||
| 				Hash:  plumbing.NewHash(revHash.String()), | ||||
| 				Force: true, | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = rs.processRepoFull(ctx, repo, repoDir) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			repoFS = osfs.New(repoDir) | ||||
| 		if old.Hash() == *revHash { | ||||
| 			slog.Info(gotext.Get("Repository up to date"), "name", repo.Name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 		fl, err := repoFS.Open("alr-repo.toml") | ||||
| 		if err != nil { | ||||
| 			slog.Warn(gotext.Get("Git repository does not appear to be a valid ALR repo"), "repo", repo.Name) | ||||
| 			continue | ||||
| 		} | ||||
| 	err = w.Checkout(&git.CheckoutOptions{ | ||||
| 		Hash:  plumbing.NewHash(revHash.String()), | ||||
| 		Force: true, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	repoFS = w.Filesystem | ||||
|  | ||||
| 		var repoCfg types.RepoConfig | ||||
| 		err = toml.NewDecoder(fl).Decode(&repoCfg) | ||||
| 	new, err := r.Head() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// If the DB was not present at startup, that means it's | ||||
| 	// empty. In this case, we need to update the DB fully | ||||
| 	// rather than just incrementally. | ||||
| 	if rs.db.IsEmpty() || freshGit { | ||||
| 		err = rs.processRepoFull(ctx, repo, repoDir) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fl.Close() | ||||
|  | ||||
| 		// If the version doesn't have a "v" prefix, it's not a standard version. | ||||
| 		// It may be "unknown" or a git version, but either way, there's no way | ||||
| 		// to compare it to the repo version, so only compare versions with the "v". | ||||
| 		if strings.HasPrefix(config.Version, "v") { | ||||
| 			if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 { | ||||
| 				slog.Warn(gotext.Get("ALR repo's minimum ALR version is greater than the current version. Try updating ALR if something doesn't work."), "repo", repo.Name) | ||||
| 			} | ||||
| 	} else { | ||||
| 		err = rs.processRepoChanges(ctx, repo, r, w, old, new) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	fl, err := repoFS.Open("alr-repo.toml") | ||||
| 	if err != nil { | ||||
| 		slog.Warn(gotext.Get("Git repository does not appear to be a valid ALR repo"), "repo", repo.Name) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	var repoCfg types.RepoConfig | ||||
| 	err = toml.NewDecoder(fl).Decode(&repoCfg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fl.Close() | ||||
|  | ||||
| 	// If the version doesn't have a "v" prefix, it's not a standard version. | ||||
| 	// It may be "unknown" or a git version, but either way, there's no way | ||||
| 	// to compare it to the repo version, so only compare versions with the "v". | ||||
| 	if strings.HasPrefix(config.Version, "v") { | ||||
| 		if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 { | ||||
| 			slog.Warn(gotext.Get("ALR repo's minimum ALR version is greater than the current version. Try updating ALR if something doesn't work."), "repo", repo.Name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func updateRemoteURL(r *git.Repository, newURL string) error { | ||||
| 	cfg, err := r.Config() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	remote, ok := cfg.Remotes[git.DefaultRemoteName] | ||||
| 	if !ok || len(remote.URLs) == 0 { | ||||
| 		return fmt.Errorf("no remote '%s' found", git.DefaultRemoteName) | ||||
| 	} | ||||
|  | ||||
| 	currentURL := remote.URLs[0] | ||||
| 	if currentURL == newURL { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	slog.Debug("Updating remote URL", "old", currentURL, "new", newURL) | ||||
|  | ||||
| 	err = r.DeleteRemote(git.DefaultRemoteName) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to delete old remote: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = r.CreateRemote(&gitConfig.RemoteConfig{ | ||||
| 		Name: git.DefaultRemoteName, | ||||
| 		URLs: []string{newURL}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to create new remote: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -26,7 +26,6 @@ import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||
| @@ -35,7 +34,7 @@ import ( | ||||
| type TestEnv struct { | ||||
| 	Ctx context.Context | ||||
| 	Cfg *TestALRConfig | ||||
| 	Db  *db.Database | ||||
| 	Db  *database.Database | ||||
| } | ||||
|  | ||||
| type TestALRConfig struct { | ||||
|   | ||||
| @@ -383,19 +383,27 @@ msgstr "" | ||||
| msgid "ERROR" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:77 | ||||
| #: internal/repos/pull.go:78 | ||||
| msgid "Trying mirror" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:84 | ||||
| msgid "Failed to pull from URL" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:151 | ||||
| msgid "Pulling repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:113 | ||||
| #: internal/repos/pull.go:188 | ||||
| msgid "Repository up to date" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:204 | ||||
| #: internal/repos/pull.go:223 | ||||
| msgid "Git repository does not appear to be a valid ALR repo" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/repos/pull.go:220 | ||||
| #: internal/repos/pull.go:239 | ||||
| msgid "" | ||||
| "ALR repo's minimum ALR version is greater than the current version. Try " | ||||
| "updating ALR if something doesn't work." | ||||
| @@ -461,63 +469,104 @@ msgstr "" | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:39 | ||||
| #: repo.go:41 | ||||
| msgid "Manage repos" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:51 repo.go:269 | ||||
| #: repo.go:55 repo.go:625 | ||||
| msgid "Remove an existing repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:53 | ||||
| #: repo.go:57 repo.go:521 | ||||
| msgid "<name>" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:83 | ||||
| #: repo.go:102 repo.go:465 repo.go:568 | ||||
| msgid "Repo \"%s\" does not exist" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:90 | ||||
| #: repo.go:109 | ||||
| msgid "Error removing repo directory" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:94 repo.go:161 repo.go:219 | ||||
| #: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | ||||
| #: repo.go:576 | ||||
| msgid "Error saving config" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:113 | ||||
| #: repo.go:132 | ||||
| msgid "Error removing packages from database" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:124 repo.go:239 | ||||
| #: repo.go:143 repo.go:595 | ||||
| msgid "Add a new repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:125 | ||||
| #: repo.go:144 repo.go:270 repo.go:345 repo.go:402 | ||||
| msgid "<name> <url>" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:150 | ||||
| #: repo.go:169 | ||||
| msgid "Repo \"%s\" already exists" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:187 | ||||
| #: repo.go:206 | ||||
| msgid "Set the reference of the repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:188 | ||||
| #: repo.go:207 | ||||
| msgid "<name> <ref>" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:246 | ||||
| #: repo.go:269 | ||||
| msgid "Set the main url of the repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:332 | ||||
| msgid "Manage mirrors of repos" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:344 | ||||
| msgid "Add a mirror URL to repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:401 | ||||
| msgid "Remove mirror from the repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:420 | ||||
| msgid "Ignore if mirror does not exist" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:425 | ||||
| msgid "Match partial URL (e.g., github.com instead of full URL)" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:490 | ||||
| msgid "No mirrors containing \"%s\" found in repo \"%s\"" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:492 | ||||
| msgid "URL \"%s\" does not exist in repo \"%s\"" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:508 repo.go:580 | ||||
| msgid "Removed %d mirrors from repo \"%s\"\n" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:520 | ||||
| msgid "Remove all mirrors from the repository" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:602 | ||||
| msgid "Name of the new repo" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:252 | ||||
| #: repo.go:608 | ||||
| msgid "URL of the new repo" | ||||
| msgstr "" | ||||
|  | ||||
| #: repo.go:276 | ||||
| #: repo.go:632 | ||||
| msgid "Name of the repo to be deleted" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
| @@ -5,15 +5,15 @@ | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: unnamed project\n" | ||||
| "PO-Revision-Date: 2025-06-15 16:05+0300\n" | ||||
| "PO-Revision-Date: 2025-06-19 18:54+0300\n" | ||||
| "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" | ||||
| "Language-Team: Russian\n" | ||||
| "Language: ru\n" | ||||
| "MIME-Version: 1.0\n" | ||||
| "Content-Type: text/plain; charset=UTF-8\n" | ||||
| "Content-Transfer-Encoding: 8bit\n" | ||||
| "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" | ||||
| "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | ||||
| "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " | ||||
| "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | ||||
| "X-Generator: Gtranslator 48.0\n" | ||||
|  | ||||
| #: build.go:42 | ||||
| @@ -356,8 +356,8 @@ msgid "" | ||||
| "This command is deprecated and would be removed in the future, use \"%s\" " | ||||
| "instead!" | ||||
| msgstr "" | ||||
| "Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s" | ||||
| "\"!" | ||||
| "Эта команда устарела и будет удалена в будущем, используйте вместо нее " | ||||
| "\"%s\"!" | ||||
|  | ||||
| #: internal/db/db.go:76 | ||||
| msgid "Database version mismatch; resetting" | ||||
| @@ -397,19 +397,27 @@ msgstr "%s %s загружается — %s/с\n" | ||||
| msgid "ERROR" | ||||
| msgstr "ОШИБКА" | ||||
|  | ||||
| #: internal/repos/pull.go:77 | ||||
| #: internal/repos/pull.go:78 | ||||
| msgid "Trying mirror" | ||||
| msgstr "Пробую зеркало" | ||||
|  | ||||
| #: internal/repos/pull.go:84 | ||||
| msgid "Failed to pull from URL" | ||||
| msgstr "Не удалось извлечь из URL" | ||||
|  | ||||
| #: internal/repos/pull.go:151 | ||||
| msgid "Pulling repository" | ||||
| msgstr "Скачивание репозитория" | ||||
|  | ||||
| #: internal/repos/pull.go:113 | ||||
| #: internal/repos/pull.go:188 | ||||
| msgid "Repository up to date" | ||||
| msgstr "Репозиторий уже обновлён" | ||||
|  | ||||
| #: internal/repos/pull.go:204 | ||||
| #: internal/repos/pull.go:223 | ||||
| msgid "Git repository does not appear to be a valid ALR repo" | ||||
| msgstr "Репозиторий Git не поддерживается репозиторием ALR" | ||||
|  | ||||
| #: internal/repos/pull.go:220 | ||||
| #: internal/repos/pull.go:239 | ||||
| msgid "" | ||||
| "ALR repo's minimum ALR version is greater than the current version. Try " | ||||
| "updating ALR if something doesn't work." | ||||
| @@ -477,63 +485,104 @@ msgstr "Ошибка при запуске приложения" | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "Скачать все изменённые репозитории" | ||||
|  | ||||
| #: repo.go:39 | ||||
| #: repo.go:41 | ||||
| msgid "Manage repos" | ||||
| msgstr "Управление репозиториями" | ||||
|  | ||||
| #: repo.go:51 repo.go:269 | ||||
| #: repo.go:55 repo.go:625 | ||||
| msgid "Remove an existing repository" | ||||
| msgstr "Удалить существующий репозиторий" | ||||
|  | ||||
| #: repo.go:53 | ||||
| #: repo.go:57 repo.go:521 | ||||
| msgid "<name>" | ||||
| msgstr "<имя>" | ||||
|  | ||||
| #: repo.go:83 | ||||
| #: repo.go:102 repo.go:465 repo.go:568 | ||||
| msgid "Repo \"%s\" does not exist" | ||||
| msgstr "Репозитория \"%s\" не существует" | ||||
|  | ||||
| #: repo.go:90 | ||||
| #: repo.go:109 | ||||
| msgid "Error removing repo directory" | ||||
| msgstr "Ошибка при удалении каталога репозитория" | ||||
|  | ||||
| #: repo.go:94 repo.go:161 repo.go:219 | ||||
| #: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | ||||
| #: repo.go:576 | ||||
| msgid "Error saving config" | ||||
| msgstr "Ошибка при сохранении конфигурации" | ||||
|  | ||||
| #: repo.go:113 | ||||
| #: repo.go:132 | ||||
| msgid "Error removing packages from database" | ||||
| msgstr "Ошибка при удалении пакетов из базы данных" | ||||
|  | ||||
| #: repo.go:124 repo.go:239 | ||||
| #: repo.go:143 repo.go:595 | ||||
| msgid "Add a new repository" | ||||
| msgstr "Добавить новый репозиторий" | ||||
|  | ||||
| #: repo.go:125 | ||||
| #: repo.go:144 repo.go:270 repo.go:345 repo.go:402 | ||||
| msgid "<name> <url>" | ||||
| msgstr "<имя> <url>" | ||||
|  | ||||
| #: repo.go:150 | ||||
| #: repo.go:169 | ||||
| msgid "Repo \"%s\" already exists" | ||||
| msgstr "Репозиторий \"%s\" уже существует" | ||||
|  | ||||
| #: repo.go:187 | ||||
| #: repo.go:206 | ||||
| msgid "Set the reference of the repository" | ||||
| msgstr "Установить ссылку на версию репозитория" | ||||
|  | ||||
| #: repo.go:188 | ||||
| #: repo.go:207 | ||||
| msgid "<name> <ref>" | ||||
| msgstr "<имя> <ссылка_на_версию>" | ||||
|  | ||||
| #: repo.go:246 | ||||
| #: repo.go:269 | ||||
| msgid "Set the main url of the repository" | ||||
| msgstr "Установить главный URL репозитория" | ||||
|  | ||||
| #: repo.go:332 | ||||
| msgid "Manage mirrors of repos" | ||||
| msgstr "Управление зеркалами репозитория" | ||||
|  | ||||
| #: repo.go:344 | ||||
| msgid "Add a mirror URL to repository" | ||||
| msgstr "Добавить зеркало репозитория" | ||||
|  | ||||
| #: repo.go:401 | ||||
| msgid "Remove mirror from the repository" | ||||
| msgstr "Удалить зеркало из репозитория" | ||||
|  | ||||
| #: repo.go:420 | ||||
| msgid "Ignore if mirror does not exist" | ||||
| msgstr "Игнорировать, если зеркала не существует" | ||||
|  | ||||
| #: repo.go:425 | ||||
| msgid "Match partial URL (e.g., github.com instead of full URL)" | ||||
| msgstr "Соответствует частичному URL (например, github.com вместо полного URL)" | ||||
|  | ||||
| #: repo.go:490 | ||||
| msgid "No mirrors containing \"%s\" found in repo \"%s\"" | ||||
| msgstr "В репозитории \"%s\" не найдено зеркал, содержащих \"%s\"" | ||||
|  | ||||
| #: repo.go:492 | ||||
| msgid "URL \"%s\" does not exist in repo \"%s\"" | ||||
| msgstr "URL \"%s\" не существует в репозитории \"%s\"" | ||||
|  | ||||
| #: repo.go:508 repo.go:580 | ||||
| msgid "Removed %d mirrors from repo \"%s\"\n" | ||||
| msgstr "Удалены зеркала %d из репозитория \"%s\"\n" | ||||
|  | ||||
| #: repo.go:520 | ||||
| msgid "Remove all mirrors from the repository" | ||||
| msgstr "Удалить все зеркала из репозитория" | ||||
|  | ||||
| #: repo.go:602 | ||||
| msgid "Name of the new repo" | ||||
| msgstr "Название нового репозитория" | ||||
|  | ||||
| #: repo.go:252 | ||||
| #: repo.go:608 | ||||
| msgid "URL of the new repo" | ||||
| msgstr "URL-адрес нового репозитория" | ||||
|  | ||||
| #: repo.go:276 | ||||
| #: repo.go:632 | ||||
| msgid "Name of the repo to be deleted" | ||||
| msgstr "Название репозитория  удалён" | ||||
|  | ||||
|   | ||||
| @@ -32,7 +32,8 @@ type Config struct { | ||||
|  | ||||
| // Repo represents a ALR repo within a configuration file | ||||
| type Repo struct { | ||||
| 	Name string `toml:"name"` | ||||
| 	URL  string `toml:"url"` | ||||
| 	Ref  string `toml:"ref"` | ||||
| 	Name    string   `toml:"name"` | ||||
| 	URL     string   `toml:"url"` | ||||
| 	Ref     string   `toml:"ref"` | ||||
| 	Mirrors []string `toml:"mirrors"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										356
									
								
								repo.go
									
									
									
									
									
								
							
							
						
						
									
										356
									
								
								repo.go
									
									
									
									
									
								
							| @@ -20,8 +20,10 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| @@ -41,6 +43,8 @@ func RepoCmd() *cli.Command { | ||||
| 			RemoveRepoCmd(), | ||||
| 			AddRepoCmd(), | ||||
| 			SetRepoRefCmd(), | ||||
| 			RepoMirrorCmd(), | ||||
| 			SetUrlCmd(), | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| @@ -51,6 +55,21 @@ func RemoveRepoCmd() *cli.Command { | ||||
| 		Usage:     gotext.Get("Remove an existing repository"), | ||||
| 		Aliases:   []string{"rm"}, | ||||
| 		ArgsUsage: gotext.Get("<name>"), | ||||
| 		BashComplete: func(c *cli.Context) { | ||||
| 			if c.NArg() == 0 { | ||||
| 				// Get repo names from config | ||||
| 				ctx := c.Context | ||||
| 				deps, err := appbuilder.New(ctx).WithConfig().Build() | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				defer deps.Defer() | ||||
|  | ||||
| 				for _, repo := range deps.Cfg.Repos() { | ||||
| 					fmt.Println(repo.Name) | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||
| 			if c.Args().Len() < 1 { | ||||
| 				return cliutils.FormatCliExit("missing args", nil) | ||||
| @@ -186,6 +205,21 @@ func SetRepoRefCmd() *cli.Command { | ||||
| 		Name:      "set-ref", | ||||
| 		Usage:     gotext.Get("Set the reference of the repository"), | ||||
| 		ArgsUsage: gotext.Get("<name> <ref>"), | ||||
| 		BashComplete: func(c *cli.Context) { | ||||
| 			if c.NArg() == 0 { | ||||
| 				// Get repo names from config | ||||
| 				ctx := c.Context | ||||
| 				deps, err := appbuilder.New(ctx).WithConfig().Build() | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				defer deps.Defer() | ||||
|  | ||||
| 				for _, repo := range deps.Cfg.Repos() { | ||||
| 					fmt.Println(repo.Name) | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||
| 			if c.Args().Len() < 2 { | ||||
| 				return cliutils.FormatCliExit("missing args", nil) | ||||
| @@ -229,6 +263,328 @@ func SetRepoRefCmd() *cli.Command { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func SetUrlCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:      "set-url", | ||||
| 		Usage:     gotext.Get("Set the main url of the repository"), | ||||
| 		ArgsUsage: gotext.Get("<name> <url>"), | ||||
| 		BashComplete: func(c *cli.Context) { | ||||
| 			if c.NArg() == 0 { | ||||
| 				// Get repo names from config | ||||
| 				ctx := c.Context | ||||
| 				deps, err := appbuilder.New(ctx).WithConfig().Build() | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				defer deps.Defer() | ||||
|  | ||||
| 				for _, repo := range deps.Cfg.Repos() { | ||||
| 					fmt.Println(repo.Name) | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||
| 			if c.Args().Len() < 2 { | ||||
| 				return cliutils.FormatCliExit("missing args", nil) | ||||
| 			} | ||||
|  | ||||
| 			name := c.Args().Get(0) | ||||
| 			repoUrl := c.Args().Get(1) | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(c.Context). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithReposNoPull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			repos := deps.Cfg.Repos() | ||||
| 			newRepos := []types.Repo{} | ||||
| 			for _, repo := range repos { | ||||
| 				if repo.Name == name { | ||||
| 					repo.URL = repoUrl | ||||
| 				} | ||||
| 				newRepos = append(newRepos, repo) | ||||
| 			} | ||||
| 			deps.Cfg.SetRepos(newRepos) | ||||
| 			err = deps.Cfg.SaveUserConfig() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) | ||||
| 			} | ||||
|  | ||||
| 			err = deps.Repos.Pull(c.Context, newRepos) | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func RepoMirrorCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:  "mirror", | ||||
| 		Usage: gotext.Get("Manage mirrors of repos"), | ||||
| 		Subcommands: []*cli.Command{ | ||||
| 			AddMirror(), | ||||
| 			RemoveMirror(), | ||||
| 			ClearMirrors(), | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func AddMirror() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:      "add", | ||||
| 		Usage:     gotext.Get("Add a mirror URL to repository"), | ||||
| 		ArgsUsage: gotext.Get("<name> <url>"), | ||||
| 		BashComplete: func(c *cli.Context) { | ||||
| 			if c.NArg() == 0 { | ||||
| 				ctx := c.Context | ||||
| 				deps, err := appbuilder.New(ctx).WithConfig().Build() | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				defer deps.Defer() | ||||
|  | ||||
| 				for _, repo := range deps.Cfg.Repos() { | ||||
| 					fmt.Println(repo.Name) | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||
| 			if c.Args().Len() < 2 { | ||||
| 				return cliutils.FormatCliExit("missing args", nil) | ||||
| 			} | ||||
|  | ||||
| 			name := c.Args().Get(0) | ||||
| 			url := c.Args().Get(1) | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(c.Context). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithReposNoPull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			repos := deps.Cfg.Repos() | ||||
| 			for i, repo := range repos { | ||||
| 				if repo.Name == name { | ||||
| 					repos[i].Mirrors = append(repos[i].Mirrors, url) | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			deps.Cfg.SetRepos(repos) | ||||
| 			err = deps.Cfg.SaveUserConfig() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func RemoveMirror() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:      "remove", | ||||
| 		Aliases:   []string{"rm"}, | ||||
| 		Usage:     gotext.Get("Remove mirror from the repository"), | ||||
| 		ArgsUsage: gotext.Get("<name> <url>"), | ||||
| 		BashComplete: func(c *cli.Context) { | ||||
| 			ctx := c.Context | ||||
| 			deps, err := appbuilder.New(ctx).WithConfig().Build() | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			if c.NArg() == 0 { | ||||
| 				for _, repo := range deps.Cfg.Repos() { | ||||
| 					fmt.Println(repo.Name) | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.BoolFlag{ | ||||
| 				Name:  "ignore-missing", | ||||
| 				Usage: gotext.Get("Ignore if mirror does not exist"), | ||||
| 			}, | ||||
| 			&cli.BoolFlag{ | ||||
| 				Name:    "partial", | ||||
| 				Aliases: []string{"p"}, | ||||
| 				Usage:   gotext.Get("Match partial URL (e.g., github.com instead of full URL)"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||
| 			if c.Args().Len() < 2 { | ||||
| 				return cliutils.FormatCliExit("missing args", nil) | ||||
| 			} | ||||
|  | ||||
| 			name := c.Args().Get(0) | ||||
| 			urlToRemove := c.Args().Get(1) | ||||
| 			ignoreMissing := c.Bool("ignore-missing") | ||||
| 			partialMatch := c.Bool("partial") | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(c.Context). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithReposNoPull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			reposSlice := deps.Cfg.Repos() | ||||
| 			repoIndex := -1 | ||||
| 			urlIndicesToRemove := []int{} | ||||
|  | ||||
| 			// Находим репозиторий | ||||
| 			for i, repo := range reposSlice { | ||||
| 				if repo.Name == name { | ||||
| 					repoIndex = i | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if repoIndex == -1 { | ||||
| 				if ignoreMissing { | ||||
| 					return nil // Тихо завершаем, если репозиторий не найден | ||||
| 				} | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" does not exist", name), nil) | ||||
| 			} | ||||
|  | ||||
| 			// Ищем зеркала для удаления | ||||
| 			repo := reposSlice[repoIndex] | ||||
| 			for j, mirror := range repo.Mirrors { | ||||
| 				var match bool | ||||
| 				if partialMatch { | ||||
| 					// Частичное совпадение - проверяем, содержит ли зеркало указанную строку | ||||
| 					match = strings.Contains(mirror, urlToRemove) | ||||
| 				} else { | ||||
| 					// Точное совпадение | ||||
| 					match = mirror == urlToRemove | ||||
| 				} | ||||
|  | ||||
| 				if match { | ||||
| 					urlIndicesToRemove = append(urlIndicesToRemove, j) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if len(urlIndicesToRemove) == 0 { | ||||
| 				if ignoreMissing { | ||||
| 					return nil | ||||
| 				} | ||||
| 				if partialMatch { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("No mirrors containing \"%s\" found in repo \"%s\"", urlToRemove, name), nil) | ||||
| 				} else { | ||||
| 					return cliutils.FormatCliExit(gotext.Get("URL \"%s\" does not exist in repo \"%s\"", urlToRemove, name), nil) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			for i := len(urlIndicesToRemove) - 1; i >= 0; i-- { | ||||
| 				urlIndex := urlIndicesToRemove[i] | ||||
| 				reposSlice[repoIndex].Mirrors = slices.Delete(reposSlice[repoIndex].Mirrors, urlIndex, urlIndex+1) | ||||
| 			} | ||||
|  | ||||
| 			deps.Cfg.SetRepos(reposSlice) | ||||
| 			err = deps.Cfg.SaveUserConfig() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) | ||||
| 			} | ||||
|  | ||||
| 			if len(urlIndicesToRemove) > 1 { | ||||
| 				fmt.Println(gotext.Get("Removed %d mirrors from repo \"%s\"\n", len(urlIndicesToRemove), name)) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ClearMirrors() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:      "clear", | ||||
| 		Aliases:   []string{"rm-all"}, | ||||
| 		Usage:     gotext.Get("Remove all mirrors from the repository"), | ||||
| 		ArgsUsage: gotext.Get("<name>"), | ||||
| 		BashComplete: func(c *cli.Context) { | ||||
| 			if c.NArg() == 0 { | ||||
| 				// Get repo names from config | ||||
| 				ctx := c.Context | ||||
| 				deps, err := appbuilder.New(ctx).WithConfig().Build() | ||||
| 				if err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				defer deps.Defer() | ||||
|  | ||||
| 				for _, repo := range deps.Cfg.Repos() { | ||||
| 					fmt.Println(repo.Name) | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||
| 			if c.Args().Len() < 1 { | ||||
| 				return cliutils.FormatCliExit("missing args", nil) | ||||
| 			} | ||||
|  | ||||
| 			name := c.Args().Get(0) | ||||
|  | ||||
| 			deps, err := appbuilder. | ||||
| 				New(c.Context). | ||||
| 				WithConfig(). | ||||
| 				WithDB(). | ||||
| 				WithReposNoPull(). | ||||
| 				Build() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer deps.Defer() | ||||
|  | ||||
| 			reposSlice := deps.Cfg.Repos() | ||||
| 			repoIndex := -1 | ||||
| 			urlIndicesToRemove := []int{} | ||||
|  | ||||
| 			// Находим репозиторий | ||||
| 			for i, repo := range reposSlice { | ||||
| 				if repo.Name == name { | ||||
| 					repoIndex = i | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if repoIndex == -1 { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Repo \"%s\" does not exist", name), nil) | ||||
| 			} | ||||
|  | ||||
| 			reposSlice[repoIndex].Mirrors = []string{} | ||||
|  | ||||
| 			deps.Cfg.SetRepos(reposSlice) | ||||
| 			err = deps.Cfg.SaveUserConfig() | ||||
| 			if err != nil { | ||||
| 				return cliutils.FormatCliExit(gotext.Get("Error saving config"), err) | ||||
| 			} | ||||
|  | ||||
| 			if len(urlIndicesToRemove) > 1 { | ||||
| 				fmt.Println(gotext.Get("Removed %d mirrors from repo \"%s\"\n", len(urlIndicesToRemove), name)) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TODO: remove | ||||
| // | ||||
| // Deprecated: use "alr repo add" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user