Allows repo search to match against "owner/repo" pattern strings (#19754)
* Allows repo search to match against "owner/repo" pattern strings * Gofumpt * Adds test case for "owner/repo" style repo search * With "owner/repo" search terms, prioritise results which match the owner field * Fixes unquoted SQL string in repo search
This commit is contained in:
parent
ba7750d6e7
commit
876cad0064
2 changed files with 39 additions and 1 deletions
|
@ -459,6 +459,15 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
||||||
likes := builder.NewCond()
|
likes := builder.NewCond()
|
||||||
for _, v := range strings.Split(opts.Keyword, ",") {
|
for _, v := range strings.Split(opts.Keyword, ",") {
|
||||||
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)})
|
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)})
|
||||||
|
|
||||||
|
// If the string looks like "org/repo", match against that pattern too
|
||||||
|
if opts.TeamID == 0 && strings.Count(opts.Keyword, "/") == 1 {
|
||||||
|
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)}))
|
||||||
|
}
|
||||||
|
|
||||||
if opts.IncludeDescription {
|
if opts.IncludeDescription {
|
||||||
likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)})
|
likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)})
|
||||||
}
|
}
|
||||||
|
@ -549,6 +558,10 @@ func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c
|
||||||
|
|
||||||
if opts.PriorityOwnerID > 0 {
|
if opts.PriorityOwnerID > 0 {
|
||||||
opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = %d THEN 0 ELSE owner_id END, %s", opts.PriorityOwnerID, opts.OrderBy))
|
opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = %d THEN 0 ELSE owner_id END, %s", opts.PriorityOwnerID, opts.OrderBy))
|
||||||
|
} else if strings.Count(opts.Keyword, "/") == 1 {
|
||||||
|
// With "owner/repo" search times, prioritise results which match the owner field
|
||||||
|
orgName := strings.Split(opts.Keyword, "/")[0]
|
||||||
|
opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE '%s' THEN 0 ELSE 1 END, %s", orgName, opts.OrderBy))
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := db.GetEngine(ctx)
|
sess := db.GetEngine(ctx)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -261,6 +262,16 @@ func TestSearchRepository(t *testing.T) {
|
||||||
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue},
|
opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue},
|
||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "OwnerSlashRepoSearch",
|
||||||
|
opts: &SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0},
|
||||||
|
count: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OwnerSlashSearch",
|
||||||
|
opts: &SearchRepoOptions{Keyword: "user20/", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0},
|
||||||
|
count: 4,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
@ -285,7 +296,21 @@ func TestSearchRepository(t *testing.T) {
|
||||||
assert.NotEmpty(t, repo.Name)
|
assert.NotEmpty(t, repo.Name)
|
||||||
|
|
||||||
if len(testCase.opts.Keyword) > 0 {
|
if len(testCase.opts.Keyword) > 0 {
|
||||||
assert.Contains(t, repo.Name, testCase.opts.Keyword)
|
// Keyword match condition is different for search terms of form "owner/repo"
|
||||||
|
if strings.Count(testCase.opts.Keyword, "/") == 1 {
|
||||||
|
// May still match as a whole...
|
||||||
|
wholeMatch := strings.Contains(repo.Name, testCase.opts.Keyword)
|
||||||
|
|
||||||
|
pieces := strings.Split(testCase.opts.Keyword, "/")
|
||||||
|
ownerName := pieces[0]
|
||||||
|
repoName := pieces[1]
|
||||||
|
// ... or match in parts
|
||||||
|
splitMatch := strings.Contains(repo.OwnerName, ownerName) && strings.Contains(repo.Name, repoName)
|
||||||
|
|
||||||
|
assert.True(t, wholeMatch || splitMatch, "Keyword '%s' does not match repo '%s/%s'", testCase.opts.Keyword, repo.Owner.Name, repo.Name)
|
||||||
|
} else {
|
||||||
|
assert.Contains(t, repo.Name, testCase.opts.Keyword)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !testCase.opts.Private {
|
if !testCase.opts.Private {
|
||||||
|
|
Reference in a new issue