You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo.go 6.4KB

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