]> source.dussan.org Git - gitea.git/commitdiff
Updated tokenizer to better matching when search for code snippets (#32261)
authorBruno Sofiato <bruno.sofiato@gmail.com>
Wed, 6 Nov 2024 20:51:20 +0000 (17:51 -0300)
committerGitHub <noreply@github.com>
Wed, 6 Nov 2024 20:51:20 +0000 (20:51 +0000)
This PR improves the accuracy of Gitea's code search.

Currently, Gitea does not consider statements such as
`onsole.log("hello")` as hits when the user searches for `log`. The
culprit is how both ES and Bleve are tokenizing the file contents (in
both cases, `console.log` is a whole token).

In ES' case, we changed the tokenizer to
[simple_pattern_split](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-simplepatternsplit-tokenizer.html#:~:text=The%20simple_pattern_split%20tokenizer%20uses%20a,the%20tokenization%20is%20generally%20faster.).
In such a case, tokens are words formed by digits and letters. In
Bleve's case, it employs a
[letter](https://blevesearch.com/docs/Tokenizers/) tokenizer.

Resolves #32220

---------

Signed-off-by: Bruno Sofiato <bruno.sofiato@gmail.com>
18 files changed:
modules/indexer/code/bleve/bleve.go
modules/indexer/code/elasticsearch/elasticsearch.go
modules/indexer/code/indexer_test.go
modules/indexer/internal/bleve/util.go
modules/indexer/internal/bleve/util_test.go
tests/gitea-repositories-meta/org42/search-by-path.git/description
tests/gitea-repositories-meta/org42/search-by-path.git/info/refs
tests/gitea-repositories-meta/org42/search-by-path.git/objects/info/commit-graph [deleted file]
tests/gitea-repositories-meta/org42/search-by-path.git/objects/info/packs
tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.bitmap [deleted file]
tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.idx [deleted file]
tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.pack [deleted file]
tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.rev [deleted file]
tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.bitmap [new file with mode: 0644]
tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.idx [new file with mode: 0644]
tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.pack [new file with mode: 0644]
tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.rev [new file with mode: 0644]
tests/gitea-repositories-meta/org42/search-by-path.git/packed-refs

index 90e5e62bcb4aafb3a47e90559e06fdb2b5bc6b8d..772317fa594ba00f1929eed33943477ac04fd830 100644 (file)
@@ -31,6 +31,7 @@ import (
        "github.com/blevesearch/bleve/v2/analysis/token/camelcase"
        "github.com/blevesearch/bleve/v2/analysis/token/lowercase"
        "github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
+       "github.com/blevesearch/bleve/v2/analysis/tokenizer/letter"
        "github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
        "github.com/blevesearch/bleve/v2/mapping"
        "github.com/blevesearch/bleve/v2/search/query"
@@ -69,7 +70,7 @@ const (
        filenameIndexerAnalyzer  = "filenameIndexerAnalyzer"
        filenameIndexerTokenizer = "filenameIndexerTokenizer"
        repoIndexerDocType       = "repoIndexerDocType"
-       repoIndexerLatestVersion = 7
+       repoIndexerLatestVersion = 8
 )
 
 // generateBleveIndexMapping generates a bleve index mapping for the repo indexer
@@ -105,7 +106,7 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
        } else if err := mapping.AddCustomAnalyzer(repoIndexerAnalyzer, map[string]any{
                "type":          analyzer_custom.Name,
                "char_filters":  []string{},
-               "tokenizer":     unicode.Name,
+               "tokenizer":     letter.Name,
                "token_filters": []string{unicodeNormalizeName, camelcase.Name, lowercase.Name},
        }); err != nil {
                return nil, err
index 669a1bafcc90883cfbc8cbd30732e171f354b9d0..1c4dd39eff0beb1960b695c13e066930df6b5ff1 100644 (file)
@@ -30,7 +30,7 @@ import (
 )
 
 const (
-       esRepoIndexerLatestVersion = 2
+       esRepoIndexerLatestVersion = 3
        // multi-match-types, currently only 2 types are used
        // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
        esMultiMatchTypeBestFields   = "best_fields"
@@ -60,6 +60,10 @@ const (
                "settings": {
                "analysis": {
                        "analyzer": {
+                                       "content_analyzer": {
+                                               "tokenizer": "content_tokenizer",
+                                               "filter" : ["lowercase"]
+                                       },
                                "filename_path_analyzer": {
                                        "tokenizer": "path_tokenizer"
                                },
@@ -68,6 +72,10 @@ const (
                                }
                        },
                                "tokenizer": {
+                                       "content_tokenizer": {
+                                               "type": "simple_pattern_split",
+                                               "pattern": "[^a-zA-Z0-9]"
+                                       },
                                        "path_tokenizer": {
                                                "type": "path_hierarchy",
                                                "delimiter": "/"
@@ -104,7 +112,8 @@ const (
                                "content": {
                                        "type": "text",
                                        "term_vector": "with_positions_offsets",
-                                       "index": true
+                                       "index": true,
+                                       "analyzer": "content_analyzer"
                                },
                                "commit_id": {
                                        "type": "keyword",
index 5b33528dcde04e3a8b20aa8a684413f563342ff8..020ccc72f8175458fd896e31cbc40c2e6b59b934 100644 (file)
@@ -181,6 +181,55 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
                                        },
                                },
                        },
+                       // Search for matches on the contents of files regardless of case.
+                       {
+                               RepoIDs: nil,
+                               Keyword: "dESCRIPTION",
+                               Langs:   1,
+                               Results: []codeSearchResult{
+                                       {
+                                               Filename: "README.md",
+                                               Content:  "# repo1\n\nDescription for repo1",
+                                       },
+                               },
+                       },
+                       // Search for an exact match on the filename within the repo '62' (case insenstive).
+                       // This scenario yields a single result (the file avocado.md on the repo '62')
+                       {
+                               RepoIDs: []int64{62},
+                               Keyword: "AVOCADO.MD",
+                               Langs:   1,
+                               Results: []codeSearchResult{
+                                       {
+                                               Filename: "avocado.md",
+                                               Content:  "# repo1\n\npineaple pie of cucumber juice",
+                                       },
+                               },
+                       },
+                       // Search for matches on the contents of files when the criteria is a expression.
+                       {
+                               RepoIDs: []int64{62},
+                               Keyword: "console.log",
+                               Langs:   1,
+                               Results: []codeSearchResult{
+                                       {
+                                               Filename: "example-file.js",
+                                               Content:  "console.log(\"Hello, World!\")",
+                                       },
+                               },
+                       },
+                       // Search for matches on the contents of files when the criteria is part of a expression.
+                       {
+                               RepoIDs: []int64{62},
+                               Keyword: "log",
+                               Langs:   1,
+                               Results: []codeSearchResult{
+                                       {
+                                               Filename: "example-file.js",
+                                               Content:  "console.log(\"Hello, World!\")",
+                                       },
+                               },
+                       },
                }
 
                for _, kw := range keywords {
index b426b39bc20dbd351f7455374039f78480603cfb..a0c3dc4ad459e6f229da7d0d44fa70dc3ba64099 100644 (file)
@@ -6,12 +6,13 @@ package bleve
 import (
        "errors"
        "os"
+       "unicode"
 
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/util"
 
        "github.com/blevesearch/bleve/v2"
-       "github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
+       unicode_tokenizer "github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
        "github.com/blevesearch/bleve/v2/index/upsidedown"
        "github.com/ethantkoenig/rupture"
 )
@@ -57,7 +58,7 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) {
 // may be different on two string and they still be considered equivalent.
 // Given a phrasse, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero.
 func GuessFuzzinessByKeyword(s string) int {
-       tokenizer := unicode.NewUnicodeTokenizer()
+       tokenizer := unicode_tokenizer.NewUnicodeTokenizer()
        tokens := tokenizer.Tokenize([]byte(s))
 
        if len(tokens) > 0 {
@@ -77,8 +78,10 @@ func guessFuzzinessByKeyword(s string) int {
        // according to https://github.com/blevesearch/bleve/issues/1563, the supported max fuzziness is 2
        // magic number 4 was chosen to determine the levenshtein distance per each character of a keyword
        // BUT, when using CJK (eg: `갃갃갃` `啊啊啊`), it mismatches a lot.
+       // Likewise, queries whose terms contains characters that are *not* letters should not use fuzziness
+
        for _, r := range s {
-               if r >= 128 {
+               if r >= 128 || !unicode.IsLetter(r) {
                        return 0
                }
        }
index ae0b12c08d42baaf920af04edfcfcb5976a4392b..8f7844464e7397ced5796c28075aceb971d15403 100644 (file)
@@ -35,6 +35,14 @@ func TestBleveGuessFuzzinessByKeyword(t *testing.T) {
                        Input:     "갃갃갃",
                        Fuzziness: 0,
                },
+               {
+                       Input:     "repo1",
+                       Fuzziness: 0,
+               },
+               {
+                       Input:     "avocado.md",
+                       Fuzziness: 0,
+               },
        }
 
        for _, scenario := range scenarios {
index 382e2d7f1012824058ff73d3ccacfcb2f80298d2..ffc40a9c48a6862a9132090634dd261bf603f6a6 100644 (file)
@@ -4,5 +4,6 @@ This repository will be used to test code search. The snippet below shows its di
 ├── avocado.md
 ├── cucumber.md
 ├── ham.md
-└── potato
-    └── ham.md
+├── potato
+|   └── ham.md
+└── example-file.js
\ No newline at end of file
index 6b948c96a8351e4e734a483db8d6d62c52730d7a..4adf83dda318956735f80e913d29ba68da8821fd 100644 (file)
@@ -3,7 +3,7 @@
 65f1bf27bc3bf70f64657658635e66094edbcb4d       refs/heads/develop
 65f1bf27bc3bf70f64657658635e66094edbcb4d       refs/heads/feature/1
 78fb907e3a3309eae4fe8fef030874cebbf1cd5e       refs/heads/home-md-img-check
-3731fe53b763859aaf83e703ee731f6b9447ff1e       refs/heads/master
+9f894b61946fd2f7b8b9d8e370e4d62f915522f5       refs/heads/master
 62fb502a7172d4453f0322a2cc85bddffa57f07a       refs/heads/pr-to-update
 4649299398e4d39a5c09eb4f534df6f1e1eb87cc       refs/heads/sub-home-md-img-check
 3fa2f829675543ecfc16b2891aebe8bf0608a8f4       refs/notes/commits
diff --git a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/info/commit-graph b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/info/commit-graph
deleted file mode 100644 (file)
index b38715b..0000000
Binary files a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/info/commit-graph and /dev/null differ
index b2af8c8378a448a0f3618e126c4d2940fde51ec5..9774923d2ea5476df1cc6de2379e951ebf336020 100644 (file)
@@ -1,2 +1,2 @@
-P pack-393dc29256bc27cb2ec73898507df710be7a3cf5.pack
+P pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.pack
 
diff --git a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.bitmap b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.bitmap
deleted file mode 100644 (file)
index 1fdef22..0000000
Binary files a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.bitmap and /dev/null differ
diff --git a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.idx b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.idx
deleted file mode 100644 (file)
index 0d930e7..0000000
Binary files a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.idx and /dev/null differ
diff --git a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.pack b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.pack
deleted file mode 100644 (file)
index f1aac1e..0000000
Binary files a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.pack and /dev/null differ
diff --git a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.rev b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.rev
deleted file mode 100644 (file)
index 869860b..0000000
Binary files a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-393dc29256bc27cb2ec73898507df710be7a3cf5.rev and /dev/null differ
diff --git a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.bitmap b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.bitmap
new file mode 100644 (file)
index 0000000..39c02c2
Binary files /dev/null and b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.bitmap differ
diff --git a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.idx b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.idx
new file mode 100644 (file)
index 0000000..38d0e6b
Binary files /dev/null and b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.idx differ
diff --git a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.pack b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.pack
new file mode 100644 (file)
index 0000000..06c0a89
Binary files /dev/null and b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.pack differ
diff --git a/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.rev b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.rev
new file mode 100644 (file)
index 0000000..b06ecca
Binary files /dev/null and b/tests/gitea-repositories-meta/org42/search-by-path.git/objects/pack/pack-a7bef76cf6e2b46bc816936ab69306fb10aea571.rev differ
index 70e69af1e1018012999c4bdc377f9bc792601d4c..2334e3da4892f5e49d8ec88f9a7f6db435b5276c 100644 (file)
@@ -4,7 +4,7 @@
 65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/develop
 65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/feature/1
 78fb907e3a3309eae4fe8fef030874cebbf1cd5e refs/heads/home-md-img-check
-3731fe53b763859aaf83e703ee731f6b9447ff1e refs/heads/master
+9f894b61946fd2f7b8b9d8e370e4d62f915522f5 refs/heads/master
 62fb502a7172d4453f0322a2cc85bddffa57f07a refs/heads/pr-to-update
 4649299398e4d39a5c09eb4f534df6f1e1eb87cc refs/heads/sub-home-md-img-check
 3fa2f829675543ecfc16b2891aebe8bf0608a8f4 refs/notes/commits