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 5: LFS objects, over SSH for writes and the proxy for reads

64ac5071a173e4e9ce8bc8d7170f9b0411bee52a · Keenan Nemetz · 18d ago · parent 35eaeccf

Phase 5: LFS objects, over SSH for writes and the proxy for reads

Large files leave the git object database and land in one content-addressed
store per instance. Reads are anonymous like clone; writes ride the SSH key
that already authenticates the push, so no token exists anywhere.

- internal/lfs: sha256-addressed store that verifies content and size before
  an object becomes visible; parser for the pointer format a client writes
- HTTP: POST .../info/lfs/objects/batch for downloads, GET .../objects/<oid>
  with the object id as ETag; upload over HTTP is refused and names SSH
- SSH: git-lfs-transfer speaking the pure SSH protocol (pkt-line), so
  `git lfs push` needs nothing but the key at the handshake
- pre-receive: a pointer whose object is missing, or whose size disagrees
  with the stored object, fails the push that introduced it; a binary over
  lfs_warn_bytes is accepted with a `git lfs track` suggestion. Checks run
  before the protected-branch gate, because an admin's approval bypass is
  about review, not about whether the commit can be checked out
- runs: a finished log becomes a content-addressed object in the same write
  that records the status, served at /r/<repo>/runs/<id>/log
- vole gc [--dry-run] [--grace]: marks pointer targets from every ref in
  every repository plus archived logs, sweeps the rest. Mark-and-sweep
  rather than reference counts: a counter that is one too low deletes an
  artifact, a walk over the refs cannot be wrong about what is reachable

internal/lfs/batch.go +60

@@ -0,0 +1,60 @@
1 package lfs
2
3 import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 )
8
9 // The JSON bodies of POST .../info/lfs/objects/batch. Only the fields Vole
10 // reads or writes are modelled; git-lfs sends more (transfers, ref, hash_algo)
11 // and ignores unknown keys in the response.
12
13 type BatchRequest struct {
14 Operation string `json:"operation"`
15 Transfers []string `json:"transfers,omitempty"`
16 HashAlgo string `json:"hash_algo,omitempty"`
17 Objects []Object `json:"objects"`
18 }
19
20 type Object struct {
21 OID string `json:"oid"`
22 Size int64 `json:"size"`
23 }
24
25 type BatchResponse struct {
26 Transfer string `json:"transfer,omitempty"`
27 HashAlgo string `json:"hash_algo,omitempty"`
28 Objects []ObjectResponse `json:"objects"`
29 }
30
31 type ObjectResponse struct {
32 OID string `json:"oid"`
33 Size int64 `json:"size"`
34 Actions map[string]Action `json:"actions,omitempty"`
35 Error *ObjectError `json:"error,omitempty"`
36 }
37
38 type Action struct {
39 Href string `json:"href"`
40 Header map[string]string `json:"header,omitempty"`
41 ExpiresIn int `json:"expires_in,omitempty"`
42 }
43
44 type ObjectError struct {
45 Code int `json:"code"`
46 Message string `json:"message"`
47 }
48
49 // ErrorBody is the top-level error git-lfs shows the user.
50 type ErrorBody struct {
51 Message string `json:"message"`
52 }
53
54 // WriteError replies with the media type git-lfs expects, so the reason reaches
55 // the user instead of "unexpected content type".
56 func WriteError(w http.ResponseWriter, status int, format string, args ...any) {
57 w.Header().Set("Content-Type", MediaType)
58 w.WriteHeader(status)
59 json.NewEncoder(w).Encode(ErrorBody{Message: fmt.Sprintf(format, args...)})
60 }