From d1fe02fa577694febbd62e7d37620389958ff7d3 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Sat, 14 Jun 2025 22:26:33 +0300 Subject: [PATCH] feat: support single package repository --- .../issue_76_single_package_repo_test.go | 53 ++++++++++ internal/build/script_resolver.go | 18 +++- internal/repos/pull.go | 96 +++++++++++++++---- internal/repos/utils.go | 18 ---- 4 files changed, 143 insertions(+), 42 deletions(-) create mode 100644 e2e-tests/issue_76_single_package_repo_test.go diff --git a/e2e-tests/issue_76_single_package_repo_test.go b/e2e-tests/issue_76_single_package_repo_test.go new file mode 100644 index 0000000..804af58 --- /dev/null +++ b/e2e-tests/issue_76_single_package_repo_test.go @@ -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 . + +//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") + }, + ) +} diff --git a/internal/build/script_resolver.go b/internal/build/script_resolver.go index 70bf763..82d7242 100644 --- a/internal/build/script_resolver.go +++ b/internal/build/script_resolver.go @@ -18,6 +18,7 @@ package build import ( "context" + "os" "path/filepath" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" @@ -40,10 +41,21 @@ func (s *ScriptResolver) ResolveScript( repodir := s.cfg.GetPaths().RepoDir 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 { - 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{ diff --git a/internal/repos/pull.go b/internal/repos/pull.go index 8a926a9..7c2b54c 100644 --- a/internal/repos/pull.go +++ b/internal/repos/pull.go @@ -277,7 +277,15 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git for _, fp := range patch.FilePatches() { 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 } @@ -316,55 +324,60 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git parser := syntax.NewParser() 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 { return fmt.Errorf("error creating process repo changes runner: %w", err) } switch action.Type { case actionDelete: - if filepath.Base(action.File) != "alr.sh" { - continue - } scriptFl, err := oldCommit.File(action.File) 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() 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) if err != nil { - return err + return fmt.Errorf("error parsing deleted script %s: %w", action.File, err) } for _, pkg := range pkgs { err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name) 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) 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() 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) 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 } +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 { - 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) 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 { runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) if err != nil { - return err + return fmt.Errorf("error creating runner for %s: %w", match, err) } scriptFl, err := os.Open(match) if err != nil { - return err + return fmt.Errorf("error opening %s: %w", match, err) } err = rs.updatePkg(ctx, repo, runner, scriptFl) + scriptFl.Close() if err != nil { - return err + return fmt.Errorf("error processing %s: %w", match, err) } } diff --git a/internal/repos/utils.go b/internal/repos/utils.go index 5b6e1cd..a354712 100644 --- a/internal/repos/utils.go +++ b/internal/repos/utils.go @@ -20,11 +20,9 @@ import ( "context" "fmt" "io" - "path/filepath" "github.com/go-git/go-git/v5" "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/client" @@ -36,22 +34,6 @@ import ( "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( ctx context.Context, repo types.Repo,