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.

Index the rest of the metadata branch

7f547ca3b8da218249120d3c910c73c9a1a1e52d · Keenan Nemetz · 10d ago · parent c9202f7f

Index the rest of the metadata branch

The index held issues and their numbers. Everything else on the branch —
change requests, their comments, approvals and closes, and release
artifacts — was read from git on every page that needed it, and the
repository header read the whole requests subtree just to print a count.

Add the tables and derive them from the same commit:

- requests, with the body preserved for search;
- request_closes and request_approvals, each in its own table so a
  backlink and a current-approval count are queries rather than a walk of
  every request;
- comments for both kinds of entity, with the anchor a review comment
  carries;
- artifacts, the release pointers, with the size from the pointer.

The refresh is still driven by the path delta between two commits: a
changed issue or request replaces its own rows, its comments and its sets,
and a deleted one removes them while keeping its `#42` number. Rebuild and
the first read go through IndexRepo, which is the definition the
incremental path has to reproduce; a rebuild now clears the derived tables
first so a deleted repository leaves nothing behind.

Schema version 4 drops the old rows on first open, which is the existing
recovery path: the index is disposable and built from the branch.

forge/index/comments.go +98

@@ -0,0 +1,98 @@
1 package index
2
3 // Comments live in one table for both kinds of entity: an issue comment and a
4 // change-request comment have the same file shape (author, created, body) and
5 // differ only in the entity they belong to, which is the (kind, parent) pair.
6 //
7 // The rows are here for search and for anything that pages through a thread
8 // without reading the branch. The ownership rule stays where it was: a comment
9 // belongs to its parent, so replacing the parent replaces its comments.
10
11 import (
12 "time"
13
14 "github.com/rodent-software/vole/metadata"
15 )
16
17 const (
18 commentKindIssue = "issue"
19 commentKindRequest = "request"
20 )
21
22 // replaceComments replaces one entity's comments. Deleting before inserting is
23 // what makes an edit or a deletion visible; the caller has already replaced the
24 // entity's own row in the same transaction.
25 func replaceComments(tx execer, repoID, kind, parent string, comments []metadata.Comment) error {
26 if _, err := tx.Exec(
27 `DELETE FROM comments WHERE repo_id=? AND kind=? AND parent=?`,
28 repoID, kind, parent); err != nil {
29 return err
30 }
31 for i := range comments {
32 c := &comments[i]
33 var path, side, blob string
34 var line int
35 if c.Anchor != nil {
36 path, side, line, blob = c.Anchor.Path, c.Anchor.Side, c.Anchor.Line, c.Anchor.Blob
37 }
38 deleted := 0
39 if c.Deleted {
40 deleted = 1
41 }
42 if _, err := tx.Exec(
43 `INSERT OR REPLACE INTO comments(repo_id, kind, parent, id, author, created, body,
44 reply_to, deleted, path, side, line, blob)
45 VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)`,
46 repoID, kind, parent, c.ID, c.Author, unixOrZero(c.Created), c.Body,
47 c.ReplyTo, deleted, path, side, line, blob); err != nil {
48 return err
49 }
50 }
51 return nil
52 }
53
54 // CommentRow is one indexed comment. It is the shape a search result and a
55 // thread renderer both need; a tombstone keeps its row so replies still have a
56 // parent to point at.
57 type CommentRow struct {
58 Kind string
59 Parent string
60 ID string
61 Author string
62 Created time.Time
63 Body string
64 ReplyTo string
65 Deleted bool
66 Path string
67 Side string
68 Line int
69 Blob string
70 }
71
72 // Comments returns one entity's comments in creation order. The detail pages
73 // read the entity from git today; this exists for the surfaces that only need
74 // the discussion, and for search.
75 func (s *Store) Comments(repoID, kind, parent string) ([]CommentRow, error) {
76 rows, err := s.db.Query(
77 `SELECT kind, parent, id, author, created, body, reply_to, deleted, path, side, line, blob
78 FROM comments WHERE repo_id=? AND kind=? AND parent=? ORDER BY created, id`,
79 repoID, kind, parent)
80 if err != nil {
81 return nil, err
82 }
83 defer rows.Close()
84 var out []CommentRow
85 for rows.Next() {
86 var c CommentRow
87 var created int64
88 var deleted int
89 if err := rows.Scan(&c.Kind, &c.Parent, &c.ID, &c.Author, &created, &c.Body,
90 &c.ReplyTo, &deleted, &c.Path, &c.Side, &c.Line, &c.Blob); err != nil {
91 return nil, err
92 }
93 c.Created = unixTime(created)
94 c.Deleted = deleted != 0
95 out = append(out, c)
96 }
97 return out, rows.Err()
98 }