forked from Plemya-x/ALR
		
	feat: add checksum for git downloader
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">18.9%</text> | ||||
|         <text x="86" y="14">18.9%</text> | ||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">20.2%</text> | ||||
|         <text x="86" y="14">20.2%</text> | ||||
|     </g> | ||||
| </svg> | ||||
|   | ||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
| @@ -20,11 +20,12 @@ import ( | ||||
| 	"context" | ||||
| 	"encoding/hex" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | ||||
| ) | ||||
|  | ||||
| type SourceDownloader struct { | ||||
| @@ -76,6 +77,7 @@ func (s *SourceDownloader) DownloadSources( | ||||
|  | ||||
| 		opts.DlCache = dlcache.New(s.cfg) | ||||
|  | ||||
| 		slog.Warn("opts", "opts", opts) | ||||
| 		err := dl.Download(ctx, opts) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
|   | ||||
| @@ -355,30 +355,6 @@ msgid "" | ||||
| "Database version does not exist. Run alr fix if something isn't working." | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/dl/dl.go:170 | ||||
| msgid "Source can be updated, updating if required" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/dl/dl.go:201 | ||||
| msgid "Source found in cache and linked to destination" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/dl/dl.go:208 | ||||
| msgid "Source updated and linked to destination" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/dl/dl.go:222 | ||||
| msgid "Downloading source" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/dl/progress_tui.go:100 | ||||
| msgid "%s: done!\n" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/dl/progress_tui.go:104 | ||||
| msgid "%s %s downloading at %s/s\n" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/logger/log.go:41 | ||||
| msgid "ERROR" | ||||
| msgstr "" | ||||
| @@ -465,6 +441,30 @@ msgstr "" | ||||
| msgid "Error while running app" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/dl/dl.go:170 | ||||
| msgid "Source can be updated, updating if required" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/dl/dl.go:201 | ||||
| msgid "Source found in cache and linked to destination" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/dl/dl.go:208 | ||||
| msgid "Source updated and linked to destination" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/dl/dl.go:222 | ||||
| msgid "Downloading source" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/dl/progress_tui.go:100 | ||||
| msgid "%s: done!\n" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/dl/progress_tui.go:104 | ||||
| msgid "%s %s downloading at %s/s\n" | ||||
| msgstr "" | ||||
|  | ||||
| #: refresh.go:30 | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "" | ||||
|   | ||||
| @@ -369,30 +369,6 @@ msgid "" | ||||
| msgstr "" | ||||
| "Версия базы данных не существует. Запустите alr fix, если что-то не работает." | ||||
|  | ||||
| #: internal/dl/dl.go:170 | ||||
| msgid "Source can be updated, updating if required" | ||||
| msgstr "Исходный код можно обновлять, обновляя при необходимости" | ||||
|  | ||||
| #: internal/dl/dl.go:201 | ||||
| msgid "Source found in cache and linked to destination" | ||||
| msgstr "Источник найден в кэше и связан с пунктом назначения" | ||||
|  | ||||
| #: internal/dl/dl.go:208 | ||||
| msgid "Source updated and linked to destination" | ||||
| msgstr "Источник обновлён и связан с пунктом назначения" | ||||
|  | ||||
| #: internal/dl/dl.go:222 | ||||
| msgid "Downloading source" | ||||
| msgstr "Скачивание источника" | ||||
|  | ||||
| #: internal/dl/progress_tui.go:100 | ||||
| msgid "%s: done!\n" | ||||
| msgstr "%s: выполнено!\n" | ||||
|  | ||||
| #: internal/dl/progress_tui.go:104 | ||||
| msgid "%s %s downloading at %s/s\n" | ||||
| msgstr "%s %s загружается — %s/с\n" | ||||
|  | ||||
| #: internal/logger/log.go:41 | ||||
| msgid "ERROR" | ||||
| msgstr "ОШИБКА" | ||||
| @@ -481,6 +457,30 @@ msgstr "Показать справку" | ||||
| msgid "Error while running app" | ||||
| msgstr "Ошибка при запуске приложения" | ||||
|  | ||||
| #: pkg/dl/dl.go:170 | ||||
| msgid "Source can be updated, updating if required" | ||||
| msgstr "Исходный код можно обновлять, обновляя при необходимости" | ||||
|  | ||||
| #: pkg/dl/dl.go:201 | ||||
| msgid "Source found in cache and linked to destination" | ||||
| msgstr "Источник найден в кэше и связан с пунктом назначения" | ||||
|  | ||||
| #: pkg/dl/dl.go:208 | ||||
| msgid "Source updated and linked to destination" | ||||
| msgstr "Источник обновлён и связан с пунктом назначения" | ||||
|  | ||||
| #: pkg/dl/dl.go:222 | ||||
| msgid "Downloading source" | ||||
| msgstr "Скачивание источника" | ||||
|  | ||||
| #: pkg/dl/progress_tui.go:100 | ||||
| msgid "%s: done!\n" | ||||
| msgstr "%s: выполнено!\n" | ||||
|  | ||||
| #: pkg/dl/progress_tui.go:104 | ||||
| msgid "%s %s downloading at %s/s\n" | ||||
| msgstr "%s %s загружается — %s/с\n" | ||||
|  | ||||
| #: refresh.go:30 | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "Скачать все изменённые репозитории" | ||||
|   | ||||
| @@ -55,7 +55,7 @@ var ( | ||||
| 
 | ||||
| // Массив доступных загрузчиков в порядке их проверки | ||||
| var Downloaders = []Downloader{ | ||||
| 	GitDownloader{}, | ||||
| 	&GitDownloader{}, | ||||
| 	TorrentDownloader{}, | ||||
| 	FileDownloader{}, | ||||
| } | ||||
| @@ -32,8 +32,8 @@ import ( | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 
 | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | ||||
| ) | ||||
| 
 | ||||
| type TestALRConfig struct{} | ||||
| @@ -20,8 +20,11 @@ | ||||
| package dl | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"encoding/hex" | ||||
| 	"errors" | ||||
| 	"log/slog" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| @@ -48,7 +51,7 @@ func (GitDownloader) MatchURL(u string) bool { | ||||
| // Download uses git to clone the repository from the specified URL. | ||||
| // It allows specifying the revision, depth and recursion options | ||||
| // via query string | ||||
| func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { | ||||
| func (d *GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { | ||||
| 	u, err := url.Parse(opts.URL) | ||||
| 	if err != nil { | ||||
| 		return 0, "", err | ||||
| @@ -60,6 +63,9 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, | ||||
| 	rev := query.Get("~rev") | ||||
| 	query.Del("~rev") | ||||
| 
 | ||||
| 	// Right now, this only affects the return value of name, | ||||
| 	// which will be used by dl_cache. | ||||
| 	// It seems wrong, but for now it's better to leave it as it is. | ||||
| 	name := query.Get("~name") | ||||
| 	query.Del("~name") | ||||
| 
 | ||||
| @@ -121,6 +127,11 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	err = d.verifyHash(opts) | ||||
| 	if err != nil { | ||||
| 		return 0, "", err | ||||
| 	} | ||||
| 
 | ||||
| 	if name == "" { | ||||
| 		name = strings.TrimSuffix(path.Base(u.Path), ".git") | ||||
| 	} | ||||
| @@ -128,12 +139,36 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, | ||||
| 	return TypeDir, name, nil | ||||
| } | ||||
| 
 | ||||
| func (GitDownloader) verifyHash(opts Options) error { | ||||
| 	if opts.Hash != nil { | ||||
| 		h, err := opts.NewHash() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = HashDir(opts.Destination, h) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		sum := h.Sum(nil) | ||||
| 
 | ||||
| 		slog.Warn("validate checksum", "real", hex.EncodeToString(sum), "expected", hex.EncodeToString(opts.Hash)) | ||||
| 
 | ||||
| 		if !bytes.Equal(sum, opts.Hash) { | ||||
| 			return ErrChecksumMismatch | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Update uses git to pull the repository and update it | ||||
| // to the latest revision. It allows specifying the depth | ||||
| // and recursion options via query string. It returns | ||||
| // true if update was successful and false if the | ||||
| // repository is already up-to-date | ||||
| func (GitDownloader) Update(opts Options) (bool, error) { | ||||
| func (d *GitDownloader) Update(opts Options) (bool, error) { | ||||
| 	u, err := url.Parse(opts.URL) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| @@ -183,18 +218,21 @@ func (GitDownloader) Update(opts Options) (bool, error) { | ||||
| 	manifestOK := err == nil | ||||
| 
 | ||||
| 	err = w.Pull(po) | ||||
| 	if errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||
| 		return false, nil | ||||
| 	} else if err != nil { | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||
| 			return false, nil | ||||
| 		} | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	err = d.verifyHash(opts) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	if manifestOK { | ||||
| 		err = writeManifest(opts.Destination, m) | ||||
| 		if err != nil { | ||||
| 			return true, err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return true, nil | ||||
| 	return true, err | ||||
| } | ||||
							
								
								
									
										183
									
								
								pkg/dl/git_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								pkg/dl/git_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | ||||
| // 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/>. | ||||
|  | ||||
| package dl_test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/hex" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | ||||
| ) | ||||
|  | ||||
| func TestGitDownloaderMatchUrl(t *testing.T) { | ||||
| 	d := dl.GitDownloader{} | ||||
| 	assert.True(t, d.MatchURL("git+https://example.com/org/project.git")) | ||||
| 	assert.False(t, d.MatchURL("https://example.com/org/project.git")) | ||||
| } | ||||
|  | ||||
| func TestGitDownloaderDownload(t *testing.T) { | ||||
| 	d := dl.GitDownloader{} | ||||
|  | ||||
| 	createTempDir := func(t *testing.T, name string) string { | ||||
| 		t.Helper() | ||||
| 		dir, err := os.MkdirTemp("", "test-"+name) | ||||
| 		assert.NoError(t, err) | ||||
| 		t.Cleanup(func() { | ||||
| 			_ = os.RemoveAll(dir) | ||||
| 		}) | ||||
| 		return dir | ||||
| 	} | ||||
|  | ||||
| 	t.Run("simple", func(t *testing.T) { | ||||
| 		dest := createTempDir(t, "simple") | ||||
|  | ||||
| 		dlType, name, err := d.Download(context.Background(), dl.Options{ | ||||
| 			URL:         "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git", | ||||
| 			Destination: dest, | ||||
| 		}) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, dl.TypeDir, dlType) | ||||
| 		assert.Equal(t, "repo-for-tests", name) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("with hash", func(t *testing.T) { | ||||
| 		dest := createTempDir(t, "with-hash") | ||||
|  | ||||
| 		hsh, err := hex.DecodeString("33c912b855352663550003ca6b948ae3df1f38e2c036f5a85775df5967e143bf") | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		dlType, name, err := d.Download(context.Background(), dl.Options{ | ||||
| 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git?~rev=init&~name=test", | ||||
| 			Destination:   dest, | ||||
| 			Hash:          hsh, | ||||
| 			HashAlgorithm: "sha256", | ||||
| 		}) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, dl.TypeDir, dlType) | ||||
| 		assert.Equal(t, "test", name) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("with hash (checksum mismatch)", func(t *testing.T) { | ||||
| 		dest := createTempDir(t, "with-hash-checksum-mismatch") | ||||
|  | ||||
| 		hsh, err := hex.DecodeString("33c912b855352663550003ca6b948ae3df1f38e2c036f5a85775df5967e143bf") | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		_, _, err = d.Download(context.Background(), dl.Options{ | ||||
| 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git", | ||||
| 			Destination:   dest, | ||||
| 			Hash:          hsh, | ||||
| 			HashAlgorithm: "sha256", | ||||
| 		}) | ||||
|  | ||||
| 		assert.ErrorIs(t, err, dl.ErrChecksumMismatch) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestGitDownloaderUpdate(t *testing.T) { | ||||
| 	d := dl.GitDownloader{} | ||||
|  | ||||
| 	createTempDir := func(t *testing.T, name string) string { | ||||
| 		t.Helper() | ||||
| 		dir, err := os.MkdirTemp("", "test-"+name) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		t.Cleanup(func() { | ||||
| 			_ = os.RemoveAll(dir) | ||||
| 		}) | ||||
| 		return dir | ||||
| 	} | ||||
|  | ||||
| 	setupOldRepo := func(t *testing.T, dest string) { | ||||
| 		t.Helper() | ||||
|  | ||||
| 		cmd := exec.Command("git", "clone", "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git", dest) | ||||
| 		err := cmd.Run() | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		cmd = exec.Command("git", "-C", dest, "reset", "--hard", "init") | ||||
| 		err = cmd.Run() | ||||
| 		assert.NoError(t, err) | ||||
| 	} | ||||
|  | ||||
| 	t.Run("simple", func(t *testing.T) { | ||||
| 		dest := createTempDir(t, "update") | ||||
|  | ||||
| 		setupOldRepo(t, dest) | ||||
|  | ||||
| 		cmd := exec.Command("git", "-C", dest, "rev-parse", "HEAD") | ||||
| 		oldHash, err := cmd.Output() | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		updated, err := d.Update(dl.Options{ | ||||
| 			URL:         "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git", | ||||
| 			Destination: dest, | ||||
| 		}) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.True(t, updated) | ||||
|  | ||||
| 		cmd = exec.Command("git", "-C", dest, "rev-parse", "HEAD") | ||||
| 		newHash, err := cmd.Output() | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.NotEqual(t, string(oldHash), string(newHash), "Repository should be updated") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("with hash", func(t *testing.T) { | ||||
| 		dest := createTempDir(t, "update") | ||||
|  | ||||
| 		setupOldRepo(t, dest) | ||||
|  | ||||
| 		hsh, err := hex.DecodeString("0dc4f3c68c435d0cd7a5ee960f965815fa9c4ee0571839cdb8f9de56e06f91eb") | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		updated, err := d.Update(dl.Options{ | ||||
| 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git~rev=test-update-git-downloader", | ||||
| 			Destination:   dest, | ||||
| 			Hash:          hsh, | ||||
| 			HashAlgorithm: "sha256", | ||||
| 		}) | ||||
|  | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.True(t, updated) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("with hash (checksum mismatch)", func(t *testing.T) { | ||||
| 		dest := createTempDir(t, "update") | ||||
|  | ||||
| 		setupOldRepo(t, dest) | ||||
|  | ||||
| 		hsh, err := hex.DecodeString("33c912b855352663550003ca6b948ae3df1f38e2c036f5a85775df5967e143bf") | ||||
| 		assert.NoError(t, err) | ||||
|  | ||||
| 		_, err = d.Update(dl.Options{ | ||||
| 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git?~rev=test-update-git-downloader", | ||||
| 			Destination:   dest, | ||||
| 			Hash:          hsh, | ||||
| 			HashAlgorithm: "sha256", | ||||
| 		}) | ||||
|  | ||||
| 		assert.ErrorIs(t, err, dl.ErrChecksumMismatch) | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										55
									
								
								pkg/dl/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								pkg/dl/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| // 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/>. | ||||
|  | ||||
| package dl | ||||
|  | ||||
| import ( | ||||
| 	"hash" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| func HashDir(dirPath string, h hash.Hash) error { | ||||
| 	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		// Skip .git directory | ||||
| 		if info.IsDir() && info.Name() == ".git" { | ||||
| 			return filepath.SkipDir | ||||
| 		} | ||||
| 		// Skip directories (only process files) | ||||
| 		if !info.Mode().IsRegular() { | ||||
| 			return nil | ||||
| 		} | ||||
| 		// Open file | ||||
| 		f, err := os.Open(path) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		defer f.Close() | ||||
| 		// Write file content to hasher | ||||
| 		if _, err := io.Copy(h, f); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -29,7 +29,7 @@ import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | ||||
| ) | ||||
| 
 | ||||
| type TestALRConfig struct { | ||||
		Reference in New Issue
	
	Block a user