feat: support single package repository
This commit is contained in:
		
							
								
								
									
										53
									
								
								e2e-tests/issue_76_single_package_repo_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								e2e-tests/issue_76_single_package_repo_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 Test75SinglePackageRepo(t *testing.T) { | ||||||
|  | 	dockerMultipleRun( | ||||||
|  | 		t, | ||||||
|  | 		"issue-76-single-package-repo", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r e2e.Runnable) { | ||||||
|  | 			execShouldNoError(t, r, | ||||||
|  | 				"sudo", | ||||||
|  | 				"alr", | ||||||
|  | 				"repo", | ||||||
|  | 				"add", | ||||||
|  | 				REPO_NAME_FOR_E2E_TESTS, | ||||||
|  | 				"https://gitea.plemya-x.ru/Maks1mS/test-single-package-alr-repo.git", | ||||||
|  | 			) | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "1075c918be") | ||||||
|  | 			execShouldNoError(t, r, "alr", "ref") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "in", "test-single-repo") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr list -U") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "5e361c50d7") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "ref") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "up") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
| @@ -18,6 +18,7 @@ package build | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| @@ -40,10 +41,21 @@ func (s *ScriptResolver) ResolveScript( | |||||||
|  |  | ||||||
| 	repodir := s.cfg.GetPaths().RepoDir | 	repodir := s.cfg.GetPaths().RepoDir | ||||||
| 	repository = pkg.Repository | 	repository = pkg.Repository | ||||||
| 	if pkg.BasePkgName != "" { |  | ||||||
| 		script = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh") | 	// First, we check if there is a root alr.sh in the repository | ||||||
|  | 	rootScriptPath := filepath.Join(repodir, repository, "alr.sh") | ||||||
|  | 	if _, err := os.Stat(rootScriptPath); err == nil { | ||||||
|  | 		// A repository with a single alr.sh at the root | ||||||
|  | 		script = rootScriptPath | ||||||
| 	} else { | 	} else { | ||||||
| 		script = filepath.Join(repodir, repository, pkg.Name, "alr.sh") | 		// Multi-package repository - we are looking for alr.sh in the subfolder | ||||||
|  | 		var scriptPath string | ||||||
|  | 		if pkg.BasePkgName != "" { | ||||||
|  | 			scriptPath = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh") | ||||||
|  | 		} else { | ||||||
|  | 			scriptPath = filepath.Join(repodir, repository, pkg.Name, "alr.sh") | ||||||
|  | 		} | ||||||
|  | 		script = scriptPath | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &ScriptInfo{ | 	return &ScriptInfo{ | ||||||
|   | |||||||
| @@ -277,7 +277,15 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git | |||||||
| 	for _, fp := range patch.FilePatches() { | 	for _, fp := range patch.FilePatches() { | ||||||
| 		from, to := fp.Files() | 		from, to := fp.Files() | ||||||
|  |  | ||||||
| 		if !isValid(from, to) { | 		var isValidPath bool | ||||||
|  | 		if from != nil { | ||||||
|  | 			isValidPath = isValidScriptPath(from.Path()) | ||||||
|  | 		} | ||||||
|  | 		if to != nil { | ||||||
|  | 			isValidPath = isValidPath || isValidScriptPath(to.Path()) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !isValidPath { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -316,55 +324,60 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git | |||||||
| 	parser := syntax.NewParser() | 	parser := syntax.NewParser() | ||||||
|  |  | ||||||
| 	for _, action := range actions { | 	for _, action := range actions { | ||||||
| 		runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(filepath.Join(repoDir, action.File))) | 		var scriptDir string | ||||||
|  | 		if filepath.Dir(action.File) == "." { | ||||||
|  | 			scriptDir = repoDir | ||||||
|  | 		} else { | ||||||
|  | 			scriptDir = filepath.Dir(filepath.Join(repoDir, action.File)) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		runner, err := rs.processRepoChangesRunner(repoDir, scriptDir) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("error creating process repo changes runner: %w", err) | 			return fmt.Errorf("error creating process repo changes runner: %w", err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		switch action.Type { | 		switch action.Type { | ||||||
| 		case actionDelete: | 		case actionDelete: | ||||||
| 			if filepath.Base(action.File) != "alr.sh" { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			scriptFl, err := oldCommit.File(action.File) | 			scriptFl, err := oldCommit.File(action.File) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil | 				slog.Warn("Failed to get deleted file from old commit", "file", action.File, "error", err) | ||||||
|  | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r, err := scriptFl.Reader() | 			r, err := scriptFl.Reader() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil | 				slog.Warn("Failed to read deleted file", "file", action.File, "error", err) | ||||||
|  | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			pkgs, err := parseScript(ctx, repo, parser, runner, r) | 			pkgs, err := parseScript(ctx, repo, parser, runner, r) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return fmt.Errorf("error parsing deleted script %s: %w", action.File, err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for _, pkg := range pkgs { | 			for _, pkg := range pkgs { | ||||||
| 				err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name) | 				err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return err | 					return fmt.Errorf("error deleting package %s: %w", pkg.Name, err) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		case actionUpdate: |  | ||||||
| 			if filepath.Base(action.File) != "alr.sh" { |  | ||||||
| 				action.File = filepath.Join(filepath.Dir(action.File), "alr.sh") |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
|  | 		case actionUpdate: | ||||||
| 			scriptFl, err := newCommit.File(action.File) | 			scriptFl, err := newCommit.File(action.File) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil | 				slog.Warn("Failed to get updated file from new commit", "file", action.File, "error", err) | ||||||
|  | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r, err := scriptFl.Reader() | 			r, err := scriptFl.Reader() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil | 				slog.Warn("Failed to read updated file", "file", action.File, "error", err) | ||||||
|  | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			err = rs.updatePkg(ctx, repo, runner, r) | 			err = rs.updatePkg(ctx, repo, runner, r) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return fmt.Errorf("error updatePkg: %w", err) | 				return fmt.Errorf("error updating package from %s: %w", action.File, err) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -372,27 +385,68 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func isValidScriptPath(path string) bool { | ||||||
|  | 	if filepath.Base(path) != "alr.sh" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dir := filepath.Dir(path) | ||||||
|  | 	return dir == "." || !strings.Contains(strings.TrimPrefix(dir, "./"), "/") | ||||||
|  | } | ||||||
|  |  | ||||||
| func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error { | func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error { | ||||||
| 	glob := filepath.Join(repoDir, "/*/alr.sh") | 	rootScript := filepath.Join(repoDir, "alr.sh") | ||||||
|  | 	if fi, err := os.Stat(rootScript); err == nil && !fi.IsDir() { | ||||||
|  | 		slog.Debug("Found root alr.sh, processing single-script repository", "repo", repo.Name) | ||||||
|  |  | ||||||
|  | 		runner, err := rs.processRepoChangesRunner(repoDir, repoDir) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error creating runner for root alr.sh: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		scriptFl, err := os.Open(rootScript) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error opening root alr.sh: %w", err) | ||||||
|  | 		} | ||||||
|  | 		defer scriptFl.Close() | ||||||
|  |  | ||||||
|  | 		err = rs.updatePkg(ctx, repo, runner, scriptFl) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error processing root alr.sh: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	glob := filepath.Join(repoDir, "*/alr.sh") | ||||||
| 	matches, err := filepath.Glob(glob) | 	matches, err := filepath.Glob(glob) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return fmt.Errorf("error globbing for alr.sh files: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if len(matches) == 0 { | ||||||
|  | 		slog.Warn("No alr.sh files found in repository", "repo", repo.Name) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Debug("Found multiple alr.sh files, processing multi-package repository", | ||||||
|  | 		"repo", repo.Name, "count", len(matches)) | ||||||
|  |  | ||||||
| 	for _, match := range matches { | 	for _, match := range matches { | ||||||
| 		runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) | 		runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return fmt.Errorf("error creating runner for %s: %w", match, err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		scriptFl, err := os.Open(match) | 		scriptFl, err := os.Open(match) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return fmt.Errorf("error opening %s: %w", match, err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		err = rs.updatePkg(ctx, repo, runner, scriptFl) | 		err = rs.updatePkg(ctx, repo, runner, scriptFl) | ||||||
|  | 		scriptFl.Close() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return fmt.Errorf("error processing %s: %w", match, err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,11 +20,9 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"path/filepath" |  | ||||||
|  |  | ||||||
| 	"github.com/go-git/go-git/v5" | 	"github.com/go-git/go-git/v5" | ||||||
| 	"github.com/go-git/go-git/v5/plumbing" | 	"github.com/go-git/go-git/v5/plumbing" | ||||||
| 	"github.com/go-git/go-git/v5/plumbing/format/diff" |  | ||||||
| 	"github.com/go-git/go-git/v5/plumbing/transport" | 	"github.com/go-git/go-git/v5/plumbing/transport" | ||||||
| 	"github.com/go-git/go-git/v5/plumbing/transport/client" | 	"github.com/go-git/go-git/v5/plumbing/transport/client" | ||||||
|  |  | ||||||
| @@ -36,22 +34,6 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // isValid makes sure the path of the file being updated is valid. |  | ||||||
| // It checks to make sure the file is not within a nested directory |  | ||||||
| // and that it is called alr.sh. |  | ||||||
| func isValid(from, to diff.File) bool { |  | ||||||
| 	var path string |  | ||||||
| 	if from != nil { |  | ||||||
| 		path = from.Path() |  | ||||||
| 	} |  | ||||||
| 	if to != nil { |  | ||||||
| 		path = to.Path() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	match, _ := filepath.Match("*/*.sh", path) |  | ||||||
| 	return match |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func parseScript( | func parseScript( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	repo types.Repo, | 	repo types.Repo, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user