diff --git a/models/issue_indexer.go b/models/issue_indexer.go index b58c9dc2d15d1..a909d3aef32b8 100644 --- a/models/issue_indexer.go +++ b/models/issue_indexer.go @@ -6,6 +6,7 @@ package models import ( "fmt" + "strconv" "code.gitea.io/gitea/modules/indexer" "code.gitea.io/gitea/modules/log" @@ -88,10 +89,11 @@ func (issue *Issue) update() indexer.IssueIndexerUpdate { return indexer.IssueIndexerUpdate{ IssueID: issue.ID, Data: &indexer.IssueIndexerData{ - RepoID: issue.RepoID, - Title: issue.Title, - Content: issue.Content, - Comments: comments, + RepoID: issue.RepoID, + Title: issue.Title, + Content: issue.Content, + Comments: comments, + IssueIndex: strconv.FormatInt(issue.Index, 10), }, } } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 95b8e0774f0a6..54b540d231d5e 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -136,6 +136,8 @@ var migrations = []Migration{ NewMigration("add tags to releases and sync existing repositories", releaseAddColumnIsTagAndSyncTags), // v43 -> v44 NewMigration("fix protected branch can push value to false", fixProtectedBranchCanPushValue), + // v44 -> v45 + NewMigration("add index field to bleve issue index", regenerateIssueIndexer), } // Migrate database to current version diff --git a/models/migrations/v44.go b/models/migrations/v44.go new file mode 100644 index 0000000000000..71dab994be434 --- /dev/null +++ b/models/migrations/v44.go @@ -0,0 +1,24 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "os" + + "code.gitea.io/gitea/modules/setting" + "github.com/go-xorm/xorm" +) + +func regenerateIssueIndexer(x *xorm.Engine) error { + _, err := os.Stat(setting.Indexer.IssuePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + return os.RemoveAll(setting.Indexer.IssuePath) +} diff --git a/modules/indexer/indexer.go b/modules/indexer/indexer.go index d5bdd51f9c5fa..3c5c4de2db812 100644 --- a/modules/indexer/indexer.go +++ b/modules/indexer/indexer.go @@ -44,6 +44,12 @@ func newMatchPhraseQuery(matchPhrase, field, analyzer string) *query.MatchPhrase return q } +func newPrefixQuery(matchPhrase, field string) *query.PrefixQuery { + q := bleve.NewPrefixQuery(matchPhrase) + q.FieldVal = field + return q +} + const unicodeNormalizeName = "unicodeNormalize" func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error { diff --git a/modules/indexer/issue.go b/modules/indexer/issue.go index 62a18e2b3baad..bfe81dfc9b5d8 100644 --- a/modules/indexer/issue.go +++ b/modules/indexer/issue.go @@ -6,6 +6,7 @@ package indexer import ( "os" + "strconv" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -22,10 +23,11 @@ var issueIndexer bleve.Index // IssueIndexerData data stored in the issue indexer type IssueIndexerData struct { - RepoID int64 - Title string - Content string - Comments []string + RepoID int64 + Title string + Content string + Comments []string + IssueIndex string } // IssueIndexerUpdate an update to the issue indexer @@ -76,6 +78,7 @@ func createIssueIndexer() error { docMapping.AddFieldMappingsAt("Title", textFieldMapping) docMapping.AddFieldMappingsAt("Content", textFieldMapping) docMapping.AddFieldMappingsAt("Comments", textFieldMapping) + docMapping.AddFieldMappingsAt("IssueIndex", textFieldMapping) if err := addUnicodeNormalizeTokenFilter(mapping); err != nil { return err @@ -104,6 +107,23 @@ func IssueIndexerBatch() *Batch { } } +func searchIssues(req *bleve.SearchRequest) ([]int64, error) { + result, err := issueIndexer.Search(req) + if err != nil { + return nil, err + } + + issueIDs := make([]int64, len(result.Hits)) + for i, hit := range result.Hits { + issueIDs[i], err = idOfIndexerID(hit.ID) + if err != nil { + return nil, err + } + } + + return issueIDs, nil +} + // SearchIssuesByKeyword searches for issues by given conditions. // Returns the matching issue IDs func SearchIssuesByKeyword(repoID int64, keyword string) ([]int64, error) { @@ -116,17 +136,16 @@ func SearchIssuesByKeyword(repoID int64, keyword string) ([]int64, error) { )) search := bleve.NewSearchRequestOptions(indexerQuery, 2147483647, 0, false) - result, err := issueIndexer.Search(search) - if err != nil { - return nil, err - } + return searchIssues(search) +} - issueIDs := make([]int64, len(result.Hits)) - for i, hit := range result.Hits { - issueIDs[i], err = idOfIndexerID(hit.ID) - if err != nil { - return nil, err - } - } - return issueIDs, nil +// SearchIssuesByIndex searches for issues by given conditions. +// Returns the matching issue IDs +func SearchIssuesByIndex(repoID, issueIndex int64) ([]int64, error) { + indexerQuery := bleve.NewConjunctionQuery( + numericEqualityQuery(repoID, "RepoID"), + newPrefixQuery(strconv.FormatInt(issueIndex, 10), "IssueIndex")) + search := bleve.NewSearchRequestOptions(indexerQuery, 2147483647, 0, false) + + return searchIssues(search) } diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 9f51022f35492..c745dc02987cd 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/indexer" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) @@ -28,21 +29,42 @@ func ListIssues(ctx *context.APIContext) { isClosed = util.OptionalBoolFalse } - issues, err := models.Issues(&models.IssuesOptions{ - RepoID: ctx.Repo.Repository.ID, - Page: ctx.QueryInt("page"), - PageSize: setting.UI.IssuePagingNum, - IsClosed: isClosed, - }) - if err != nil { - ctx.Error(500, "Issues", err) - return + issueIndex := ctx.QueryInt64("index") + var forceEmpty bool + var issueIDs []int64 + var err error + if issueIndex > 0 { + issueIDs, err = indexer.SearchIssuesByIndex(ctx.Repo.Repository.ID, issueIndex) + if err != nil { + ctx.Error(500, "SearchIssuesByIDPartialy", err) + return + } + if len(issueIDs) == 0 { + forceEmpty = true + } } - err = models.IssueList(issues).LoadAttributes() - if err != nil { - ctx.Error(500, "LoadAttributes", err) - return + var issues []*models.Issue + if forceEmpty { + issues = []*models.Issue{} + } else { + issues, err = models.Issues(&models.IssuesOptions{ + RepoID: ctx.Repo.Repository.ID, + Page: ctx.QueryInt("page"), + PageSize: setting.UI.IssuePagingNum, + IsClosed: isClosed, + IssueIDs: issueIDs, + }) + if err != nil { + ctx.Error(500, "Issues", err) + return + } + + err = models.IssueList(issues).LoadAttributes() + if err != nil { + ctx.Error(500, "LoadAttributes", err) + return + } } apiIssues := make([]*api.Issue, len(issues))