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
client/branch.go +24−3
| @@ -178,13 +178,34 @@ func (b *Branch) Issues() ([]*metadata.Issue, error) { | |||
| 178 | 178 | return metadata.LoadIssues(b.Repo, metadata.Ref) | |
| 179 | 179 | } | |
| 180 | 180 | ||
| 181 | // ResolveIssueID turns what a human typed into a full id. `list` prints short ids, | ||
| 182 | // so every id-taking command has to accept a prefix or the output is not usable. | ||
| 181 | // Numbers returns the `#42` aliases of this branch. The numbering is derived from | ||
| 182 | // history, so a clone that has the branch can answer without the server. | ||
| 183 | func (b *Branch) Numbers() (*metadata.Numbers, error) { | ||
| 184 | return metadata.LoadNumbers(b.Repo, metadata.Ref) | ||
| 185 | } | ||
| 186 | |||
| 187 | // ResolveIssueID turns what a human typed into a full id. `list` prints numbers and | ||
| 188 | // short ids, so every id-taking command has to accept a number or a prefix or the | ||
| 189 | // output is not usable. | ||
| 183 | 190 | func (b *Branch) ResolveIssueID(given string) (string, error) { | |
| 184 | given = strings.ToUpper(strings.TrimSpace(given)) | ||
| 191 | given = strings.ToUpper(metadata.TrimReference(given)) | ||
| 185 | 192 | if given == "" { | |
| 186 | 193 | return "", fmt.Errorf("an issue id is required") | |
| 187 | 194 | } | |
| 195 | if num, ok := metadata.ParseNumber(given); ok { | ||
| 196 | nums, err := b.Numbers() | ||
| 197 | if err != nil { | ||
| 198 | return "", err | ||
| 199 | } | ||
| 200 | found, kind, ok := nums.At(num) | ||
| 201 | if !ok { | ||
| 202 | return "", fmt.Errorf("no issue or change request is #%d", num) | ||
| 203 | } | ||
| 204 | if kind != metadata.KindIssue { | ||
| 205 | return "", fmt.Errorf("#%d is a change request, not an issue: %s", num, found) | ||
| 206 | } | ||
| 207 | return found, nil | ||
| 208 | } | ||
| 188 | 209 | list, err := b.Issues() | |
| 189 | 210 | if err != nil { | |
| 190 | 211 | return "", err | |