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

repo.go 6.2 kB

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