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/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.

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 ./...).

internal/config/config.go +16

@@ -5,6 +5,7 @@ import (
5 5 "fmt"
6 6 "os"
7 7 "path/filepath"
8 "strings"
8 9 "time"
9 10
10 11 "github.com/BurntSushi/toml"
@@ -42,6 +43,12 @@ type Config struct {
42 43 // PagesCacheBytes caps the in-memory blob cache. The cache is required, not an
43 44 // optimisation: cold git reads are ~1.2 ms p50, warm ones 0.075 ms.
44 45 PagesCacheBytes int64 `toml:"pages_cache_bytes"`
46 // PublicURL is where this instance is reachable from a browser, when that is
47 // knowable: "https://forge.example.com" behind a proxy, or the listen address
48 // on a LAN. It is optional and used for one thing — telling a client where to
49 // read what it just wrote (the CLI prints the URL of an issue or comment it
50 // created). Empty means the CLI prints ids only, which it did before.
51 PublicURL string `toml:"public_url"`
45 52
46 53 Path string `toml:"-"`
47 54 }
@@ -111,6 +118,10 @@ func (c *Config) Lease() time.Duration {
111 118 func (c *Config) InstanceKey() string { return filepath.Join(c.DataDir, "instance.key") }
112 119 func (c *Config) SSHHostKey() string { return filepath.Join(c.DataDir, "ssh_host_ed25519_key") }
113 120
121 // BaseURL is PublicURL without a trailing slash, so a caller can append
122 // "/r/<repo>/issues/<id>" without thinking about it.
123 func (c *Config) BaseURL() string { return strings.TrimRight(c.PublicURL, "/") }
124
114 125 // Sample renders a starter config for `vole init`.
115 126 func Sample() string {
116 127 return `# Vole configuration
@@ -124,6 +135,11 @@ http_addr = "127.0.0.1:8080"
124 135 # The only write transport. Identity is the SSH key presented at handshake.
125 136 ssh_addr = "127.0.0.1:2222"
126 137
138 # Where a browser reads this instance, if that differs from http_addr — a proxy
139 # endpoint, or a LAN address. Optional: the CLI uses it to print the page for
140 # something it just created. Trailing slash is ignored.
141 # public_url = "https://forge.example.com"
142
127 143 # Reject individual blobs above this size (pre-receive). Point people at
128 144 # git-lfs instead.
129 145 max_blob_bytes = 104857600