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.
Apply a push's index delta as the push lands, and check the ref by reading it
dced06da1bfc4a8e542e5d86d67888362077ed39 · Keenan Nemetz · 9d ago · parent 26c9be33
Apply a push's index delta as the push lands, and check the ref by reading it A push's metadata delta used to be applied by the next reader, so the first page after a big push paid for the whole push. And search checked every visible repository before querying: a `symbolic-ref` to open it and a `rev-parse` to see whether the metadata branch moved, so N repositories cost 2N git processes per search. - Post-receive now opens the index (the SSH server hands it `VOLE_INDEX_DB` and the repository id) and runs Ensure after the refs have moved, which is also after a followed request's head has moved. A failure is reported and ignored: the refs have landed, and the next read still repairs it. - `git.Repo.RefCommit` resolves a branch ref by reading the loose ref file git writes for every branch update, and falls back to `rev-parse` for a packed ref, a symbolic ref, or a missing one. The index's Ensure and the web's metadata-head read use it. - `repo.Store.Meta` builds a repository handle without a HEAD lookup, and search uses it: the refresh needs the directory and the ACL, not the default branch. The per-repository check is now two file reads instead of two processes. Load harness, `search-find` at concurrency 4 over three repositories: 510.2 req/s and p50 7 ms before, 814.8 req/s and p50 4 ms after. A push that changes the `vole` branch leaves the rows current before the push returns; a test queries them with no Ensure in between.
core/git/git.go +46
| @@ -12,6 +12,7 @@ import ( | |||
| 12 | 12 | "io" | |
| 13 | 13 | "os" | |
| 14 | 14 | "os/exec" | |
| 15 | "path/filepath" | ||
| 15 | 16 | "sort" | |
| 16 | 17 | "strconv" | |
| 17 | 18 | "strings" | |
| @@ -153,6 +154,51 @@ func (r *Repo) RevParse(rev string) (string, error) { | |||
| 153 | 154 | return strings.TrimSpace(string(out)), nil | |
| 154 | 155 | } | |
| 155 | 156 | ||
| 157 | // RefCommit resolves a branch ref to its commit. A loose ref is read directly, | ||
| 158 | // which is how git stores every branch it updates, and anything else — a packed | ||
| 159 | // ref, a symbolic ref, a ref that is not there — falls back to rev-parse. The | ||
| 160 | // fast path exists because the index checks the metadata ref on every page and | ||
| 161 | // every search refresh; a file read is far cheaper than a process. | ||
| 162 | // | ||
| 163 | // It is for branch refs. A tag ref can point at a tag object, which RevParse | ||
| 164 | // peels and this does not. | ||
| 165 | func (r *Repo) RefCommit(ref string) (string, error) { | ||
| 166 | name := strings.TrimPrefix(ref, "refs/heads/") | ||
| 167 | if name != ref && looseRefName(name) { | ||
| 168 | if data, err := os.ReadFile(filepath.Join(r.Dir, "refs", "heads", name)); err == nil { | ||
| 169 | if sha := strings.TrimSpace(string(data)); isObjectID(sha) { | ||
| 170 | return sha, nil | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | return r.RevParse(ref) | ||
| 175 | } | ||
| 176 | |||
| 177 | // looseRefName rejects a name that cannot be a branch path segment, so the fast | ||
| 178 | // path never reads outside the ref store. | ||
| 179 | func looseRefName(name string) bool { | ||
| 180 | if name == "" || strings.HasPrefix(name, "/") || strings.HasSuffix(name, "/") || | ||
| 181 | strings.Contains(name, "..") || strings.ContainsAny(name, "\\\x00\n") { | ||
| 182 | return false | ||
| 183 | } | ||
| 184 | return true | ||
| 185 | } | ||
| 186 | |||
| 187 | // isObjectID reports the form of a git object id: 40 hex digits for SHA-1, 64 | ||
| 188 | // for SHA-256. A ref file holding anything else (a symbolic ref, a comment) is | ||
| 189 | // not answered by the fast path. | ||
| 190 | func isObjectID(s string) bool { | ||
| 191 | if len(s) != 40 && len(s) != 64 { | ||
| 192 | return false | ||
| 193 | } | ||
| 194 | for _, r := range s { | ||
| 195 | if (r < '0' || r > '9') && (r < 'a' || r > 'f') { | ||
| 196 | return false | ||
| 197 | } | ||
| 198 | } | ||
| 199 | return true | ||
| 200 | } | ||
| 201 | |||
| 156 | 202 | // RevParseRaw resolves a rev without peeling to a commit, returning "" when it | |
| 157 | 203 | // does not exist. Use this to detect a ref that points at the wrong object type. | |
| 158 | 204 | func (r *Repo) RevParseRaw(rev string) (string, error) { | |