diff options
author | Vincent Tiu <46623413+Invincibot@users.noreply.github.com> | 2020-11-17 21:35:31 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-17 21:35:31 +0800 |
commit | 466b5183c371b4ac7f905e33c4f8f36ddb7db9ff (patch) | |
tree | 1b477423060292af296b32971cda1ca0045e0c0d /cache | |
parent | 902997f53771babb2f9ea1bb6288c2ec295c4c9e (diff) | |
parent | ee3841155e5c58b85408f29a8d44630da5de63f7 (diff) | |
download | git-bug-466b5183c371b4ac7f905e33c4f8f36ddb7db9ff.tar.gz |
Merge pull request #446 from MichaelMure/bug-fts
bug full text search
Diffstat (limited to 'cache')
-rw-r--r-- | cache/repo_cache.go | 19 | ||||
-rw-r--r-- | cache/repo_cache_bug.go | 98 |
2 files changed, 115 insertions, 2 deletions
diff --git a/cache/repo_cache.go b/cache/repo_cache.go index 4fc88015..8bce3d8c 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -15,6 +15,7 @@ import ( "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/process" + "github.com/blevesearch/bleve" ) // 1: original format @@ -56,6 +57,8 @@ type RepoCache struct { muBug sync.RWMutex // excerpt of bugs data for all bugs bugExcerpts map[entity.Id]*BugExcerpt + // searchable cache of all bugs + searchCache bleve.Index // bug loaded in memory bugs map[entity.Id]*BugCache // loadedBugs is an LRU cache that records which bugs the cache has loaded in @@ -116,6 +119,7 @@ func (c *RepoCache) load() error { if err != nil { return err } + return c.loadIdentityCache() } @@ -166,6 +170,11 @@ func (c *RepoCache) Close() error { c.bugs = make(map[entity.Id]*BugCache) c.bugExcerpts = nil + if c.searchCache != nil { + c.searchCache.Close() + c.searchCache = nil + } + lockPath := repoLockFilePath(c.repo) return os.Remove(lockPath) } @@ -198,6 +207,11 @@ func (c *RepoCache) buildCache() error { allBugs := bug.ReadAllLocal(c.repo) + err := c.createBleveIndex() + if err != nil { + return fmt.Errorf("Unable to create search cache. Error: %v", err) + } + for b := range allBugs { if b.Err != nil { return b.Err @@ -205,9 +219,14 @@ func (c *RepoCache) buildCache() error { snap := b.Bug.Compile() c.bugExcerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, &snap) + + if err := c.addBugToSearchIndex(&snap); err != nil { + return err + } } _, _ = fmt.Fprintln(os.Stderr, "Done.") + return nil } diff --git a/cache/repo_cache_bug.go b/cache/repo_cache_bug.go index 0c583e0d..d57e1bce 100644 --- a/cache/repo_cache_bug.go +++ b/cache/repo_cache_bug.go @@ -8,15 +8,20 @@ import ( "os" "path" "sort" + "strings" "time" "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/query" "github.com/MichaelMure/git-bug/repository" + "github.com/blevesearch/bleve" ) -const bugCacheFile = "bug-cache" +const ( + bugCacheFile = "bug-cache" + searchCacheDir = "search-cache" +) var errBugNotInCache = errors.New("bug missing from cache") @@ -24,6 +29,10 @@ func bugCacheFilePath(repo repository.Repo) string { return path.Join(repo.GetPath(), "git-bug", bugCacheFile) } +func searchCacheDirPath(repo repository.Repo) string { + return path.Join(repo.GetPath(), "git-bug", searchCacheDir) +} + // bugUpdated is a callback to trigger when the excerpt of a bug changed, // that is each time a bug is updated func (c *RepoCache) bugUpdated(id entity.Id) error { @@ -43,6 +52,10 @@ func (c *RepoCache) bugUpdated(id entity.Id) error { c.bugExcerpts[id] = NewBugExcerpt(b.bug, b.Snapshot()) c.muBug.Unlock() + if err := c.addBugToSearchIndex(b.Snapshot()); err != nil { + return err + } + // we only need to write the bug cache return c.writeBugCache() } @@ -74,6 +87,42 @@ func (c *RepoCache) loadBugCache() error { } c.bugExcerpts = aux.Excerpts + + blevePath := searchCacheDirPath(c.repo) + searchCache, err := bleve.Open(blevePath) + if err != nil { + return fmt.Errorf("Unable to open search cache. Error: %v", err) + } + c.searchCache = searchCache + + count, err := c.searchCache.DocCount() + if err != nil { + return err + } + if count != uint64(len(c.bugExcerpts)) { + return fmt.Errorf("count mismatch between bleve and bug excerpts") + } + + return nil +} + +func (c *RepoCache) createBleveIndex() error { + blevePath := searchCacheDirPath(c.repo) + + _ = os.RemoveAll(blevePath) + + mapping := bleve.NewIndexMapping() + mapping.DefaultAnalyzer = "en" + + dir := searchCacheDirPath(c.repo) + + bleveIndex, err := bleve.New(dir, mapping) + if err != nil { + return err + } + + c.searchCache = bleveIndex + return nil } @@ -255,8 +304,34 @@ func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id { matcher := compileMatcher(q.Filters) var filtered []*BugExcerpt + var foundBySearch map[entity.Id]*BugExcerpt - for _, excerpt := range c.bugExcerpts { + if q.Search != nil { + foundBySearch = map[entity.Id]*BugExcerpt{} + + terms := make([]string, len(q.Search)) + copy(terms, q.Search) + for i, search := range q.Search { + if strings.Contains(search, " ") { + terms[i] = fmt.Sprintf("\"%s\"", search) + } + } + + bleveQuery := bleve.NewQueryStringQuery(strings.Join(terms, " ")) + bleveSearch := bleve.NewSearchRequest(bleveQuery) + searchResults, err := c.searchCache.Search(bleveSearch) + if err != nil { + panic("bleve search failed") + } + + for _, hit := range searchResults.Hits { + foundBySearch[entity.Id(hit.ID)] = c.bugExcerpts[entity.Id(hit.ID)] + } + } else { + foundBySearch = c.bugExcerpts + } + + for _, excerpt := range foundBySearch { if matcher.Match(excerpt, c) { filtered = append(filtered, excerpt) } @@ -423,3 +498,22 @@ func (c *RepoCache) RemoveBug(prefix string) error { return c.writeBugCache() } + +func (c *RepoCache) addBugToSearchIndex(snap *bug.Snapshot) error { + searchableBug := struct { + Text []string + }{} + + for _, comment := range snap.Comments { + searchableBug.Text = append(searchableBug.Text, comment.Message) + } + + searchableBug.Text = append(searchableBug.Text, snap.Title) + + err := c.searchCache.Index(snap.Id().String(), searchableBug) + if err != nil { + return err + } + + return nil +} |