Artifact 1f021eb9504b2e2111ca622798dcee4fcf08dea89151aa09a43ef12a69fd21d0:
- File
docs/site/layouts/index.html
— part of check-in
[6273a0750d]
at
2026-07-22 23:03:24
on branch trunk
— fix: correct go.mod module path to go-libfossil (#76) (#117)
* fix: correct go.mod module path to go-libfossil (#76)
The module directive still declared github.com/danmestas/libfossil after the repository was renamed to go-libfossil. pkg.go.dev/godoc scraping matches the declared module path against its hosting path, so the stale path broke SDK doc generation.
Rename the module path across all four in-repo modules (root, the two db/driver submodules, observer/otel, and docs/site) and every internal import, plus the release workflow, the doc-generation script, and the docs.
No tag has been published under the go-libfossil path yet, so the cross-module requires cannot resolve from the proxy. Add replace directives pointing the in-repo requires at their local directories so GOWORK=off builds and the CI matrix resolve without a published version. These replaces are ignored by downstream consumers and can be dropped once new-path tags are cut.
* Guard releases against replace directives; regenerate SDK docs
The go-libfossil rename bridges the in-repo modules with `replace` directives. Go refuses a main module that carries them, so a tag cut while they are present breaks `go install .../cmd/libfossil@<tag>` and every `@version` fetch of the driver submodules. Add a Release workflow step that fails the job before anything is published if any tracked go.mod still contains a replace directive, so the bridge is a CI-enforced TODO rather than a silent landmine. The step comment records what unblocks a release: re-tag the submodules with the corrected module paths, bump the root requires, then drop the replace blocks.
Regenerate the SDK reference docs. The previous pass only substituted URLs over the pre-existing generated files, leaving stale signatures (Repo.Timeline was still documented as taking LogOpts rather than TimelineOpts). Note that gomarkdoc must be run outside a git worktree — inside one it silently strips every source link.
Also reword the README rename note, which read as though the import path had not changed, and re-sort the ncruces require block. (user: [email protected], size: 22488) [annotate] [blame] [check-ins using]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content="go-libfossil — pure-Go library and CLI for Fossil SCM repositories. Zero CGo, static binaries, WASM-ready."> <meta name="color-scheme" content="light dark"> <title>go-libfossil — pure-Go Fossil SCM library</title> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;700&display=swap" rel="stylesheet"> <style> :root { --paper: #f5f1e6; --paper-2:#ece7d8; --ink: #14110b; --mute: #5e5b53; --rule: #14110b; --ember: #c63a0f; --measure: 76ch; --gap: clamp(2.25rem, 5vw, 3.5rem); --mono: "Geist Mono", ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Consolas, "Courier New", monospace; } *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html { font-size: 16px; -webkit-text-size-adjust: 100%; } body { background: var(--paper); color: var(--ink); font: 400 clamp(15px, 1.05vw, 17px)/1.7 var(--mono); font-feature-settings: "tnum" 1, "calt" 1, "ss01" 1; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; } .skip { position: absolute; left: -9999px; } .skip:focus { position: fixed; top: 0.75rem; left: 0.75rem; background: var(--ember); color: var(--paper); padding: 0.5rem 0.75rem; z-index: 10; text-decoration: none; } header.topbar { border-bottom: 1px solid var(--rule); padding: clamp(0.625rem, 2.5vw, 0.75rem) clamp(1rem, 4vw, 1.5rem); display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; flex-wrap: wrap; font-size: 0.78125rem; letter-spacing: 0.04em; } .topbar a { color: inherit; text-decoration: none; border-bottom: 1px solid currentColor; } .topbar a:hover { color: var(--ember); } .topbar .name { font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; } .topbar .meta { color: var(--mute); display: flex; gap: 1.25rem; flex-wrap: wrap; } .topbar .meta span { display: inline-flex; gap: 0.4rem; } .topbar .meta b { color: var(--ink); font-weight: 500; } main { max-width: 88ch; margin: 0 auto; padding: clamp(2rem, 4vw, 3rem) clamp(1rem, 4vw, 1.5rem) clamp(3rem, 6vw, 4.5rem); overflow-wrap: anywhere; } .title { display: grid; grid-template-columns: 1fr; gap: 1.25rem; padding-bottom: clamp(2rem, 4vw, 2.75rem); margin-bottom: clamp(2rem, 4vw, 3rem); border-bottom: 1px solid var(--rule); } .title h1 { font-size: clamp(2.75rem, 13vw, 6.5rem); font-weight: 700; letter-spacing: -0.05em; line-height: 0.92; } .title .lead { font-size: clamp(1rem, 1.2vw, 1.0625rem); max-width: var(--measure); color: var(--ink); line-height: 1.6; } .spec { width: 100%; border-collapse: collapse; font-size: 0.78125rem; margin-top: 0.5rem; letter-spacing: 0.02em; } .spec td { padding: 0.35rem 0; border-top: 1px dashed var(--rule); vertical-align: top; } .spec td:first-child { width: 12rem; color: var(--mute); text-transform: uppercase; letter-spacing: 0.14em; font-size: 0.6875rem; padding-top: 0.5rem; } .spec tr:first-child td { border-top: 1px solid var(--rule); } .spec tr:last-child td { border-bottom: 1px solid var(--rule); } section { margin-top: var(--gap); } section > .num { display: block; font-size: 0.6875rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--mute); margin-bottom: 0.4rem; } h2 { font-size: clamp(1.125rem, 1.6vw, 1.3125rem); font-weight: 700; letter-spacing: -0.01em; margin-bottom: 1.25rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--rule); display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; flex-wrap: wrap; } h2 .anchor { font-size: 0.6875rem; font-weight: 400; color: var(--mute); letter-spacing: 0.14em; text-transform: uppercase; } p { max-width: var(--measure); margin-bottom: 1rem; } p:last-child { margin-bottom: 0; } strong { font-weight: 700; } a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--ink); } a:hover { color: var(--ember); border-bottom-color: var(--ember); } code { font: inherit; font-size: 0.94em; background: var(--paper-2); padding: 0.05em 0.35em; } ol, ul { padding-left: 0; max-width: var(--measure); list-style: none; } ol li, ul li { margin-bottom: 0.875rem; padding-left: 2.75rem; position: relative; line-height: 1.65; } ol { counter-reset: item; } ol li { counter-increment: item; } ol li::before { content: counter(item, decimal-leading-zero); position: absolute; left: 0; top: 0.05em; font-weight: 700; font-size: 0.78125rem; letter-spacing: 0.04em; color: var(--ember); width: 2rem; } ul li::before { content: "—"; position: absolute; left: 0.5rem; color: var(--mute); } .code-block { margin-bottom: 1rem; border: 1px solid var(--rule); background: var(--paper-2); } .code-bar { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; padding: 0.35rem 0.5rem 0.35rem 0.85rem; border-bottom: 1px solid var(--rule); background: var(--paper); font-size: 0.625rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--mute); } pre { padding: 0.875rem 1.25rem; font-size: 0.85rem; line-height: 1.65; overflow-x: auto; white-space: pre; font-family: var(--mono); color: var(--ink); } pre.ascii { background: transparent; border: 1px solid var(--rule); padding: clamp(0.75rem, 2.5vw, 1.25rem); font-size: clamp(0.7rem, 1.7vw, 0.8125rem); line-height: 1.45; color: var(--ink); margin-bottom: 1rem; overflow-x: auto; } .diagram { display: block; width: 100%; min-width: 30rem; height: auto; margin-bottom: 0.5rem; font-family: var(--mono); color: var(--ink); } .diagram .rule { stroke: var(--rule); stroke-width: 1; opacity: 0.5; } .diagram .box { fill: none; stroke: var(--rule); stroke-width: 1; } .diagram .head { font-size: 11px; letter-spacing: 1.4px; text-transform: uppercase; fill: var(--mute); } .diagram .item { font-size: 13px; fill: var(--ink); } .diagram .stack { font-size: 12px; fill: var(--mute); font-style: italic; } .diagram .flow line { stroke: var(--ember); stroke-width: 1.5; fill: none; } .diagram .arrowhead { fill: var(--ember); } .diagram .flow-label { font-size: 10px; fill: var(--ember); letter-spacing: 0.6px; text-transform: uppercase; } .copy-btn { background: transparent; color: var(--ink); border: 1px solid var(--ink); font: inherit; font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase; padding: 0.15rem 0.55rem; cursor: pointer; flex-shrink: 0; } .copy-btn:hover, .copy-btn:focus-visible { background: var(--ink); color: var(--paper); outline: none; } .copy-btn[data-state="copied"] { color: var(--ember); border-color: var(--ember); background: var(--paper); } .table-scroll { margin: 0 calc(-1 * clamp(0rem, 2vw, 0.5rem)); overflow-x: auto; -webkit-overflow-scrolling: touch; } table.cmp { width: 100%; min-width: 30rem; border-collapse: collapse; font-size: 0.875rem; margin-bottom: 0.75rem; } table.cmp th, table.cmp td { text-align: left; padding: 0.625rem 0.875rem; vertical-align: top; border-bottom: 1px dashed var(--rule); } table.cmp thead th { border-bottom: 1px solid var(--rule); border-top: 1px solid var(--rule); font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.14em; background: var(--paper-2); } table.cmp td:first-child, table.cmp th:first-child { padding-left: 0.875rem; width: 30%; font-weight: 500; color: var(--mute); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.75rem; } table.cmp td:last-child, table.cmp th:last-child { padding-right: 0.875rem; } table.cmp .col-self { color: var(--ember); font-weight: 700; } table.cmp tbody tr:last-child td { border-bottom: 1px solid var(--rule); } .fineprint { font-size: 0.75rem; color: var(--mute); margin-top: 0.5rem; letter-spacing: 0.02em; } footer { border-top: 1px solid var(--rule); margin-top: var(--gap); padding: 1.5rem 0; font-size: 0.78125rem; color: var(--mute); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 1rem; letter-spacing: 0.04em; } footer a { color: inherit; border-bottom-color: var(--mute); } @media (prefers-color-scheme: dark) { :root { --paper: #15140f; --paper-2:#1f1d17; --ink: #ece8dc; --mute: #908b80; --rule: #ece8dc; --ember: #ff5a26; } } @media print { body { background: white; color: black; font-size: 11pt; line-height: 1.5; } .code-block, pre { background: white; color: black; border: 1px solid black; } a { color: black; border-bottom: 0; text-decoration: underline; } a::after { content: " (" attr(href) ")"; font-size: 0.85em; color: #555; } .copy-btn, .skip { display: none; } section { break-inside: avoid; } } @media (max-width: 540px) { header.topbar { flex-direction: column; align-items: flex-start; gap: 0.5rem; } .topbar .meta { gap: 0.75rem; row-gap: 0.35rem; } .spec td:first-child { width: 8rem; font-size: 0.625rem; } .spec td { padding: 0.4rem 0; } table.cmp { font-size: 0.6875rem; min-width: 0; table-layout: auto; } table.cmp th, table.cmp td { padding: 0.45rem 0.4rem; } table.cmp tbody td { white-space: normal; word-break: normal; overflow-wrap: break-word; } table.cmp thead th { font-size: 0.5rem; letter-spacing: 0.06em; padding: 0.5rem 0.3rem; white-space: nowrap; } table.cmp td:first-child, table.cmp th:first-child { padding-left: 0.55rem; width: 28%; font-size: 0.5625rem; letter-spacing: 0.04em; } table.cmp td:last-child, table.cmp th:last-child { padding-right: 0.55rem; } ol li, ul li { padding-left: 2.25rem; } ol li::before { width: 1.75rem; } footer { flex-direction: column; gap: 0.5rem; align-items: flex-start; } .copy-btn { font-size: 0.5625rem; padding: 0.1rem 0.4rem; } pre { font-size: 0.78rem; padding: 0.75rem 0.9rem; } .code-bar { padding: 0.3rem 0.4rem 0.3rem 0.7rem; } } @media (max-width: 380px) { .topbar .name { letter-spacing: 0.12em; } .spec td:first-child { width: 7rem; } table.cmp { font-size: 0.625rem; } table.cmp th, table.cmp td { padding: 0.4rem 0.25rem; } table.cmp thead th { font-size: 0.4375rem; padding: 0.45rem 0.2rem; } table.cmp td:first-child, table.cmp th:first-child { font-size: 0.5rem; padding-left: 0.4rem; width: 26%; } } </style> </head> <body> <a href="#main" class="skip">skip to content</a> <header class="topbar"> <div class="name">go-libfossil</div> <div class="meta"> <span>v <b>{{ .Site.Params.version | default "0.4.2" }}</b></span> <span>license <b>MIT</b></span> <span><a href="https://github.com/danmestas/go-libfossil">github</a></span> <span><a href="/docs/">docs</a></span> </div> </header> <main id="main"> <section class="title"> <h1>go-libfossil</h1> <p class="lead">A pure-Go library for Fossil SCM repositories. Zero CGo. Static binaries. WASM-ready.</p> <table class="spec"> <tbody> <tr><td>Project</td> <td>go-libfossil — pure-Go Fossil SCM library</td></tr> <tr><td>Language</td> <td>Go (module <code>github.com/danmestas/go-libfossil</code>)</td></tr> <tr><td>Runtime deps</td> <td>none · pluggable SQLite drivers (modernc · ncruces)</td></tr> <tr><td>Binaries</td> <td>1 (libfossil CLI)</td></tr> <tr><td>License</td> <td>MIT</td></tr> <tr><td>Status</td> <td>v{{ .Site.Params.version | default "0.4.2" }} · alpha · breaking changes possible</td></tr> </tbody> </table> </section> <section> <span class="num">§01</span> <h2>Install <span class="anchor">install</span></h2> <div class="code-block"> <div class="code-bar"> <span>shell</span> <button type="button" class="copy-btn" data-copy="install" aria-label="Copy install command">copy</button> </div> <pre id="install">$ go install github.com/danmestas/go-libfossil/cmd/libfossil@latest $ libfossil --help</pre> </div> <p>That is the entire install. The binary is pure-Go, statically linked, and CGo-free — drop it on any Linux/macOS/Windows host with no runtime dependency.</p> </section> <section> <span class="num">§02</span> <h2>What it is <span class="anchor">overview</span></h2> <p>libfossil is a pure-Go reader and writer for <a href="https://fossil-scm.org">Fossil SCM</a> repositories — the SQLite-backed <code>.fossil</code> files used by Fossil. It exposes a Go API for opening repos, reading the timeline, committing, and syncing over HTTP, plus a <code>libfossil</code> CLI driving the same code paths.</p> <p>No CGo means static binaries that cross-compile cleanly. A <code>GOOS=wasip1</code> build runs in browsers and edge runtimes. Two SQLite drivers (<code>modernc</code>, <code>ncruces</code>) are swappable depending on whether you want pure-Go ergonomics or a smaller WASM footprint.</p> <ul> <li><strong>One library, three pluggable axes.</strong> Swap the SQLite driver (<code>modernc</code>/<code>ncruces</code>), the sync transport (HTTP/custom), or the observer (OTel/custom) without touching application code.</li> <li><strong>Static everywhere.</strong> Static binaries cross-compile cleanly. WASM works.</li> <li><strong>Embeddable.</strong> Drop <code>libfossil.Open</code> into any Go service that needs to read <code>.fossil</code> repos — build pipelines, mirror servers, code-search indexers.</li> </ul> </section> <section> <span class="num">§03</span> <h2>Why you might want it <span class="anchor">why</span></h2> <p>Three reasons to reach for libfossil instead of shelling out to the upstream <code>fossil</code> binary or using CGo bindings.</p> <p><strong>You're embedding Fossil into a Go service.</strong> Build pipelines, code-search indexers, mirror servers, and AI-agent tools that read repository history all benefit from a library API instead of subprocess parsing. No exec calls, no version drift between hosts, observable through Go's standard <code>context.Context</code> and OpenTelemetry hooks.</p> <p><strong>You're shipping to constrained targets.</strong> Static binaries cross-compile in seconds; CGo bindings don't. WASM compilation works out of the box. The cost is a slightly slower SQLite (modernc is pure Go); the upside is the binary runs on any target without a build farm.</p> <p><strong>You want one library, not five subprocesses.</strong> Calling <code>fossil</code> over <code>os/exec</code> means re-parsing CLI text on every request. libfossil exposes typed Go values directly.</p> </section> <section> <span class="num">§04</span> <h2>How it fits together <span class="anchor">architecture</span></h2> <div class="table-scroll"> <svg class="diagram" viewBox="0 0 640 260" role="img" aria-labelledby="arch-title arch-desc" preserveAspectRatio="xMidYMid meet"> <title id="arch-title">go-libfossil architecture</title> <desc id="arch-desc">Callers (cli, library, wasm) invoke methods on the Repo handle. The Repo delegates to three pluggable axes: db driver, transport, observer. Repository state lives on disk in a .fossil SQLite file.</desc> <defs> <marker id="lf-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> <path class="arrowhead" d="M0,0 L10,5 L0,10 Z"/> </marker> </defs> <g class="rule"> <line x1="40" y1="38" x2="160" y2="38"/> <line x1="240" y1="38" x2="400" y2="38"/> <line x1="480" y1="38" x2="600" y2="38"/> </g> <g class="head"> <text x="100" y="28" text-anchor="middle">callers</text> <text x="320" y="28" text-anchor="middle">core</text> <text x="540" y="28" text-anchor="middle">plugins</text> </g> <g class="item"> <text x="100" y="76" text-anchor="middle">cli</text> <text x="100" y="100" text-anchor="middle">library</text> <text x="100" y="124" text-anchor="middle">wasm</text> </g> <rect class="box" x="240" y="60" width="160" height="100"/> <g class="item"> <text x="320" y="88" text-anchor="middle">Open</text> <text x="320" y="112" text-anchor="middle">Sync</text> <text x="320" y="136" text-anchor="middle">Timeline</text> </g> <rect class="box" x="490" y="60" width="100" height="100"/> <g class="item"> <text x="540" y="88" text-anchor="middle">db driver</text> <text x="540" y="112" text-anchor="middle">transport</text> <text x="540" y="136" text-anchor="middle">observer</text> </g> <rect class="box" x="240" y="200" width="160" height="40"/> <g class="item"> <text x="320" y="225" text-anchor="middle">.fossil (SQLite)</text> </g> <g class="flow"> <line x1="160" y1="100" x2="234" y2="100" marker-end="url(#lf-arrow)"/> <line x1="400" y1="88" x2="486" y2="88" marker-end="url(#lf-arrow)"/> <line x1="490" y1="136" x2="404" y2="136" marker-end="url(#lf-arrow)"/> <line x1="320" y1="160" x2="320" y2="196" marker-end="url(#lf-arrow)"/> </g> <g class="flow-label"> <text x="197" y="92" text-anchor="middle">calls</text> <text x="443" y="80" text-anchor="middle">delegate</text> <text x="447" y="154" text-anchor="middle">events</text> <text x="334" y="183" text-anchor="start">persist</text> </g> </svg> </div> <p class="fineprint"><code>Repo</code> is the only handle your code touches. Three pluggable axes around it decide how SQLite runs, how sync talks to remotes, and where telemetry goes — swappable without changing application code.</p> </section> <section> <span class="num">§05</span> <h2>Compared to neighbors <span class="anchor">comparison</span></h2> <div class="table-scroll"> <table class="cmp"> <thead> <tr> <th></th> <th class="col-self">go-libfossil</th> <th>shell-out to fossil</th> <th>CGo libfossil bindings</th> </tr> </thead> <tbody> <tr><td>Runtime deps</td> <td class="col-self">none</td> <td><code>fossil</code> binary in PATH</td> <td>C toolchain + libsqlite3</td></tr> <tr><td>Static binary</td> <td class="col-self">yes</td> <td>host fossil only</td> <td>host SQLite only</td></tr> <tr><td>WASM target</td> <td class="col-self">yes (wasip1)</td> <td>no</td> <td>no</td></tr> <tr><td>Observability</td> <td class="col-self">context + OTel</td> <td>parse stderr</td> <td>none built-in</td></tr> <tr><td>Embeddable in Go</td> <td class="col-self">yes</td> <td>subprocess only</td> <td>yes (with CGo)</td></tr> <tr><td>API stability</td> <td class="col-self">alpha; v0.x</td> <td>stable</td> <td>tracks C upstream</td></tr> </tbody> </table> </div> <p class="fineprint">Comparison reflects libfossil's current scope. The upstream Fossil binary remains the reference implementation; libfossil targets the embedding-into-Go-services niche.</p> </section> <section> <span class="num">§06</span> <h2>What it is not <span class="anchor">limits</span></h2> <p><strong>No GUI.</strong> Fossil ships an embedded web UI; libfossil does not. If you need it, run the upstream <code>fossil</code> binary alongside.</p> <p><strong>No hosting (yet).</strong> libfossil reads and writes <code>.fossil</code> files; it does not run an HTTP server that serves them to other Fossil clients. Mirroring works as a sync target, not a host.</p> <p><strong>No magic schema migrations.</strong> libfossil tracks the upstream Fossil schema. Breaking format changes upstream require a libfossil update before you can open the new format.</p> </section> <section> <span class="num">§07</span> <h2>Get going <span class="anchor">start</span></h2> <div class="code-block"> <div class="code-bar"> <span>shell</span> <button type="button" class="copy-btn" data-copy="quickstart" aria-label="Copy quickstart commands">copy</button> </div> <pre id="quickstart">$ go install github.com/danmestas/go-libfossil/cmd/libfossil@latest $ libfossil --help # CLI commands $ libfossil --version</pre> </div> <p>Walkthrough: <a href="/docs/quickstart/">/docs/quickstart</a> · Architecture: <a href="/docs/architecture/">/docs/architecture</a> · SDK reference: <a href="/docs/reference/sdk/">/docs/reference/sdk</a> · Source: <a href="https://github.com/danmestas/go-libfossil">github.com/danmestas/go-libfossil</a>.</p> </section> </main> <footer> <div>go-libfossil · MIT · v{{ .Site.Params.version | default "0.4.2" }} · {{ now.Year }} · <a href="https://github.com/danmestas/go-libfossil">github.com/danmestas/go-libfossil</a></div> </footer> <script> (function () { var btns = document.querySelectorAll(".copy-btn"); btns.forEach(function (b) { b.addEventListener("click", function () { var t = document.getElementById(b.dataset.copy); if (!t) return; var s = t.textContent.replace(/^\s*\$\s?/gm, "").replace(/\s+#.*$/gm, "").trim(); var done = function () { b.dataset.state = "copied"; b.textContent = "copied"; setTimeout(function () { b.dataset.state = ""; b.textContent = "copy"; }, 1500); }; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(s).then(done).catch(function () { b.textContent = "select+copy"; }); } else { var ta = document.createElement("textarea"); ta.value = s; ta.style.position = "fixed"; ta.style.left = "-9999px"; document.body.appendChild(ta); ta.select(); try { document.execCommand("copy"); done(); } catch (e) { b.textContent = "select+copy"; } document.body.removeChild(ta); } }); }); })(); </script> </body> </html>