Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

Phase 7: the CLI knows who you are and where you wrote it

a63475ce8a689107f6572150d7c58be1d623f6f0 · Keenan Nemetz · 17d ago · parent 79c88d72

Phase 7: the CLI knows who you are and where you wrote it

Three things a first-time user hit in a row, all measured against a live
instance before they were fixed.

1. Identity was guessed. Every issue and request was stamped with a name from
   `git config user.name`, sanitised: "Keenan Nemetz" became `keenan-nemetz`, an
   account that does not exist, and nothing complained — the issue page showed it,
   and the assignee rule accepted it because that rule only checks the shape.
   `vole-whoami` is a new SSH verb, and `vole whoami` is the CLI command that
   reads it: the instance is the only place a key maps to an account, and it is
   one call on the connection already open. `vole issue` and `vole request` stamp
   the account's current name and id, and `--author` is gone from both, because a
   name you can type is a name you can get wrong. Read commands still need no
   identity — `issue list` and `issue show` keep working with the instance
   unreachable — and the git-config guess survives only as a warned fallback for
   `--no-push`.

2. `--remote ssh://…` did not fetch. `fetch` checked for a configured remote name,
   so a URL was skipped: the first write in a fresh repository succeeded (nothing
   to fast-forward from) and every later one pushed from a tip that had never been
   updated, was rejected, and then retried as a "lost race" that never happened,
   reporting `already exists`. A URL is now fetched like a named remote, so
   `git init` plus a URL is the real no-clone filing path the roadmap described.

3. A command answered with an id and stopped. `new` and `comment` now print the
   page they wrote, and a comment link lands on that comment: the instance reports
   its own `public_url` in the same whoami reply (new optional config key), and the
   web puts an `id="c-<ulid>"` anchor on every comment. Empty states on the issues
   and requests pages print the command that fills them, because four of the five
   things a reader wants to do are writes the web cannot take, and "no comments
   yet" is a dead end.

Verified live, not only in tests: `vole whoami` in a clone whose `user.name` is
"Keenan Nemetz" printed `keenan` while the issue it then filed carried
`author: keenan` on the branch; two repositories that never clone both wrote
through the URL (the exact case that failed before); and a comment printed a
`#c-01M5N3M0…` permalink whose anchor is on the served page. Tests:
TestWhoAmIAnswersFromTheInstance (including `--author` now being an unknown
option), TestReadCommandsNeedNoIdentity (closes the SSH listener, then lists),
TestRequestAuthorComesFromTheKey, TestAURLRemoteIsFetchedBeforeWriting,
TestEmptyStatesPrintAWorkingCommand, and the comment-empty-state assertion added
to TestIssuePushedThenRendered. scripts/ci.sh green (gofmt, vet, build, markdown
links, go test -race ./...).

docs/roadmap.md +22−8

@@ -585,14 +585,25 @@ tag marked annotated, the download returned the bytes, and `git log vole` read
585 585
586 586 Planned:
587 587
588 - Identity without ceremony: `vole whoami` resolves the key a person pushes with to an
589 account (the roster is one SSH verb away, `vole-roster`), and `vole request approve`
590 stops requiring `--as`. The name someone types has to match their key anyway, so
591 asking for it is a trap rather than a check.
592 - Filing an issue or opening a request without a clone (`--remote ssh://…`), because
593 "clone the repository to report a bug" is the roughest edge in the current flow.
594 - Navigation and empty states: repo tabs with counts, breadcrumbs everywhere, and an
595 empty state that prints the exact command that fills it.
588 - ✅ **Identity without ceremony.** `vole whoami` asks the instance which account
589 the key resolves to (`vole-whoami` over the same SSH channel), and `vole issue`
590 and `vole request` stamp that account's name on what they write. `--author` is
591 gone from both: a name you can type is a name you can get wrong, and the old
592 default — `git config user.name`, sanitised — turned "Keenan Nemetz" into
593 `keenan-nemetz`, an account that does not exist, silently. The fallback when the
594 instance cannot be reached is the git-config guess **with a warning**, because a
595 `--no-push` run on a train still has to commit something. `vole request approve
596 --as <user>` still exists; it is the one remaining place a name can be typed, and
597 the hook refuses a forged approval anyway.
598 - ✅ **Filing an issue or opening a request without a clone** (`--remote ssh://…`).
599 It worked once per repository and then failed, because `fetch` skipped a URL:
600 the push came from a tip that had never been updated and the retry reported a
601 "lost race" that had not happened. A URL is now fetched like a named remote, so
602 `git init` in an empty directory plus a URL is a working filing path (measured by
603 two repositories that never clone, both landing).
604 - ✅ **Empty states print the command that fills them**, and `new`/`comment` print
605 the page they wrote when `public_url` is set. Repo tabs with counts and
606 breadcrumbs everywhere are still open.
596 607 - Per-file history (`/commits/<ref>/<path>`), so "when did this change" is one click.
597 608
598 609 Releases: decisions and what is left. Design settled by
@@ -776,6 +787,9 @@ Filled in as phases land. Append; don't rewrite.
776 787 | 2026-10-23 | An edit that changes nothing is not a commit | `vole issue assign` on an already-assigned person would otherwise add an empty commit to a linear branch, and a script that reruns has to be idempotent without growing the log. `Update` reports `ErrNoChange` and the branch does not move |
777 788 | 2026-10-23 | A comment is edited in place, deleted by removing its file, and has no `edited` field | The file is the comment: keeping the path keeps the id, the thread order and one `git log -p -- <path>` that answers "what did it say". A deleted comment leaves its text in history, which is honest — redaction is still an admin force-push — and a flat ULID-keyed thread has no replies to orphan. `edited:` would repeat the argument that dropped `updated`: a line rewritten on every edit, when the commit already carries the time |
778 789 | 2026-10-23 | Comment authorship is checked against the old blob, and only for comments | The old blob was validated when it landed; the new one is whatever the pusher wrote, so comparing it would let anyone adopt a comment by editing its author line in the same push. Restricting the rule to comments is deliberate: `state`, `labels` and `assignees` are shared fields and a maintainer who cannot close or assign someone else's issue has no triage. Admins may still repair a thread, audited |
790 | 2026-10-23 | The author of a metadata write is the account the key resolves to, asked with `vole-whoami`; `--author` is gone | `git config user.name` is a name somebody typed for commits: "Keenan Nemetz" sanitises to `keenan-nemetz`, which is not an account, so every issue filed from a normal machine was attributed to nobody and nothing complained. The instance is the only place that maps a key to an account, and it is one SSH verb away on the connection already open. A fallback exists for `--no-push` with the instance unreachable, and it warns loudly rather than guessing silently |
791 | 2026-10-23 | A remote given as an SSH URL is fetched like a named one | Skipping the fetch for a URL is what made `--remote ssh://…` write on a branch that had never been updated: the first write in a fresh repository succeeded (nothing to fast-forward from) and every later one failed as a "lost race" that never happened, re-applying on the same stale tip and reporting "already exists". `git init` plus a URL is now the real no-clone filing path |
792 | 2026-10-23 | The instance tells the client where it is served (`public_url`), so a command can print the page it wrote | A command that answers with an id leaves the reader to assemble a URL, and the instance is the only party that knows its own address behind a proxy. Optional: unset means ids only, which is what it printed before |
779 793
780 794 ## Open questions
781 795