fix(query): keep pacman.conf repo order for exact name matches (#2987)

Motivation
With separatesources enabled (the default), a package with the exact
searched name that exists in several sync repos can be listed in the
wrong order. For example, if [cachyos-extra-v3] comes before [extra] in
pacman.conf, extra/chromium can still show up as the top pick instead of
cachyos-extra-v3/chromium. pacman prefers the repo listed first.

separateSourceScore returned a flat 50 for every exact match, whatever
its source. As a result, all exact matches got the same metric
(50 + 1.0). The unstable slices.SortFunc then left their relative order
unspecified, so the ordering added for non-exact results did not apply
to exact ones.

Approach
Move the repo-order scoring into a repoOrderScore helper (same logic and
cache as before). For exact matches, add repoOrderScore/separateSourceMax
to the 50: a bonus between 0 and 1. Exact matches still outrank every
non-exact result (which scores at most 45 + a metric below 1). Among
exact matches, repos earlier in pacman.conf now come first. Exact AUR
matches keep 50, so an exact sync-repo match now ranks strictly above an
exact AUR match instead of tying with it. Scoring for non-exact results
is unchanged, and nothing changes when separatesources is disabled.

Validation
- go test -race ./pkg/query/... -count=1: passes.
- New tests TestSourceQueryBuilderExactMatchSortsByRepoOrder (top-down
  and bottom-up) and TestSeparateSourceScore_ExactMatchKeepsRepoOrder
  fail with the previous metric.go and pass with this change.
- golangci-lint run ./pkg/query/...: 0 issues; gofmt clean.
- go test ./... on macOS has failures that also occur on the unchanged
  base: Linux-only syscall fields in pkg/settings/exe, and a
  timezone-dependent test in pkg/text. Not tested against a real
  pacman setup with custom repos.

Report: https://github.com/Jguer/yay/issues/2986

Assisted-by: claude-opus-5-5 (via Claude Code)

Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Co-authored-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
This commit is contained in:
Pujitha PaladuguandPujitha Paladugu authored and GitHub committed 2026-09-26 23:56:01 +02:00
1 parent a2a06f4cf9
commit ce6bcbd775
3 files changed
+109 -3

No files matched your search

+12 -3
View File
@@ -57,10 +57,21 @@ func (a *abstractResults) separateSourceScore(source string, score float64) floa
return 0
}
sourceScore := a.repoOrderScore(source)
// Exact matches always rank above everything else; among them, keep
// pacman.conf repo order by adding a bonus in [0, 1].
if score == 1.0 {
return 50
return 50 + sourceScore/separateSourceMax
}
return sourceScore
}
// repoOrderScore scores sync repositories based on pacman.conf order (as reflected by dbExecutor.Repos()).
// First repo gets max, last repo gets min, evenly distributed across the range.
// AUR and unknown sources get 0.
func (a *abstractResults) repoOrderScore(source string) float64 {
if v, ok := a.separateSourceCache[source]; ok {
return v
}
@@ -70,8 +81,6 @@ func (a *abstractResults) separateSourceScore(source string, score float64) floa
return 0
}
// Score sync repositories based on pacman.conf order (as reflected by dbExecutor.Repos()).
// First repo gets max, last repo gets min, evenly distributed across the range.
for i, repo := range a.repoOrder {
if repo != source {
continue
+20
View File
@@ -43,3 +43,23 @@ func TestSeparateSourceScore_UsesRepoOrderEvenlyDistributed(t *testing.T) {
assert.Equal(t, 0.0, a.separateSourceScore("aur", sim))
})
}
func TestSeparateSourceScore_ExactMatchKeepsRepoOrder(t *testing.T) {
t.Parallel()
a := &abstractResults{
separateSources: true,
repoOrder: []string{"cachyos-extra-v3", "core", "extra"},
separateSourceCache: map[string]float64{},
}
custom := a.separateSourceScore("cachyos-extra-v3", 1.0)
extra := a.separateSourceScore("extra", 1.0)
aurScore := a.separateSourceScore("aur", 1.0)
assert.Greater(t, custom, extra)
assert.Greater(t, extra, aurScore)
assert.Equal(t, 50.0, aurScore)
// Exact matches must still outrank any non-exact match.
assert.Greater(t, extra+1.0, a.separateSourceScore("cachyos-extra-v3", 0.99)+0.99)
}
+77
View File
@@ -445,6 +445,83 @@ func TestSourceQueryBuilderTieSortsByRepoOrder(t *testing.T) {
}
}
func TestSourceQueryBuilderExactMatchSortsByRepoOrder(t *testing.T) {
t.Parallel()
testCases := []struct {
desc string
bottomUp bool
repoOrder []string
dbOrder []string
wantSources []string
}{
{
desc: "topdown custom repo first",
repoOrder: []string{"cachyos-extra-v3", "core", "extra"},
dbOrder: []string{"extra", "cachyos-extra-v3"},
wantSources: []string{"cachyos-extra-v3", "extra", "aur"},
},
{
desc: "topdown extra first",
repoOrder: []string{"core", "extra", "cachyos-extra-v3"},
dbOrder: []string{"cachyos-extra-v3", "extra"},
wantSources: []string{"extra", "cachyos-extra-v3", "aur"},
},
{
desc: "bottomup custom repo first",
bottomUp: true,
repoOrder: []string{"cachyos-extra-v3", "core", "extra"},
dbOrder: []string{"extra", "cachyos-extra-v3"},
wantSources: []string{"aur", "extra", "cachyos-extra-v3"},
},
}
mockAUR := &mockaur.MockAUR{
GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) {
return []aur.Pkg{{Name: "chromium", PackageBase: "chromium", Version: "1-1"}}, nil
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()
mockDB := &mock.DBExecutor{
ReposFn: func() []string { return tc.repoOrder },
SyncPackagesFn: func(pkgs ...string) []mock.IPackage {
res := make([]mock.IPackage, 0, len(tc.dbOrder))
for _, dbName := range tc.dbOrder {
res = append(res, &mock.Package{
PBase: "chromium",
PName: "chromium",
PVersion: "153.0.8010.52-1",
PDescription: "A web browser built for speed, simplicity, and security",
PDB: mock.NewDB(dbName),
PArchitecture: "x86_64",
})
}
return res
},
LocalPackageFn: func(string) mock.IPackage { return nil },
}
queryBuilder := NewSourceQueryBuilder(mockAUR,
text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), false, "test"),
"", parser.ModeAny, "", tc.bottomUp,
false, true)
queryBuilder.Execute(t.Context(), mockDB, []string{"chromium"})
gotSources := make([]string, len(queryBuilder.results))
for i, result := range queryBuilder.results {
gotSources[i] = result.source
}
assert.Equal(t, tc.wantSources, gotSources)
})
}
}
func TestSourceQueryBuilderTieDoesNotSeparateSources(t *testing.T) {
t.Parallel()