vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Make the CLI speak git's dialect
f956874883e9fdbb95f13b5429bb1104dcec2756 · Keenan Nemetz · 17d ago · parent c1e3e3ca
Make the CLI speak git's dialect The flags were wrong in the way that matters: `-title` is a single-dash long option, which anything that thinks like getopt reads as `-t -i -t -l -e`. Git's conventions (gitcli(7)) are `--long`, `-o`, and no bundling, so this replaces the parser and every call site. Now: - `--long value`, `--long=value`, `-o value`, `-ovalue`. - Short options are never bundled: `-ab` is not `-a -b`. For an option that takes a value, `-mm` is the stuck form and means the value "m"; for a boolean, a bundle is an error. - `-o=value` is refused with a message pointing at `-ov` or `--long=v`, because silently taking "=value" is the kind of thing that costs an hour later. - Options come before arguments, and an option after an argument is an error naming the argument rather than something to guess about. - `--` and `--end-of-options` end option parsing; `-h`/`--help` print the subcommand's usage and exit 0; usage mistakes exit 129, as git does. - `-m`/`-F` mirror `git commit`: mutually exclusive, `-F -` reads stdin. - `-l` is repeatable; comma-separated still works for convenience. - `issue list --format` takes `%(id)`, `%(shortid)`, `%(state)`, `%(author)`, `%(title)`, `%(labels)`, `%(created)` for scripts. - `vole.author` is consulted before `user.name` for the default author. Two bugs came out of the change, both caught by tests rather than by reading: - The new parser stored `--config` as an ordinary value and never copied it to the path the commands actually use, so every command silently fell back to ./vole.toml. A test that rebuilds an index in a temp directory failed loudly. - The integration test itself passed `-config`, which the new parser read as `-c onfig` — exactly the ambiguity this commit removes, in the wild. Verified by hand against a live instance: repeatable `-l`, stuck `--title=x`, `--format`, `issue comment -m text <id>` accepted, `issue comment <id> -m text` refused with exit 129 and "options must come before arguments", `-h` exiting 0, and a missing option value exiting 129.
docs/roadmap.md +1
| @@ -321,6 +321,7 @@ Filled in as phases land. Append; don't rewrite. | |||
| 321 | 321 | | 2026-10-22 | `receive.maxInputSize` replaces the HTTP byte cap | git enforces it on both transports; the proxy never buffers a pack | | |
| 322 | 322 | | 2026-10-22 | Issues are push-based on `refs/heads/issues`, IDs are client-generated ULIDs | No authenticated web write surface exists, and a branch makes issues travel with a clone | | |
| 323 | 323 | | 2026-10-22 | Issue trees validated in `pre-receive` | Reject bad data at the boundary instead of rendering it | | |
| 324 | | 2026-10-22 | CLI follows gitcli(7): `--long`, `-o`, options first, `--`, exit 129 | A tool that speaks git's dialect composes with git and with muscle memory | | ||
| 324 | 325 | ||
| 325 | 326 | ## Open questions | |
| 326 | 327 | ||