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
server/web/markdown.go +13−3
| @@ -23,7 +23,12 @@ import ( | |||
| 23 | 23 | var ( | |
| 24 | 24 | markdown = goldmark.New(goldmark.WithExtensions(extension.GFM, extension.Typographer)) | |
| 25 | 25 | sanitizer = bluemonday.UGCPolicy() | |
| 26 | xrefRE = regexp.MustCompile(`#([0-9A-HJKMNP-TV-Z]{26})\b`) | ||
| 26 | // xrefRE matches the two ways a body names another entity: the durable ULID and | ||
| 27 | // the short alias. The alias is deliberately only digits, so it can never be | ||
| 28 | // confused with a ULID prefix, and a number longer than nine digits is not a | ||
| 29 | // plausible issue number while a 26-character ULID that happens to be all digits | ||
| 30 | // is still too long to match here. | ||
| 31 | xrefRE = regexp.MustCompile(`#([0-9A-HJKMNP-TV-Z]{26})\b|#([0-9]{1,9})\b`) | ||
| 27 | 32 | ) | |
| 28 | 33 | ||
| 29 | 34 | // treeDoc is a document that lives at a path in a repository, which is what a | |
| @@ -162,8 +167,13 @@ func linkCrossReferences(repoName, src string) string { | |||
| 162 | 167 | continue | |
| 163 | 168 | } | |
| 164 | 169 | out.WriteString(xrefRE.ReplaceAllStringFunc(line, func(m string) string { | |
| 165 | id := strings.TrimPrefix(m, "#") | ||
| 166 | return "[#" + id + "](/r/" + repoName + "/issues/" + id + ")" | ||
| 170 | groups := xrefRE.FindStringSubmatch(m) | ||
| 171 | if groups[1] != "" { | ||
| 172 | return "[#" + groups[1] + "](/r/" + repoName + "/issues/" + groups[1] + ")" | ||
| 173 | } | ||
| 174 | // A number is kind-agnostic: it may name an issue or a change request, and | ||
| 175 | // only the server can say which. One route answers both. | ||
| 176 | return "[#" + groups[2] + "](/r/" + repoName + "/n/" + groups[2] + ")" | ||
| 167 | 177 | })) | |
| 168 | 178 | } | |
| 169 | 179 | return out.String() | |