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 state changes for the activity feed
4d938fe894d56ff29b87fa0f2ebec50a744f2993 · Keenan Nemetz · 10d ago · parent e2a58f3a
Index state changes for the activity feed The activity feed found closed, reopened and merged events by reading the last 200 metadata commit subjects on every render: one git process per repository, so the instance home with N repositories ran N of them. It was the last derived read still going to git. - A `state_changes` table is fed by the same refresh: a delta reads the commits in its range, a rebuild reads the whole metadata branch. Rows are keyed by the commit, so a retried or overlapping refresh cannot duplicate an event, and an ordinal keeps two changes in one second in branch order (commit times are seconds; a close and a reopen often share one). - `Store.StateChanges` returns the events newest first; the feed reads it and keeps the metadata log as the fallback for a server without an index or a failed read. - `git.Repo.Log` and `CommitsBetween` treat a limit of zero as no limit, which the rebuild needs, and `metadata.ParseStateSubject` is exported so the index derives the events without a second copy of the format. The activity page now starts no metadata log; the instance home and the instance activity page lose one process per visible repository. On the load harness with three repositories: activity 496 req/s (p50 7 ms), instance activity 230 req/s (p50 17 ms), home 202 req/s (p50 20 ms). Schema version 7 rebuilds each repository on its first read after upgrade.
forge/index/index.go +22−3
| @@ -179,7 +179,25 @@ CREATE TABLE IF NOT EXISTS artifacts ( | |||
| 179 | 179 | oid TEXT NOT NULL, | |
| 180 | 180 | size INTEGER NOT NULL DEFAULT 0, | |
| 181 | 181 | PRIMARY KEY (repo_id, tag, name) | |
| 182 | );`); err != nil { | ||
| 182 | ); | ||
| 183 | |||
| 184 | -- state_changes is the closed/reopened/merged history the activity feed shows. | ||
| 185 | -- It is derived from the metadata commit subjects, and keyed by the commit so a | ||
| 186 | -- refresh that sees the same range twice cannot duplicate an event. | ||
| 187 | CREATE TABLE IF NOT EXISTS state_changes ( | ||
| 188 | repo_id TEXT NOT NULL, | ||
| 189 | sha TEXT NOT NULL, | ||
| 190 | kind TEXT NOT NULL, | ||
| 191 | id TEXT NOT NULL, | ||
| 192 | state TEXT NOT NULL, | ||
| 193 | actor TEXT NOT NULL, | ||
| 194 | at INTEGER NOT NULL, | ||
| 195 | -- ordinal is the order the commits entered the branch. Commit times are | ||
| 196 | -- seconds, so two changes in one second need this to keep their order. | ||
| 197 | ordinal INTEGER NOT NULL DEFAULT 0, | ||
| 198 | PRIMARY KEY (repo_id, sha) | ||
| 199 | ); | ||
| 200 | CREATE INDEX IF NOT EXISTS state_changes_recent ON state_changes(repo_id, ordinal DESC);`); err != nil { | ||
| 183 | 201 | db.Close() | |
| 184 | 202 | return nil, err | |
| 185 | 203 | } | |
| @@ -215,7 +233,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5( | |||
| 215 | 233 | // and leaves every repo with an empty issues_sha, which makes the next read | |
| 216 | 234 | // rebuild that repo from git. A stale row is worse than a rebuild, and a rebuild is the | |
| 217 | 235 | // operation the index already has to survive. | |
| 218 | const schemaVersion = 6 | ||
| 236 | const schemaVersion = 7 | ||
| 219 | 237 | ||
| 220 | 238 | func (s *Store) migrate() error { | |
| 221 | 239 | var v int | |
| @@ -235,6 +253,7 @@ DELETE FROM request_closes; | |||
| 235 | 253 | DELETE FROM request_approvals; | |
| 236 | 254 | DELETE FROM comments; | |
| 237 | 255 | DELETE FROM artifacts; | |
| 256 | DELETE FROM state_changes; | ||
| 238 | 257 | DELETE FROM search; | |
| 239 | 258 | UPDATE repos SET issues_sha = '';`); err != nil { | |
| 240 | 259 | return err | |
| @@ -280,7 +299,7 @@ func (s *Store) Rebuild(st *repo.Store) (int, error) { | |||
| 280 | 299 | // longer exists must not survive it. | |
| 281 | 300 | for _, table := range []string{ | |
| 282 | 301 | "issues", "issue_labels", "issue_assignees", "requests", "request_closes", | |
| 283 | "request_approvals", "comments", "artifacts", "search", "entity_numbers", | ||
| 302 | "request_approvals", "comments", "artifacts", "state_changes", "search", "entity_numbers", | ||
| 284 | 303 | } { | |
| 285 | 304 | if _, err := tx.Exec(`DELETE FROM ` + table); err != nil { | |
| 286 | 305 | return 0, err | |