Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
e8202060d8 | |||
c4a92c67d4 | |||
85878f69d3 | |||
6bccce1db4 | |||
b5474b1eb4 | |||
51fdea781b | |||
4c1f2ea90f | |||
7fa7f8ba82 | |||
25d001c1c9 | |||
f86b3003b1 |
@ -84,31 +84,31 @@ jobs:
|
|||||||
sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh
|
sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh
|
||||||
sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
|
sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
|
||||||
|
|
||||||
- name: Install alr
|
# - name: Install alr
|
||||||
run: |
|
# run: |
|
||||||
make install
|
# make install
|
||||||
|
#
|
||||||
|
# # temporary fix
|
||||||
|
# groupadd wheel
|
||||||
|
# usermod -aG wheel root
|
||||||
|
|
||||||
# temporary fix
|
# - name: Build packages
|
||||||
groupadd wheel
|
# run: |
|
||||||
usermod -aG wheel root
|
# SCRIPT_PATH=alr-default/alr-bin/alr.sh
|
||||||
|
# ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
||||||
|
# ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
||||||
|
# ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH"
|
||||||
|
# ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH"
|
||||||
|
|
||||||
- name: Build packages
|
# - name: Upload assets
|
||||||
run: |
|
# uses: akkuman/gitea-release-action@v1
|
||||||
SCRIPT_PATH=alr-default/alr-bin/alr.sh
|
# with:
|
||||||
ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
# body: ${{ steps.changes.outputs.changes }}
|
||||||
ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
|
# files: |-
|
||||||
ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH"
|
# alr-bin+alr-default_${{ env.VERSION }}-1.red80_amd64.deb \
|
||||||
ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH"
|
# alr-bin+alr-default-${{ env.VERSION }}-1-x86_64.pkg.tar.zst \
|
||||||
|
# alr-bin+alr-default-${{ env.VERSION }}-1.red80.x86_64.rpm \
|
||||||
- name: Upload assets
|
# alr-bin+alr-default-${{ env.VERSION }}-alt1.x86_64.rpm
|
||||||
uses: akkuman/gitea-release-action@v1
|
|
||||||
with:
|
|
||||||
body: ${{ steps.changes.outputs.changes }}
|
|
||||||
files: |-
|
|
||||||
alr-bin+alr-default_${{ env.VERSION }}-1.red80_amd64.deb \
|
|
||||||
alr-bin+alr-default-${{ env.VERSION }}-1-x86_64.pkg.tar.zst \
|
|
||||||
alr-bin+alr-default-${{ env.VERSION }}-1.red80.x86_64.rpm \
|
|
||||||
alr-bin+alr-default-${{ env.VERSION }}-alt1.x86_64.rpm
|
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
run: |
|
run: |
|
||||||
|
1
Makefile
1
Makefile
@ -21,6 +21,7 @@ build: check-no-root $(BIN)
|
|||||||
|
|
||||||
export CGO_ENABLED := 0
|
export CGO_ENABLED := 0
|
||||||
$(BIN):
|
$(BIN):
|
||||||
|
go generate ./...
|
||||||
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
|
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
|
||||||
|
|
||||||
check-no-root:
|
check-no-root:
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
<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="15" fill="#010101" fill-opacity=".3">coverage</text>
|
||||||
<text x="33.5" y="14">coverage</text>
|
<text x="33.5" y="14">coverage</text>
|
||||||
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.0%</text>
|
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.7%</text>
|
||||||
<text x="86" y="14">19.0%</text>
|
<text x="86" y="14">19.7%</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</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")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
251
generators/alrsh-package/main.go
Normal file
251
generators/alrsh-package/main.go
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/format"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolvedStructGenerator(buf *bytes.Buffer, fields []*ast.Field) {
|
||||||
|
contentTemplate := template.Must(template.New("").Parse(`
|
||||||
|
type {{ .EntityNameLower }}Resolved struct {
|
||||||
|
{{ .StructFields }}
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
|
||||||
|
var structFieldsBuilder strings.Builder
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
// Поле с типом
|
||||||
|
fieldTypeStr := exprToString(field.Type)
|
||||||
|
|
||||||
|
// Структура поля
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("\t")
|
||||||
|
buf.WriteString(name.Name)
|
||||||
|
buf.WriteString(" ")
|
||||||
|
buf.WriteString(fieldTypeStr)
|
||||||
|
|
||||||
|
// Обработка json-тега
|
||||||
|
jsonTag := ""
|
||||||
|
if field.Tag != nil {
|
||||||
|
raw := strings.Trim(field.Tag.Value, "`")
|
||||||
|
tag := reflect.StructTag(raw)
|
||||||
|
if val := tag.Get("json"); val != "" {
|
||||||
|
jsonTag = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if jsonTag == "" {
|
||||||
|
jsonTag = strings.ToLower(name.Name)
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf(" `json:\"%s\"`", jsonTag))
|
||||||
|
buf.WriteString("\n")
|
||||||
|
structFieldsBuilder.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := struct {
|
||||||
|
EntityNameLower string
|
||||||
|
StructFields string
|
||||||
|
}{
|
||||||
|
EntityNameLower: "package",
|
||||||
|
StructFields: structFieldsBuilder.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := contentTemplate.Execute(buf, params)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execute template: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toResolvedFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
|
||||||
|
contentTemplate := template.Must(template.New("").Parse(`
|
||||||
|
func {{ .EntityName }}ToResolved(src *{{ .EntityName }}) {{ .EntityNameLower }}Resolved {
|
||||||
|
return {{ .EntityNameLower }}Resolved{
|
||||||
|
{{ .Assignments }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
|
||||||
|
var assignmentsBuilder strings.Builder
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
var assignBuf bytes.Buffer
|
||||||
|
assignBuf.WriteString("\t\t")
|
||||||
|
assignBuf.WriteString(name.Name)
|
||||||
|
assignBuf.WriteString(": ")
|
||||||
|
if isOverridableField(field.Type) {
|
||||||
|
assignBuf.WriteString(fmt.Sprintf("src.%s.Resolved()", name.Name))
|
||||||
|
} else {
|
||||||
|
assignBuf.WriteString(fmt.Sprintf("src.%s", name.Name))
|
||||||
|
}
|
||||||
|
assignBuf.WriteString(",\n")
|
||||||
|
assignmentsBuilder.Write(assignBuf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := struct {
|
||||||
|
EntityName string
|
||||||
|
EntityNameLower string
|
||||||
|
Assignments string
|
||||||
|
}{
|
||||||
|
EntityName: "Package",
|
||||||
|
EntityNameLower: "package",
|
||||||
|
Assignments: assignmentsBuilder.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := contentTemplate.Execute(buf, params)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execute template: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
|
||||||
|
contentTemplate := template.Must(template.New("").Parse(`
|
||||||
|
func Resolve{{ .EntityName }}(pkg *{{ .EntityName }}, overrides []string) {
|
||||||
|
{{.Code}}}
|
||||||
|
`))
|
||||||
|
|
||||||
|
var codeBuilder strings.Builder
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
for _, name := range field.Names {
|
||||||
|
if isOverridableField(field.Type) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(fmt.Sprintf("\t\tpkg.%s.Resolve(overrides)\n", name.Name))
|
||||||
|
codeBuilder.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := struct {
|
||||||
|
EntityName string
|
||||||
|
Code string
|
||||||
|
}{
|
||||||
|
EntityName: "Package",
|
||||||
|
Code: codeBuilder.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := contentTemplate.Execute(buf, params)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execute template: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
path := os.Getenv("GOFILE")
|
||||||
|
if path == "" {
|
||||||
|
log.Fatal("GOFILE must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
node, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parsing file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entityName := "Package" // имя структуры, которую анализируем
|
||||||
|
|
||||||
|
found := false
|
||||||
|
|
||||||
|
fields := make([]*ast.Field, 0)
|
||||||
|
|
||||||
|
// Ищем структуру с нужным именем
|
||||||
|
for _, decl := range node.Decls {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok || genDecl.Tok != token.TYPE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, spec := range genDecl.Specs {
|
||||||
|
typeSpec := spec.(*ast.TypeSpec)
|
||||||
|
if typeSpec.Name.Name != entityName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
structType, ok := typeSpec.Type.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields = structType.Fields.List
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
log.Fatalf("struct %s not found", entityName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
buf.WriteString("// DO NOT EDIT MANUALLY. This file is generated.\n")
|
||||||
|
buf.WriteString("package alrsh")
|
||||||
|
|
||||||
|
resolvedStructGenerator(&buf, fields)
|
||||||
|
toResolvedFuncGenerator(&buf, fields)
|
||||||
|
resolveFuncGenerator(&buf, fields)
|
||||||
|
|
||||||
|
// Форматируем вывод
|
||||||
|
formatted, err := format.Source(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("formatting: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outPath := strings.TrimSuffix(path, ".go") + "_gen.go"
|
||||||
|
outFile, err := os.Create(outPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("create file: %v", err)
|
||||||
|
}
|
||||||
|
_, err = outFile.Write(formatted)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("writing output: %v", err)
|
||||||
|
}
|
||||||
|
outFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func exprToString(expr ast.Expr) string {
|
||||||
|
if t, ok := expr.(*ast.IndexExpr); ok {
|
||||||
|
if ident, ok := t.X.(*ast.Ident); ok && ident.Name == "OverridableField" {
|
||||||
|
return exprToString(t.Index) // T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := format.Node(&buf, token.NewFileSet(), expr); err != nil {
|
||||||
|
return "<invalid>"
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOverridableField(expr ast.Expr) bool {
|
||||||
|
indexExpr, ok := expr.(*ast.IndexExpr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ident, ok := indexExpr.X.(*ast.Ident)
|
||||||
|
return ok && ident.Name == "OverridableField"
|
||||||
|
}
|
7
go.mod
7
go.mod
@ -9,6 +9,7 @@ require (
|
|||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/PuerkitoBio/purell v1.2.0
|
github.com/PuerkitoBio/purell v1.2.0
|
||||||
github.com/alecthomas/chroma/v2 v2.9.1
|
github.com/alecthomas/chroma/v2 v2.9.1
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.8.1
|
||||||
github.com/caarlos0/env v3.5.0+incompatible
|
github.com/caarlos0/env v3.5.0+incompatible
|
||||||
github.com/charmbracelet/bubbles v0.20.0
|
github.com/charmbracelet/bubbles v0.20.0
|
||||||
github.com/charmbracelet/bubbletea v1.2.4
|
github.com/charmbracelet/bubbletea v1.2.4
|
||||||
@ -17,12 +18,12 @@ require (
|
|||||||
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
|
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
|
||||||
github.com/go-git/go-billy/v5 v5.6.0
|
github.com/go-git/go-billy/v5 v5.6.0
|
||||||
github.com/go-git/go-git/v5 v5.13.0
|
github.com/go-git/go-git/v5 v5.13.0
|
||||||
|
github.com/goccy/go-yaml v1.18.0
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/goreleaser/nfpm/v2 v2.41.0
|
github.com/goreleaser/nfpm/v2 v2.41.0
|
||||||
github.com/hashicorp/go-hclog v0.14.1
|
github.com/hashicorp/go-hclog v0.14.1
|
||||||
github.com/hashicorp/go-plugin v1.6.3
|
github.com/hashicorp/go-plugin v1.6.3
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
|
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
|
||||||
github.com/leonelquinteros/gotext v1.7.0
|
github.com/leonelquinteros/gotext v1.7.0
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||||
@ -38,7 +39,6 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||||
golang.org/x/sys v0.31.0
|
golang.org/x/sys v0.31.0
|
||||||
golang.org/x/text v0.23.0
|
golang.org/x/text v0.23.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
modernc.org/sqlite v1.25.0
|
modernc.org/sqlite v1.25.0
|
||||||
mvdan.cc/sh/v3 v3.10.0
|
mvdan.cc/sh/v3 v3.10.0
|
||||||
xorm.io/xorm v1.3.9
|
xorm.io/xorm v1.3.9
|
||||||
@ -62,7 +62,7 @@ require (
|
|||||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.4.5 // indirect
|
github.com/charmbracelet/x/ansi v0.4.5 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
github.com/cloudflare/circl v1.3.8 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/connesc/cipherio v0.2.1 // indirect
|
github.com/connesc/cipherio v0.2.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
github.com/creack/pty v1.1.24 // indirect
|
github.com/creack/pty v1.1.24 // indirect
|
||||||
@ -139,6 +139,7 @@ require (
|
|||||||
google.golang.org/grpc v1.58.3 // indirect
|
google.golang.org/grpc v1.58.3 // indirect
|
||||||
google.golang.org/protobuf v1.36.1 // indirect
|
google.golang.org/protobuf v1.36.1 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
lukechampine.com/uint128 v1.2.0 // indirect
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
modernc.org/cc/v3 v3.40.0 // indirect
|
||||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||||
|
15
go.sum
15
go.sum
@ -67,6 +67,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
|
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
|
||||||
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
|
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
|
||||||
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
|
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
|
||||||
@ -102,8 +104,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
|||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
||||||
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
@ -156,13 +158,14 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi
|
|||||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
||||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
|
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
|
||||||
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@ -251,8 +254,6 @@ github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3m
|
|||||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
|
||||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
@ -281,9 +282,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8=
|
github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8=
|
||||||
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
|
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|
||||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
|
||||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
@ -302,7 +300,6 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei
|
|||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
44
info.go
44
info.go
@ -23,10 +23,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/jeandeaual/go-locale"
|
"github.com/jeandeaual/go-locale"
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||||
@ -121,35 +121,27 @@ func InfoCmd() *cli.Command {
|
|||||||
systemLang = "en"
|
systemLang = "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !all {
|
info, err := distro.ParseOSRelease(ctx)
|
||||||
info, err := distro.ParseOSRelease(ctx)
|
if err != nil {
|
||||||
if err != nil {
|
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
|
}
|
||||||
}
|
names, err = overrides.Resolve(
|
||||||
names, err = overrides.Resolve(
|
info,
|
||||||
info,
|
overrides.DefaultOpts.
|
||||||
overrides.DefaultOpts.
|
WithLanguages([]string{systemLang}),
|
||||||
WithLanguages([]string{systemLang}),
|
)
|
||||||
)
|
if err != nil {
|
||||||
if err != nil {
|
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err)
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
if !all {
|
alrsh.ResolvePackage(&pkg, names)
|
||||||
alrsh.ResolvePackage(&pkg, names)
|
view := alrsh.NewPackageView(pkg)
|
||||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
view.Resolved = !all
|
||||||
if err != nil {
|
err = yaml.NewEncoder(os.Stdout, yaml.UseJSONMarshaler(), yaml.OmitEmpty()).Encode(view)
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
if err != nil {
|
||||||
}
|
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
||||||
} else {
|
|
||||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
|
||||||
if err != nil {
|
|
||||||
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("---")
|
fmt.Println("---")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +367,7 @@ func (b *Builder) BuildPackage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("ViewScript")
|
slog.Debug("ViewScript")
|
||||||
slog.Debug("", "varsOfPackages", varsOfPackages)
|
slog.Debug("", "varsOfPackages", varsOfPackages[0])
|
||||||
err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
|
err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -28,7 +28,6 @@ import (
|
|||||||
"github.com/goreleaser/nfpm/v2/files"
|
"github.com/goreleaser/nfpm/v2/files"
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
)
|
)
|
||||||
@ -51,6 +50,92 @@ var binaryDirectories = []string{
|
|||||||
"/usr/local/bin/",
|
"/usr/local/bin/",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func moveWithSymlinkHandling(src, dst string) error {
|
||||||
|
srcInfo, err := os.Lstat(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get source info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create destination directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcInfo.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return moveSymlink(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(src, dst); err != nil {
|
||||||
|
return copyAndRemove(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveSymlink(src, dst string) error {
|
||||||
|
target, err := os.Readlink(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read symlink: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Symlink(target, dst); err != nil {
|
||||||
|
return fmt.Errorf("failed to create symlink: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(src); err != nil {
|
||||||
|
os.Remove(dst)
|
||||||
|
return fmt.Errorf("failed to remove original symlink: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyAndRemove(src, dst string) error {
|
||||||
|
srcFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open source: %w", err)
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
dstFile, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create destination: %w", err)
|
||||||
|
}
|
||||||
|
defer dstFile.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||||
|
return fmt.Errorf("failed to copy content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcInfo, err := srcFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get source stats: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dstFile.Chmod(srcInfo.Mode()); err != nil {
|
||||||
|
return fmt.Errorf("failed to set permissions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(src); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveFileWithErrorHandling(src, dst string) error {
|
||||||
|
err := moveWithSymlinkHandling(src, dst)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
return fmt.Errorf("permission denied: %w", err)
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("source file does not exist: %w", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to move file: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func applyFirejailIntegration(
|
func applyFirejailIntegration(
|
||||||
vars *alrsh.Package,
|
vars *alrsh.Package,
|
||||||
dirs types.Directories,
|
dirs types.Directories,
|
||||||
@ -143,12 +228,15 @@ func createFirejailedBinary(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := osutils.Move(content.Source, filepath.Join(dirs.PkgDir, origFilePath)); err != nil {
|
if err := moveFileWithErrorHandling(filepath.Join(dirs.PkgDir, content.Destination), filepath.Join(dirs.PkgDir, origFilePath)); err != nil {
|
||||||
return nil, fmt.Errorf("failed to move original binary: %w", err)
|
return nil, fmt.Errorf("failed to move original binary: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content.Type = "file"
|
||||||
|
content.Source = filepath.Join(dirs.PkgDir, content.Destination)
|
||||||
|
|
||||||
// Create wrapper script
|
// Create wrapper script
|
||||||
if err := createWrapperScript(content.Source, origFilePath, dest); err != nil {
|
if err := createWrapperScript(filepath.Join(dirs.PkgDir, content.Destination), origFilePath, dest); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
|
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,10 @@ func TestCreateFirejailedBinary(t *testing.T) {
|
|||||||
os.MkdirAll(pkgDir, 0o755)
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
os.MkdirAll(scriptDir, 0o755)
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
srcBinary := filepath.Join(tmpDir, "test-binary")
|
binDir := filepath.Join(pkgDir, "usr", "bin")
|
||||||
|
os.MkdirAll(binDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(binDir, "test-binary")
|
||||||
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
|
||||||
|
|
||||||
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||||
@ -154,7 +157,7 @@ func TestCreateFirejailedBinary(t *testing.T) {
|
|||||||
|
|
||||||
content := &files.Content{
|
content := &files.Content{
|
||||||
Source: srcBinary,
|
Source: srcBinary,
|
||||||
Destination: "./usr/bin/test-binary",
|
Destination: "/usr/bin/test-binary",
|
||||||
Type: "file",
|
Type: "file",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +175,10 @@ func TestCreateFirejailedBinary(t *testing.T) {
|
|||||||
os.MkdirAll(pkgDir, 0o755)
|
os.MkdirAll(pkgDir, 0o755)
|
||||||
os.MkdirAll(scriptDir, 0o755)
|
os.MkdirAll(scriptDir, 0o755)
|
||||||
|
|
||||||
srcBinary := filepath.Join(tmpDir, "special-binary")
|
binDir := filepath.Join(pkgDir, "usr", "bin")
|
||||||
|
os.MkdirAll(binDir, 0o755)
|
||||||
|
|
||||||
|
srcBinary := filepath.Join(binDir, "special-binary")
|
||||||
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755)
|
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755)
|
||||||
|
|
||||||
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
defaultProfile := filepath.Join(scriptDir, "default.profile")
|
||||||
@ -191,7 +197,7 @@ func TestCreateFirejailedBinary(t *testing.T) {
|
|||||||
|
|
||||||
content := &files.Content{
|
content := &files.Content{
|
||||||
Source: srcBinary,
|
Source: srcBinary,
|
||||||
Destination: "./usr/bin/special-binary",
|
Destination: "/usr/bin/special-binary",
|
||||||
Type: "file",
|
Type: "file",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SourceDownloader struct {
|
type SourceDownloader struct {
|
||||||
|
@ -65,6 +65,8 @@ func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{}
|
|||||||
var chLogLevel chLog.Level
|
var chLogLevel chLog.Level
|
||||||
if msg == "plugin process exited" ||
|
if msg == "plugin process exited" ||
|
||||||
strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") ||
|
strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") ||
|
||||||
|
strings.HasPrefix(msg, "[WARN] error closing client during Kill") ||
|
||||||
|
strings.HasPrefix(msg, "[WARN] plugin failed to exit gracefully") ||
|
||||||
strings.HasPrefix(msg, "[DEBUG] plugin") {
|
strings.HasPrefix(msg, "[DEBUG] plugin") {
|
||||||
chLogLevel = chLog.DebugLevel
|
chLogLevel = chLog.DebugLevel
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,7 +31,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
"github.com/go-git/go-billy/v5/osfs"
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
gitConfig "github.com/go-git/go-git/v5/config"
|
gitConfig "github.com/go-git/go-git/v5/config"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
@ -69,159 +68,219 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
repoURL, err := url.Parse(repo.URL)
|
err := rs.pullRepo(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
slog.Info(gotext.Get("Pulling repository"), "name", repo.Name)
|
return nil
|
||||||
repoDir := filepath.Join(rs.cfg.GetPaths().RepoDir, repo.Name)
|
}
|
||||||
|
|
||||||
var repoFS billy.Filesystem
|
func (rs *Repos) pullRepo(ctx context.Context, repo types.Repo) error {
|
||||||
gitDir := filepath.Join(repoDir, ".git")
|
urls := []string{repo.URL}
|
||||||
// Only pull repos that contain valid git repos
|
urls = append(urls, repo.Mirrors...)
|
||||||
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{
|
var lastErr error
|
||||||
Progress: os.Stderr,
|
|
||||||
Force: true,
|
|
||||||
})
|
|
||||||
if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := r.Worktree()
|
for i, repoURL := range urls {
|
||||||
if err != nil {
|
if i > 0 {
|
||||||
return err
|
slog.Info(gotext.Get("Trying mirror"), "repo", repo.Name, "mirror", repoURL)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fl, err := repoFS.Open("alr-repo.toml")
|
err := rs.pullRepoFromURL(ctx, repoURL, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn(gotext.Get("Git repository does not appear to be a valid ALR repo"), "repo", repo.Name)
|
lastErr = err
|
||||||
|
slog.Warn(gotext.Get("Failed to pull from URL"), "repo", repo.Name, "url", repoURL, "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var repoCfg types.RepoConfig
|
// Success
|
||||||
err = toml.NewDecoder(fl).Decode(&repoCfg)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to pull repository %s from any URL: %w", repo.Name, lastErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fl.Close()
|
|
||||||
|
|
||||||
// If the version doesn't have a "v" prefix, it's not a standard version.
|
if old.Hash() == *revHash {
|
||||||
// It may be "unknown" or a git version, but either way, there's no way
|
slog.Info(gotext.Get("Repository up to date"), "name", repo.Name)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() || freshGit {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"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"
|
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/internal/repos"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
|
||||||
@ -35,7 +34,7 @@ import (
|
|||||||
type TestEnv struct {
|
type TestEnv struct {
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
Cfg *TestALRConfig
|
Cfg *TestALRConfig
|
||||||
Db *db.Database
|
Db *database.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestALRConfig struct {
|
type TestALRConfig struct {
|
||||||
|
@ -23,7 +23,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -75,75 +74,99 @@ func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
|||||||
// DecodeVar decodes a variable to val using reflection.
|
// DecodeVar decodes a variable to val using reflection.
|
||||||
// Structs should use the "sh" struct tag.
|
// Structs should use the "sh" struct tag.
|
||||||
func (d *Decoder) DecodeVar(name string, val any) error {
|
func (d *Decoder) DecodeVar(name string, val any) error {
|
||||||
variable := d.getVar(name)
|
origType := reflect.TypeOf(val).Elem()
|
||||||
if variable == nil {
|
isOverridableField := strings.Contains(origType.String(), "OverridableField[")
|
||||||
return VarNotFoundError{name}
|
|
||||||
}
|
|
||||||
|
|
||||||
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
if !isOverridableField {
|
||||||
WeaklyTypedInput: true,
|
variable := d.getVarNoOverrides(name)
|
||||||
DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
|
if variable == nil {
|
||||||
if strings.Contains(to.Type().String(), "alrsh.OverridableField") {
|
return VarNotFoundError{name}
|
||||||
if to.Kind() != reflect.Ptr && to.CanAddr() {
|
}
|
||||||
to = to.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
if err != nil {
|
WeaklyTypedInput: true,
|
||||||
return nil, err
|
Result: val, // передаем указатель на новое значение
|
||||||
}
|
TagName: "sh",
|
||||||
|
DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
|
||||||
isNotSet := true
|
if from.Kind() == reflect.Slice && to.Kind() == reflect.String {
|
||||||
|
s, ok := from.Interface().([]string)
|
||||||
setMethod := to.MethodByName("Set")
|
if ok && len(s) == 1 {
|
||||||
setResolvedMethod := to.MethodByName("SetResolved")
|
return s[0], nil
|
||||||
|
|
||||||
for _, varName := range names {
|
|
||||||
val := d.getVarNoOverrides(varName)
|
|
||||||
if val == nil {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t := setMethod.Type().In(1)
|
|
||||||
|
|
||||||
newVal := from
|
|
||||||
|
|
||||||
if !newVal.Type().AssignableTo(t) {
|
|
||||||
newVal = reflect.New(t)
|
|
||||||
err = d.DecodeVar(name, newVal.Interface())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newVal = newVal.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if isNotSet {
|
|
||||||
setResolvedMethod.Call([]reflect.Value{newVal})
|
|
||||||
}
|
|
||||||
|
|
||||||
override := strings.TrimPrefix(strings.TrimPrefix(varName, name), "_")
|
|
||||||
setMethod.Call([]reflect.Value{reflect.ValueOf(override), newVal})
|
|
||||||
}
|
}
|
||||||
|
return from.Interface(), nil
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return to, nil
|
switch variable.Kind {
|
||||||
|
case expand.Indexed:
|
||||||
|
return dec.Decode(variable.List)
|
||||||
|
case expand.Associative:
|
||||||
|
return dec.Decode(variable.Map)
|
||||||
|
default:
|
||||||
|
return dec.Decode(variable.Str)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vars := d.getVarsByPrefix(name)
|
||||||
|
|
||||||
|
if len(vars) == 0 {
|
||||||
|
return VarNotFoundError{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
reflectVal := reflect.ValueOf(val)
|
||||||
|
overridableVal := reflect.ValueOf(val).Elem()
|
||||||
|
|
||||||
|
dataField := overridableVal.FieldByName("data")
|
||||||
|
if !dataField.IsValid() {
|
||||||
|
return fmt.Errorf("data field not found in OverridableField")
|
||||||
|
}
|
||||||
|
mapType := dataField.Type() // map[string]T
|
||||||
|
elemType := mapType.Elem() // T
|
||||||
|
|
||||||
|
var overridablePtr reflect.Value
|
||||||
|
if reflectVal.Kind() == reflect.Ptr {
|
||||||
|
overridablePtr = reflectVal
|
||||||
|
} else {
|
||||||
|
if !reflectVal.CanAddr() {
|
||||||
|
return fmt.Errorf("OverridableField value is not addressable")
|
||||||
}
|
}
|
||||||
return from.Interface(), nil
|
overridablePtr = reflectVal.Addr()
|
||||||
}),
|
}
|
||||||
Result: val,
|
|
||||||
TagName: "sh",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
slog.Warn("err", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch variable.Kind {
|
setValue := overridablePtr.MethodByName("Set")
|
||||||
case expand.Indexed:
|
if !setValue.IsValid() {
|
||||||
return dec.Decode(variable.List)
|
return fmt.Errorf("method Set not found on OverridableField")
|
||||||
case expand.Associative:
|
}
|
||||||
return dec.Decode(variable.Map)
|
|
||||||
default:
|
for _, v := range vars {
|
||||||
return dec.Decode(variable.Str)
|
varName := v.Name
|
||||||
|
|
||||||
|
key := strings.TrimPrefix(strings.TrimPrefix(varName, name), "_")
|
||||||
|
newVal := reflect.New(elemType)
|
||||||
|
|
||||||
|
if err := d.DecodeVar(varName, newVal.Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyValue := reflect.ValueOf(key)
|
||||||
|
setValue.Call([]reflect.Value{keyValue, newVal.Elem()})
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveValue := overridablePtr.MethodByName("Resolve")
|
||||||
|
if !resolveValue.IsValid() {
|
||||||
|
return fmt.Errorf("method Resolve not found on OverridableField")
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := overrides.Resolve(d.info, overrides.DefaultOpts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveValue.Call([]reflect.Value{reflect.ValueOf(names)})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,23 +307,6 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getVar gets a variable based on its name, taking into account
|
|
||||||
// override variables and nameref variables.
|
|
||||||
func (d *Decoder) getVar(name string) *expand.Variable {
|
|
||||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, varName := range names {
|
|
||||||
res := d.getVarNoOverrides(varName)
|
|
||||||
if res != nil {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Decoder) getVarNoOverrides(name string) *expand.Variable {
|
func (d *Decoder) getVarNoOverrides(name string) *expand.Variable {
|
||||||
val, ok := d.Runner.Vars[name]
|
val, ok := d.Runner.Vars[name]
|
||||||
if ok {
|
if ok {
|
||||||
@ -318,6 +324,32 @@ func (d *Decoder) getVarNoOverrides(name string) *expand.Variable {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type vars struct {
|
||||||
|
Name string
|
||||||
|
Value *expand.Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) getVarsByPrefix(prefix string) []*vars {
|
||||||
|
result := make([]*vars, 0)
|
||||||
|
for name, val := range d.Runner.Vars {
|
||||||
|
if !strings.HasPrefix(name, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch prefix {
|
||||||
|
case "auto_req":
|
||||||
|
if strings.HasPrefix(name, "auto_req_skiplist") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case "auto_prov":
|
||||||
|
if strings.HasPrefix(name, "auto_prov_skiplist") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = append(result, &vars{name, &val})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func IsTruthy(value string) bool {
|
func IsTruthy(value string) bool {
|
||||||
value = strings.ToLower(strings.TrimSpace(value))
|
value = strings.ToLower(strings.TrimSpace(value))
|
||||||
return value == "true" || value == "yes" || value == "1"
|
return value == "true" || value == "yes" || value == "1"
|
||||||
|
@ -32,24 +32,25 @@ import (
|
|||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
||||||
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BuildVars struct {
|
type BuildVars struct {
|
||||||
Name string `sh:"name,required"`
|
Name string `sh:"name,required"`
|
||||||
Version string `sh:"version,required"`
|
Version string `sh:"version,required"`
|
||||||
Release int `sh:"release,required"`
|
Release int `sh:"release,required"`
|
||||||
Epoch uint `sh:"epoch"`
|
Epoch uint `sh:"epoch"`
|
||||||
Description string `sh:"desc"`
|
Description alrsh.OverridableField[string] `sh:"desc"`
|
||||||
Homepage string `sh:"homepage"`
|
Homepage string `sh:"homepage"`
|
||||||
Maintainer string `sh:"maintainer"`
|
Maintainer string `sh:"maintainer"`
|
||||||
Architectures []string `sh:"architectures"`
|
Architectures []string `sh:"architectures"`
|
||||||
Licenses []string `sh:"license"`
|
Licenses []string `sh:"license"`
|
||||||
Provides []string `sh:"provides"`
|
Provides []string `sh:"provides"`
|
||||||
Conflicts []string `sh:"conflicts"`
|
Conflicts []string `sh:"conflicts"`
|
||||||
Depends []string `sh:"deps"`
|
Depends []string `sh:"deps"`
|
||||||
BuildDepends []string `sh:"build_deps"`
|
BuildDepends alrsh.OverridableField[[]string] `sh:"build_deps"`
|
||||||
Replaces []string `sh:"replaces"`
|
Replaces alrsh.OverridableField[[]string] `sh:"replaces"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const testScript = `
|
const testScript = `
|
||||||
@ -113,22 +114,34 @@ func TestDecodeVars(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expected := BuildVars{
|
expected := BuildVars{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Version: "0.0.1",
|
Version: "0.0.1",
|
||||||
Release: 1,
|
Release: 1,
|
||||||
Epoch: 2,
|
Epoch: 2,
|
||||||
Description: "Test package",
|
Description: alrsh.OverridableFromMap(map[string]string{
|
||||||
|
"": "Test package",
|
||||||
|
}),
|
||||||
Homepage: "https://gitea.plemya-x.ru/xpamych/ALR",
|
Homepage: "https://gitea.plemya-x.ru/xpamych/ALR",
|
||||||
Maintainer: "Евгений Храмов <xpamych@yandex.ru>",
|
Maintainer: "Евгений Храмов <xpamych@yandex.ru>",
|
||||||
Architectures: []string{"arm64", "amd64"},
|
Architectures: []string{"arm64", "amd64"},
|
||||||
Licenses: []string{"GPL-3.0-or-later"},
|
Licenses: []string{"GPL-3.0-or-later"},
|
||||||
Provides: []string{"test"},
|
Provides: []string{"test"},
|
||||||
Conflicts: []string{"test"},
|
Conflicts: []string{"test"},
|
||||||
Replaces: []string{"test-legacy"},
|
Replaces: alrsh.OverridableFromMap(map[string][]string{
|
||||||
Depends: []string{"sudo"},
|
"": {"test-old"},
|
||||||
BuildDepends: []string{"go"},
|
"test_os": {"test-legacy"},
|
||||||
|
}),
|
||||||
|
Depends: []string{"sudo"},
|
||||||
|
BuildDepends: alrsh.OverridableFromMap(map[string][]string{
|
||||||
|
"": {"golang"},
|
||||||
|
"arch": {"go"},
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expected.Description.SetResolved("Test package")
|
||||||
|
expected.Replaces.SetResolved([]string{"test-legacy"})
|
||||||
|
expected.BuildDepends.SetResolved([]string{"go"})
|
||||||
|
|
||||||
if !reflect.DeepEqual(bv, expected) {
|
if !reflect.DeepEqual(bv, expected) {
|
||||||
t.Errorf("Expected %v, got %v", expected, bv)
|
t.Errorf("Expected %v, got %v", expected, bv)
|
||||||
}
|
}
|
||||||
|
178
internal/shutils/helpers/files_find.go
Normal file
178
internal/shutils/helpers/files_find.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// 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 helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func matchNamePattern(name, pattern string) bool {
|
||||||
|
matched, err := filepath.Match(pattern, name)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDir(dirPath, commandName string) error {
|
||||||
|
info, err := os.Stat(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", commandName, err)
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
return fmt.Errorf("%s: %s is not a directory", commandName, dirPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputFiles(hc interp.HandlerContext, files []string) {
|
||||||
|
for _, file := range files {
|
||||||
|
fmt.Fprintln(hc.Stdout, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRelativePath(basePath, fullPath string) (string, error) {
|
||||||
|
relPath, err := filepath.Rel(basePath, fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "./" + relPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesFindLangCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
|
namePattern := "*.mo"
|
||||||
|
if len(args) > 0 {
|
||||||
|
namePattern = args[0] + ".mo"
|
||||||
|
}
|
||||||
|
|
||||||
|
localePath := "./usr/share/locale/"
|
||||||
|
realPath := path.Join(hc.Dir, localePath)
|
||||||
|
|
||||||
|
if err := validateDir(realPath, "files-find-lang"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var langFiles []string
|
||||||
|
err := filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
|
||||||
|
relPath, relErr := makeRelativePath(hc.Dir, p)
|
||||||
|
if relErr != nil {
|
||||||
|
return relErr
|
||||||
|
}
|
||||||
|
langFiles = append(langFiles, relPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("files-find-lang: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles(hc, langFiles)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesFindDocCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
|
namePattern := "*"
|
||||||
|
if len(args) > 0 {
|
||||||
|
namePattern = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
docPath := "./usr/share/doc/"
|
||||||
|
docRealPath := path.Join(hc.Dir, docPath)
|
||||||
|
|
||||||
|
if err := validateDir(docRealPath, "files-find-doc"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var docFiles []string
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(docRealPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("files-find-doc: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if matchNamePattern(entry.Name(), namePattern) {
|
||||||
|
targetPath := filepath.Join(docRealPath, entry.Name())
|
||||||
|
targetInfo, err := os.Stat(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("files-find-doc: %w", err)
|
||||||
|
}
|
||||||
|
if targetInfo.IsDir() {
|
||||||
|
err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error {
|
||||||
|
if subErr != nil {
|
||||||
|
return subErr
|
||||||
|
}
|
||||||
|
relPath, err := makeRelativePath(hc.Dir, subPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
docFiles = append(docFiles, relPath)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("files-find-doc: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles(hc, docFiles)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesFindCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return fmt.Errorf("find-files: at least one glob pattern is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundFiles []string
|
||||||
|
|
||||||
|
for _, globPattern := range args {
|
||||||
|
searchPath := path.Join(hc.Dir, globPattern)
|
||||||
|
|
||||||
|
basepath, pattern := doublestar.SplitPattern(searchPath)
|
||||||
|
fsys := os.DirFS(basepath)
|
||||||
|
matches, err := doublestar.Glob(fsys, pattern, doublestar.WithNoFollow())
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("find-files: invalid glob pattern", "pattern", globPattern, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, match := range matches {
|
||||||
|
relPath, err := makeRelativePath(hc.Dir, path.Join(basepath, match))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
foundFiles = append(foundFiles, relPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFiles(hc, foundFiles)
|
||||||
|
return nil
|
||||||
|
}
|
@ -24,7 +24,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -57,6 +56,7 @@ var Helpers = handlers.ExecFuncs{
|
|||||||
"install-library": installLibraryCmd,
|
"install-library": installLibraryCmd,
|
||||||
"git-version": gitVersionCmd,
|
"git-version": gitVersionCmd,
|
||||||
|
|
||||||
|
"files-find": filesFindCmd,
|
||||||
"files-find-lang": filesFindLangCmd,
|
"files-find-lang": filesFindLangCmd,
|
||||||
"files-find-doc": filesFindDocCmd,
|
"files-find-doc": filesFindDocCmd,
|
||||||
}
|
}
|
||||||
@ -65,6 +65,7 @@ var Helpers = handlers.ExecFuncs{
|
|||||||
// that don't modify any state
|
// that don't modify any state
|
||||||
var Restricted = handlers.ExecFuncs{
|
var Restricted = handlers.ExecFuncs{
|
||||||
"git-version": gitVersionCmd,
|
"git-version": gitVersionCmd,
|
||||||
|
"files-find": filesFindCmd,
|
||||||
"files-find-lang": filesFindLangCmd,
|
"files-find-lang": filesFindLangCmd,
|
||||||
"files-find-doc": filesFindDocCmd,
|
"files-find-doc": filesFindDocCmd,
|
||||||
}
|
}
|
||||||
@ -265,114 +266,6 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func filesFindLangCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
|
||||||
namePattern := "*.mo"
|
|
||||||
if len(args) > 0 {
|
|
||||||
namePattern = args[0] + ".mo"
|
|
||||||
}
|
|
||||||
|
|
||||||
localePath := "./usr/share/locale/"
|
|
||||||
realPath := path.Join(hc.Dir, localePath)
|
|
||||||
|
|
||||||
info, err := os.Stat(realPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("files-find-lang: %w", err)
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
return fmt.Errorf("files-find-lang: %s is not a directory", localePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
var langFiles []string
|
|
||||||
err = filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
|
|
||||||
relPath, relErr := filepath.Rel(hc.Dir, p)
|
|
||||||
if relErr != nil {
|
|
||||||
return relErr
|
|
||||||
}
|
|
||||||
langFiles = append(langFiles, "./"+relPath)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("files-find-lang: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range langFiles {
|
|
||||||
fmt.Fprintln(hc.Stdout, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func filesFindDocCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
|
||||||
namePattern := "*"
|
|
||||||
if len(args) > 0 {
|
|
||||||
namePattern = args[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
docPath := "./usr/share/doc/"
|
|
||||||
docRealPath := path.Join(hc.Dir, docPath)
|
|
||||||
|
|
||||||
info, err := os.Stat(docRealPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("files-find-doc: %w", err)
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
return fmt.Errorf("files-find-doc: %s is not a directory", docPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
var docFiles []string
|
|
||||||
|
|
||||||
entries, err := os.ReadDir(docRealPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
if matchNamePattern(entry.Name(), namePattern) {
|
|
||||||
targetPath := filepath.Join(docRealPath, entry.Name())
|
|
||||||
targetInfo, err := os.Stat(targetPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if targetInfo.IsDir() {
|
|
||||||
err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error {
|
|
||||||
relPath, err := filepath.Rel(hc.Dir, subPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
docFiles = append(docFiles, "./"+relPath)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("files-find-doc: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range docFiles {
|
|
||||||
fmt.Fprintln(hc.Stdout, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchNamePattern(name, pattern string) bool {
|
|
||||||
matched, err := filepath.Match(pattern, name)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return matched
|
|
||||||
}
|
|
||||||
|
|
||||||
func helperInstall(from, to string, perms os.FileMode) error {
|
func helperInstall(from, to string, perms os.FileMode) error {
|
||||||
err := os.MkdirAll(filepath.Dir(to), 0o755)
|
err := os.MkdirAll(filepath.Dir(to), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,12 +31,18 @@ import (
|
|||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
|
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type symlink struct {
|
||||||
|
linkPath string
|
||||||
|
targetPath string
|
||||||
|
}
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
dirsToCreate []string
|
dirsToCreate []string
|
||||||
filesToCreate []string
|
filesToCreate []string
|
||||||
expectedOutput []string
|
expectedOutput []string
|
||||||
args string
|
symlinksToCreate []symlink
|
||||||
|
args string
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindFilesDoc(t *testing.T) {
|
func TestFindFilesDoc(t *testing.T) {
|
||||||
@ -214,3 +220,94 @@ files-find-lang ` + tc.args
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindFiles(t *testing.T) {
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
name: "With file and dir symlinks",
|
||||||
|
dirsToCreate: []string{
|
||||||
|
"usr/share/locale/ru/LC_MESSAGES",
|
||||||
|
"usr/share/locale/tr/LC_MESSAGES",
|
||||||
|
"opt/app",
|
||||||
|
"opt/app/internal",
|
||||||
|
},
|
||||||
|
filesToCreate: []string{
|
||||||
|
"usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo",
|
||||||
|
"usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo",
|
||||||
|
"usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo",
|
||||||
|
"opt/app/internal/test",
|
||||||
|
},
|
||||||
|
symlinksToCreate: []symlink{
|
||||||
|
{
|
||||||
|
linkPath: "/opt/app/etc",
|
||||||
|
targetPath: "/etc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOutput: []string{
|
||||||
|
"./usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo",
|
||||||
|
"./usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo",
|
||||||
|
"./usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo",
|
||||||
|
"./opt/app/etc",
|
||||||
|
"./opt/app/internal",
|
||||||
|
"./opt/app/internal/test",
|
||||||
|
},
|
||||||
|
args: "\"/usr/share/locale/*/LC_MESSAGES/*.mo\" \"/opt/app/**/*\"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tempDir, err := os.MkdirTemp("", "test-files-find")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
for _, dir := range tc.dirsToCreate {
|
||||||
|
dirPath := filepath.Join(tempDir, dir)
|
||||||
|
err := os.MkdirAll(dirPath, 0o755)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range tc.filesToCreate {
|
||||||
|
filePath := filepath.Join(tempDir, file)
|
||||||
|
err := os.WriteFile(filePath, []byte("test content"), 0o644)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sl := range tc.symlinksToCreate {
|
||||||
|
linkFullPath := filepath.Join(tempDir, sl.linkPath)
|
||||||
|
targetFullPath := sl.targetPath
|
||||||
|
|
||||||
|
// make sure parent dir exists
|
||||||
|
err := os.MkdirAll(filepath.Dir(linkFullPath), 0o755)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = os.Symlink(targetFullPath, linkFullPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
helpers := handlers.ExecFuncs{
|
||||||
|
"files-find": filesFindCmd,
|
||||||
|
}
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
runner, err := interp.New(
|
||||||
|
interp.Dir(tempDir),
|
||||||
|
interp.StdIO(os.Stdin, buf, os.Stderr),
|
||||||
|
interp.ExecHandler(helpers.ExecHandler(interp.DefaultExecHandler(1000))),
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
scriptContent := `
|
||||||
|
shopt -s globstar
|
||||||
|
files-find ` + tc.args
|
||||||
|
|
||||||
|
script, err := syntax.NewParser().Parse(strings.NewReader(scriptContent), "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = runner.Run(context.Background(), script)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
contents := strings.Fields(strings.TrimSpace(buf.String()))
|
||||||
|
assert.ElementsMatch(t, tc.expectedOutput, contents)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -138,11 +138,11 @@ msgstr ""
|
|||||||
msgid "Can't detect system language"
|
msgid "Can't detect system language"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: info.go:135
|
#: info.go:134
|
||||||
msgid "Error resolving overrides"
|
msgid "Error resolving overrides"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: info.go:144 info.go:149
|
#: info.go:143
|
||||||
msgid "Error encoding script variables"
|
msgid "Error encoding script variables"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ msgstr ""
|
|||||||
msgid "AutoReq is not implemented for this package format, so it's skipped"
|
msgid "AutoReq is not implemented for this package format, so it's skipped"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/build/firejail.go:59
|
#: internal/build/firejail.go:144
|
||||||
msgid "Applying FireJail integration"
|
msgid "Applying FireJail integration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -355,47 +355,31 @@ msgid ""
|
|||||||
"Database version does not exist. Run alr fix if something isn't working."
|
"Database version does not exist. Run alr fix if something isn't working."
|
||||||
msgstr ""
|
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
|
#: internal/logger/log.go:41
|
||||||
msgid "ERROR"
|
msgid "ERROR"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:77
|
#: internal/repos/pull.go:88
|
||||||
|
msgid "Trying mirror"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:94
|
||||||
|
msgid "Failed to pull from URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:158
|
||||||
msgid "Pulling repository"
|
msgid "Pulling repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:113
|
#: internal/repos/pull.go:195
|
||||||
msgid "Repository up to date"
|
msgid "Repository up to date"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:204
|
#: internal/repos/pull.go:230
|
||||||
msgid "Git repository does not appear to be a valid ALR repo"
|
msgid "Git repository does not appear to be a valid ALR repo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internal/repos/pull.go:220
|
#: internal/repos/pull.go:246
|
||||||
msgid ""
|
msgid ""
|
||||||
"ALR repo's minimum ALR version is greater than the current version. Try "
|
"ALR repo's minimum ALR version is greater than the current version. Try "
|
||||||
"updating ALR if something doesn't work."
|
"updating ALR if something doesn't work."
|
||||||
@ -457,67 +441,132 @@ msgstr ""
|
|||||||
msgid "Error while running app"
|
msgid "Error while running app"
|
||||||
msgstr ""
|
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
|
#: refresh.go:30
|
||||||
msgid "Pull all repositories that have changed"
|
msgid "Pull all repositories that have changed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:39
|
#: repo.go:41
|
||||||
msgid "Manage repos"
|
msgid "Manage repos"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:51 repo.go:269
|
#: repo.go:55 repo.go:625
|
||||||
msgid "Remove an existing repository"
|
msgid "Remove an existing repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:53
|
#: repo.go:57 repo.go:521
|
||||||
msgid "<name>"
|
msgid "<name>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:83
|
#: repo.go:102 repo.go:465 repo.go:568
|
||||||
msgid "Repo \"%s\" does not exist"
|
msgid "Repo \"%s\" does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:90
|
#: repo.go:109
|
||||||
msgid "Error removing repo directory"
|
msgid "Error removing repo directory"
|
||||||
msgstr ""
|
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"
|
msgid "Error saving config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:113
|
#: repo.go:132
|
||||||
msgid "Error removing packages from database"
|
msgid "Error removing packages from database"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:124 repo.go:239
|
#: repo.go:143 repo.go:595
|
||||||
msgid "Add a new repository"
|
msgid "Add a new repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:125
|
#: repo.go:144 repo.go:270 repo.go:345 repo.go:402
|
||||||
msgid "<name> <url>"
|
msgid "<name> <url>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:150
|
#: repo.go:169
|
||||||
msgid "Repo \"%s\" already exists"
|
msgid "Repo \"%s\" already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:187
|
#: repo.go:206
|
||||||
msgid "Set the reference of the repository"
|
msgid "Set the reference of the repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:188
|
#: repo.go:207
|
||||||
msgid "<name> <ref>"
|
msgid "<name> <ref>"
|
||||||
msgstr ""
|
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"
|
msgid "Name of the new repo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:252
|
#: repo.go:608
|
||||||
msgid "URL of the new repo"
|
msgid "URL of the new repo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: repo.go:276
|
#: repo.go:632
|
||||||
msgid "Name of the repo to be deleted"
|
msgid "Name of the repo to be deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -5,15 +5,15 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: unnamed project\n"
|
"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"
|
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
"X-Generator: Gtranslator 48.0\n"
|
"X-Generator: Gtranslator 48.0\n"
|
||||||
|
|
||||||
#: build.go:42
|
#: build.go:42
|
||||||
@ -145,11 +145,11 @@ msgstr "Ошибка при поиске пакетов"
|
|||||||
msgid "Can't detect system language"
|
msgid "Can't detect system language"
|
||||||
msgstr "Ошибка при определении языка системы"
|
msgstr "Ошибка при определении языка системы"
|
||||||
|
|
||||||
#: info.go:135
|
#: info.go:134
|
||||||
msgid "Error resolving overrides"
|
msgid "Error resolving overrides"
|
||||||
msgstr "Ошибка устранения переорпеделений"
|
msgstr "Ошибка устранения переорпеделений"
|
||||||
|
|
||||||
#: info.go:144 info.go:149
|
#: info.go:143
|
||||||
msgid "Error encoding script variables"
|
msgid "Error encoding script variables"
|
||||||
msgstr "Ошибка кодирования переменных скрита"
|
msgstr "Ошибка кодирования переменных скрита"
|
||||||
|
|
||||||
@ -231,7 +231,7 @@ msgid "AutoReq is not implemented for this package format, so it's skipped"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
|
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
|
||||||
|
|
||||||
#: internal/build/firejail.go:59
|
#: internal/build/firejail.go:144
|
||||||
msgid "Applying FireJail integration"
|
msgid "Applying FireJail integration"
|
||||||
msgstr "Применение интеграции FireJail"
|
msgstr "Применение интеграции FireJail"
|
||||||
|
|
||||||
@ -356,8 +356,8 @@ msgid ""
|
|||||||
"This command is deprecated and would be removed in the future, use \"%s\" "
|
"This command is deprecated and would be removed in the future, use \"%s\" "
|
||||||
"instead!"
|
"instead!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Эта команда устарела и будет удалена в будущем, используйте вместо нее "
|
"Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s"
|
||||||
"\"%s\"!"
|
"\"!"
|
||||||
|
|
||||||
#: internal/db/db.go:76
|
#: internal/db/db.go:76
|
||||||
msgid "Database version mismatch; resetting"
|
msgid "Database version mismatch; resetting"
|
||||||
@ -369,47 +369,31 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Версия базы данных не существует. Запустите alr fix, если что-то не работает."
|
"Версия базы данных не существует. Запустите 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
|
#: internal/logger/log.go:41
|
||||||
msgid "ERROR"
|
msgid "ERROR"
|
||||||
msgstr "ОШИБКА"
|
msgstr "ОШИБКА"
|
||||||
|
|
||||||
#: internal/repos/pull.go:77
|
#: internal/repos/pull.go:88
|
||||||
|
msgid "Trying mirror"
|
||||||
|
msgstr "Пробую зеркало"
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:94
|
||||||
|
msgid "Failed to pull from URL"
|
||||||
|
msgstr "Не удалось извлечь из URL"
|
||||||
|
|
||||||
|
#: internal/repos/pull.go:158
|
||||||
msgid "Pulling repository"
|
msgid "Pulling repository"
|
||||||
msgstr "Скачивание репозитория"
|
msgstr "Скачивание репозитория"
|
||||||
|
|
||||||
#: internal/repos/pull.go:113
|
#: internal/repos/pull.go:195
|
||||||
msgid "Repository up to date"
|
msgid "Repository up to date"
|
||||||
msgstr "Репозиторий уже обновлён"
|
msgstr "Репозиторий уже обновлён"
|
||||||
|
|
||||||
#: internal/repos/pull.go:204
|
#: internal/repos/pull.go:230
|
||||||
msgid "Git repository does not appear to be a valid ALR repo"
|
msgid "Git repository does not appear to be a valid ALR repo"
|
||||||
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
|
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
|
||||||
|
|
||||||
#: internal/repos/pull.go:220
|
#: internal/repos/pull.go:246
|
||||||
msgid ""
|
msgid ""
|
||||||
"ALR repo's minimum ALR version is greater than the current version. Try "
|
"ALR repo's minimum ALR version is greater than the current version. Try "
|
||||||
"updating ALR if something doesn't work."
|
"updating ALR if something doesn't work."
|
||||||
@ -473,67 +457,132 @@ msgstr "Показать справку"
|
|||||||
msgid "Error while running app"
|
msgid "Error while running app"
|
||||||
msgstr "Ошибка при запуске приложения"
|
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
|
#: refresh.go:30
|
||||||
msgid "Pull all repositories that have changed"
|
msgid "Pull all repositories that have changed"
|
||||||
msgstr "Скачать все изменённые репозитории"
|
msgstr "Скачать все изменённые репозитории"
|
||||||
|
|
||||||
#: repo.go:39
|
#: repo.go:41
|
||||||
msgid "Manage repos"
|
msgid "Manage repos"
|
||||||
msgstr "Управление репозиториями"
|
msgstr "Управление репозиториями"
|
||||||
|
|
||||||
#: repo.go:51 repo.go:269
|
#: repo.go:55 repo.go:625
|
||||||
msgid "Remove an existing repository"
|
msgid "Remove an existing repository"
|
||||||
msgstr "Удалить существующий репозиторий"
|
msgstr "Удалить существующий репозиторий"
|
||||||
|
|
||||||
#: repo.go:53
|
#: repo.go:57 repo.go:521
|
||||||
msgid "<name>"
|
msgid "<name>"
|
||||||
msgstr "<имя>"
|
msgstr "<имя>"
|
||||||
|
|
||||||
#: repo.go:83
|
#: repo.go:102 repo.go:465 repo.go:568
|
||||||
msgid "Repo \"%s\" does not exist"
|
msgid "Repo \"%s\" does not exist"
|
||||||
msgstr "Репозитория \"%s\" не существует"
|
msgstr "Репозитория \"%s\" не существует"
|
||||||
|
|
||||||
#: repo.go:90
|
#: repo.go:109
|
||||||
msgid "Error removing repo directory"
|
msgid "Error removing repo directory"
|
||||||
msgstr "Ошибка при удалении каталога репозитория"
|
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"
|
msgid "Error saving config"
|
||||||
msgstr "Ошибка при сохранении конфигурации"
|
msgstr "Ошибка при сохранении конфигурации"
|
||||||
|
|
||||||
#: repo.go:113
|
#: repo.go:132
|
||||||
msgid "Error removing packages from database"
|
msgid "Error removing packages from database"
|
||||||
msgstr "Ошибка при удалении пакетов из базы данных"
|
msgstr "Ошибка при удалении пакетов из базы данных"
|
||||||
|
|
||||||
#: repo.go:124 repo.go:239
|
#: repo.go:143 repo.go:595
|
||||||
msgid "Add a new repository"
|
msgid "Add a new repository"
|
||||||
msgstr "Добавить новый репозиторий"
|
msgstr "Добавить новый репозиторий"
|
||||||
|
|
||||||
#: repo.go:125
|
#: repo.go:144 repo.go:270 repo.go:345 repo.go:402
|
||||||
msgid "<name> <url>"
|
msgid "<name> <url>"
|
||||||
msgstr "<имя> <url>"
|
msgstr "<имя> <url>"
|
||||||
|
|
||||||
#: repo.go:150
|
#: repo.go:169
|
||||||
msgid "Repo \"%s\" already exists"
|
msgid "Repo \"%s\" already exists"
|
||||||
msgstr "Репозиторий \"%s\" уже существует"
|
msgstr "Репозиторий \"%s\" уже существует"
|
||||||
|
|
||||||
#: repo.go:187
|
#: repo.go:206
|
||||||
msgid "Set the reference of the repository"
|
msgid "Set the reference of the repository"
|
||||||
msgstr "Установить ссылку на версию репозитория"
|
msgstr "Установить ссылку на версию репозитория"
|
||||||
|
|
||||||
#: repo.go:188
|
#: repo.go:207
|
||||||
msgid "<name> <ref>"
|
msgid "<name> <ref>"
|
||||||
msgstr "<имя> <ссылка_на_версию>"
|
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"
|
msgid "Name of the new repo"
|
||||||
msgstr "Название нового репозитория"
|
msgstr "Название нового репозитория"
|
||||||
|
|
||||||
#: repo.go:252
|
#: repo.go:608
|
||||||
msgid "URL of the new repo"
|
msgid "URL of the new repo"
|
||||||
msgstr "URL-адрес нового репозитория"
|
msgstr "URL-адрес нового репозитория"
|
||||||
|
|
||||||
#: repo.go:276
|
#: repo.go:632
|
||||||
msgid "Name of the repo to be deleted"
|
msgid "Name of the repo to be deleted"
|
||||||
msgstr "Название репозитория удалён"
|
msgstr "Название репозитория удалён"
|
||||||
|
|
||||||
|
@ -68,10 +68,12 @@ func (o *OverridableField[T]) Resolve(overrides []string) {
|
|||||||
for _, override := range overrides {
|
for _, override := range overrides {
|
||||||
if v, ok := o.Has(override); ok {
|
if v, ok := o.Has(override); ok {
|
||||||
o.SetResolved(v)
|
o.SetResolved(v)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Database serialization (JSON)
|
||||||
func (f *OverridableField[T]) ToDB() ([]byte, error) {
|
func (f *OverridableField[T]) ToDB() ([]byte, error) {
|
||||||
var data map[string]T
|
var data map[string]T
|
||||||
|
|
||||||
@ -103,6 +105,7 @@ func (f *OverridableField[T]) FromDB(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gob serialization
|
||||||
type overridableFieldGobPayload[T any] struct {
|
type overridableFieldGobPayload[T any] struct {
|
||||||
Data map[string]T
|
Data map[string]T
|
||||||
Resolved T
|
Resolved T
|
||||||
@ -136,6 +139,48 @@ func (f *OverridableField[T]) GobDecode(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type overridableFieldJSONPayload[T any] struct {
|
||||||
|
Resolved *T `json:"resolved,omitempty,omitzero"`
|
||||||
|
Data map[string]T `json:"overrides,omitempty,omitzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f OverridableField[T]) MarshalJSON() ([]byte, error) {
|
||||||
|
data := make(map[string]T)
|
||||||
|
|
||||||
|
for k, v := range f.data {
|
||||||
|
if k == "" {
|
||||||
|
data["default"] = v
|
||||||
|
} else {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := overridableFieldJSONPayload[T]{
|
||||||
|
Data: data,
|
||||||
|
Resolved: &f.resolved,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OverridableField[T]) UnmarshalJSON(data []byte) error {
|
||||||
|
var payload overridableFieldJSONPayload[T]
|
||||||
|
if err := json.Unmarshal(data, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.Data == nil {
|
||||||
|
payload.Data = make(map[string]T)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.data = payload.Data
|
||||||
|
if payload.Resolved != nil {
|
||||||
|
f.resolved = *payload.Resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func OverridableFromMap[T any](data map[string]T) OverridableField[T] {
|
func OverridableFromMap[T any](data map[string]T) OverridableField[T] {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
data = make(map[string]T)
|
data = make(map[string]T)
|
||||||
|
@ -14,9 +14,12 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:generate go run ../../generators/alrsh-package
|
||||||
|
|
||||||
package alrsh
|
package alrsh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@ -39,38 +42,38 @@ func ParseNames(dec *decoder.Decoder) (*PackageNames, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Package struct {
|
type Package struct {
|
||||||
Repository string `xorm:"pk 'repository'"`
|
Repository string `xorm:"pk 'repository'" json:"repository"`
|
||||||
Name string `xorm:"pk 'name'"`
|
Name string `xorm:"pk 'name'" json:"name"`
|
||||||
BasePkgName string `xorm:"notnull 'basepkg_name'"`
|
BasePkgName string `xorm:"notnull 'basepkg_name'" json:"basepkg_name"`
|
||||||
|
|
||||||
Version string `sh:"version" xorm:"notnull 'version'"`
|
Version string `sh:"version" xorm:"notnull 'version'" json:"version"`
|
||||||
Release int `sh:"release" xorm:"notnull 'release'"`
|
Release int `sh:"release" xorm:"notnull 'release'" json:"release"`
|
||||||
Epoch uint `sh:"epoch" xorm:"'epoch'"`
|
Epoch uint `sh:"epoch" xorm:"'epoch'" json:"epoch"`
|
||||||
Architectures []string `sh:"architectures" xorm:"json 'architectures'"`
|
Architectures []string `sh:"architectures" xorm:"json 'architectures'" json:"architectures"`
|
||||||
Licenses []string `sh:"license" xorm:"json 'licenses'"`
|
Licenses []string `sh:"license" xorm:"json 'licenses'" json:"license"`
|
||||||
Provides []string `sh:"provides" xorm:"json 'provides'"`
|
Provides []string `sh:"provides" xorm:"json 'provides'" json:"provides"`
|
||||||
Conflicts []string `sh:"conflicts" xorm:"json 'conflicts'"`
|
Conflicts []string `sh:"conflicts" xorm:"json 'conflicts'" json:"conflicts"`
|
||||||
Replaces []string `sh:"replaces" xorm:"json 'replaces'"`
|
Replaces []string `sh:"replaces" xorm:"json 'replaces'" json:"replaces"`
|
||||||
|
|
||||||
Summary OverridableField[string] `sh:"summary" xorm:"'summary'"`
|
Summary OverridableField[string] `sh:"summary" xorm:"'summary'" json:"summary"`
|
||||||
Description OverridableField[string] `sh:"desc" xorm:"'description'"`
|
Description OverridableField[string] `sh:"desc" xorm:"'description'" json:"description"`
|
||||||
Group OverridableField[string] `sh:"group" xorm:"'group_name'"`
|
Group OverridableField[string] `sh:"group" xorm:"'group_name'" json:"group"`
|
||||||
Homepage OverridableField[string] `sh:"homepage" xorm:"'homepage'"`
|
Homepage OverridableField[string] `sh:"homepage" xorm:"'homepage'" json:"homepage"`
|
||||||
Maintainer OverridableField[string] `sh:"maintainer" xorm:"'maintainer'"`
|
Maintainer OverridableField[string] `sh:"maintainer" xorm:"'maintainer'" json:"maintainer"`
|
||||||
Depends OverridableField[[]string] `sh:"deps" xorm:"'depends'"`
|
Depends OverridableField[[]string] `sh:"deps" xorm:"'depends'" json:"deps"`
|
||||||
BuildDepends OverridableField[[]string] `sh:"build_deps" xorm:"'builddepends'"`
|
BuildDepends OverridableField[[]string] `sh:"build_deps" xorm:"'builddepends'" json:"build_deps"`
|
||||||
OptDepends OverridableField[[]string] `sh:"opt_deps" xorm:"'optdepends'"`
|
OptDepends OverridableField[[]string] `sh:"opt_deps" xorm:"'optdepends'" json:"opt_deps,omitempty"`
|
||||||
Sources OverridableField[[]string] `sh:"sources" xorm:"-"`
|
Sources OverridableField[[]string] `sh:"sources" xorm:"-" json:"sources"`
|
||||||
Checksums OverridableField[[]string] `sh:"checksums" xorm:"-"`
|
Checksums OverridableField[[]string] `sh:"checksums" xorm:"-" json:"checksums,omitempty"`
|
||||||
Backup OverridableField[[]string] `sh:"backup" xorm:"-"`
|
Backup OverridableField[[]string] `sh:"backup" xorm:"-" json:"backup"`
|
||||||
Scripts OverridableField[Scripts] `sh:"scripts" xorm:"-"`
|
Scripts OverridableField[Scripts] `sh:"scripts" xorm:"-" json:"scripts,omitempty"`
|
||||||
AutoReq OverridableField[[]string] `sh:"auto_req" xorm:"-"`
|
AutoReq OverridableField[[]string] `sh:"auto_req" xorm:"-" json:"auto_req"`
|
||||||
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-"`
|
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-" json:"auto_prov"`
|
||||||
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-"`
|
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-" json:"auto_req_skiplist,omitempty"`
|
||||||
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-"`
|
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-" json:"auto_prov_skiplist,omitempty"`
|
||||||
|
|
||||||
FireJailed OverridableField[bool] `sh:"firejailed" xorm:"-"`
|
FireJailed OverridableField[bool] `sh:"firejailed" xorm:"-" json:"firejailed"`
|
||||||
FireJailProfiles OverridableField[map[string]string] `sh:"firejail_profiles" xorm:"-"`
|
FireJailProfiles OverridableField[map[string]string] `sh:"firejail_profiles" xorm:"-" json:"firejail_profiles,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Scripts struct {
|
type Scripts struct {
|
||||||
@ -84,25 +87,70 @@ type Scripts struct {
|
|||||||
PostTrans string `sh:"posttrans"`
|
PostTrans string `sh:"posttrans"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResolvePackage(p *Package, overrides []string) {
|
func (p Package) MarshalJSONWithOptions(includeOverrides bool) ([]byte, error) {
|
||||||
val := reflect.ValueOf(p).Elem()
|
// Сначала сериализуем обычным способом для получения базовой структуры
|
||||||
typ := val.Type()
|
type PackageAlias Package
|
||||||
|
baseData, err := json.Marshal(PackageAlias(p))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for i := range val.NumField() {
|
// Десериализуем в map для модификации
|
||||||
field := val.Field(i)
|
var result map[string]json.RawMessage
|
||||||
fieldType := typ.Field(i)
|
if err := json.Unmarshal(baseData, &result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if !field.CanInterface() {
|
// Теперь заменяем OverridableField поля
|
||||||
|
v := reflect.ValueOf(p)
|
||||||
|
t := reflect.TypeOf(p)
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
fieldType := t.Field(i)
|
||||||
|
|
||||||
|
jsonTag := fieldType.Tag.Get("json")
|
||||||
|
if jsonTag == "" || jsonTag == "-" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if field.Kind() == reflect.Struct && strings.HasPrefix(fieldType.Type.String(), "alrsh.OverridableField") {
|
fieldName := jsonTag
|
||||||
of := field.Addr().Interface()
|
if commaIdx := strings.Index(jsonTag, ","); commaIdx != -1 {
|
||||||
if res, ok := of.(interface {
|
fieldName = jsonTag[:commaIdx]
|
||||||
Resolve([]string)
|
}
|
||||||
}); ok {
|
|
||||||
res.Resolve(overrides)
|
if field.Type().Name() == "OverridableField" ||
|
||||||
|
(field.Type().Kind() == reflect.Struct &&
|
||||||
|
strings.Contains(field.Type().String(), "OverridableField")) {
|
||||||
|
|
||||||
|
fieldPtr := field.Addr()
|
||||||
|
|
||||||
|
resolvedMethod := fieldPtr.MethodByName("Resolved")
|
||||||
|
if resolvedMethod.IsValid() {
|
||||||
|
resolved := resolvedMethod.Call(nil)[0]
|
||||||
|
|
||||||
|
fieldData := map[string]interface{}{
|
||||||
|
"resolved": resolved.Interface(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if includeOverrides {
|
||||||
|
allMethod := field.MethodByName("All")
|
||||||
|
if allMethod.IsValid() {
|
||||||
|
overrides := allMethod.Call(nil)[0]
|
||||||
|
if !overrides.IsNil() && overrides.Len() > 0 {
|
||||||
|
fieldData["overrides"] = overrides.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldJSON, err := json.Marshal(fieldData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[fieldName] = json.RawMessage(fieldJSON)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return json.Marshal(result)
|
||||||
}
|
}
|
||||||
|
105
pkg/alrsh/package_gen.go
Normal file
105
pkg/alrsh/package_gen.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
// DO NOT EDIT MANUALLY. This file is generated.
|
||||||
|
package alrsh
|
||||||
|
|
||||||
|
type packageResolved struct {
|
||||||
|
Repository string `json:"repository"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
BasePkgName string `json:"basepkg_name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Release int `json:"release"`
|
||||||
|
Epoch uint `json:"epoch"`
|
||||||
|
Architectures []string `json:"architectures"`
|
||||||
|
Licenses []string `json:"license"`
|
||||||
|
Provides []string `json:"provides"`
|
||||||
|
Conflicts []string `json:"conflicts"`
|
||||||
|
Replaces []string `json:"replaces"`
|
||||||
|
Summary string `json:"summary"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Group string `json:"group"`
|
||||||
|
Homepage string `json:"homepage"`
|
||||||
|
Maintainer string `json:"maintainer"`
|
||||||
|
Depends []string `json:"deps"`
|
||||||
|
BuildDepends []string `json:"build_deps"`
|
||||||
|
OptDepends []string `json:"opt_deps,omitempty"`
|
||||||
|
Sources []string `json:"sources"`
|
||||||
|
Checksums []string `json:"checksums,omitempty"`
|
||||||
|
Backup []string `json:"backup"`
|
||||||
|
Scripts Scripts `json:"scripts,omitempty"`
|
||||||
|
AutoReq []string `json:"auto_req"`
|
||||||
|
AutoProv []string `json:"auto_prov"`
|
||||||
|
AutoReqSkipList []string `json:"auto_req_skiplist,omitempty"`
|
||||||
|
AutoProvSkipList []string `json:"auto_prov_skiplist,omitempty"`
|
||||||
|
FireJailed bool `json:"firejailed"`
|
||||||
|
FireJailProfiles map[string]string `json:"firejail_profiles,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PackageToResolved(src *Package) packageResolved {
|
||||||
|
return packageResolved{
|
||||||
|
Repository: src.Repository,
|
||||||
|
Name: src.Name,
|
||||||
|
BasePkgName: src.BasePkgName,
|
||||||
|
Version: src.Version,
|
||||||
|
Release: src.Release,
|
||||||
|
Epoch: src.Epoch,
|
||||||
|
Architectures: src.Architectures,
|
||||||
|
Licenses: src.Licenses,
|
||||||
|
Provides: src.Provides,
|
||||||
|
Conflicts: src.Conflicts,
|
||||||
|
Replaces: src.Replaces,
|
||||||
|
Summary: src.Summary.Resolved(),
|
||||||
|
Description: src.Description.Resolved(),
|
||||||
|
Group: src.Group.Resolved(),
|
||||||
|
Homepage: src.Homepage.Resolved(),
|
||||||
|
Maintainer: src.Maintainer.Resolved(),
|
||||||
|
Depends: src.Depends.Resolved(),
|
||||||
|
BuildDepends: src.BuildDepends.Resolved(),
|
||||||
|
OptDepends: src.OptDepends.Resolved(),
|
||||||
|
Sources: src.Sources.Resolved(),
|
||||||
|
Checksums: src.Checksums.Resolved(),
|
||||||
|
Backup: src.Backup.Resolved(),
|
||||||
|
Scripts: src.Scripts.Resolved(),
|
||||||
|
AutoReq: src.AutoReq.Resolved(),
|
||||||
|
AutoProv: src.AutoProv.Resolved(),
|
||||||
|
AutoReqSkipList: src.AutoReqSkipList.Resolved(),
|
||||||
|
AutoProvSkipList: src.AutoProvSkipList.Resolved(),
|
||||||
|
FireJailed: src.FireJailed.Resolved(),
|
||||||
|
FireJailProfiles: src.FireJailProfiles.Resolved(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolvePackage(pkg *Package, overrides []string) {
|
||||||
|
pkg.Summary.Resolve(overrides)
|
||||||
|
pkg.Description.Resolve(overrides)
|
||||||
|
pkg.Group.Resolve(overrides)
|
||||||
|
pkg.Homepage.Resolve(overrides)
|
||||||
|
pkg.Maintainer.Resolve(overrides)
|
||||||
|
pkg.Depends.Resolve(overrides)
|
||||||
|
pkg.BuildDepends.Resolve(overrides)
|
||||||
|
pkg.OptDepends.Resolve(overrides)
|
||||||
|
pkg.Sources.Resolve(overrides)
|
||||||
|
pkg.Checksums.Resolve(overrides)
|
||||||
|
pkg.Backup.Resolve(overrides)
|
||||||
|
pkg.Scripts.Resolve(overrides)
|
||||||
|
pkg.AutoReq.Resolve(overrides)
|
||||||
|
pkg.AutoProv.Resolve(overrides)
|
||||||
|
pkg.AutoReqSkipList.Resolve(overrides)
|
||||||
|
pkg.AutoProvSkipList.Resolve(overrides)
|
||||||
|
pkg.FireJailed.Resolve(overrides)
|
||||||
|
pkg.FireJailProfiles.Resolve(overrides)
|
||||||
|
}
|
37
pkg/alrsh/view.go
Normal file
37
pkg/alrsh/view.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 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 alrsh
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type PackageView struct {
|
||||||
|
pkg Package
|
||||||
|
|
||||||
|
Resolved bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPackageView(v Package) PackageView {
|
||||||
|
return PackageView{pkg: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PackageView) MarshalJSON() ([]byte, error) {
|
||||||
|
if p.Resolved {
|
||||||
|
return json.Marshal(PackageToResolved(&p.pkg))
|
||||||
|
} else {
|
||||||
|
return json.Marshal(p.pkg)
|
||||||
|
}
|
||||||
|
}
|
@ -55,7 +55,7 @@ var (
|
|||||||
|
|
||||||
// Массив доступных загрузчиков в порядке их проверки
|
// Массив доступных загрузчиков в порядке их проверки
|
||||||
var Downloaders = []Downloader{
|
var Downloaders = []Downloader{
|
||||||
GitDownloader{},
|
&GitDownloader{},
|
||||||
TorrentDownloader{},
|
TorrentDownloader{},
|
||||||
FileDownloader{},
|
FileDownloader{},
|
||||||
}
|
}
|
@ -32,8 +32,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"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/pkg/dl"
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
|
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestALRConfig struct{}
|
type TestALRConfig struct{}
|
@ -48,7 +48,7 @@ func (GitDownloader) MatchURL(u string) bool {
|
|||||||
// Download uses git to clone the repository from the specified URL.
|
// Download uses git to clone the repository from the specified URL.
|
||||||
// It allows specifying the revision, depth and recursion options
|
// It allows specifying the revision, depth and recursion options
|
||||||
// via query string
|
// 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)
|
u, err := url.Parse(opts.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", err
|
return 0, "", err
|
||||||
@ -60,6 +60,9 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string,
|
|||||||
rev := query.Get("~rev")
|
rev := query.Get("~rev")
|
||||||
query.Del("~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")
|
name := query.Get("~name")
|
||||||
query.Del("~name")
|
query.Del("~name")
|
||||||
|
|
||||||
@ -121,6 +124,11 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = VerifyHashFromLocal("", opts)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = strings.TrimSuffix(path.Base(u.Path), ".git")
|
name = strings.TrimSuffix(path.Base(u.Path), ".git")
|
||||||
}
|
}
|
||||||
@ -133,7 +141,7 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string,
|
|||||||
// and recursion options via query string. It returns
|
// and recursion options via query string. It returns
|
||||||
// true if update was successful and false if the
|
// true if update was successful and false if the
|
||||||
// repository is already up-to-date
|
// 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)
|
u, err := url.Parse(opts.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -183,18 +191,21 @@ func (GitDownloader) Update(opts Options) (bool, error) {
|
|||||||
manifestOK := err == nil
|
manifestOK := err == nil
|
||||||
|
|
||||||
err = w.Pull(po)
|
err = w.Pull(po)
|
||||||
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
if err != nil {
|
||||||
return false, nil
|
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||||
} else if err != nil {
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = VerifyHashFromLocal("", opts)
|
||||||
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if manifestOK {
|
if manifestOK {
|
||||||
err = writeManifest(opts.Destination, m)
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -71,7 +71,17 @@ func (TorrentDownloader) Download(ctx context.Context, opts Options) (Type, stri
|
|||||||
return 0, "", err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return determineType(opts.Destination)
|
dlType, name, err := determineType(opts.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = VerifyHashFromLocal(name, opts)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlType, name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeTorrentFiles(path string) error {
|
func removeTorrentFiles(path string) error {
|
95
pkg/dl/utils.go
Normal file
95
pkg/dl/utils.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If the checksum does not match, returns ErrChecksumMismatch
|
||||||
|
func VerifyHashFromLocal(path string, opts Options) error {
|
||||||
|
if opts.Hash != nil {
|
||||||
|
h, err := opts.NewHash()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = HashLocal(filepath.Join(opts.Destination, path), h)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := h.Sum(nil)
|
||||||
|
|
||||||
|
slog.Debug("validate checksum", "real", hex.EncodeToString(sum), "expected", hex.EncodeToString(opts.Hash))
|
||||||
|
|
||||||
|
if !bytes.Equal(sum, opts.Hash) {
|
||||||
|
return ErrChecksumMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HashLocal(path string, h hash.Hash) error {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Mode().IsRegular() {
|
||||||
|
// Single file
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = io.Copy(h, f)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
// Walk directory
|
||||||
|
return filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() && info.Name() == ".git" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = io.Copy(h, f)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unsupported file type: %s", path)
|
||||||
|
}
|
@ -29,7 +29,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
|
"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 {
|
type TestALRConfig struct {
|
@ -32,7 +32,8 @@ type Config struct {
|
|||||||
|
|
||||||
// Repo represents a ALR repo within a configuration file
|
// Repo represents a ALR repo within a configuration file
|
||||||
type Repo struct {
|
type Repo struct {
|
||||||
Name string `toml:"name"`
|
Name string `toml:"name"`
|
||||||
URL string `toml:"url"`
|
URL string `toml:"url"`
|
||||||
Ref string `toml:"ref"`
|
Ref string `toml:"ref"`
|
||||||
|
Mirrors []string `toml:"mirrors"`
|
||||||
}
|
}
|
||||||
|
356
repo.go
356
repo.go
@ -20,8 +20,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -41,6 +43,8 @@ func RepoCmd() *cli.Command {
|
|||||||
RemoveRepoCmd(),
|
RemoveRepoCmd(),
|
||||||
AddRepoCmd(),
|
AddRepoCmd(),
|
||||||
SetRepoRefCmd(),
|
SetRepoRefCmd(),
|
||||||
|
RepoMirrorCmd(),
|
||||||
|
SetUrlCmd(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,6 +55,21 @@ func RemoveRepoCmd() *cli.Command {
|
|||||||
Usage: gotext.Get("Remove an existing repository"),
|
Usage: gotext.Get("Remove an existing repository"),
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
ArgsUsage: gotext.Get("<name>"),
|
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 {
|
Action: utils.RootNeededAction(func(c *cli.Context) error {
|
||||||
if c.Args().Len() < 1 {
|
if c.Args().Len() < 1 {
|
||||||
return cliutils.FormatCliExit("missing args", nil)
|
return cliutils.FormatCliExit("missing args", nil)
|
||||||
@ -186,6 +205,21 @@ func SetRepoRefCmd() *cli.Command {
|
|||||||
Name: "set-ref",
|
Name: "set-ref",
|
||||||
Usage: gotext.Get("Set the reference of the repository"),
|
Usage: gotext.Get("Set the reference of the repository"),
|
||||||
ArgsUsage: gotext.Get("<name> <ref>"),
|
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 {
|
Action: utils.RootNeededAction(func(c *cli.Context) error {
|
||||||
if c.Args().Len() < 2 {
|
if c.Args().Len() < 2 {
|
||||||
return cliutils.FormatCliExit("missing args", nil)
|
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
|
// TODO: remove
|
||||||
//
|
//
|
||||||
// Deprecated: use "alr repo add"
|
// Deprecated: use "alr repo add"
|
||||||
|
Reference in New Issue
Block a user