Artifact 3557da5373d530ea924fbe1e9d87704183854ad2568fad2c15f6d3385238277e:
- File
docs/site/layouts/index.html
— part of check-in
[7377cb7c97]
at
2026-10-23 21:25:06
on branch main
— docs: size from the measured TinyGo build of the whole API
go-libfossil now builds with TinyGo, sync and signing included (examples/tinygo). The landing page reads the measured sizes from data/wasm.json instead of an outside project's engine-only build, and the install page says how to build it and what differs under TinyGo. (user: [email protected], size: 19965) [annotate] [blame] [check-ins using]
{{- /* The landing page. A page of its own, outside Hextra: one dark identity. The hero figure, the conformance table and every number on the page come from data/*.json and site params, written by scripts/gen-docs.sh and scripts/sync-version.sh; nothing here is typed by hand that the code knows. */ -}} {{- $engine := hugo.Data.engine -}} {{- $conf := hugo.Data.conformance -}} {{- $facts := hugo.Data.facts -}} {{- $words := dict "1" "One" "2" "Two" "3" "Three" "4" "Four" "5" "Five" "6" "Six" "7" "Seven" "8" "Eight" "9" "Nine" -}} {{- $fossil := $facts.fossilVersion -}} {{- $goVersion := replaceRE `^(\d+\.\d+).*$` "$1" site.Params.goVersion -}} {{- $css := resources.Get "css/landing.css" | minify | fingerprint -}} <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="description" content="go-libfossil reads, writes and syncs Fossil repositories in Go without cgo: a command-line tool, a library, and an engine that compiles to WebAssembly."> <meta name="color-scheme" content="dark"> <meta name="theme-color" content="#14110d"> <title>go-libfossil · a Fossil repository engine in Go</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,[email protected],500;9..144,600;9..144,700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;600&display=swap"> <link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}"> </head> <body> <a class="skip" href="#main">Skip to content</a> <div class="gutter"><div class="inner"> <header class="nav"> <a class="brand" href="{{ site.Home.RelPermalink }}"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2 17h20M2 12h20M2 7h20" stroke="#a99c86" stroke-width="1.6"/><path d="M2 12h20" stroke="#d9663b" stroke-width="1.8" stroke-dasharray="3 2.5"/></svg>go-libfossil</a> <nav aria-label="Main"> {{- range site.Menus.main }} {{- if ne (.Params.type | default "") "search" }} <a href="{{ .URL }}">{{ .Name }}</a> {{- end }} {{- end }} </nav> <span class="ver" title="The latest release tag">v{{ site.Params.version }}</span> </header> </div></div> <main id="main"> <div class="gutter"><div class="inner"> <div class="hero wide"> <div class="stack"> <h1>A Fossil repository engine that <em>compiles to WebAssembly.</em></h1> <p class="lede">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.</p> <div class="ctas"><a class="btn solid" href="/docs/get-started/">Get started</a><a class="btn line" href="/docs/concepts/">Read the concepts</a></div> <div class="install"><span>$</span> go get github.com/danmestas/go-libfossil@latest</div> </div> {{- with $engine }} {{- $m := .manifest }} {{- $changed := slice }} {{- range .rows.mlink }}{{ $changed = $changed | append .file }}{{ end }} <figure class="engine" style="margin:0"> <figcaption class="cap"><span>one check-in, three views · output of a real go-libfossil run</span><span>{{ cond (eq (len $m.name) 64) "sha3-256" "sha1" }} · fossil {{ $fossil }} schema · sync protocol</span></figcaption> <div class="panes"> <div class="pane"> <h4>1 · The artifact</h4> <p class="sub">A check-in is a text manifest. Its name is the hash of its bytes, so anything that names it pins its exact content.</p> <div class="cards"> {{- $labels := dict "C" "comment" "D" "time" "R" "tree MD5" "U" "user" "Z" "card MD5" "P" "parent check-in" }} {{- range split (strings.TrimSuffix "\n" $m.text) "\n" }} {{- $card := substr . 0 1 }} {{- $fields := split (substr . 2) " " }} {{- $shown := slice }} {{- range $fields }}{{ $shown = $shown | append (partial "landing/short.html" .) }}{{ end }} {{- $label := index $labels $card | default "" }} {{- if eq $card "F" }}{{ $label = cond (in $changed (index $fields 0)) "file → blob" "unchanged" }}{{ end }} <div class="ln"><b>{{ $card }}</b><span>{{ delimit $shown " " }}{{ with $label }} <i>{{ . }}</i>{{ end }}</span></div> {{- end }} </div> <div class="name">name = {{ cond (eq (len $m.name) 64) "sha3-256" "sha1" }}({{ $m.bytes }} bytes) =<br><span>{{ $m.name }}</span></div> </div> <div class="pane"> <h4>2 · The repository</h4> <p class="sub">Storing it derives the rows fossil derives, in fossil's own tables. <code>fossil rebuild</code> on the same blobs writes the same rows.</p> <div class="rows"> <div><div class="t"><b>blob</b> rid · uuid · size</div>{{ range .rows.blob }}<div class="r">{{ .rid }} · {{ partial "landing/short.html" .uuid }} · {{ .size }}</div>{{ end }}</div> <div><div class="t"><b>event</b> type · objid · user · comment</div>{{ range .rows.event }}<div class="r">{{ .type }} · {{ .objid }} · {{ .user }} · {{ .comment }}</div>{{ end }}</div> <div><div class="t"><b>plink</b> pid → cid · primary</div>{{ range .rows.plink }}<div class="r">{{ .pid }} → {{ .cid }} · {{ .isprim }}</div>{{ end }}</div> <div><div class="t"><b>mlink</b> mid · fid · pid · file</div>{{ range .rows.mlink }}<div class="r">{{ .mid }} · {{ .fid }} · {{ .pid }} · {{ .file }}</div>{{ end }}</div> <div><div class="t"><b>tagxref</b> tag · value · rid</div>{{ range .rows.tagxref }}<div class="r">{{ .tag }} · {{ .value }} · {{ .rid }}</div>{{ end }}</div> <div><div class="t"><b>leaf</b> rid</div>{{ range .rows.leaf }}<div class="r">{{ . }}</div>{{ end }}</div> </div> </div> <div class="pane"> <h4>3 · The wire</h4> {{- $rounds := len .pull }} <p class="sub">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 }}.</p> <div class="wire"> {{- $announced := false }} {{- range $i, $round := .pull }} <div class="dir">→ round {{ add $i 1 }}</div> {{- range $round.request }} {{- $f := split . " " }} {{- $rest := slice }} {{- range after 1 $f }}{{ $rest = $rest | append (partial "landing/short.html" .) }}{{ end }} <div><span class="k">{{ index $f 0 }}</span> {{ delimit $rest " " }}{{ if and (eq (index $f 0) "igot") (eq (index $f 1) $m.parent.uuid) }} <span class="s"># check-in {{ $m.parent.rid }}</span>{{ end }}</div> {{- end }} <div class="dir">← reply</div> {{- range $round.reply }} {{- $f := split . " " }} {{- $rest := slice }} {{- range after 1 $f }}{{ $rest = $rest | append (partial "landing/short.html" .) }}{{ end }} {{- $new := and (not $announced) (eq (index $f 0) "igot") (eq (index $f 1) $m.name) }} {{- if $new }}{{ $announced = true }}{{ end }} <div><span class="k">{{ index $f 0 }}</span> {{ delimit $rest " " }}{{ if $new }} <span class="s"># new</span>{{ end }}</div> {{- end }} {{- end }} </div> </div> </div> <div class="hoststrip"> <div><b>native</b> · one static binary, no cgo</div> <div><b>js/wasm</b> · SQLite itself in wasm</div> <div><b>TinyGo</b> · whole API {{ printf "%.1f" (div hugo.Data.wasm.tinygo_oz.gzip 1000000.0) }} MB gzipped</div> </div> </figure> {{- end }} </div> </div></div> <section class="gutter"><div class="inner stack" style="gap:30px"> <div class="stack" style="gap:10px"><span class="label">Where it runs</span><h2>Version control inside the application, not beside it.</h2> <p class="muted">The repository is a SQLite database with Fossil's own tables, so wherever SQLite runs, so does the repository. A <code>database/sql</code> driver is the only thing a new host has to supply.</p></div> <div class="runs"> <div class="run"><span class="label">Server and CLI</span><h3>One static binary</h3><p class="muted">The <code>libfossil</code> command and the Go API build to a single binary for Linux, macOS and Windows, with no fossil installation and no C toolchain.</p><p class="idea">Serve repositories, mirror them, or read history from a build pipeline through typed Go values instead of parsed command output.</p></div> <div class="run"><span class="label">Browser</span><h3>A repository in the page</h3><p class="muted">Built for <code>js/wasm</code> 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.</p><p class="idea">A document that explains a protocol can let its reader commit, branch, merge and sync in the page, and watch the tables change.</p></div> <div class="run"><span class="label">Edge storage</span><h3>History next to the data</h3><p class="muted">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.</p><p class="idea">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.</p></div> </div> </div></section> {{- $w := hugo.Data.wasm -}} {{- $mb := 1000000.0 -}} <section class="gutter"><div class="inner split"> <div class="stack"><span class="label">Size</span><h2>Small enough to ship with the page.</h2> <p class="muted">The whole public API, the HTTP sync client and signing included, built with TinyGo {{ $w.tinygo }} and one <code>wasm-opt -Oz</code> pass is {{ printf "%.1f" (div $w.tinygo_oz.gzip $mb) }} MB compressed. The standard Go toolchain builds the same program about {{ printf "%.0f" (div (float $w.standard.gzip) $w.tinygo_oz.gzip) }} times larger, which is still useful for debugging. A program that leaves out sync is smaller.</p> <p class="small">Measured from <code>examples/tinygo</code> (Go {{ $w.go }}); CI builds it with TinyGo on every change. Under TinyGo a sync redirect ends the request with an error instead of being followed.</p></div> <div class="bars"> <div class="bar tiny"><div class="row"><span>TinyGo {{ $w.tinygo }}, gzipped</span><span class="mono">{{ printf "%.2f" (div $w.tinygo_oz.gzip $mb) }} MB</span></div><div class="track"><div class="fill" style="width:{{ printf "%.0f" (mul 100 (div (float $w.tinygo_oz.gzip) $w.standard.gzip)) }}%"></div></div></div> <div class="bar"><div class="row"><span>Standard Go, gzipped</span><span class="mono">{{ printf "%.2f" (div $w.standard.gzip $mb) }} MB</span></div><div class="track"><div class="fill" style="width:100%"></div></div></div> <div class="bar tiny"><div class="row"><span>TinyGo {{ $w.tinygo }}, raw</span><span class="mono">{{ printf "%.2f" (div $w.tinygo_oz.raw $mb) }} MB</span></div><div class="track"><div class="fill" style="width:{{ printf "%.0f" (mul 100 (div (float $w.tinygo_oz.raw) $w.standard.raw)) }}%"></div></div></div> <div class="bar"><div class="row"><span>Standard Go, raw</span><span class="mono">{{ printf "%.1f" (div $w.standard.raw $mb) }} MB</span></div><div class="track"><div class="fill" style="width:100%"></div></div></div> </div> </div></section> <section class="gutter"><div class="inner stack" style="gap:30px"> <div class="stack" style="gap:10px"><span class="label">How we know it is right</span><h2>Checked against the specification, requirement by requirement.</h2> <p class="muted">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.</p></div> {{- $notes := dict "artifact" "every card type, parse and write" "delta" "compared with fossil's own output" "repostate" "derived tables match <code>fossil rebuild</code>" "sync" "client and server" "submanifest" "extension, shared with zit" "sparsecone" "extension, shared with zit" "horizon" "extension, shared with zit" }} <div class="scroll"><table class="ledger"> <tr><th>Draft</th><th style="text-align:right">Requirements</th><th style="text-align:right">Met</th><th>Notes</th></tr> {{- range $conf.suites }} <tr><td><a href="/docs/reference/conformance/#{{ .suite }}">{{ .title }}</a></td><td class="n">{{ .requirements }}</td><td class="n{{ if eq .covered .requirements }} full{{ end }}">{{ .covered }}</td><td class="muted">{{ index $notes .suite | safeHTML }}{{ with .partial }}; {{ . }} partial, its scope in the table{{ end }}{{ with .failing }}; {{ . }} failing{{ end }}{{ with .gap }}; {{ . }} without a case{{ end }}</td></tr> {{- end }} </table></div> <div class="facts"> <div class="fact"><h3>Against the real implementations</h3><p class="muted">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.</p></div> <div class="fact"><h3>Deterministic simulation</h3><p class="muted">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.</p></div> </div> </div></section> <section class="gutter"><div class="inner split"> <div class="stack"><span class="label">Partial history</span><h2>Hold the history you need.</h2> <p class="muted">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.</p> <p class="small">A structural clone of fossil-scm.org's repository, 21,008 check-ins, takes about 30 seconds. <a href="/docs/concepts/horizons/">How horizons work</a>.</p></div> <figure class="strata" style="margin:0"> <svg viewBox="0 0 420 240" role="img" aria-label="A commit graph cut by a horizon line: check-ins above it are held, those below are left on the origin"> <defs><linearGradient id="below" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#d9663b" stop-opacity=".12"/><stop offset="1" stop-color="#d9663b" stop-opacity="0"/></linearGradient></defs> <rect x="0" y="134" width="420" height="106" fill="url(#below)"/> <line x1="12" y1="134" x2="408" y2="134" stroke="#d9663b" stroke-width="1.5" stroke-dasharray="6 5"/> <g font-family="JetBrains Mono, monospace" font-size="10"><text x="14" y="127" fill="#d9663b">horizon C</text><text x="14" y="152" fill="#a99c86">on the origin</text><text x="14" y="32" fill="#a99c86">held</text></g> <g stroke="#a99c86" stroke-width="1.6" fill="none"> <path d="M210 220 V30"/><path d="M210 168 C 280 158, 300 126, 300 98 V62"/><path d="M300 62 C 290 44, 240 38, 210 30"/><path d="M210 98 C 150 90, 140 72, 140 56"/> </g> <g font-family="JetBrains Mono, monospace" font-size="9.5" fill="#f1e9da"> <circle cx="210" cy="220" r="6" fill="#30291f" stroke="#a99c86"/><circle cx="210" cy="168" r="6" fill="#30291f" stroke="#a99c86"/> <circle cx="210" cy="134" r="7" fill="#d9663b"/><text x="222" y="138">C</text> <circle cx="210" cy="98" r="6" fill="#f1e9da"/><circle cx="210" cy="62" r="6" fill="#f1e9da"/><circle cx="210" cy="30" r="6" fill="#f1e9da"/><text x="222" y="34">trunk</text> <circle cx="300" cy="98" r="6" fill="#f1e9da"/><circle cx="300" cy="62" r="6" fill="#f1e9da"/><text x="312" y="66">feature</text> <circle cx="140" cy="56" r="6" fill="#f1e9da"/><text x="74" y="60">release</text> </g> </svg> <figcaption class="legend"><span><i style="background:#f1e9da"></i>held</span><span><i style="background:#30291f;border:1px solid #a99c86"></i>left on the origin</span><span><i style="background:#d9663b"></i>horizon, with a signed seed</span></figcaption> </figure> </div></section> <section class="gutter"><div class="inner split"> <div class="stack"><span class="label">Start</span><h2>Open a repository and commit.</h2> <p class="muted">From the command line, or from Go. The <code>libfossil</code> command drives the same code as the library, and the file it writes is an ordinary Fossil repository: stock <code>fossil</code> reads it.</p> <div class="ctas"><a class="btn solid" href="/docs/get-started/quickstart/">Get started</a><a class="btn line" href="/docs/reference/sdk/libfossil/api/">API reference</a></div></div> <div class="stack" style="gap:16px"> <div class="code"><div class="cap">shell · the check-in name depends on the time</div><pre><span class="k">$</span> go install github.com/danmestas/go-libfossil/cmd/libfossil@latest <span class="k">$</span> libfossil repo new notes.fossil <span class="c">created repository: notes.fossil</span> <span class="k">$</span> mkdir work && cd work <span class="k">$</span> libfossil -R ../notes.fossil repo open . <span class="k">$</span> echo '# notes' > README.md <span class="k">$</span> libfossil repo add README.md <span class="c">ADDED README.md</span> <span class="k">$</span> libfossil repo ci -m "first notes" --user ana <span class="c">checkin <name> (rid=2)</span> <span class="k">$</span> fossil timeline -R ../notes.fossil <span class="c"><time> [<name>] first notes (user: ana tags: trunk)</span></pre></div> <div class="code"><div class="cap">main.go</div><pre><span class="k">import</span> ( <span class="s">"github.com/danmestas/go-libfossil"</span> _ <span class="s">"github.com/danmestas/go-libfossil/db/driver/modernc"</span> ) r, err := libfossil.<span class="k">Create</span>(<span class="s">"notes.fossil"</span>, libfossil.CreateOpts{User: <span class="s">"ana"</span>}) <span class="c">// handle err</span> rid, uuid, err := r.<span class="k">Commit</span>(libfossil.CommitOpts{ Files: []libfossil.FileToCommit{{"{{"}}Name: <span class="s">"README"</span>, Content: readme}}, Comment: <span class="s">"first notes"</span>, User: <span class="s">"ana"</span>, }) <span class="c">// fossil open notes.fossil sees the same check-in.</span></pre></div> </div> </div></section> <section class="gutter"><div class="inner stack"> <span class="label">What it is not</span> <ul class="nots"> <li>Not a replacement for the fossil binary's web interface, wiki or forum server.</li> <li>Not a hosting service: it is a library and a command-line tool you run.</li> <li>Not a Git bridge. It speaks Fossil's formats and protocol only.</li> </ul> </div></section> </main> <footer class="gutter"><div class="inner"><span>go-libfossil · <a href="https://github.com/danmestas/go-libfossil/blob/main/LICENSE">MIT</a></span><span class="mono">fossil {{ $fossil }} · zit · Go {{ $goVersion }}</span></div></footer> </body> </html>