fix(repo): rank exact repository name matches first (#39038)

The quick repository switcher orders results alphabetically, so a
repository whose name exactly matches the query can appear below names
that merely contain it.

- Rank exact repository name matches first when no explicit sort order
is requested
- For "owner/repo" queries, rank the exact pair first, then the owner's
other repositories
- Match the owner of "owner/repo" queries case-insensitively, mixed-case
owners were missed

---------

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
Lunny Xiaoandsilverwind authored and GitHub committed 2026-11-11 19:01:43 +00:00
1 parent aa073fb1be
commit e867cfbf0d
2 files changed
+26 -5

No files matched your search

+7 -5
View File
@@ -475,7 +475,7 @@ func SearchRepositoryCondition(opts SearchRepoOptions) builder.Cond {
pieces := strings.Split(opts.Keyword, "/")
ownerName := pieces[0]
repoName := pieces[1]
likes = likes.Or(builder.And(builder.Like{"owner_name", strings.ToLower(ownerName)}, builder.Like{"lower_name", strings.ToLower(repoName)}))
likes = likes.Or(builder.And(db.BuildCaseInsensitiveLike("owner_name", ownerName), builder.Like{"lower_name", strings.ToLower(repoName)}))
}
if opts.IncludeDescription {
@@ -609,10 +609,12 @@ func searchRepositoryByCondition(ctx context.Context, opts SearchRepoOptions, co
orderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = ? THEN 0 ELSE owner_id END, %s", orderBy))
args = append(args, opts.PriorityOwnerID)
} else if strings.Count(opts.Keyword, "/") == 1 {
// With "owner/repo" search times, prioritise results which match the owner field
orgName, _, _ := strings.Cut(opts.Keyword, "/")
orderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE ? THEN 0 ELSE 1 END, %s", orderBy))
args = append(args, orgName)
ownerName, repoName, _ := strings.Cut(strings.ToLower(opts.Keyword), "/")
orderBy = db.SearchOrderBy(fmt.Sprintf("CASE LOWER(owner_name) WHEN ? THEN CASE lower_name WHEN ? THEN 0 ELSE 1 END ELSE 2 END, %s", orderBy))
args = append(args, ownerName, repoName)
} else if len(opts.OrderBy) == 0 && opts.Keyword != "" && !strings.ContainsAny(opts.Keyword, ",/") { // unlike "owner/repo", a bare name never overrides an explicit sort
orderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN lower_name = ? THEN 0 ELSE 1 END, %s", orderBy))
args = append(args, strings.ToLower(opts.Keyword))
}
sess := db.GetEngine(ctx)
+19
View File
@@ -195,6 +195,25 @@ func TestSearchRepository(t *testing.T) {
t.Run("SearchRepositoryWithInDescription", testSearchRepositoryWithInDescription)
t.Run("SearchRepositoryNotInDescription", testSearchRepositoryNotInDescription)
t.Run("SearchRepositoryCases", testSearchRepositoryCases)
t.Run("SearchRepositoryRanksExactMatchesCaseInsensitivelyUnlessSorted", func(t *testing.T) {
afoo := &repo_model.Repository{OwnerID: 2, OwnerName: "User2", LowerName: "afoo", Name: "afoo"}
aafoo := &repo_model.Repository{OwnerID: 20, OwnerName: "user20", LowerName: "aafoo", Name: "aafoo"}
foo := &repo_model.Repository{OwnerID: 2, OwnerName: "User2", LowerName: "foo", Name: "foo"}
require.NoError(t, db.Insert(t.Context(), afoo, aafoo, foo))
for _, tc := range []struct {
keyword string
orderBy db.SearchOrderBy
want []int64
}{
{"Foo", "", []int64{foo.ID, aafoo.ID, afoo.ID}},
{"Foo", db.SearchOrderByID, []int64{afoo.ID, aafoo.ID, foo.ID}},
{"user2/foo", "", []int64{foo.ID, afoo.ID, aafoo.ID}},
} {
repos, _, err := repo_model.SearchRepositoryByName(t.Context(), repo_model.SearchRepoOptions{Keyword: tc.keyword, OrderBy: tc.orderBy})
require.NoError(t, err)
assert.Equal(t, tc.want, repoIDs(repos), "%s %s", tc.keyword, tc.orderBy)
}
})
}
func testSearchRepositoryPublic(t *testing.T) {