本站源代码
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

258 lines
7.0KB

  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package indexer
  5. import (
  6. "strings"
  7. "sync"
  8. "code.gitea.io/gitea/modules/log"
  9. "code.gitea.io/gitea/modules/setting"
  10. "github.com/blevesearch/bleve"
  11. "github.com/blevesearch/bleve/analysis/analyzer/custom"
  12. "github.com/blevesearch/bleve/analysis/token/lowercase"
  13. "github.com/blevesearch/bleve/analysis/tokenizer/unicode"
  14. "github.com/blevesearch/bleve/search/query"
  15. "github.com/ethantkoenig/rupture"
  16. )
  17. const (
  18. repoIndexerAnalyzer = "repoIndexerAnalyzer"
  19. repoIndexerDocType = "repoIndexerDocType"
  20. repoIndexerLatestVersion = 4
  21. )
  22. type bleveIndexerHolder struct {
  23. index bleve.Index
  24. mutex sync.RWMutex
  25. cond *sync.Cond
  26. }
  27. func newBleveIndexerHolder() *bleveIndexerHolder {
  28. b := &bleveIndexerHolder{}
  29. b.cond = sync.NewCond(b.mutex.RLocker())
  30. return b
  31. }
  32. func (r *bleveIndexerHolder) set(index bleve.Index) {
  33. r.mutex.Lock()
  34. defer r.mutex.Unlock()
  35. r.index = index
  36. r.cond.Broadcast()
  37. }
  38. func (r *bleveIndexerHolder) get() bleve.Index {
  39. r.mutex.RLock()
  40. defer r.mutex.RUnlock()
  41. if r.index == nil {
  42. r.cond.Wait()
  43. }
  44. return r.index
  45. }
  46. // repoIndexer (thread-safe) index for repository contents
  47. var indexerHolder = newBleveIndexerHolder()
  48. // RepoIndexerOp type of operation to perform on repo indexer
  49. type RepoIndexerOp int
  50. const (
  51. // RepoIndexerOpUpdate add/update a file's contents
  52. RepoIndexerOpUpdate = iota
  53. // RepoIndexerOpDelete delete a file
  54. RepoIndexerOpDelete
  55. )
  56. // RepoIndexerData data stored in the repo indexer
  57. type RepoIndexerData struct {
  58. RepoID int64
  59. Content string
  60. }
  61. // Type returns the document type, for bleve's mapping.Classifier interface.
  62. func (d *RepoIndexerData) Type() string {
  63. return repoIndexerDocType
  64. }
  65. // RepoIndexerUpdate an update to the repo indexer
  66. type RepoIndexerUpdate struct {
  67. Filepath string
  68. Op RepoIndexerOp
  69. Data *RepoIndexerData
  70. }
  71. // AddToFlushingBatch adds the update to the given flushing batch.
  72. func (update RepoIndexerUpdate) AddToFlushingBatch(batch rupture.FlushingBatch) error {
  73. id := filenameIndexerID(update.Data.RepoID, update.Filepath)
  74. switch update.Op {
  75. case RepoIndexerOpUpdate:
  76. return batch.Index(id, update.Data)
  77. case RepoIndexerOpDelete:
  78. return batch.Delete(id)
  79. default:
  80. log.Error("Unrecognized repo indexer op: %d", update.Op)
  81. }
  82. return nil
  83. }
  84. // InitRepoIndexer initialize repo indexer
  85. func InitRepoIndexer(populateIndexer func() error) {
  86. indexer, err := openIndexer(setting.Indexer.RepoPath, repoIndexerLatestVersion)
  87. if err != nil {
  88. log.Fatal("InitRepoIndexer: %v", err)
  89. }
  90. if indexer != nil {
  91. indexerHolder.set(indexer)
  92. return
  93. }
  94. if err = createRepoIndexer(setting.Indexer.RepoPath, repoIndexerLatestVersion); err != nil {
  95. log.Fatal("CreateRepoIndexer: %v", err)
  96. }
  97. if err = populateIndexer(); err != nil {
  98. log.Fatal("PopulateRepoIndex: %v", err)
  99. }
  100. }
  101. // createRepoIndexer create a repo indexer if one does not already exist
  102. func createRepoIndexer(path string, latestVersion int) error {
  103. docMapping := bleve.NewDocumentMapping()
  104. numericFieldMapping := bleve.NewNumericFieldMapping()
  105. numericFieldMapping.IncludeInAll = false
  106. docMapping.AddFieldMappingsAt("RepoID", numericFieldMapping)
  107. textFieldMapping := bleve.NewTextFieldMapping()
  108. textFieldMapping.IncludeInAll = false
  109. docMapping.AddFieldMappingsAt("Content", textFieldMapping)
  110. mapping := bleve.NewIndexMapping()
  111. if err := addUnicodeNormalizeTokenFilter(mapping); err != nil {
  112. return err
  113. } else if err := mapping.AddCustomAnalyzer(repoIndexerAnalyzer, map[string]interface{}{
  114. "type": custom.Name,
  115. "char_filters": []string{},
  116. "tokenizer": unicode.Name,
  117. "token_filters": []string{unicodeNormalizeName, lowercase.Name},
  118. }); err != nil {
  119. return err
  120. }
  121. mapping.DefaultAnalyzer = repoIndexerAnalyzer
  122. mapping.AddDocumentMapping(repoIndexerDocType, docMapping)
  123. mapping.AddDocumentMapping("_all", bleve.NewDocumentDisabledMapping())
  124. indexer, err := bleve.New(path, mapping)
  125. if err != nil {
  126. return err
  127. }
  128. indexerHolder.set(indexer)
  129. return rupture.WriteIndexMetadata(path, &rupture.IndexMetadata{
  130. Version: latestVersion,
  131. })
  132. }
  133. func filenameIndexerID(repoID int64, filename string) string {
  134. return indexerID(repoID) + "_" + filename
  135. }
  136. func filenameOfIndexerID(indexerID string) string {
  137. index := strings.IndexByte(indexerID, '_')
  138. if index == -1 {
  139. log.Error("Unexpected ID in repo indexer: %s", indexerID)
  140. }
  141. return indexerID[index+1:]
  142. }
  143. // RepoIndexerBatch batch to add updates to
  144. func RepoIndexerBatch() rupture.FlushingBatch {
  145. return rupture.NewFlushingBatch(indexerHolder.get(), maxBatchSize)
  146. }
  147. // DeleteRepoFromIndexer delete all of a repo's files from indexer
  148. func DeleteRepoFromIndexer(repoID int64) error {
  149. query := numericEqualityQuery(repoID, "RepoID")
  150. searchRequest := bleve.NewSearchRequestOptions(query, 2147483647, 0, false)
  151. result, err := indexerHolder.get().Search(searchRequest)
  152. if err != nil {
  153. return err
  154. }
  155. batch := RepoIndexerBatch()
  156. for _, hit := range result.Hits {
  157. if err = batch.Delete(hit.ID); err != nil {
  158. return err
  159. }
  160. }
  161. return batch.Flush()
  162. }
  163. // RepoSearchResult result of performing a search in a repo
  164. type RepoSearchResult struct {
  165. RepoID int64
  166. StartIndex int
  167. EndIndex int
  168. Filename string
  169. Content string
  170. }
  171. // SearchRepoByKeyword searches for files in the specified repo.
  172. // Returns the matching file-paths
  173. func SearchRepoByKeyword(repoIDs []int64, keyword string, page, pageSize int) (int64, []*RepoSearchResult, error) {
  174. phraseQuery := bleve.NewMatchPhraseQuery(keyword)
  175. phraseQuery.FieldVal = "Content"
  176. phraseQuery.Analyzer = repoIndexerAnalyzer
  177. var indexerQuery query.Query
  178. if len(repoIDs) > 0 {
  179. var repoQueries = make([]query.Query, 0, len(repoIDs))
  180. for _, repoID := range repoIDs {
  181. repoQueries = append(repoQueries, numericEqualityQuery(repoID, "RepoID"))
  182. }
  183. indexerQuery = bleve.NewConjunctionQuery(
  184. bleve.NewDisjunctionQuery(repoQueries...),
  185. phraseQuery,
  186. )
  187. } else {
  188. indexerQuery = phraseQuery
  189. }
  190. from := (page - 1) * pageSize
  191. searchRequest := bleve.NewSearchRequestOptions(indexerQuery, pageSize, from, false)
  192. searchRequest.Fields = []string{"Content", "RepoID"}
  193. searchRequest.IncludeLocations = true
  194. result, err := indexerHolder.get().Search(searchRequest)
  195. if err != nil {
  196. return 0, nil, err
  197. }
  198. searchResults := make([]*RepoSearchResult, len(result.Hits))
  199. for i, hit := range result.Hits {
  200. var startIndex, endIndex int = -1, -1
  201. for _, locations := range hit.Locations["Content"] {
  202. location := locations[0]
  203. locationStart := int(location.Start)
  204. locationEnd := int(location.End)
  205. if startIndex < 0 || locationStart < startIndex {
  206. startIndex = locationStart
  207. }
  208. if endIndex < 0 || locationEnd > endIndex {
  209. endIndex = locationEnd
  210. }
  211. }
  212. searchResults[i] = &RepoSearchResult{
  213. RepoID: int64(hit.Fields["RepoID"].(float64)),
  214. StartIndex: startIndex,
  215. EndIndex: endIndex,
  216. Filename: filenameOfIndexerID(hit.ID),
  217. Content: hit.Fields["Content"].(string),
  218. }
  219. }
  220. return int64(result.Total), searchResults, nil
  221. }
上海开阖软件有限公司 沪ICP备12045867号-1