A Fossil repository engine that compiles to WebAssembly.
go-libfossil reads, writes and syncs Fossil repositories: the artifact format, the derived tables and the sync protocol, in Go without cgo. The same engine runs as a command-line tool, inside a server, in a browser tab, or inside a storage object at the edge.
1 · The artifact
A check-in is a text manifest. Its name is the hash of its bytes, so anything that names it pins its exact content.
{{ $m.name }}
2 · The repository
Storing it derives the rows fossil derives, in fossil's own tables. fossil rebuild on the same blobs writes the same rows.
3 · The wire
{{- $rounds := len .pull }}A clone that has check-in {{ $m.parent.rid }} pulls. Each side announces what it holds; the client asks for what it lacks. {{ index $words (string $rounds) | default $rounds }} round{{ if ne $rounds 1 }}s{{ end }}.
Version control inside the application, not beside it.
The repository is a SQLite database with Fossil's own tables, so wherever SQLite runs, so does the repository. A database/sql driver is the only thing a new host has to supply.
One static binary
The libfossil command and the Go API build to a single binary for Linux, macOS and Windows, with no fossil installation and no C toolchain.
Serve repositories, mirror them, or read history from a build pipeline through typed Go values instead of parsed command output.
A repository in the page
Built for js/wasm with the ncruces SQLite driver, which ships SQLite itself as WebAssembly. A page holds a whole repository and syncs it with a server when it chooses to.
A document that explains a protocol can let its reader commit, branch, merge and sync in the page, and watch the tables change.
History next to the data
Built with TinyGo, the whole API fits inside a storage object that runs its own SQLite. Each request is one call into the engine inside one transaction, so it completes or leaves nothing behind.
A publishing tool can keep every revision of what it publishes as Fossil check-ins in the object that serves it, and show the history without a separate service.
Small enough to ship with the page.
The whole public API, the HTTP sync client and signing included, built with TinyGo {{ $w.tinygo }} is {{ printf "%.1f" (div $w.tinygo_build.gzip $mb) }} MB compressed. The standard Go toolchain builds the same program about {{ printf "%.0f" (div (float $w.standard.gzip) $w.tinygo_build.gzip) }} times larger, which is still useful for debugging. A program that leaves out sync is smaller.
Measured from examples/tinygo: the TinyGo size is what CI builds on every change; the standard build is Go {{ $w.go }} with -ldflags='-s -w'. Under TinyGo a sync redirect ends the request with an error instead of being followed.
Checked against the specification, requirement by requirement.
Fossil's behaviour is written down as a set of RFC drafts. The test suite turns each draft into a table of requirements and runs at least one case against every row; this table is that test's output, {{ $conf.covered }} of {{ $conf.requirements }} requirements met.
fossil rebuild"
"sync" "client and server"
"submanifest" "extension, shared with zit"
"sparsecone" "extension, shared with zit"
"horizon" "extension, shared with zit"
}}
| Draft | Requirements | Met | Notes |
|---|---|---|---|
| {{ .title }} | {{ .requirements }} | {{ .covered }} | {{ index $notes .suite | safeHTML }}{{ with .partial }}; {{ . }} partial, its scope in the table{{ end }}{{ with .failing }}; {{ . }} failing{{ end }}{{ with .gap }}; {{ . }} without a case{{ end }} |
Against the real implementations
Interop tests run the fossil {{ $fossil }} binary and zit against go-libfossil in both directions: each clones, pulls and pushes the other's repositories, and each verifies what it received.
Deterministic simulation
Sync is also run in a seeded simulation that drops requests, truncates replies and partitions nodes. After each run it checks blob integrity, delta chains and convergence. Any failure replays exactly from its seed. The repository also carries {{ lower (index $words (string $facts.fuzzTargets) | default (string $facts.fuzzTargets)) }} fuzz targets.
Hold the history you need.
A repository does not have to hold everything. A cone keeps the content of chosen paths. A horizon keeps history from a chosen check-in forward and leaves the rest on the origin, fetched only when an operation needs it. At the structural depth the clone also drops the manifests below the horizon and derives branch and tag state from a seed the origin signs.
A structural clone of fossil-scm.org's repository, 21,008 check-ins, takes about 30 seconds. How horizons work.
Open a repository and commit.
From the command line, or from Go. The libfossil command drives the same code as the library, and the file it writes is an ordinary Fossil repository: stock fossil reads it.
$ go install github.com/danmestas/go-libfossil/cmd/libfossil@latest $ libfossil repo new notes.fossil created repository: notes.fossil $ mkdir work && cd work $ libfossil -R ../notes.fossil repo open . $ echo '# notes' > README.md $ libfossil repo add README.md ADDED README.md $ libfossil repo ci -m "first notes" --user ana checkin <name> (rid=2) $ fossil timeline -R ../notes.fossil <time> [<name>] first notes (user: ana tags: trunk)
import ( "github.com/danmestas/go-libfossil" _ "github.com/danmestas/go-libfossil/db/driver/modernc" ) r, err := libfossil.Create("notes.fossil", libfossil.CreateOpts{User: "ana"}) // handle err rid, uuid, err := r.Commit(libfossil.CommitOpts{ Files: []libfossil.FileToCommit{{"{{"}}Name: "README", Content: readme}}, Comment: "first notes", User: "ana", }) // fossil open notes.fossil sees the same check-in.
- Not a replacement for the fossil binary's web interface, wiki or forum server.
- Not a hosting service: it is a library and a command-line tool you run.
- Not a Git bridge. It speaks Fossil's formats and protocol only.