Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Add a search table over the whole metadata branch

6762a520b7af700dfebd7f613e1a610a42631139 · Keenan Nemetz · 10d ago · parent b739915c

Add a search table over the whole metadata branch

Search is the other thing the derived rows are for: one document per
issue, change request, and comment, with the title, the body and the
author. FTS5 tokenizes them, so a query ranks by relevance instead of
scanning every body, and `#42` or a ULID prefix is answered from the
numbering table first so the entity someone named comes first.

The documents are maintained by the same refresh as the list rows: an
entity's text is replaced with the entity, a thread's comments are
replaced with the thread, and a deletion removes both. A comment row
carries the parent's title and id, so a result can say what it is on and
link to the fragment without a second read.

The store never decides visibility: Search takes the repository ids the
caller may show, and returns nothing outside them.

This needs SQLite's FTS5 module. The build tag that enables it in the
driver's bundled copy is added to the build commands in the next commit;
a build without it fails at index.Open with that reason.

forge/index/index.go +19−2

@@ -176,6 +176,22 @@ CREATE TABLE IF NOT EXISTS artifacts (
176 176 oid TEXT NOT NULL,
177 177 size INTEGER NOT NULL DEFAULT 0,
178 178 PRIMARY KEY (repo_id, tag, name)
179 );
180
181 -- search is the universal search surface: one row for every issue, change
182 -- request and comment, with the text FTS5 tokenizes. The first four columns are
183 -- UNINDEXED because they identify the result rather than match it; title and
184 -- body are what a query looks at. The build must include SQLite's FTS5 module
185 -- (see README.md), or Open fails here with that reason.
186 CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5(
187 repo_id UNINDEXED,
188 kind UNINDEXED,
189 entity_id UNINDEXED,
190 parent_id UNINDEXED,
191 title,
192 body,
193 author UNINDEXED,
194 tokenize = 'unicode61 remove_diacritics 2'
179 195 );`); err != nil {
180 196 db.Close()
181 197 return nil, err
@@ -192,7 +208,7 @@ CREATE TABLE IF NOT EXISTS artifacts (
192 208 // and leaves every repo with an empty issues_sha, which makes the next read
193 209 // rebuild that repo from git. A stale row is worse than a rebuild, and a rebuild is the
194 210 // operation the index already has to survive.
195 const schemaVersion = 4
211 const schemaVersion = 5
196 212
197 213 func (s *Store) migrate() error {
198 214 var v int
@@ -212,6 +228,7 @@ DELETE FROM request_closes;
212 228 DELETE FROM request_approvals;
213 229 DELETE FROM comments;
214 230 DELETE FROM artifacts;
231 DELETE FROM search;
215 232 UPDATE repos SET issues_sha = '';`); err != nil {
216 233 return err
217 234 }
@@ -256,7 +273,7 @@ func (s *Store) Rebuild(st *repo.Store) (int, error) {
256 273 // longer exists must not survive it.
257 274 for _, table := range []string{
258 275 "issues", "issue_labels", "issue_assignees", "requests", "request_closes",
259 "request_approvals", "comments", "artifacts", "entity_numbers",
276 "request_approvals", "comments", "artifacts", "search", "entity_numbers",
260 277 } {
261 278 if _, err := tx.Exec(`DELETE FROM ` + table); err != nil {
262 279 return 0, err