diff --git a/assets/coverage-badge.svg b/assets/coverage-badge.svg
index 1a4a8cc..b891de3 100644
--- a/assets/coverage-badge.svg
+++ b/assets/coverage-badge.svg
@@ -11,7 +11,7 @@
coverage
coverage
- 16.4%
- 16.4%
+ 17.0%
+ 17.0%
diff --git a/e2e-tests/issue_75_ref_specify_test.go b/e2e-tests/issue_75_ref_specify_test.go
new file mode 100644
index 0000000..623381c
--- /dev/null
+++ b/e2e-tests/issue_75_ref_specify_test.go
@@ -0,0 +1,62 @@
+// ALR - Any Linux Repository
+// Copyright (C) 2025 The ALR Authors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//go:build e2e
+
+package e2etests_test
+
+import (
+ "testing"
+
+ "github.com/alecthomas/assert/v2"
+ "github.com/efficientgo/e2e"
+)
+
+func TestE2EIssue75InstallWithDeps(t *testing.T) {
+ dockerMultipleRun(
+ t,
+ "issue-75-ref-specify",
+ COMMON_SYSTEMS,
+ func(t *testing.T, r e2e.Runnable) {
+ err := r.Exec(e2e.NewCommand(
+ "sudo",
+ "alr",
+ "addrepo",
+ "--name",
+ "alr-repo",
+ "--url",
+ "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git",
+ ))
+ assert.NoError(t, err)
+
+ err = r.Exec(e2e.NewCommand(
+ "sudo", "alr", "ref",
+ ))
+ assert.NoError(t, err)
+
+ // TODO: replace with alr command when it be added
+ err = r.Exec(e2e.NewCommand(
+ "sudo", "sh", "-c", "sed -i 's/ref = .*/ref = \"bd26236cd7\"/' /etc/alr/alr.toml",
+ ))
+ assert.NoError(t, err)
+
+ err = r.Exec(e2e.NewCommand(
+ "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1",
+ ))
+ assert.NoError(t, err)
+ },
+ )
+}
diff --git a/internal/translations/default.pot b/internal/translations/default.pot
index bc63d5b..400e5e4 100644
--- a/internal/translations/default.pot
+++ b/internal/translations/default.pot
@@ -413,19 +413,19 @@ msgstr ""
msgid "Executing %s()"
msgstr ""
-#: pkg/repos/pull.go:79
+#: pkg/repos/pull.go:80
msgid "Pulling repository"
msgstr ""
-#: pkg/repos/pull.go:103
+#: pkg/repos/pull.go:116
msgid "Repository up to date"
msgstr ""
-#: pkg/repos/pull.go:160
+#: pkg/repos/pull.go:207
msgid "Git repository does not appear to be a valid ALR repo"
msgstr ""
-#: pkg/repos/pull.go:176
+#: pkg/repos/pull.go:223
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po
index 5afa67b..6180148 100644
--- a/internal/translations/po/ru/default.po
+++ b/internal/translations/po/ru/default.po
@@ -425,19 +425,19 @@ msgstr "Выполнение build()"
msgid "Executing %s()"
msgstr "Выполнение %s()"
-#: pkg/repos/pull.go:79
+#: pkg/repos/pull.go:80
msgid "Pulling repository"
msgstr "Скачивание репозитория"
-#: pkg/repos/pull.go:103
+#: pkg/repos/pull.go:116
msgid "Repository up to date"
msgstr "Репозиторий уже обновлён"
-#: pkg/repos/pull.go:160
+#: pkg/repos/pull.go:207
msgid "Git repository does not appear to be a valid ALR repo"
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
-#: pkg/repos/pull.go:176
+#: pkg/repos/pull.go:223
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
diff --git a/internal/types/config.go b/internal/types/config.go
index 55480e5..f6a6238 100644
--- a/internal/types/config.go
+++ b/internal/types/config.go
@@ -34,4 +34,5 @@ type Config struct {
type Repo struct {
Name string `toml:"name"`
URL string `toml:"url"`
+ Ref string `toml:"ref"`
}
diff --git a/pkg/repos/pull.go b/pkg/repos/pull.go
index 881b87f..fc254be 100644
--- a/pkg/repos/pull.go
+++ b/pkg/repos/pull.go
@@ -33,6 +33,7 @@ import (
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5"
+ gitConfig "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/leonelquinteros/gotext"
"github.com/pelletier/go-toml/v2"
@@ -88,6 +89,14 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
return err
}
+ err = r.FetchContext(ctx, &git.FetchOptions{
+ Progress: os.Stderr,
+ Force: true,
+ })
+ if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
+ return err
+ }
+
w, err := r.Worktree()
if err != nil {
return err
@@ -98,34 +107,41 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
return err
}
- err = w.PullContext(ctx, &git.PullOptions{Progress: os.Stderr})
- if errors.Is(err, git.NoErrAlreadyUpToDate) {
+ 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)
- } else if err != nil {
+ }
+
+ err = w.Checkout(&git.CheckoutOptions{
+ Hash: plumbing.NewHash(revHash.String()),
+ Force: true,
+ })
+ if err != nil {
return err
}
repoFS = w.Filesystem
- // Make sure the DB is created even if the repo is up to date
- if !errors.Is(err, git.NoErrAlreadyUpToDate) || rs.db.IsEmpty(ctx) {
- new, err := r.Head()
+ 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(ctx) {
+ err = rs.processRepoFull(ctx, repo, repoDir)
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(ctx) {
- 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 = rs.processRepoChanges(ctx, repo, r, w, old, new)
+ if err != nil {
+ return err
}
}
} else {
@@ -139,9 +155,40 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
return err
}
- _, err = git.PlainCloneContext(ctx, repoDir, false, &git.CloneOptions{
- URL: repoURL.String(),
+ 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
@@ -268,7 +315,8 @@ func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Ru
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
- interp.Dir(scriptDir),
+ // Use temp dir instead script dir because runner may be for deleted file
+ interp.Dir(os.TempDir()),
)
}
@@ -285,7 +333,7 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
patch, err := oldCommit.Patch(newCommit)
if err != nil {
- return err
+ return fmt.Errorf("error to create patch: %w", err)
}
var actions []action
@@ -319,6 +367,7 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
},
)
default:
+ slog.Debug("unexpected, but I'll try to do")
actions = append(actions, action{
Type: actionUpdate,
File: to.Path(),
@@ -332,7 +381,7 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
for _, action := range actions {
runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(filepath.Join(repoDir, action.File)))
if err != nil {
- return err
+ return fmt.Errorf("error creating process repo changes runner: %w", err)
}
switch action.Type {
@@ -340,7 +389,6 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
if filepath.Base(action.File) != "alr.sh" {
continue
}
-
scriptFl, err := oldCommit.File(action.File)
if err != nil {
return nil
@@ -378,7 +426,7 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git
err = rs.updatePkg(ctx, repo, runner, r)
if err != nil {
- return err
+ return fmt.Errorf("error updatePkg: %w", err)
}
}
}
diff --git a/pkg/repos/utils.go b/pkg/repos/utils.go
index 7df2b08..bc2315a 100644
--- a/pkg/repos/utils.go
+++ b/pkg/repos/utils.go
@@ -18,12 +18,18 @@ package repos
import (
"context"
+ "fmt"
"io"
"path/filepath"
"reflect"
"strings"
+ "github.com/go-git/go-git/v5"
+ "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/diff"
+ "github.com/go-git/go-git/v5/plumbing/transport"
+ "github.com/go-git/go-git/v5/plumbing/transport/client"
+
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
@@ -137,3 +143,59 @@ func resolveOverrides(runner *interp.Runner, pkg *db.Package) {
}
}
}
+
+func getHeadReference(r *git.Repository) (plumbing.ReferenceName, error) {
+ remote, err := r.Remote(git.DefaultRemoteName)
+ if err != nil {
+ return "", err
+ }
+
+ endpoint, err := transport.NewEndpoint(remote.Config().URLs[0])
+ if err != nil {
+ return "", err
+ }
+
+ gitClient, err := client.NewClient(endpoint)
+ if err != nil {
+ return "", err
+ }
+
+ session, err := gitClient.NewUploadPackSession(endpoint, nil)
+ if err != nil {
+ return "", err
+ }
+
+ info, err := session.AdvertisedReferences()
+ if err != nil {
+ return "", err
+ }
+
+ refs, err := info.AllReferences()
+ if err != nil {
+ return "", err
+ }
+
+ return refs["HEAD"].Target(), nil
+}
+
+func resolveHash(r *git.Repository, ref string) (*plumbing.Hash, error) {
+ var err error
+
+ if ref == "" {
+ reference, err := getHeadReference(r)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get head reference %w", err)
+ }
+ ref = reference.Short()
+ }
+
+ hsh, err := r.ResolveRevision(git.DefaultRemoteName + "/" + plumbing.Revision(ref))
+ if err != nil {
+ hsh, err = r.ResolveRevision(plumbing.Revision(ref))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return hsh, nil
+}