// 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 . package repos import ( "context" "errors" "fmt" "io" "path/filepath" "reflect" "strings" "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" "mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/syntax" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/distro" "gitea.plemya-x.ru/Plemya-x/ALR/internal/parser" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" "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, syntaxParser *syntax.Parser, runner *interp.Runner, r io.ReadCloser, ) ([]*db.Package, error) { fl, err := syntaxParser.Parse(r, "alr.sh") if err != nil { return nil, err } runner.Reset() err = runner.Run(ctx, fl) if err != nil { return nil, err } d := decoder.New(&distro.OSRelease{}, runner) d.Overrides = false d.LikeDistros = false pkgNames, err := parser.ParseNames(d) if err != nil { return nil, fmt.Errorf("failed parsing package names: %w", err) } if len(pkgNames.Names) == 0 { return nil, errors.New("package name is missing") } var dbPkgs []*db.Package if len(pkgNames.Names) > 1 { if pkgNames.BasePkgName == "" { pkgNames.BasePkgName = pkgNames.Names[0] } for _, pkgName := range pkgNames.Names { pkgInfo := PackageInfo{} funcName := fmt.Sprintf("meta_%s", pkgName) runner.Reset() err = runner.Run(ctx, fl) if err != nil { return nil, err } meta, ok := d.GetFuncWithSubshell(funcName) if !ok { return nil, fmt.Errorf("func %s is missing", funcName) } r, err := meta(ctx) if err != nil { return nil, err } d := decoder.New(&distro.OSRelease{}, r) d.Overrides = false d.LikeDistros = false err = d.DecodeVars(&pkgInfo) if err != nil { return nil, err } pkg := pkgInfo.ToPackage(repo.Name) resolveOverrides(r, pkg) pkg.Name = pkgName pkg.BasePkgName = pkgNames.BasePkgName dbPkgs = append(dbPkgs, pkg) } return dbPkgs, nil } pkg := EmptyPackage(repo.Name) err = d.DecodeVars(pkg) if err != nil { return nil, err } resolveOverrides(runner, pkg) dbPkgs = append(dbPkgs, pkg) return dbPkgs, nil } type PackageInfo struct { Version string `sh:"version,required"` Release int `sh:"release,required"` Epoch uint `sh:"epoch"` Architectures db.JSON[[]string] `sh:"architectures"` Licenses db.JSON[[]string] `sh:"license"` Provides db.JSON[[]string] `sh:"provides"` Conflicts db.JSON[[]string] `sh:"conflicts"` Replaces db.JSON[[]string] `sh:"replaces"` } func (inf *PackageInfo) ToPackage(repoName string) *db.Package { pkg := EmptyPackage(repoName) pkg.Version = inf.Version pkg.Release = inf.Release pkg.Epoch = inf.Epoch pkg.Architectures = inf.Architectures pkg.Licenses = inf.Licenses pkg.Provides = inf.Provides pkg.Conflicts = inf.Conflicts pkg.Replaces = inf.Replaces return pkg } func EmptyPackage(repoName string) *db.Package { return &db.Package{ Group: db.NewJSON(map[string]string{}), Summary: db.NewJSON(map[string]string{}), Description: db.NewJSON(map[string]string{}), Homepage: db.NewJSON(map[string]string{}), Maintainer: db.NewJSON(map[string]string{}), Depends: db.NewJSON(map[string][]string{}), BuildDepends: db.NewJSON(map[string][]string{}), Repository: repoName, } } var overridable = map[string]string{ "deps": "Depends", "build_deps": "BuildDepends", "desc": "Description", "homepage": "Homepage", "maintainer": "Maintainer", "group": "Group", "summary": "Summary", } func resolveOverrides(runner *interp.Runner, pkg *db.Package) { pkgVal := reflect.ValueOf(pkg).Elem() for name, val := range runner.Vars { for prefix, field := range overridable { if strings.HasPrefix(name, prefix) { override := strings.TrimPrefix(name, prefix) override = strings.TrimPrefix(override, "_") field := pkgVal.FieldByName(field) varVal := field.FieldByName("Val") varType := varVal.Type() switch varType.Elem().String() { case "[]string": varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.List)) case "string": varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.Str)) } break } } } } func getHeadReference(r *git.Repository) (plumbing.ReferenceName, error) { remote, err := r.Remote(git.DefaultRemoteName) if err != nil { return "", err } endpoint, err := transport.NewEndpoint(remote.Config().URLs[0]) if err != nil { return "", err } gitClient, err := client.NewClient(endpoint) if err != nil { return "", err } session, err := gitClient.NewUploadPackSession(endpoint, nil) if err != nil { return "", err } info, err := session.AdvertisedReferences() if err != nil { return "", err } refs, err := info.AllReferences() if err != nil { return "", err } return refs["HEAD"].Target(), nil } func resolveHash(r *git.Repository, ref string) (*plumbing.Hash, error) { var err error if ref == "" { reference, err := getHeadReference(r) if err != nil { return nil, fmt.Errorf("failed to get head reference %w", err) } ref = reference.Short() } hsh, err := r.ResolveRevision(git.DefaultRemoteName + "/" + plumbing.Revision(ref)) if err != nil { hsh, err = r.ResolveRevision(plumbing.Revision(ref)) if err != nil { return nil, err } } return hsh, nil }