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.
Number issues and change requests for human references
e05f1c48d4ee55635cd7b77889f0545137198106 · Keenan Nemetz · 13d ago · parent 41d1fee9
Number issues and change requests for human references A ULID is an unreadable handle, and the 8-character prefix the list showed is mostly timestamp: this repository's 27 issues shared seven distinct six-character prefixes and two pairs already collided at eight. The part people actually type carried almost no information. Issues and change requests now share one gap-free sequence (#1, #42, ...) derived from the creation order in the metadata branch's history, the way one forge counts both in a single run. The number is not stored. It is the rank of the entity's creation commit, so nothing is written when an entity is created, a repository needs no backfill, the same history gives the same numbers on every clone, and `vole admin index rebuild` reproduces them from git. A ULID stays the identity: paths, front matter, `closes:` and audit entries are unchanged, and data/index.db only caches the mapping so a page render does not walk history. Every id-taking command accepts a number, `#42` links in a body through a kind-agnostic /r/<repo>/n/42 route, and --closes resolves an alias to the ULID before writing. Closes #45
forge/index/index.go +21−1
| @@ -96,6 +96,22 @@ CREATE INDEX IF NOT EXISTS issue_assignees_lookup ON issue_assignees(repo_id, as | |||
| 96 | 96 | db.Close() | |
| 97 | 97 | return nil, err | |
| 98 | 98 | } | |
| 99 | // entity_numbers is the `#42` alias. It is derived from history | ||
| 100 | // (metadata.LoadNumbers), never written back to the metadata branch, and the | ||
| 101 | // primary key on the number is what makes a duplicate impossible rather than | ||
| 102 | // merely unlikely. | ||
| 103 | if _, err := db.Exec(` | ||
| 104 | CREATE TABLE IF NOT EXISTS entity_numbers ( | ||
| 105 | repo_id TEXT NOT NULL, | ||
| 106 | number INTEGER NOT NULL, | ||
| 107 | kind TEXT NOT NULL, | ||
| 108 | id TEXT NOT NULL, | ||
| 109 | PRIMARY KEY (repo_id, number) | ||
| 110 | ); | ||
| 111 | CREATE INDEX IF NOT EXISTS entity_numbers_id ON entity_numbers(repo_id, id);`); err != nil { | ||
| 112 | db.Close() | ||
| 113 | return nil, err | ||
| 114 | } | ||
| 99 | 115 | if err := s.migrate(); err != nil { | |
| 100 | 116 | db.Close() | |
| 101 | 117 | return nil, err | |
| @@ -108,7 +124,7 @@ CREATE INDEX IF NOT EXISTS issue_assignees_lookup ON issue_assignees(repo_id, as | |||
| 108 | 124 | // leaves every repo with an empty issues_sha, which makes the next read rebuild | |
| 109 | 125 | // that repo from git. A stale row is worse than a rebuild, and a rebuild is the | |
| 110 | 126 | // operation the index already has to survive. | |
| 111 | const schemaVersion = 2 | ||
| 127 | const schemaVersion = 3 | ||
| 112 | 128 | ||
| 113 | 129 | func (s *Store) migrate() error { | |
| 114 | 130 | var v int | |
| @@ -122,6 +138,7 @@ func (s *Store) migrate() error { | |||
| 122 | 138 | DELETE FROM issues; | |
| 123 | 139 | DELETE FROM issue_labels; | |
| 124 | 140 | DELETE FROM issue_assignees; | |
| 141 | DELETE FROM entity_numbers; | ||
| 125 | 142 | UPDATE repos SET issues_sha = '';`); err != nil { | |
| 126 | 143 | return err | |
| 127 | 144 | } | |
| @@ -140,6 +157,9 @@ type IssueRow struct { | |||
| 140 | 157 | Labels []string | |
| 141 | 158 | Assignees []string | |
| 142 | 159 | Comments int | |
| 160 | // Number is the `#42` alias from entity_numbers, or 0 when the branch has not | ||
| 161 | // been indexed yet. It is a display alias; ID remains the identity. | ||
| 162 | Number int | ||
| 143 | 163 | } | |
| 144 | 164 | ||
| 145 | 165 | func (s *Store) Close() error { return s.db.Close() } | |