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.

issue.go 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. "os"
  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/token/unicodenorm"
  13. "github.com/blevesearch/bleve/analysis/tokenizer/unicode"
  14. )
  15. // issueIndexer (thread-safe) index for searching issues
  16. var issueIndexer bleve.Index
  17. // IssueIndexerData data stored in the issue indexer
  18. type IssueIndexerData struct {
  19. RepoID int64
  20. Title string
  21. Content string
  22. Comments []string
  23. }
  24. // IssueIndexerUpdate an update to the issue indexer
  25. type IssueIndexerUpdate struct {
  26. IssueID int64
  27. Data *IssueIndexerData
  28. }
  29. const issueIndexerAnalyzer = "issueIndexer"
  30. // InitIssueIndexer initialize issue indexer
  31. func InitIssueIndexer(populateIndexer func() error) {
  32. _, err := os.Stat(setting.Indexer.IssuePath)
  33. if err != nil {
  34. if os.IsNotExist(err) {
  35. if err = createIssueIndexer(); err != nil {
  36. log.Fatal(4, "CreateIssuesIndexer: %v", err)
  37. }
  38. if err = populateIndexer(); err != nil {
  39. log.Fatal(4, "PopulateIssuesIndex: %v", err)
  40. }
  41. } else {
  42. log.Fatal(4, "InitIssuesIndexer: %v", err)
  43. }
  44. } else {
  45. issueIndexer, err = bleve.Open(setting.Indexer.IssuePath)
  46. if err != nil {
  47. log.Error(4, "Unable to open issues indexer (%s)."+
  48. " If the error is due to incompatible versions, try deleting the indexer files;"+
  49. " gitea will recreate them with the appropriate version the next time it runs."+
  50. " Deleting the indexer files will not result in loss of data.",
  51. setting.Indexer.IssuePath)
  52. log.Fatal(4, "InitIssuesIndexer, open index: %v", err)
  53. }
  54. }
  55. }
  56. // createIssueIndexer create an issue indexer if one does not already exist
  57. func createIssueIndexer() error {
  58. mapping := bleve.NewIndexMapping()
  59. docMapping := bleve.NewDocumentMapping()
  60. docMapping.AddFieldMappingsAt("RepoID", bleve.NewNumericFieldMapping())
  61. textFieldMapping := bleve.NewTextFieldMapping()
  62. docMapping.AddFieldMappingsAt("Title", textFieldMapping)
  63. docMapping.AddFieldMappingsAt("Content", textFieldMapping)
  64. docMapping.AddFieldMappingsAt("Comments", textFieldMapping)
  65. const unicodeNormNFC = "unicodeNormNFC"
  66. if err := mapping.AddCustomTokenFilter(unicodeNormNFC, map[string]interface{}{
  67. "type": unicodenorm.Name,
  68. "form": unicodenorm.NFC,
  69. }); err != nil {
  70. return err
  71. } else if err = mapping.AddCustomAnalyzer(issueIndexerAnalyzer, map[string]interface{}{
  72. "type": custom.Name,
  73. "char_filters": []string{},
  74. "tokenizer": unicode.Name,
  75. "token_filters": []string{unicodeNormNFC, lowercase.Name},
  76. }); err != nil {
  77. return err
  78. }
  79. mapping.DefaultAnalyzer = issueIndexerAnalyzer
  80. mapping.AddDocumentMapping("issues", docMapping)
  81. var err error
  82. issueIndexer, err = bleve.New(setting.Indexer.IssuePath, mapping)
  83. return err
  84. }
  85. // UpdateIssue update the issue indexer
  86. func UpdateIssue(update IssueIndexerUpdate) error {
  87. return issueIndexer.Index(indexerID(update.IssueID), update.Data)
  88. }
  89. // BatchUpdateIssues perform a batch update of the issue indexer
  90. func BatchUpdateIssues(updates ...IssueIndexerUpdate) error {
  91. batch := issueIndexer.NewBatch()
  92. for _, update := range updates {
  93. err := batch.Index(indexerID(update.IssueID), update.Data)
  94. if err != nil {
  95. return err
  96. }
  97. }
  98. return issueIndexer.Batch(batch)
  99. }
  100. // SearchIssuesByKeyword searches for issues by given conditions.
  101. // Returns the matching issue IDs
  102. func SearchIssuesByKeyword(repoID int64, keyword string) ([]int64, error) {
  103. indexerQuery := bleve.NewConjunctionQuery(
  104. numericEqualityQuery(repoID, "RepoID"),
  105. bleve.NewDisjunctionQuery(
  106. newMatchPhraseQuery(keyword, "Title", issueIndexerAnalyzer),
  107. newMatchPhraseQuery(keyword, "Content", issueIndexerAnalyzer),
  108. newMatchPhraseQuery(keyword, "Comments", issueIndexerAnalyzer),
  109. ))
  110. search := bleve.NewSearchRequestOptions(indexerQuery, 2147483647, 0, false)
  111. result, err := issueIndexer.Search(search)
  112. if err != nil {
  113. return nil, err
  114. }
  115. issueIDs := make([]int64, len(result.Hits))
  116. for i, hit := range result.Hits {
  117. issueIDs[i], err = idOfIndexerID(hit.ID)
  118. if err != nil {
  119. return nil, err
  120. }
  121. }
  122. return issueIDs, nil
  123. }