# go-libfossil Documentation (Full)
> Every documentation page concatenated in sidebar order. Curated overview: https://libfossil-docs.daniel-mestas.workers.dev/llms.txt
---
# Documentation
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/
go-libfossil reads, writes and syncs Fossil repositories in Go, without cgo. It ships as a Go module and as the `libfossil` command.
- **[Get started](/docs/get-started/)**: install, a first repository from the command line, the library from Go, and a first sync.
- **[Concepts](/docs/concepts/)**: artifacts, the derived tables, the sync protocol, sub-manifests, sparse cones, horizons and signing.
- **[Guides](/docs/guides/)**: production use, migrating from fossil, embedding the CLI, extension points and testing.
- **[Reference](/docs/reference/)**: the Go API, the command line, settings, and conformance with the drafts.
- **[Examples](/docs/examples/)**: runnable programs in the repository.
---
# Get started
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/get-started/
Install the `libfossil` command, make a first repository from the command line, embed the library in a Go program, and sync with another repository.
- [Install](/docs/get-started/install/): the command, the Go module, and the SQLite driver.
- [Quickstart: the CLI](/docs/get-started/quickstart/): create a repository, commit, and read it back with stock `fossil`.
- [Embed in Go](/docs/get-started/embed-in-go/): create, commit, open and read history from Go.
- [First sync](/docs/get-started/first-sync/): serve a repository, clone it, pull and push.
---
# Install
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/get-started/install/
go-libfossil is a Go module and a command. Neither needs cgo, a C toolchain or an installed `fossil`.
## The command
```sh
go install github.com/danmestas/go-libfossil/cmd/libfossil@latest
libfossil --help
```
This needs the Go toolchain named in `go.mod` (Go 1.26 or later). The binary is statically linked and runs on Linux, macOS and Windows. Every command is listed in the [CLI reference](/docs/reference/cli/).
## The library
```sh
go get github.com/danmestas/go-libfossil@latest
```
The library talks to SQLite through `database/sql` and does not pick a driver for you. Blank-import exactly one of the two driver modules in your `main` package; importing both panics at start-up.
| Driver | Import | Use it for |
|---|---|---|
| modernc | `_ "github.com/danmestas/go-libfossil/db/driver/modernc"` | servers, command-line tools, desktop: SQLite translated to Go |
| ncruces | `_ "github.com/danmestas/go-libfossil/db/driver/ncruces"` | `GOOS=js` and `GOOS=wasip1` builds: SQLite itself as WebAssembly |
Each driver is its own module, so `go get` it too:
```sh
go get github.com/danmestas/go-libfossil/db/driver/modernc@latest
```
## WebAssembly
With the ncruces driver the module builds for `GOOS=js GOARCH=wasm` (a browser) and `GOOS=wasip1 GOARCH=wasm` (any WASI Preview 1 host). CI builds the module's packages for `js/wasm` on every change. The [wasm example](https://github.com/danmestas/go-libfossil/tree/main/examples/wasm) builds and runs a WASI module.
### TinyGo
The whole public API, the HTTP sync client and signing included, also builds with [TinyGo](https://tinygo.org) for the `wasm` target, which is what makes it small enough to ship with a page or an edge function:
```
tinygo build -target=wasm -no-debug -opt=z -stack-size=1MB -o libfossil.wasm ./examples/tinygo/
wasm-opt -Oz --detect-features --strip-debug --strip-producers libfossil.wasm -o libfossil.wasm
```
- **Size:** for [`examples/tinygo`](https://github.com/danmestas/go-libfossil/tree/main/examples/tinygo), about 1.3 MB gzipped as CI builds it, against about 4.1 MB from the standard toolchain. A program without sync is smaller.
- **Driver:** a TinyGo build imports no SQLite driver. The host registers a `database/sql` driver over its own SQLite; ncruces is for standard Go builds and modernc has no wasm build.
- **Stack:** `-stack-size=1MB`, because TinyGo's default goroutine stack is too small for the deeper paths.
- **Differences:** under TinyGo a sync redirect ends the request with an error instead of being followed, and DNS failures are not classified as transient.
CI builds `examples/tinygo` with TinyGo 0.43 on every change.
## Next
- [Quickstart: the CLI](/docs/get-started/quickstart/)
- [Embed in Go](/docs/get-started/embed-in-go/)
---
# Quickstart: the CLI
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/get-started/quickstart/
Create a repository, commit a file, and read the result with stock `fossil`. The `libfossil` command drives the same code as the library, and the file it writes is an ordinary Fossil repository.
## Create a repository and a checkout
```sh
libfossil repo new notes.fossil
mkdir work && cd work
libfossil -R ../notes.fossil repo open .
```
```text
created repository: notes.fossil
opened checkout in . (repo: /home/ana/notes.fossil)
empty repository -- no checkins yet
```
`repo open` writes a `.fslckout` file in the directory. Commands run inside the checkout find the repository through it, so `-R` is only needed to open it.
## Commit
```sh
echo '# notes' > README.md
libfossil repo add README.md
libfossil repo ci -m "first notes" --user ana
```
```text
ADDED README.md
checkin 542a963405 (rid=2)
```
The check-in name is the hash of the manifest, which includes the time, so yours differs. `--user` sets the name on the check-in; without it the OS user name is used.
## Read the history
```sh
libfossil repo timeline
```
```text
542a963405 2026-10-23 21:00 ana first notes
```
The repository is a Fossil repository, so the `fossil` binary reads it unchanged:
```sh
fossil timeline -R ../notes.fossil
```
```text
=== 2026-10-23 ===
21:00:14 [542a963405] first notes (user: ana tags: trunk)
+++ no more data (1) +++
```
## Next
- [Embed in Go](/docs/get-started/embed-in-go/): the same steps from a Go program.
- [First sync](/docs/get-started/first-sync/): serve the repository and clone it.
- [CLI reference](/docs/reference/cli/): every command and flag.
- [Migrating from fossil](/docs/guides/migrating-from-fossil/): how the commands map to fossil's.
---
# Embed in Go
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/get-started/embed-in-go/
The `libfossil` package does what the command does, as typed Go values instead of command output. This program creates a repository, commits a file and reads the history back.
## Set up
```sh
mkdir notes && cd notes
go mod init example.com/notes
go get github.com/danmestas/go-libfossil@latest github.com/danmestas/go-libfossil/db/driver/modernc@latest
```
The blank import of `db/driver/modernc` registers the SQLite driver. A program registers exactly one; see [Install](/docs/get-started/install/) for the choice.
## Create, commit, read
```go
package main
import (
"fmt"
"log"
"github.com/danmestas/go-libfossil"
_ "github.com/danmestas/go-libfossil/db/driver/modernc"
)
func main() {
r, err := libfossil.Create("notes.fossil", libfossil.CreateOpts{User: "ana"})
if err != nil {
log.Fatal(err)
}
defer r.Close()
rid, uuid, err := r.Commit(libfossil.CommitOpts{
Files: []libfossil.FileToCommit{{Name: "README.md", Content: []byte("# notes\n")}},
Comment: "first notes",
User: "ana",
})
if err != nil {
log.Fatal(err)
}
fmt.Println("committed", uuid[:10], "rid", rid)
entries, err := r.Timeline(libfossil.TimelineOpts{Limit: 10})
if err != nil {
log.Fatal(err)
}
for _, e := range entries {
fmt.Println(e.UUID[:10], e.User, e.Comment)
}
files, err := r.ListFiles(rid)
if err != nil {
log.Fatal(err)
}
for _, f := range files {
fmt.Println(f.Name)
}
}
```
```sh
go run .
```
```text
committed ee68ff62c5 rid 2
ee68ff62c5 ana first notes
README.md
```
`Commit` returns the check-in's row id in the `blob` table and its full name. `CommitOpts.Files` lists file contents directly; no checkout or working directory is involved. To commit on top of an existing check-in, set `ParentID`: the files you pass replace the parent's entries of the same name and the rest of the parent's tree is carried forward, as `fossil commit` does.
The file is an ordinary Fossil repository: `fossil timeline -R notes.fossil` shows the same check-in.
## Open an existing repository
```go
r, err := libfossil.Open("notes.fossil")
if err != nil {
log.Fatal(err)
}
defer r.Close()
tip, err := r.BranchTip("trunk")
if err != nil {
log.Fatal(err)
}
files, err := r.ListFiles(tip)
```
`Open` fails if the file does not exist; `Create` fails if it does. Reads through one `*Repo` may run concurrently; writes serialize in SQLite. Close the handle once, when you are done with it. [Production patterns](/docs/guides/production/) covers error handling, contexts and handle lifetime.
## Next
- [First sync](/docs/get-started/first-sync/): clone, pull and push over HTTP.
- [SDK reference](/docs/reference/sdk/libfossil/api/): every exported type and function.
- [Examples](/docs/examples/): runnable programs in the repository.
---
# First sync
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/get-started/first-sync/
Two repositories exchange check-ins with Fossil's sync protocol over HTTP. `libfossil repo serve` is a sync server; `Clone`, `Repo.Sync` and `Repo.Pull` are the client. Either side can be stock `fossil`. [Sync](/docs/concepts/sync/) explains the protocol.
## Serve a repository
```sh
libfossil -R notes.fossil repo serve
```
```text
serving /home/ana/notes.fossil on http://127.0.0.1:8080 (Ctrl-C to stop)
```
`--addr` changes the bind address. The server answers sync requests only; it has no web interface.
An unauthenticated client gets the capabilities of the `nobody` user, which a new repository sets as `fossil new` does: clone and read, no check-in. To let a client push, give a user the check-in capability `i` and a password:
```sh
libfossil -R notes.fossil repo user add --cap=i bo
libfossil -R notes.fossil repo user passwd bo
```
Both commands print the user's generated password.
## Clone
From the command line, with either tool:
```sh
libfossil repo clone http://127.0.0.1:8080 copy.fossil
fossil clone http://127.0.0.1:8080 copy2.fossil
```
```text
Cloned into copy.fossil
Rounds: 2
Blobs: 2
Project-code: ec1581be7066e95dd3b9ee14b91ab3f258221ce4
```
`repo clone` also clones part of a repository: `--cone` by path and `--horizon` by age. See [Sparse cones](/docs/concepts/sparse-cones/) and [Horizons](/docs/concepts/horizons/).
## Clone, commit, push and pull from Go
```go
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/danmestas/go-libfossil"
_ "github.com/danmestas/go-libfossil/db/driver/modernc"
)
func main() {
ctx := context.Background()
url := "http://127.0.0.1:8080"
r, res, err := libfossil.Clone(ctx, "copy.fossil", libfossil.NewHTTPTransport(url), libfossil.CloneOpts{})
if err != nil {
log.Fatal(err)
}
defer r.Close()
fmt.Println("cloned", res.BlobsRecvd, "blobs in", res.Rounds, "rounds")
tip, err := r.BranchTip("trunk")
if err != nil {
log.Fatal(err)
}
if _, _, err := r.Commit(libfossil.CommitOpts{
Files: []libfossil.FileToCommit{{Name: "TODO.md", Content: []byte("- ship\n")}},
Comment: "add a todo",
User: "bo",
ParentID: tip,
}); err != nil {
log.Fatal(err)
}
pushed, err := r.Sync(ctx, libfossil.NewHTTPTransport(url), libfossil.SyncOpts{
Push: true, Pull: true, User: "bo", Password: os.Getenv("NOTES_PASSWORD"),
})
if err != nil {
log.Fatal(err)
}
fmt.Println("sent", pushed.FilesSent, "received", pushed.FilesRecvd)
pulled, err := r.Pull(ctx, url, libfossil.PullOpts{})
if err != nil {
log.Fatal(err)
}
fmt.Println("pull rounds", pulled.Rounds)
}
```
```sh
NOTES_PASSWORD=... go run .
```
```text
cloned 2 blobs in 2 rounds
sent 2 received 0
pull rounds 1
```
`Clone` creates the file and fails if it exists. `Sync` with `Push` and `Pull` runs one session in both directions until neither side has anything the other lacks; `Pull` is a pull-only shorthand that takes a URL. Without `User`, a session runs as `nobody`. Pass a context with a deadline in a service: a stalled peer otherwise holds the call open.
The commit made in the clone went to the origin:
```sh
libfossil -R notes.fossil repo timeline
```
```text
fb5af4c078 2026-10-23 21:02 bo add a todo
ee68ff62c5 2026-10-23 21:02 ana first notes
```
## Next
- [Sync](/docs/concepts/sync/): the cards and rounds behind these calls.
- [Production patterns](/docs/guides/production/): contexts, errors and telemetry for a long-running service.
- [Signing](/docs/concepts/signing/): signed check-ins and signed horizon seeds.
---
# Concepts
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/concepts/
How a Fossil repository is put together, and how go-libfossil implements it. Each page names the Internet-Draft in [`conformance/rfcs/`](https://github.com/danmestas/go-libfossil/tree/main/conformance/rfcs) that specifies the behaviour.
- [Architecture](/docs/concepts/architecture/): packages, drivers, observers and the simulator.
- [Artifacts](/docs/concepts/artifacts/): the text manifests that make up history, and how they are named.
- [Repository state](/docs/concepts/repository-state/): the tables derived from the artifacts.
- [Sync](/docs/concepts/sync/): the card protocol that clones, pulls and pushes.
- [Sub-manifests](/docs/concepts/sub-manifests/): check-ins written as trees instead of flat file lists.
- [Sparse cones](/docs/concepts/sparse-cones/): holding part of a repository by path.
- [Horizons](/docs/concepts/horizons/): holding part of a repository by age.
- [Signing](/docs/concepts/signing/): clear-signed check-ins and signed horizon seeds.
---
# Architecture
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/concepts/architecture/
## Overview
libfossil is a pure-Go library and CLI that reads and writes Fossil SCM's
`.fossil` SQLite repositories. The public surface (package `libfossil`)
exposes `Repo`, `Create`, `Open`, `Clone`, checkout, and check-in
operations; internals live under `internal/`. SQLite access is mediated
by a pluggable driver layer in `db/`, with two shippable drivers
(`modernc`, `ncruces`) selected by blank import. Instrumentation is
pulled in by implementing the `SyncObserver` / `CheckoutObserver`
interfaces; an optional OTel adapter lives in its own submodule. A
deterministic simulator under `dst/` plus a `BUGGIFY` fault-injection
harness in `simio/` exercise concurrent repo operations under a seed.
## Module layout
The library is a Go workspace (`go.work`) of four modules: the root, the
two driver modules, and the OTel observer. (The documentation site under
`docs/site` has its own `go.mod` and is not in the workspace.) Layout:
- **Root package `libfossil`** — thin facade over `internal/repo`,
`internal/sync`, and `internal/checkout`. Main files: `fossil.go`
(Create, Open, Clone), `repo.go` (Repo handle + DB accessor),
`checkout.go`, `checkout_sparse.go` (cones and horizons),
`repo_checkout.go`, `repo_merge.go`, `repo_sync.go`, `repo_pull.go`,
`repo_sparse.go`, `repo_submanifest.go`, `repo_history.go`,
`repo_admin.go`, `repo_extras.go`, `required_content*.go`,
`horizon_*.go`, `signing.go`, `transport.go`, `observer.go`, `types.go`,
`errors.go`, `julian.go`.
- **`cli/`** — Kong command definitions. `repo.go` aggregates 43
subcommands (`new`, `clone`, `ci`, `co`, `timeline`, `diff`, `merge`,
`tag`, `branch`, `uv`, `stash`, `bisect`, `annotate`, `user`, `serve`,
`cone`, `submanifests`, etc.). Intended to be embedded by any `main` that
wants a fossil-shaped CLI.
- **`cmd/libfossil/`** — the shipped binary. `main.go` wires Kong and
dispatches to `cli`; `driver_modernc.go` and `driver_ncruces.go` select
the driver by build tag (`modernc` by default, `ncruces` under
`test_ncruces`).
- **`internal/`** — private implementation, grouped by concern:
- Storage layer: `blob`, `content`, `manifest`, `deck`, `delta`,
`hash`, `fsltype`, `repo` (schema + DB boot), `tag`, `leaf`,
`clearsign`.
- Sparse and horizon stores: `cone`, `horizon` (held set and seed).
- Working-tree operations: `checkout` (checkin, extract, update,
revert, fork, rename, status), `vfile`, `merge` (three-way, ancestor,
detect, resolve, strategies), `diff`, `annotate`, `undo`, `stash`,
`bisect`, `branch`, `path`.
- Sync / transport: `sync` (client, handler, clone, session,
ckin-lock, serve-http, table-sync, UV, sparse and horizon serving),
`xfer` (card codec).
- Housekeeping: `auth`, `shun`, `verify`, `search`, `uv`, `testdriver`.
- **`db/`** — SQLite abstraction (see below).
- **`db/driver/modernc`**, **`db/driver/ncruces`** — separate modules,
each registers exactly one `database/sql` driver.
- **`observer/otel/`** — separate module implementing the observer
interfaces against OpenTelemetry.
- **`dst/`** — deterministic simulator (`simulator.go`, `node.go`,
`network.go`, `peer_network.go`, `mock_fossil.go`, `invariants.go`,
`event.go`) with scenario-style tests.
- **`simio/`** — deterministic IO primitives: `SimClock`, seeded
`Rand`, in-memory storage, and the global `Buggify` switch.
- **`testutil/`** — shared test helpers for the rest of the tree.
## Storage model
A `.fossil` file is a SQLite database. All repository state — blobs,
manifests, tags, branches, unversioned files, tickets, wiki, config,
users — lives in that file. libfossil does not maintain any sidecar
state for the repo itself (a working tree has its own SQLite DB).
Content is stored **content-addressed** in the `blob` table, keyed by
either SHA1 or SHA3-256 names (the repository's hash policy picks which
names new content; see [Artifacts](/docs/concepts/artifacts/)). Every blob is
zlib-compressed with a 4-byte big-endian uncompressed-size prefix;
`internal/blob.Compress` and `Decompress` handle that framing. Blobs may
be stored as full content or as deltas against a parent; reconstruction
walks the chain and applies deltas via `internal/content.Expand`, with
cycle detection. `internal/content.Verify` rehashes expanded content
against the stored UUID.
**Structural artifacts** are themselves blobs whose text describes a
check-in (a manifest), a tag change, a wiki edit, a ticket change, an
attachment, a technote, a forum post, or a cluster. `internal/deck`
parses and marshals them and `internal/manifest` builds check-ins;
"crosslinking" (`internal/manifest/crosslink.go`) is the step that
projects an artifact's effects into derived tables (`mlink`, `plink`,
`event`, `tagxref`, etc.) so queries like timeline and checkout work
without re-parsing blobs. See [Repository state](/docs/concepts/repository-state/).
## Driver layer
`db/` is a thin wrapper over `database/sql`. See
[`db/doc.go`](https://github.com/danmestas/go-libfossil/blob/main/db/doc.go). A driver registers at `init` time via
`db.Register(DriverConfig{Name, BuildDSN})`; exactly one registration is
allowed (a second call panics). Library embedders blank-import exactly one
driver. `db.Open` / `db.OpenWith` look up the registered driver, run
`BuildDSN(path, pragmas)` to produce the DSN, and then call `sql.Open` with
that driver name. Both shipped drivers construct
`file:?_pragma=k(v)&...` DSNs — differing only in the `database/sql`
driver name they register under (`"sqlite"` for modernc, `"sqlite3"` for
ncruces).
Shipped drivers:
- **`db/driver/modernc`** — default for `cmd/libfossil`. Blank-imports
`modernc.org/sqlite`, a pure-Go, CGo-free translation of SQLite with full
filesystem access.
- **`db/driver/ncruces`** — blank-imports `github.com/ncruces/go-sqlite3`,
which runs SQLite as WebAssembly. Has a `ncruces_js.go` variant for
`GOOS=js` / browser targets. Used for `GOOS=wasip1` builds and by
`go build -tags test_ncruces ./cmd/libfossil`.
## Sync protocol
`internal/sync` implements Fossil's sync-over-HTTP protocol as a client
and a stateless server, with the card codec in `internal/xfer`. See
[Sync](/docs/concepts/sync/) for the protocol and where each part lives.
## Observer model
The core keeps instrumentation as a non-goal; hooks are plain Go
interfaces on the public API. `SyncObserver` and `CheckoutObserver` in
[`observer.go`](https://github.com/danmestas/go-libfossil/blob/main/observer.go) define lifecycle events (session
start/end, per-round stats, extract/scan/commit phases). `NopSyncObserver`
/ `NopCheckoutObserver` are zero-cost defaults; `StdoutSyncObserver` /
`StdoutCheckoutObserver` log to stderr for quick debugging.
For OpenTelemetry, [`observer/otel`](https://github.com/danmestas/go-libfossil/tree/main/observer/otel/) is a separate Go
module so the core doesn't pull in the OTel SDK. Users import it
explicitly when they want traces, metrics, or structured logs.
See [Extension Points](/docs/guides/extension-points/) for the full observer
contract and a worked example of a custom observer.
## DST / simulation
`dst/` runs many Fossil nodes deterministically under a seed. A
`Simulator` (`dst/simulator.go`) owns a priority-queue event loop, a
`SimNetwork`, a `simio.SimClock`, and a set of `Node` actors. Scenarios
schedule clones, check-ins, merges, UV pushes, and partitions, and
tests assert invariants (`dst/invariants.go`, `crosslink_completeness_test.go`).
Fault injection has two forms. `simio.Buggify(p)` is a global
probability gate that returns `false` in production but flips `true`
with probability `p` once the simulator enables it; the blob and content
layers use it. The sync code instead asks a `BuggifyChecker` passed in
its options, at named sites such as `clone.emitCloneBatch.truncate` and
`handler.handleGimme.skip`; a nil checker injects nothing. `SeededBuggify`
in `dst` implements `BuggifyChecker` from a seeded random source.
See [Testing](/docs/guides/testing/) for running the DST matrix, seed sweeps,
and the driver test matrix.
---
# Artifacts
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/concepts/artifacts/
Everything a Fossil repository records is an **artifact**: a run of bytes, named by its own hash, that is never changed once written. File contents are artifacts. So are check-ins, tags, wiki pages and tickets, which are written as text **cards**. The format is `draft-fossil-artifact-format-00`.
## Names
An artifact's name is the hash of its exact stored bytes, with nothing added or stripped (§3.1). A 40-digit name is SHA-1; a 64-digit name is SHA3-256. The length alone tells which (§3.2). A signed artifact's name covers its signature wrapper too, so signing a check-in changes its name.
The repository's **hash policy** decides which algorithm names new content (§7.1):
| Policy | Names new content with |
|---|---|
| `sha1`, `auto` | SHA-1 |
| `sha3`, `sha3-only`, `shun-sha1` | SHA3-256 |
A repository with no policy set uses `sha3` unless it holds SHA-1 names and no SHA3 ones, in which case it uses `auto` (§7.2). A new repository is `sha3`. Names already assigned never change (§7.6). `Repo.SetHashPolicy(name)` sets the policy as `fossil hash-policy` does.
## Structural artifacts and cards
A structural artifact is lines of cards: a capital letter, then space-separated arguments, with the cards in sorted order and a `Z` card last (§4). There are eight types, and each allows a fixed set of cards (§5.1, §5.2):
| Type | External name | Allowed cards |
|---|---|---|
| MANIFEST | `checkin` | B C D F N P Q R T U Z |
| CLUSTER | `cluster` | M Z |
| CONTROL | `tag` | D T U Z |
| WIKI | `wiki` | C D L N P U W Z |
| TICKET | `ticket` | D J K U Z |
| ATTACHMENT | `attachment` | A C D N U Z |
| EVENT | `technote` | C D E N P T U W Z |
| FORUM | `forumpost` | D G H I N P U W Z |
Any other byte string is plain content, such as a file's bytes. A parser rejects a structural artifact with a card its type does not allow, or without one its type requires.
[Sub-manifests](/docs/concepts/sub-manifests/) add an `S` card and a ninth type.
## A check-in manifest
This is a check-in made with `libfossil repo ci`:
```
C unsigned
D 2026-10-23T20:45:21.324
F docs/x.md a09fd4d0069e85b78c5234233ffb67179d30e19c2ec94629d224257a920c1234
F src/a.c 7eae391285a140af77b34475a35c4790095acb1f0f5e6ec7e10e962b2405de07
F src/vendor/v.c bedef797cd3184c3c7e620c5c231bbdd523f52ab19ca6301d037c7f2a3f99780
P 2afc4615dadbd7743716f8037223578d7d07a79ebc2cbfd5ba1b6028f133c7f2
R 43fe2394e6714e382e2f6563c13cf020
U dmestas
Z 56b668a3573378eb3e2387902909b385
```
- `C`: the check-in comment.
- `D`: the date, UTC.
- `F`: one per file: path and content hash, then optional permissions and an old name for a rename.
- `P`: the parent check-ins. The first is the primary parent; the rest are merge parents.
- `R`: an MD5 over every file's name, size and content (§6.3). A commit reads its manifest back and checks it. Over a sparse store there is no `R` card.
- `U`: the user.
- `Z`: the MD5 of every byte before it (§6.2).
The first check-in on a branch also carries `T` cards, such as `T *branch * trunk` and `T *sym-trunk *`. A **delta manifest** carries a `B` card naming a baseline manifest, and its `F` cards record only the differences from it (§8).
The name of this check-in is the SHA3-256 of exactly these bytes: `libfossil repo hash --sha3` over the output of `repo cat` prints `7507cc8c3e...`, the name the timeline shows.
## Deltas
Content is stored in the `blob` table, zlib-compressed behind a 4-byte size prefix. A blob can also be stored as a **delta** against another blob, with the pair recorded in the `delta` table (`rid`, `srcid`). Reading it applies the chain of deltas back to a full blob, and the result must hash to the blob's name.
A delta is a header with the target length, a run of copy and insert segments, and a trailer with a checksum of the result (`draft-fossil-delta-format-00` §3). Integers are written in base 64. A delta does not name its source; the storage layer records that (§1).
As in fossil, deltas run backwards: a new version is stored whole and its predecessor is rewritten as a delta against it, so the newest version of a file is always one read away.
## Where the code is
- `internal/deck`: parse and marshal cards, the `Z` card, signature-wrapper stripping.
- `internal/manifest`: build check-ins, compute the `R` card, crosslink.
- `internal/delta`: create and apply deltas (`Create`, `Apply`).
- `internal/hash`: SHA-1 and SHA3-256 names and the hash policy.
- `internal/blob` and `internal/content`: compressed storage, delta chains, verification.
---
# Repository state
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/concepts/repository-state/
[Artifacts](/docs/concepts/artifacts/) are the only facts a repository records. Everything else, such as which check-in is the tip of trunk, which branch a check-in is on, or which leaves are open, follows from them. `draft-fossil-repo-state-00` specifies that derivation, so two implementations reading the same artifacts reach the same answers. The one exception is the current trunk, which also reads the repository's `main-branch` setting (§9).
## The tables
A `.fossil` file stores the artifacts in `blob` and `delta`, and keeps the derived state in tables beside them, so a query does not re-parse every artifact.
| Table | Holds |
|---|---|
| `blob` | Every artifact, compressed, by `rid` and name (`uuid`). A row with no content is a phantom. |
| `delta` | Which blobs are stored as deltas, and against what. |
| `phantom` | Names the repository knows of but does not hold yet. |
| `event` | One row per check-in, tag, wiki edit, ticket change and so on: the timeline. |
| `plink` | Parent links between check-ins, with which parent is primary (§3). |
| `mlink` | Which file changed in which check-in, and from which earlier version. |
| `filename` | The file names `mlink` refers to. |
| `tag`, `tagxref` | Tag names, and each tag's effect on each check-in: cancel, singleton or propagating (§5). |
| `leaf` | Check-ins with no child on their own branch (§7). |
| `orphan` | Delta manifests whose baseline is absent. |
`blob` and `delta` are the source. The others are derived.
## Crosslinking
**Crosslinking** reads one artifact and writes its effects into the derived tables (§4). A check-in adds its `plink` and `mlink` rows and its `event` row. Its `T` cards, or a control artifact's, set `tagxref` rows and propagate along primary children until a cancel or another value blocks them (§5.4). Leaves are rechecked as parents gain children and as tags move (§7.6).
Artifacts can arrive in any order. Sync, clone, a commit and a rebuild all crosslink, and an artifact whose parent has not arrived yet is linked again when it does.
Branches are not stored as such. A check-in's branch is the value of its `branch` tag, inherited from its primary parent unless a tag on it says otherwise (§6). Name resolution (§8) turns a branch name, tag or `trunk` into one check-in: `Repo.ResolveVersion(name)`. `Repo.CurrentTrunk()` returns the tip of the main branch (§9).
## Rebuild gives the same rows
Because the derived tables are a function of the artifacts, they can be thrown away and computed again. go-libfossil's rebuild (`verify.Rebuild`, internal) empties the crosslinked tables and runs the same crosslink sweep a clone runs, so derived state has one owner. The tables it refills are `event`, `plink`, `leaf`, `mlink`, `tagxref`, `forumpost`, `attachment`, `backlink`, `cherrypick` and `orphan`, plus the sync bookkeeping tables `unclustered` and `unsent`.
The conformance suite checks this against fossil itself:
- Each scripted case in `conformance/repostate_*_test.go` loads a sequence of artifacts into go-libfossil (one sweep, one artifact at a time, and through rebuild) and into fossil(1). The rows must equal the expectation written from the draft, and fossil's rows must equal it too: its per-artifact crosslink for a crosslink case, `fossil rebuild` on a copy for a rebuild case. The compared tables are `plink`, `tagxref`, `event`, `phantom`, `leaf`, `orphan` and `mlink`.
- `TestRepoStateCorpus` takes real repositories (named in `CONFORMANCE_REPOS`) and derives their tables three ways: `fossil rebuild` on one copy, as the reference; go-libfossil's rebuild on a second; a crosslink sweep over emptied tables on a third. It compares `event`, `leaf`, `mlink`, `phantom`, `plink` and `tagxref` column by column. It runs only when fossil(1) is present.
Where fossil departs from the draft text, the case records it and names the draft change proposed. The requirements are listed in `conformance/requirements/repostate.tsv`.
## Where the code is
- `internal/manifest`: crosslinking (`crosslink.go`), re-derivation (`rederive.go`), the timeline.
- `internal/tag`: tag application and propagation.
- `internal/leaf`: the leaf table.
- `internal/verify`: integrity checks and rebuild.
- `internal/repo`: the schema and the `main-branch` setting.
---
# Sync
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/concepts/sync/
Two repositories exchange artifacts with Fossil's sync protocol: text cards carried in HTTP POST bodies, one request and one reply per round. go-libfossil implements both ends, and talks to stock fossil, zit and other go-libfossil peers. The governing text is `draft-fossil-sync-protocol-00`.
## Messages and cards
A message is a list of cards, one per line: a card name, then space-separated arguments, and for content cards a byte payload after the line (§5). Unknown pragmas are ignored, so a peer can add new ones without breaking old ones (§3.5).
The client POSTs each request to the repository URL (§4). The body is a 4-byte big-endian length followed by a zlib stream (§4.1). A clone reply may be sent uncompressed, because its `cfile` payloads are already compressed.
The cards that move artifacts:
| Card | Meaning | Draft |
|---|---|---|
| `igot HASH` | I hold this artifact. `igot HASH 1` marks it private. | §8.6 |
| `gimme HASH` | Send me this artifact. | §8.6 |
| `file HASH SIZE` | The artifact's bytes, or a delta against another artifact (`file HASH DELTASRC SIZE`). Used by push and pull. | §7.1 |
| `cfile HASH USIZE CSIZE` | The artifact as the sender stores it, compressed, sent verbatim. Used by clone. | §7.2 |
| `push` / `pull` | The operation, with the server and project codes. | §3.2 |
| `clone 3 SEQNO` / `clone_seqno N` | The clone loop's position. | §8.2 |
| `login USER NONCE SIGNATURE` | Authentication. | §6.1 |
| `pragma NAME ...` | Version negotiation and extensions. | §9 |
| `error` / `message` | Errors and notices. | §10 |
Unversioned files travel as `uvigot`, `uvgimme` and `uvfile` (§7.3, §8.5.1). Configuration travels as `reqconfig` and `config` (§9.3).
## Rounds
A session is a loop of rounds. Each round the client says what it holds (`igot`) and what it wants (`gimme`), and sends the content the server asked for in the last reply. The server answers in kind. A `gimme` is never sent for a shunned artifact or one the peer has said it lacks (§8.8).
The client decides when to stop. It issues another round only while the last round made progress: it stored content and still has phantoms (artifacts it knows by name but does not hold), it queued content to send, or one of the unversioned-file or clone conditions holds (§3.7). Phantoms alone do not keep the loop running. The server sends no completion card.
## Clone, pull, push
- **Clone** starts with `clone 3 1`. The server sends its stored blobs as `cfile` cards in batches and a `clone_seqno` saying where to continue; zero ends the transfer and one cleanup round follows (§8.1 to §8.4). Artifacts the rounds leave missing stay phantoms, as with fossil's clone, unless `CloneOpts.PullPhantoms` is set.
- **Pull** sends `pull` each round and receives what it lacks.
- **Push** sends `push` each round and answers the server's `gimme` cards with `file` cards.
- **Sync** sends both.
After artifacts arrive they are [crosslinked](/docs/concepts/repository-state/), so branches, tags and leaves follow from the artifacts each side holds, not from anything sent on the wire (§8.6.1).
## Login
A named user signs each request (§6). `NONCE` is the SHA-1 of the request body after the login line. The shared secret is `SHA1(project-code "/" login "/" password)`, and `SIGNATURE` is `SHA1(NONCE || shared-secret)`. The first clone round carries no login. A request with no login runs as `nobody`. Once the server announces a version that reads it, the login moves to the `x-f-l-c` cookie (§6.5).
The server checks the user's capabilities from its `user` table (§6.6). In a new repository `nobody` may clone and pull but not push; grant more with `Repo.SetCaps`.
## The server is stateless
Each request is handled on its own (§8.9). The server keeps no session between rounds, so any number of clients can sync against one repository, and a server can sit behind any HTTP stack.
```go
repo, err := libfossil.Open("repo.fossil")
if err != nil {
log.Fatal(err)
}
defer repo.Close()
mux := http.NewServeMux()
mux.HandleFunc("/", repo.XferHandler())
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {})
log.Fatal(http.ListenAndServe(":8080", mux))
```
- `Repo.XferHandler()` returns an `http.HandlerFunc` that decodes a request, handles it and encodes the reply. A non-POST request gets a plain page.
- `Repo.ServeHTTP(ctx, addr)` runs a server on `addr` until `ctx` is cancelled. `libfossil repo serve --addr 127.0.0.1:8080` does the same from the command line.
- `Repo.HandleSync(ctx, payload)` and `HandleSyncWithOpts` take and return raw message bytes, for an embedder with its own transport. Over HTTP, pass the `x-f-l-c` cookie in `HandleOpts.LoginCookie` and the client address in `HandleOpts.RemoteAddr`; `XferHandler` and `ServeHTTP` fill both themselves.
## The client
```go
t := libfossil.NewHTTPTransport("https://example.com/repo")
repo, res, err := libfossil.Clone(ctx, "repo.fossil", t, libfossil.CloneOpts{
User: "alice", Password: "secret",
})
sr, err := repo.Sync(ctx, t, libfossil.SyncOpts{
Push: true, Pull: true, User: "alice", Password: "secret",
})
```
`Transport` is one method, `RoundTrip(ctx, payload) ([]byte, error)`, so any byte pipe works. `NewHTTPTransport` takes `WithHTTPClient` for a custom `http.Client`. `Repo.Pull(ctx, url, PullOpts{})` is a pull-only shortcut over HTTP.
`SyncOpts` also turns on unversioned files (`UV`), private artifacts (`Private`, §7.5), fossil's `--share-links` (`ShareLinks`, §9.5) and go-libfossil's own table sync (`XTableSync`). `SyncResult` reports rounds, files and bytes each way, and errors.
The `libfossil` binary clones (`repo clone`) and serves (`repo serve`). Pull, push and sync are library calls.
## Check-in lock
A client that is about to commit can ask the server to hold the parent with `pragma ci-lock` (§9.4). The server records a `ci-lock-` row in its `config` table and reports a conflicting holder with `ci-lock-fail`. A lock goes stale after the repository's `lock-timeout` setting, 60 seconds when unset (`DefaultCkinLockTimeout`). Fossil uses the same row, so a repository served by both shares one lock table.
## Shunned artifacts
A shunned artifact is never requested, and one that arrives is dropped rather than stored. The shun list itself can travel as the `shun` configuration group (§9.3).
## Extensions
Pragmas carry the extensions. Each is ignored by a peer that does not know it.
- [Sparse cones](/docs/concepts/sparse-cones/): `pragma cone`, answered by `cone-served` (`draft-fossil-sparse-cone-00` §6).
- [Horizons](/docs/concepts/horizons/): `horizon-served`, the seed and the cursor (`draft-fossil-horizon-00` §6).
- [Sub-manifests](/docs/concepts/sub-manifests/): `send-submanifests` and `avoid-submanifests` (`draft-fossil-submanifests-00` §7.2).
## Where the code is
- **Client** (`internal/sync/client.go`, `clone.go`, `session.go`): drives the rounds. Supports clone, pull, push and sync, plus unversioned files and config table sync (`client_tablesync.go`).
- **Server** (`internal/sync/handler.go`, `serve_http.go`, `handler_uv.go`, `handler_tablesync.go`): one request in, one reply out.
- **Cards** (`internal/xfer`): encoding and decoding of messages and cards.
- **Check-in lock** (`internal/sync/ckin_lock.go`).
- **Table sync deletes** (`internal/sync/xdelete.go`): an `xdelete` card removes a synced table row, or leaves a tombstone where the row is absent.
- **Public entry points**: `Clone` in `fossil.go`; `Repo.Sync`, `HandleSync`, `XferHandler` and `ServeHTTP` in `repo_sync.go`; `Repo.Pull` in `repo_pull.go`; `NewHTTPTransport` in `transport.go`.
---
# Sub-manifests
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/concepts/sub-manifests/
A check-in manifest has one `F` card per file in the tree, not per changed file. Its size grows with the width of the tree, and every check-in pays it. Sub-manifests split the file list into a tree of smaller artifacts, so a typical check-in rewrites the root manifest and one leaf. The format is `draft-fossil-submanifests-00`.
Two implementations exist: zit writes, reads and serves sub-manifests, and go-libfossil writes and reads them. The two interoperate. Fossil does not implement the draft (§1.3).
## The tree
A **sub-manifest** carries a contiguous slice of a check-in's sorted file list: `F` cards in a leaf, or `S` cards that point to lower sub-manifests in an interior node. A manifest whose files are given by `S` cards is a **tree manifest**. Walking the tree in order gives exactly the file list a flat manifest would carry (§5.1).
```
S src/a.c 5d0c...
```
An `S` card names the first file of the slice and the sub-manifest that holds it (§3.6). Sub-manifests are a ninth artifact type, `submanifest` (§4.1).
Where the boundaries fall depends only on file names. A name starts a new leaf when the SHA3-256 of the name has at least `k` leading zero bits (§6.1). Editing a file changes one leaf. Adding or removing a name can change a few until the boundaries line up again (§6.5). `k` is never written into an artifact, and readers do not need it.
## Settings
Three repository settings control writing. They travel by configuration exchange: a clone asks for them in its cleanup round and adopts them, and a pull does not (§6.3).
| Setting | Meaning | Default |
|---|---|---|
| `enable-submanifests` | Write new check-ins as trees (§8.1). | off |
| `submanifest-k` | The partition parameter, 1 to 12 (§6.3). | unpinned |
| `submanifest-auto-retune` | Let a commit re-pin `k` when it drifts (§6.4). | off |
When emission is first enabled with no pinned `k`, one is selected by the cost in §6.3 over the file list of the main branch's tip (the newest check-in when no check-in is on the main branch), then refined by replaying the last 200 check-ins. A tree with too few files for any `k` to split has no selection, and enabling fails; give `--k` explicitly. A pinned `k` outside 1 to 12 makes commits fall back to flat manifests with a warning.
A re-tune is forward only. The next commit shares only the sub-manifests whose bytes are the same under both values. With `submanifest-auto-retune` on, a commit re-pins `k` when the best candidate costs more than 15% less than the pinned one, zit's margin. With it off, the drift is only logged.
## Command line
```
$ libfossil -R repo.fossil repo submanifests status
enable-submanifests: off
submanifest-k: (unpinned)
submanifest-auto-retune: off
trees in repository: no
$ libfossil -R repo.fossil repo submanifests enable --k 4
$ libfossil -R repo.fossil repo submanifests retune --k 5
$ libfossil -R repo.fossil repo submanifests disable
```
- `enable [--k N]`: write new check-ins as trees, pinning `k` (selected when `--k` is absent). It prints a warning that fossil peers will be refused the trees.
- `retune --k N`: re-pin `k` for later commits.
- `disable`: write flat manifests again. Existing trees stay.
- `status`: show the three settings and whether the repository holds any tree.
The library calls are `Repo.EnableSubmanifests(k)` (0 selects; it returns the pinned `k` and the warning), `Repo.RetuneSubmanifests(k)` and `Repo.DisableSubmanifests()`.
## Sync
A peer that reads sub-manifests says so with `pragma send-submanifests` in every request and reply (§7.2). go-libfossil always sends it, whatever its settings. A peer that has not sent it is refused every sub-manifest and every tree manifest, with an explicit error. Nothing is converted or downgraded. Every fossil release is such a peer, so a repository with trees serves fossil only the history before the first tree.
A server whose `enable-submanifests` is off also sends `pragma avoid-submanifests`. It binds only commits made during that sync session. go-libfossil commits outside a session and pushes afterwards, so the pragma never binds its commits: whether a commit writes a tree is decided by the local `enable-submanifests` alone. The pragma does not stop a server storing a tree pushed to it.
## Things to know before enabling
- **It is a flag day.** Every reader of the repository must understand sub-manifests before any writer emits one (§8). A repository that a fossil peer serves must not enable them.
- **Trees cannot be retired.** Disabling stops new trees; artifact names are immutable, so the ones written stay, and fossil peers are still refused them.
- **Sparse stores hold every tree node.** A [sparse store](/docs/concepts/sparse-cones/) keeps all sub-manifest nodes, so history stays readable without the file content.
## Where the code is
- `internal/manifest`: tree construction (`treebuild.go`), writing and settings (`submanifest_write.go`), reading (`tree.go`).
- `internal/sync/submanifest.go`: the capability gate, both pragmas, and configuration exchange of the settings.
- `repo_submanifest.go` and `cli/repo_submanifests.go`: the public API and command.
---
# Sparse cones
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/concepts/sparse-cones/
A repository does not have to hold everything. A **cone** limits it by path. A [horizon](/docs/concepts/horizons/) limits it by age, and is carried as a member of a cone. Both leave the history intact, and the blobs they skip can be fetched later.
Cones follow `draft-fossil-sparse-cone-00`. They are implemented in step with zit.
## Cones
A cone is a set of repository paths. Each member is a directory subtree or a single file, and each is either an include or an exclude. Members are literal prefixes, not patterns (§3.4).
The longest member that covers a path decides it. An exclude carves a part out of an include, and a deeper include re-includes part of an exclude (§3.3):
```
include src
exclude src/vendor
include src/vendor/keep
```
`src/a.c` is covered. `src/vendor/v.c` is not. `src/vendor/keep/k.c` is covered again.
A cone must have at least one include, at most 255 members, and no exclude that no include covers. Order does not matter; members are sorted and reduced to the smallest set with the same coverage (§3.2).
### Checkout cones and store cones
A **checkout cone** limits the working directory (§4). The repository still holds every file. A sparse checkout writes, scans and tracks only the covered files. A commit carries the uncovered files from its parent unchanged, so it is the same check-in a dense checkout would make. Adding, removing, renaming or reverting an uncovered path fails with `ErrOutsideCone`.
A **store cone** limits the repository itself (§5). Blobs outside the cone are not fetched, and can be dropped by compaction. A checkout opens over a sparse store only if its own cone stays inside the store's.
Manifests, [sub-manifest](/docs/concepts/sub-manifests/) nodes and control artifacts are always held, so the whole history stays readable. Only file content is elided.
### Elided is not damaged
An artifact the store lacks is classified one of three ways (§5.2):
- **transit**: not here yet, and sync will ask for it.
- **shunned**: deliberately refused.
- **elided**: the cone says the store need not hold it.
Only elided is by design. Sync never requests an elided artifact, whether the store is the client or the server, and `verify` reports a store that is complete within its cone as OK. Elision is computed from the cone and never stored, so changing the cone reclassifies. Losing the cone, or one this version cannot read, turns every elided artifact back into a transit one and the store heals toward dense.
## Library usage
### Build a cone
```go
cone, err := libfossil.NewCone(
[]string{"src", "docs"}, // include
[]string{"src/vendor"}, // exclude
)
if err != nil {
log.Fatal(err)
}
fmt.Println(cone.Covers("src/main.go")) // true
covered, member := cone.Decide("src/vendor/x.c") // false, "src/vendor"
```
A `Cone` is immutable. `With`, `Without` and `WithHorizon` return new ones. `Include`, `Exclude`, `Digest`, `String` and `Horizon` read it.
### Sparse checkout
```go
repo, err := libfossil.Open("repo.fossil")
if err != nil {
log.Fatal(err)
}
defer repo.Close()
co, err := repo.CreateCheckout("work", libfossil.CheckoutCreateOpts{Cone: cone})
if err != nil {
log.Fatal(err)
}
defer co.Close()
// Widen, narrow, or pass nil to make the checkout dense.
change, err := co.SetCone(wider)
if err != nil {
log.Fatal(err)
}
fmt.Println(change.Materialized, change.Dematerialized)
```
`SetCone` writes the files the new cone adds and removes the ones it drops from the working directory. The repository keeps them. It refuses, changing nothing, when a file leaving the cone has local changes or a file entering it would overwrite an untracked one. A cone that carries a horizon fails with `ErrHorizonCheckout`.
Over a sparse store, widening needs content the store may not hold. `SetConeContext` takes a `RequiredContentOpts{Peer: transport}` and fetches it. With no peer the widening is refused and nothing changes.
### Clone into a sparse store
```go
t := libfossil.NewHTTPTransport("https://example.com/repo")
repo, res, err := libfossil.Clone(ctx, "repo.fossil", t, libfossil.CloneOpts{
StoreCone: cone,
})
if err != nil {
log.Fatal(err)
}
defer repo.Close()
fmt.Println(res.Elided, res.ConeServed)
```
The result counts:
- `Elided`: artifacts left absent under the cone.
- `ConeServed`: replies where the server acknowledged applying the cone (`pragma cone-served`, §6.3). The other rounds were served densely and filtered locally.
Both are zero for a dense clone. `SyncResult` has the same fields. A server that will not serve a sparse clone refuses it. A cone with a horizon clones a horizon store; see [Horizons](/docs/concepts/horizons/).
### Set, widen and drop on an existing store
```go
// Set or replace, without a peer. Narrowing deletes nothing.
err = repo.SetStoreCone(next)
// Read it.
cur, err := repo.StoreCone()
// Widen, and fetch what that newly covers.
sr, err := repo.SetStoreConeContext(ctx, wider, libfossil.RequiredContentOpts{
Peer: t,
})
// Drop the cone. Fetch everything it elided.
sr, err = repo.SetStoreConeContext(ctx, nil, libfossil.RequiredContentOpts{Peer: t})
// Drop held blobs the cone elides. See Limits.
n, err := repo.CompactStore(ctx)
```
`SetStoreCone` moves no content. Widening makes the newly covered elided artifacts wanted again, and the next pull fetches them. `SetStoreConeContext` does the pull itself through an ordinary cone-scoped sync. With artifacts to fetch and no `Peer`, it refuses, names the count, and leaves the cone unchanged. If the transfer fails after the cone is replaced, the wider policy stays and the next pull heals it. While a store cone is set, a commit carries no R card. `CompactStore` on a dense store fails with `ErrNoStoreCone`.
## Command line
### Open a sparse checkout
```
libfossil -R repo.fossil repo open [DIR] --cone src --cone docs --cone-not src/vendor
```
- `--cone=CONE,...`: make a sparse checkout; materialize and track only this path's subtree (repeatable).
- `--cone-not=CONE-NOT,...`: exclude this path's subtree from an enclosing `--cone` (repeatable).
### Edit the checkout cone
All of these take `-d, --dir` for the checkout directory (default `.`).
```
libfossil repo cone show
libfossil repo cone add src/extra
libfossil repo cone add --not src/gen
libfossil repo cone remove src/extra
libfossil repo cone set src docs --not src/vendor
libfossil repo cone dense
libfossil repo cone test src/a.c src/vendor/v.c
```
- `show`: list the cone's members ("dense" when there is none).
- `add `: widen the cone, or with `--not` carve paths out of it.
- `remove `: remove members of either polarity.
- `set `: replace the cone. `--not` takes the exclusions.
- `dense`: drop the cone and materialize every file.
- `test `: classify paths against the cone and name the deciding member. It exits non-zero when any path is elided, so it works as a predicate. `-q` prints nothing and reports through the status only.
`add`, `remove`, `set` and `dense` can need content the store lacks. They take `--remote` (default: the saved last-sync-url), `--sync-user`, `--sync-pass` (never saved), `--sync-private` and `--timeout` (default 10m; 0 disables it).
### Clone a sparse store
```
libfossil repo clone URL repo.fossil --cone src --cone-not src/vendor
```
- `--cone=CONE,...`: clone a sparse store: fetch only this path's subtree (repeatable).
- `--cone-not=CONE-NOT,...`: carve this path out of a `--cone` (repeatable).
`--horizon`, `--leaves`, `--structural` and `--seed-key` combine with these; see [Horizons](/docs/concepts/horizons/).
### Change the store cone
```
libfossil -R repo.fossil repo cone store # show it
libfossil -R repo.fossil repo cone store src docs --not src/vendor
libfossil -R repo.fossil repo cone store --dense
libfossil -R repo.fossil repo cone compact
```
- With no paths, `store` shows the store cone. With paths, it replaces it and fetches what the widening covers. `--not` takes the exclusions.
- `--dense`: drop the store cone, fetching everything it elided.
- `compact`: drop held blobs the store cone elides (irreversible locally).
`store` takes the same `--remote`, `--sync-user`, `--sync-pass`, `--sync-private` and `--timeout` flags as above. Its `--horizon`, `--leaves`, `--structural` and `--no-horizon` flags are described under [Horizons](/docs/concepts/horizons/).
## Interoperability
**zit.** The cone format matches zit's byte for byte, so a cone's digest is the same in both. Sparse sync works in both directions against zit's `horizon-rfc` revision. An implementation that predates horizons refuses a cone that carries one.
**Stock fossil 2.28.** A sparse store must not be served as if it were dense, because the clone would be silently incomplete. A store held sparsely is served only when its `partial-mirror` setting is on (`repo config set partial-mirror on`, `SettingPartialMirror`); otherwise it refuses. The refusal puts the push card ahead of the error so fossil 2.28 aborts instead of carrying on. Fossil cannot read [sub-manifest](/docs/concepts/sub-manifests/) check-ins either, so a repository that uses them is for libfossil and zit peers only.
## Limits
- **Compaction is irreversible locally.** `CompactStore` and `repo cone compact` delete held blobs (§5.4). Widening later fetches them again, which needs a peer that still has them. Structural artifacts, sub-manifest nodes, unsent or private content and the delta bases of retained artifacts are kept.
- **A sparse store lacks file content.** Commands that read an elided blob need a peer to fetch it, or fail.
- **A cone is not an authorization boundary** (§6.5). It limits what a store holds and asks for, not what a peer may read.
---
# Horizons
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/concepts/horizons/
A horizon cuts a store by time. It names a check-in **C**, a rule, and a depth. The check-ins the rule keeps are the **held set**. A blob is elided when no check-in in the held set names it. Structure (manifests, tags, control artifacts) is never elided by a content horizon.
A horizon is a member of a store cone, so everything on [Sparse cones](/docs/concepts/sparse-cones/) about store cones, elided artifacts and compaction applies to it too. The behaviour follows `draft-fossil-horizon-00`, implemented in step with zit.
The held set is computed from the check-in graph the repository holds. It is stored nowhere (§3.1).
## Rules
- **descendants** (the default): C and every check-in descended from it, through any parent, merges included.
- **leaves**: the descendants, plus, for every childless check-in that does not descend from C, every descendant of each check-in where its ancestry meets C's.
A check-in is childless when no other check-in names it as a parent, open or closed. A branch tip merged into another branch has a child, so it is not childless. Under the leaves rule, any two childless check-ins have their merge base in the held set, so merging branches that are still being extended never crosses the horizon. Both rules give a set that includes every child of every check-in in it.
The leaves rule has a cost you should check before using it. An old branch that was never merged forks from C's ancestry near the root, and the rule then holds every descendant of that fork. On fossil-scm.org's repository it held 20,907 of 21,008 check-ins at every depth tried. The descendants rule, with C 10, 100, 426 and 1000 check-ins back from the tip, saves 97, 86, 56 and 41 percent of that history (§3.4). Use the leaves rule when the repository keeps few old unmerged branches.
`repo cone cost ` (`Repo.HorizonCost`) reports what each rule would hold at a check-in, read from a repository that has the history:
```
$ libfossil -R repo.fossil repo cone cost trunk
horizon at trunk
descendants 1 check-ins, 3 blobs, 6 B expanded, 57 B stored
leaves 1 check-ins, 3 blobs, 6 B expanded, 57 B stored
```
## Depth
- **content** (the default): which blobs are held. Every check-in's structure is still held (§4).
- **structural** (`Horizon.Structural`, `--structural`): also cuts the manifests below C (§5). It needs a seed, a control artifact the origin mints so a clone can derive tag and branch state without C's ancestry.
A content horizon keeps all structure and elides only blobs; a structural horizon also leaves out the manifests below C.
## Seeds are never pushed
A seed is private to the clone that holds it, so a go-libfossil origin refuses any pushed control artifact that looks like one (§5.2.3):
- its U card is `go-libfossil`, the label go-libfossil writes on every seed it mints; or
- it carries a `horizon-seed` T card, whatever its U card.
Each test runs on the artifact's content after any delta is applied, including a delta whose base arrives later in the same push. The refusal is atomic: nothing the push carried is stored, and the reply is one error card, `seed refused: a seed is private to the clone that holds it; it is never pushed`, which a go-libfossil pusher reports among its sync errors. A delta whose base has not arrived cannot be read yet, so it is stored as any other dangling delta is, and judged when a later push supplies the base. Until it is judged it is withheld: never announced or sent. Whatever fills the base, a later push, a pull, a check-in or tag that writes the same bytes, judges it in the same transaction, and the crosslink step judges anything still pending before it links it. The push or pull that supplied the base is stored as usual, with no error, and the seed is recorded as refused in a table of the store's own that no peer pulls: it is never crosslinked (a rebuild included), announced or sent. A seed-shaped artifact that was never pushed, one pulled from a peer, say, is ordinary.
Two consequences follow:
- Another implementation's label alone is ordinary. A control artifact under zit's `U zit-seed` with no `horizon-seed` T card is stored when pushed; one that carries a `horizon-seed` card is refused.
- A user whose login is `go-libfossil` cannot push control artifacts (tags, branch moves, closes) to a go-libfossil origin, since they carry that login as their U card. go-libfossil does not forbid the login; do not give it to a user.
## Signed seeds
An origin with the `clearsign` setting on (and the legacy `omitsign` off) signs each seed it mints with its `pgp-command`, the same settings fossil and zit sign check-ins with (§5.2.4). It keeps one signature per seed, so a seed keeps its name while the tag state it records is unchanged.
A clone given the origin's public key verifies every seed against it: at clone time, on every pull, and when it rebuilds. A seed that is unsigned, or signed by another key, is discarded and the clone falls back to the content horizon. A clone with no key accepts an unsigned seed. The key is the clone's own `horizon-seed-key` setting, never something the origin sends. `CloneOpts.SeedKey` and `repo clone --seed-key` set it when the clone is made; `Repo.SetConfig` refuses a value that is neither an SSH public key nor an OpenPGP fingerprint.
- **SSH:** the origin signs with `ssh-keygen -q -Y sign -n fossilscm -f KEY`; the clone's key is the public key line, verified in process.
- **PGP:** the origin signs with `gpg --clearsign -o`, fossil's default; the clone's key is the key's 40 or 64 digit fingerprint, verified by `gpg` against the user's keyring.
An origin signs only for a requester that may read, after the request is authorized, and keeps the signature in its own table; a store that cannot write, such as a read-only mirror, signs again for every request. Setting the origin's own `horizon-seed-key` to its public key makes it check a kept signature before serving it, so replacing the key behind an unchanged `pgp-command` takes effect at once. A clone that cannot run its verifier (gpg missing, or timed out) keeps the seed it holds and reports the error rather than falling back. Settings are read from the repository only: go-libfossil reads no global fossil configuration, so `clearsign` and `pgp-command` set with `fossil settings --global` do not apply.
[Signing](/docs/concepts/signing/) covers the signing settings and the verifier in more detail.
## Moving the horizon
Moving C earlier and pulling fetches what the deeper horizon keeps. Moving it later keeps what you hold until you compact. A horizon store cannot be a checkout's cone: a horizon governs what the repository holds, not which paths a checkout writes.
## Library usage
### Build a horizon cone
```go
h := libfossil.Horizon{
Checkin: "9f2c...", // full 40 or 64 digit hash
Rule: libfossil.HorizonDescendants, // or HorizonLeaves
Structural: false, // true for the structural cut
}
// Horizon only: every path, cut by time.
hc, err := libfossil.NewHorizonCone(nil, nil, h)
// Horizon plus paths.
hc, err = libfossil.NewHorizonCone([]string{"src"}, nil, h)
got, ok := hc.Horizon() // read it back
noHorizon, err := hc.WithHorizon(nil) // drop it, keep the paths
```
`Checkin` must be a full hash, and it is lower-cased. Dropping the horizon of a horizon-only cone is an error, since nothing would be left; use `SetStoreCone(nil)` to make the store dense.
### Clone into a horizon store
```go
t := libfossil.NewHTTPTransport("https://example.com/repo")
repo, res, err := libfossil.Clone(ctx, "repo.fossil", t, libfossil.CloneOpts{
StoreCone: hc,
SeedKey: "ssh-ed25519 AAAA...", // optional; structural only
})
if err != nil {
log.Fatal(err)
}
defer repo.Close()
fmt.Println(res.Elided, res.HorizonServed, res.StructuralServed, res.HorizonFallback)
```
- `HorizonServed`: replies where the server cut at the horizon (`pragma horizon-served`, §6.2.5). The other rounds were classified locally.
- `StructuralServed`: those served at the structural level.
- `HorizonFallback`: a structural clone kept the content horizon instead (§6.3).
`SyncResult` has the same fields.
`HorizonHistoryURL`, on both results, is the address the origin gives for querying history below the horizon outside the sync protocol, when it gives one. An origin sets it with `repo config set horizon-history-url https://...` (`SettingHorizonHistoryURL`); only one http or https URL with no spaces or credentials is accepted. `repo clone` prints it as `History:`.
### Deepen, merge across, and verify the seed
Setting, replacing and dropping a horizon on an existing store use `SetStoreCone` and `SetStoreConeContext`, as for any [store cone](/docs/concepts/sparse-cones/).
```go
// Move the horizon down far enough to reach a check-in, which may lie
// below it. C moves to that check-in when it is an ancestor of C, else to
// a common ancestor of the two, asked of the origin when the store cannot
// see one. The rule, the depth and the path members stay.
sr, err := repo.DeepenHorizon(ctx, target, libfossil.RequiredContentOpts{Peer: t})
// Merge. With a Peer, a merge where either side's ancestry crosses a
// structural horizon asks the origin for the base, deepening to it when it
// lies below. Without one it fails with an error matching
// libfossil.ErrBelowHorizon, even when the store finds a base it holds.
mr, err := co.MergeContext(ctx, libfossil.CheckoutMergeOpts{Version: rid},
libfossil.RequiredContentOpts{Peer: t})
// Check the seed against the history it summarizes. This fetches every
// manifest below C and leaves a content-horizon store.
v, err := repo.VerifySeed(ctx, libfossil.RequiredContentOpts{Peer: t})
```
Deepening a structural store discards its seed before it fetches the new one, so the old seed cannot shadow the new one. If the transfer fails, the old horizon and seed are put back (§5.8).
A merge never runs against a base the store does not hold. A bisection whose good end lies below the horizon is refused the same way, naming the horizon and the deepen.
An origin checks each pushed check-in that creates a branch: if the branch it cancels is not the one this origin derives for the parent, the clone's seed was stale, and the whole push is refused with `stale seed: ...` (§5.7). Pull, push a control artifact that cancels the parent's real branch tag on the check-in, and push again.
## Command line
### Clone a horizon store
```
libfossil repo clone URL repo.fossil --horizon
libfossil repo clone URL repo.fossil --horizon --leaves --cone src
libfossil repo clone URL repo.fossil --horizon --structural --seed-key "ssh-ed25519 AAAA..."
```
- `--horizon=STRING`: clone a horizon store: elide blobs below this check-in, given as its full 40 or 64 digit hash.
- `--leaves`: with `--horizon`, also hold every open leaf's ancestry back to the check-in.
- `--structural`: with `--horizon`, also elide the manifests below the check-in, deriving their branch and tag state from the origin's seed.
- `--seed-key=STRING`: the origin's public key, an SSH public key line or an OpenPGP fingerprint. With `--structural`, a seed not signed by it is discarded and the clone falls back to the content horizon. It is kept as the `horizon-seed-key` setting.
`--cone` and `--cone-not` combine with these. The clone needs a full hash because nothing is local yet to resolve a name against.
### Change the horizon
```
libfossil -R repo.fossil repo cone store --horizon
libfossil -R repo.fossil repo cone store --horizon --leaves
libfossil -R repo.fossil repo cone store --horizon --structural
libfossil -R repo.fossil repo cone store --no-horizon
libfossil -R repo.fossil repo cone deepen
libfossil -R repo.fossil repo cone cost
```
- `--horizon=STRING`: elide blobs below this check-in (name, branch or hash prefix), fetching what a deeper horizon keeps. Unlike `clone`, a local name is fine.
- `--leaves`: with `--horizon`, also hold every open leaf's ancestry back to the check-in.
- `--structural`: with `--horizon`, also elide the manifests below the check-in; the next pull fetches the origin's seed.
- `--no-horizon`: drop the horizon, keeping the path members, and fetch what it elided.
- `cone deepen`: move the horizon down to reach the check-in, fetching what the deeper horizon keeps.
- `cone cost`: report what each rule would hold at the check-in, from this repository.
### Merge and verify
```
libfossil -R repo.fossil repo merge -d DIR
libfossil -R repo.fossil repo verify --seed
```
- `merge`: a merge where either side's ancestry crosses a structural horizon asks the remote for the base, and deepens to it when it lies below. A merge whose ancestry stays above the horizon contacts no peer.
- `verify --seed`: verify the structural seed by fetching the history below the horizon. The store stays a content-horizon store.
`store`, `deepen`, `merge` and `verify` take `--remote` (default: the saved last-sync-url), `--sync-user`, `--sync-pass` (never saved), `--sync-private` and `--timeout` (default 10m; 0 disables it).
## Interoperability
**zit.** The horizon format matches zit's byte for byte. Horizon sync works in both directions against zit's `horizon-rfc` revision.
**Stock fossil 2.28.** A horizon store refuses a dense clone, because the clone would be silently incomplete.
**A peer that knows nothing of horizons.** A horizon clone from stock Fossil works. The client asks for nothing below the horizon, records the below-horizon blobs it is sent as elided instead of storing them, and classifies locally. `HorizonServed` stays 0. Stock Fossil can announce a new check-in only inside a cluster. A horizon store holds no clusters, so it requests the cluster members as transit and classifies what arrives (§4.5). Without that a new check-in would not be learned until a later one named it.
## Limits
- **The structural horizon needs an origin that serves it.** Against a peer that does not answer `horizon-served structural` (stock fossil, or a server without a current seed) the clone falls back to the content horizon and reports `HorizonFallback` (§6.3).
- **PGP verification needs gpg.** A PGP-signed seed is verified by running `gpg`, so a clone keyed with a fingerprint needs gpg installed and the key in its keyring; on js/wasm only SSH keys verify. gpg-agent hangs at start under a very large open-file limit, which a macOS shell can set to 2^31 (`ulimit -n`): lower it, to 4096 say, in the environment that signs or verifies with gpg.
- **The horizon cursor needs a transport that names its peer.** The cursor (`pragma horizon-cursor`, §6.2.6) lets an exchange skip re-announcing what both sides hold. The HTTP transport keys it by URL; a custom `Transport` keeps none and announces in full.
- **A horizon is not a checkout cone.** Give a checkout a horizon and you get `ErrHorizonCheckout`.
- **Compaction is irreversible locally.** `repo cone compact` drops the held blobs below the horizon; deepening later needs a peer that still has them.
---
# Signing
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/concepts/signing/
go-libfossil signs two kinds of artifact: check-ins, as fossil and zit sign them, and the seeds an origin mints for [structural horizons](/docs/concepts/horizons/). Both use the same settings and the same two envelopes, a PGP clear-signed message or the SSH envelope fossil builds around a detached `ssh-keygen` signature (`draft-fossil-artifact-format-00` §4.1.1).
## Settings
| Setting | Constant | Meaning |
|---|---|---|
| `clearsign` | `SettingClearsign` | Sign check-ins and horizon seeds. Off by default. |
| `omitsign` | | Fossil's legacy setting. On, it turns signing off again. |
| `pgp-command` | `SettingPGPCommand` | The signing command. Unset, it is fossil's default, `gpg --clearsign -o `. |
| `horizon-seed-key` | `SettingHorizonSeedKey` | The public key a clone verifies its origin's seeds with. |
Settings are read from the repository only. go-libfossil reads no global fossil configuration, so values set with `fossil settings --global` do not apply.
## Signing check-ins
With `clearsign` on, every check-in a commit or a branch creation stores is signed. The command runs as fossil runs it:
- **PGP:** `COMMAND OUTFILE INFILE`, which writes a clear-signed copy of the manifest.
- **SSH:** a command whose name starts with `ssh` runs as `COMMAND INFILE` and writes a detached `INFILE.sig`, which is wrapped in fossil's SSH envelope. The namespace must be `fossilscm`.
```
libfossil -R repo.fossil repo config set clearsign on
libfossil -R repo.fossil repo config set pgp-command \
"ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519"
```
The command runs through the shell, so `~` and environment variables expand. A signed check-in looks like this:
```
-----BEGIN SSH SIGNED MESSAGE-----
C signed
D 2026-10-23T20:45:21.089
F docs/x.md a09fd4d0...
...
Z 0e57ef44fd0eec16824d0a54ab247953
-----BEGIN SSH SIGNATURE-----
...
-----END SSH SIGNATURE-----
```
The stored bytes are the whole envelope, so the check-in's name is the hash of the signed bytes (§3.1). Readers strip the envelope before parsing, and the `Z` card covers the manifest inside it (§6.2).
To store one check-in unsigned, as fossil's `--nosign` does:
- `libfossil repo ci --nosign` and `libfossil repo branch new --nosign`.
- `CommitOpts.NoSign` (`Repo.Commit`) and `CheckoutCommitOpts.NoSign` (`Checkout.Checkin`).
### What fails
- **The signer fails or times out** (after two minutes). The commit is abandoned and nothing is stored.
- **The signer changes what it signs.** The envelope must strip back to exactly the manifest that was given, as zit checks it. A dash-escaped or rewritten manifest is refused rather than stored under a name its cards do not match.
- **On js/wasm** no external command can run, so signing fails there.
go-libfossil does not verify check-in signatures when it reads them. To check one, take the output of `libfossil repo cat` and run the signer's own verifier: `gpg --verify` on a PGP envelope, or, for SSH, `ssh-keygen -Y verify -n fossilscm -s SIG` with the manifest between the header and the signature block on standard input. The conformance test `TestSignedCheckin` checks that fossil and zit read the signed check-ins, and that a check-in zit signs with the same SSH key verifies here.
## Signing horizon seeds
An origin with `clearsign` on signs each structural horizon seed it mints with the same `pgp-command` (`draft-fossil-horizon-00` §5.2.4). It keeps one signature per seed in its own table, so a seed keeps its name while the tag state it records is unchanged. It signs only for a requester allowed to read, after the request is authorized. A store that cannot write, such as a read-only mirror, signs again for every request.
A clone verifies seeds against its `horizon-seed-key` setting:
```go
repo, res, err := libfossil.Clone(ctx, "repo.fossil", t, libfossil.CloneOpts{
StoreCone: hc, // a cone with a structural horizon
SeedKey: "ssh-ed25519 AAAA... origin",
})
```
```
libfossil repo clone URL repo.fossil --horizon --structural \
--seed-key "ssh-ed25519 AAAA... origin"
```
The key is an SSH public key line, verified in process, or a 40 or 64 digit OpenPGP fingerprint, verified by `gpg` against the user's keyring. `Repo.SetConfig` refuses a `horizon-seed-key` that is neither. The key is the clone's own; the origin never sends it.
The clone verifies every seed it holds: at clone time, on every pull, and when it rebuilds.
| Seed | Key set | Result |
|---|---|---|
| Signed by the key | yes | Held. |
| Unsigned, or signed by another key | yes | Discarded; the store falls back to the content horizon. |
| Any | no | Held. A clone with no key accepts an unsigned seed. |
| Verifier could not run (gpg missing or timed out) | yes | The seed already held is kept and the error is reported. A new seed is refused. Nothing falls back. |
Setting the origin's own `horizon-seed-key` to its public key makes it check a kept signature before serving it, so replacing the key behind an unchanged `pgp-command` takes effect at once.
A signature proves where a seed came from, not that its derivation is right (§5.2.4). `repo verify --seed` (`Repo.VerifySeed`) checks the seed against the history it summarizes.
## Limits
- **PGP verification needs gpg** installed, with the origin's key in the keyring. On js/wasm only SSH keys verify.
- **gpg-agent hangs under a very large open-file limit.** A macOS shell can set `ulimit -n` to 2^31. Lower it, to 4096 say, in the environment that signs or verifies with gpg.
## Where the code is
- `signing.go`: the setting constants.
- `internal/clearsign`: running the signer, the SSH envelope, SSH verification in process, gpg verification.
- `internal/manifest/manifest.go`: signing a check-in before it is stored.
- `internal/sync/horizon_seedsign.go` and `internal/horizon/seed`: signing seeds at the origin and verifying them at the clone.
---
# Guides
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/guides/
Task-oriented pages for running go-libfossil in a service, moving from fossil, and testing changes.
- [Production patterns](/docs/guides/production/): errors, contexts, telemetry, handle lifetime, driver choice.
- [Migrating from fossil](/docs/guides/migrating-from-fossil/): what carries over from the fossil binary and what differs.
- [Embedding the CLI](/docs/guides/cli-embedding/): driving `libfossil` as a child process.
- [Extension points](/docs/guides/extension-points/): observers, SQLite drivers and the `db.DB` abstraction.
- [Testing](/docs/guides/testing/): the test suite, deterministic simulation, fuzzing and interop.
---
# Production patterns
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/guides/production/
The [Quickstart](/docs/get-started/quickstart/) uses `log.Fatal` to keep the snippets short. This page shows the patterns you'll actually want when libfossil is embedded in a service.
## Error handling
Don't `log.Fatal` from a long-running process — return errors up to a caller that can log, retry, or fail the request.
```go
func openRepo(path string) (*libfossil.Repo, error) {
repo, err := libfossil.Open(path)
if err != nil {
return nil, fmt.Errorf("open %s: %w", path, err)
}
return repo, nil
}
```
The errors you get back are wrapped Go errors; use `errors.Is` and `errors.As` against the sentinel errors exported from the [`libfossil` package](/docs/reference/sdk/libfossil/api/) when you need to branch on a specific failure (for example `ErrVersionNotFound`, `ErrAmbiguousVersion` or `ErrArtifactNotFound`).
## Context cancellation
`Sync`, `Pull`, and `Clone` accept a `context.Context`. Pass a request-scoped context with a deadline so a stalled remote doesn't tie up your goroutine forever.
```go
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
if _, err := repo.Sync(ctx, transport, libfossil.SyncOpts{Pull: true}); err != nil {
return fmt.Errorf("sync: %w", err)
}
```
The HTTP transport honors the deadline at the network boundary; the rest of the sync state machine checks `ctx.Done()` between rounds.
## Telemetry
Wire an observer to your Repo's operations to surface sync rounds, checkout events, and per-error events to your existing logging or metrics stack. See [Extension points](/docs/guides/extension-points/) for the full interface, and [`observer/otel`](/docs/reference/sdk/observer/otel/api/) for an OpenTelemetry-ready implementation that emits spans + counters.
```go
import (
libfossil "github.com/danmestas/go-libfossil"
otelobs "github.com/danmestas/go-libfossil/observer/otel"
)
obs := otelobs.NewSyncObserver()
_, err := repo.Sync(ctx, transport, libfossil.SyncOpts{
Pull: true,
Observer: obs,
})
```
`observer/otel` is a separate Go module so the OpenTelemetry SDK doesn't leak into consumers that don't want it.
## Repo handle lifetime
A `*Repo` wraps a SQLite database connection pool. Open it once at service startup, keep it for the lifetime of the process, and `Close()` it during shutdown — don't open and close per request.
```go
type service struct {
repo *libfossil.Repo
}
func newService(path string) (*service, error) {
repo, err := libfossil.Open(path)
if err != nil {
return nil, err
}
return &service{repo: repo}, nil
}
func (s *service) Shutdown() error {
return s.repo.Close()
}
```
Concurrent reads through one Repo are safe — SQLite handles the locking. Writes serialize at the database level (the drivers open write transactions with `BEGIN IMMEDIATE`, and a writer waits up to the 5-second busy timeout for the lock); if your service has heavy write traffic, queue write operations through a single goroutine rather than relying on lock contention to back-pressure.
## Driver selection
The shipped `cmd/libfossil` CLI uses `modernc` by default; `go build -tags test_ncruces ./cmd/libfossil` selects `ncruces`.
For library embedders targeting Linux/macOS/Windows, the default `modernc` driver is the right choice — pure Go, static binaries, no surprises. Switch to `ncruces` only when you're shipping a WASI/browser binary or when you need ncruces-specific behavior. Blank-import exactly one driver:
```go
import _ "github.com/danmestas/go-libfossil/db/driver/ncruces"
// Do not also import:
// _ "github.com/danmestas/go-libfossil/db/driver/modernc"
```
Drivers are mutually exclusive — a process can register exactly one. See [`db`](/docs/reference/sdk/db/api/) for the registry contract.
## Signing check-ins
With the `clearsign` setting on, every check-in that `Repo.Commit`, `Checkout.Checkin` or `repo branch new` stores is signed, as fossil and zit sign them. Fossil's legacy `omitsign` setting, when on, turns signing off again. The signer is the `pgp-command` setting, fossil's contract:
- **PGP:** unset, it is `gpg --clearsign -o `, run as `COMMAND OUTFILE INFILE`.
- **SSH:** a command whose name starts with `ssh`, such as `ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519`, is run as `COMMAND INFILE` and its detached signature is wrapped in fossil's SSH envelope. The namespace must be `fossilscm`.
```
libfossil -R repo.fossil repo config set clearsign on
libfossil -R repo.fossil repo config set pgp-command "ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519"
```
The signed text is the artifact, so a signed check-in is named by the hash of the signed bytes. Stock fossil and zit clone and read signed check-ins. A signer that fails abandons the commit. `CommitOpts.NoSign`, `CheckoutCommitOpts.NoSign` and `--nosign` store one check-in unsigned. The command runs through the shell, so `~` and environment variables expand. It runs inside the commit's transaction, so a signer that waits on a passphrase prompt holds the repository's write lock until it returns or two minutes pass. On js/wasm there is no external signer.
The same settings sign the horizon seeds an origin mints. A clone verifies them against the `horizon-seed-key` setting, which `repo clone --seed-key` (or `CloneOpts.SeedKey`) sets. See [Signing](/docs/concepts/signing/) for the envelope, verification and seed details.
---
# Migrating from fossil
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/guides/migrating-from-fossil/
This guide is for users of upstream [Fossil SCM](https://fossil-scm.org) (the C
binary distributed by `fossil-scm.org`) who are evaluating `libfossil` — a
pure-Go implementation of the Fossil repository format and sync protocol.
It is intentionally honest about gaps. If a capability is not listed under
"Command coverage" below, assume it is not implemented yet.
For a structural overview of how libfossil is organised — the package layout, storage model, and pluggable axes — see [Architecture](/docs/concepts/architecture/).
## Repo format compatibility
`libfossil` reads and writes the same on-disk `.fossil` SQLite repository
format as upstream Fossil. A repository created by either tool can be opened
by the other at the file level — there is no import/export step and no data
migration is needed. Both implementations share the same blob table layout,
manifest/control artifact encoding, and delta chain representation.
One caveat: features introduced in very recent upstream Fossil releases
(bleeding-edge artifact card types, new Fossil-specific SQL functions used in
triggers) may not yet be parsed by `libfossil`. The tests that compare
against stock fossil run against the pinned release, 2.28. See
[Conformance](/docs/reference/conformance/) and [Testing](/docs/guides/testing/).
## Binary name and invocation
The compiled binary is named `libfossil` (not `fossil`) to avoid `$PATH`
collisions when both tools are installed on the same machine:
```
go install github.com/danmestas/go-libfossil/cmd/libfossil@latest
```
`libfossil` uses a subcommand-grouped syntax: every command lives under the `repo` group,
and individual operations live beneath it. Upstream Fossil uses a flat
command style. Examples:
| Upstream Fossil | libfossil |
|------------------------------|----------------------------------------|
| `fossil clone URL repo.fossil` | `libfossil repo clone URL repo.fossil` |
| `fossil ci -m "msg" file.txt` | `libfossil -R repo.fossil repo ci -m "msg" file.txt` |
| `fossil timeline` | `libfossil -R repo.fossil repo timeline` |
The `-R ` flag selects the repository and is a global flag (before the
`repo` subcommand group).
## Command coverage
The following `repo` subcommands are implemented today. Every entry below
corresponds to a registered Kong command in `cli/repo.go` in the libfossil
source tree. `libfossil --help` lists them all.
### Repository creation
- `repo new` — create a new, empty repository file.
- `repo clone` — clone a remote Fossil server over HTTP(S). With `--cone`
it clones a sparse store; with `--structural` it derives branch and tag
state from the origin's horizon seed; `--seed-key` pins the key the seed
must be signed by. See [Sparse cones](/docs/concepts/sparse-cones/) and
[Horizons](/docs/concepts/horizons/).
### Working tree and checkout
- `repo open` — materialize a checkout in a directory (creates `.fslckout`).
- `repo co` — check out a named version.
- `repo ls` — list files in a version.
- `repo status` — show pending changes in the working directory.
- `repo add` — stage files for addition.
- `repo rm` — stage files for removal.
- `repo rename` — rename a tracked file.
- `repo revert` — undo staging changes.
- `repo ci` — commit the checkout's changes, or only the named files,
with any pending merge as a merge parent. `--nosign` stores one check-in
unsigned; see [Signing](/docs/concepts/signing/).
- `repo cone` — show or edit the checkout's sparse cone (`show`, `add`,
`remove`, `set`, `dense`, `test`) and the repository's store cone
(`store`, `deepen`, `compact`, `cost`).
### History and inspection
- `repo timeline` — repository history (`-n` sets the count, default 20).
- `repo log --path` — the check-ins that changed a path (`fossil finfo`).
- `repo snapshot` — commit one file's bytes onto trunk without a checkout;
see [Embedding the CLI](/docs/guides/cli-embedding/).
- `repo diff` — show changes vs. a version.
- `repo annotate` / `repo blame` — annotate file lines with commit history.
- `repo cat` — output artifact content.
- `repo hash` — hash files (SHA1 or SHA3-256).
- `repo info` — repository statistics.
- `repo resolve` — resolve a symbolic name to a UUID.
- `repo schema` — operate on the synced table schema.
- `repo query` — run ad-hoc SQL against the repository DB.
### Branching and merging
- `repo branch` — `ls`, `new` and `close`.
- `repo tag` — `ls` and `add`.
- `repo merge` — merge a divergent version into the current checkout.
- `repo conflicts` — `ls` lists files holding merge conflict markers and
conflict-fork entries; `show`, `pick`, `merge` and `extract` inspect and
resolve one conflicted file.
- `repo mark-resolved` — resolve a conflict-fork entry. A merge conflict
needs no marking: as in fossil, editing the markers out resolves it.
- `repo bisect` — binary-search commits for bug introductions (`good`,
`bad`, `next`, `skip`, `reset`, `ls`, `status`).
- `repo undo` / `repo redo` — undo/redo the last operation.
- `repo stash` — `save`, `pop`, `apply`, `ls`, `drop` and `clear`.
### Collaboration and admin
- `repo user` — user management.
- `repo invite` — generate an invite token for a user.
- `repo config` — get/set/list repository configuration.
- `repo wiki` — wiki page operations.
- `repo uv` — unversioned file operations.
- `repo extract` — extract files from a version.
- `repo delta` — create/apply delta operations.
- `repo verify` — verify repository integrity.
- `repo serve` — serve the repository over HTTP for clone and sync
(`-P host:port`, default `127.0.0.1:8080`). Access follows the user
table. It is a sync endpoint, not a web UI.
- `repo submanifests` — `enable`, `retune`, `disable` and `status` for
sub-manifest trees; see [Sub-manifests](/docs/concepts/sub-manifests/).
### Known gaps vs. upstream Fossil CLI
The following upstream commands have no `libfossil` equivalent today:
- `fossil ui` / `fossil http` — no web UI or HTTP admin server is shipped.
`repo serve` answers sync and clone requests only; it is the counterpart
of `fossil server` for the sync protocol and nothing else.
- `fossil sync` / `fossil push` / `fossil pull` — there is no CLI verb; sync
is exposed via the Go API (`Repo.Sync`, `Repo.Pull`, `Repo.XferHandler`)
but not as a subcommand.
- `fossil update` — merging a newer trunk into a checkout has no dedicated
CLI command; use `repo co` plus `repo merge` as needed.
- `fossil search` — no CLI front-end for full-text search.
- `fossil fts-config` / FTS index management — no CLI command.
## What's not supported yet
Beyond the gaps listed above, several broader features of upstream Fossil are
not yet exposed by `libfossil`:
- **Web UI / HTTP admin** — no equivalent of `fossil ui`. The sync protocol
handler exists as `Repo.XferHandler()` and `repo serve`, but the HTML UI,
wiki viewer, ticket tracker, and forum are not implemented.
- **Interactive conflict resolution** — there is no interactive merge
tool. `repo conflicts` lists, shows and resolves conflicts by picking a
version or re-merging; otherwise edit out fossil's conflict markers in
your editor.
- **Full-text search CLI** — `internal/search` contains indexing and query
primitives, but no CLI command surfaces them. Use the Go API directly.
- **Ticket and forum subsystems** — no commands or pages. Ticket and forum
artifacts are synced and stored like any other artifact.
- **Mirror / export bridges** to Git, svn, and other systems — not implemented.
If any of these are load-bearing for your workflow, stay on upstream Fossil
for now.
## Interop patterns
Where `libfossil` shines is in environments where the upstream C binary is
inconvenient to run:
- **CGo-free CI pipelines** — `libfossil` links statically with a pure-Go
SQLite driver (`db/driver/modernc`), so clone/sync steps work on stripped
container images and cross-compiled targets with no system dependencies.
- **Embedding in Go services** — the `Repo` handle can be used directly from
a Go program to read history, extract artifacts, or accept pushes over a
custom transport (WebSocket, NATS, libp2p).
- **WASI / browser deployments** — the `db/driver/ncruces` SQLite driver
compiles to WebAssembly, enabling repo access from edge runtimes.
See [Examples](/docs/examples/) for runnable starting points. The
`cmd/libfossil` package is also a short working example: `main.go` wires the
CLI, while `driver_modernc.go` registers the default driver and
`driver_ncruces.go` shows the tagged WebAssembly-capable alternative.
---
# Embedding the CLI
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/guides/cli-embedding/
An application that keeps version history in a Fossil repository can run the `libfossil` binary as a child process: no checkout, no shadow working tree. `repo new` and four more commands make up the interface; their output and exit statuses are stable.
## Commands
```
libfossil -R repo.fossil repo new [--user U]
libfossil -R repo.fossil repo snapshot -m MSG [--user U] --path notes/x.md [--stdin] [--json]
libfossil -R repo.fossil repo log --path notes/x.md [--json] [-n N]
libfossil -R repo.fossil repo cat --at VERSION notes/x.md
libfossil -R repo.fossil repo diff [--json] A B [PATH]
```
**new** creates the repository file, empty and with no check-ins. The path is the `-R` path, or PATH as an argument (`repo new PATH`); when both are given PATH is the file created. With neither it exits 1. `--user U` names the admin user, which gets the setup capability; the default is the process's login name, as `fossil new` uses. The hash policy is `sha3`, fossil's default for a new repository, and the project code is generated. There is no flag for either. On success it prints `created repository: PATH` and a newline to stdout, exits 0, and leaves no file but the repository. Stock fossil 2.28 opens the result, and `fossil info`, `fossil sql` and `fossil hash-policy` read it.
`new` never overwrites. A file, an empty file or a directory already at PATH is refused with exit 1 and `repository already exists: PATH` on stderr, and what was there is untouched; `fossil new` also refuses an existing file, with exit 1. A PATH whose parent directory does not exist is refused with exit 1 and `parent directory does not exist: DIR` on stderr, and nothing is created; `fossil new` fails there with exit 1 as well, with a SQLite error. Create the parent first.
**snapshot** commits one file's bytes onto trunk and carries every other file forward. The bytes come from stdin with `--stdin`, otherwise from `--path` read on disk relative to the working directory. `--path` must be a name fossil accepts for a new file: a clean relative path (`notes/x.md`; not `/x`, `../x`, `./x`, `a//b`, `a/`, `a\b`) in valid UTF-8, with no control character (a byte below 0x20), and not one of the names fossil reserves for its own files (`_FOSSIL_` or `.fslckout`, in any case, with or without a `-wal`, `-shm` or `-journal` suffix, at the top or as the last element of the path). A rejected path exits 1 and says why. When the bytes equal the file at the trunk tip, nothing is committed and the existing tip is reported. The first snapshot into an empty repository becomes the root check-in. A repository that has check-ins but none on its main branch (`trunk`, or the branch the `main-branch` setting names) is refused with exit 1, rather than given a second, unrelated root. The check-in time is the current time.
**log** lists the check-ins that changed the path, newest first, as `fossil finfo` does. `-n` caps the count; the default is all.
**Paths.** `log --path`, `cat --at` and `diff` match a PATH as fossil does inside a check-out. They normalize it: a leading `./`, a trailing `/`, doubled `/` and `/./` or `/x/../` components are dropped, so `./Notes//A.md` and `Notes/A.md/` name `Notes/A.md`. They compare it with the repository's `case-sensitive` setting: when it is off, names that differ only in ASCII letter case are the same file, so `notes/a.md` finds `Notes/A.md`; `log` then merges the history of every name that matches, and `cat` reads the first matching file in the version. Without that setting the default is fossil's platform default: off on macOS and Windows, on elsewhere. Fossil also consults a global setting in `~/.fossil`; go-libfossil does not, so a `case-sensitive` set with `fossil settings --global` is ignored. A repository's names are never folded or rewritten, only matched. What is stored and printed is the name as the check-in recorded it.
A PATH is relative to the repository root, so there is no check-out for it to be outside of. `../x` and an absolute path such as `/x` are not special: they match no file, and exit 2 as not found. Fossil refuses them as outside the check-out and exits 1; go-libfossil keeps one status for every path that names nothing.
The exit status for a PATH or version that is not found is 2 in go-libfossil's own contract. `fossil` exits 1 for the same failures, so a caller that checks for 2 should not expect fossil's status.
A rename ends a name's history, as it does in `fossil finfo`. After `a.md` is renamed to `b.md`, `log --path b.md` lists the check-ins that changed `b.md`, then the rename itself (`action` `renamed`, `prev_name` `a.md`), and stops: it does not continue into `a.md`'s history. To follow a file across renames, run `log` again with `prev_name`. `log --path a.md` lists the rename first, as the check-in that removed `a.md` (`action` `deleted`), then `a.md`'s own changes. That first row is the one difference from `fossil finfo`, which lists only check-ins that hold a version of the file.
**cat** writes the file's bytes as VERSION holds them, as `fossil cat -r` does. VERSION is anything `repo resolve` accepts: `tip`, `trunk`, a branch, a uuid or a unique prefix of one. Without `--at`, `repo cat` still outputs an artifact by uuid.
**diff** with two versions prints the unified diff between them for PATH, or for every file that differs when PATH is omitted. PATH is matched as `fossil diff --from A --to B PATH` matches it: a file of that name, or every file under a directory of that name, in either version. A PATH that matches nothing in either version is not found (exit 2; fossil exits 1 with `not found`); a PATH that matches only unchanged files is an empty diff. With one or no version it compares the working directory, as before.
## Output
Every JSON document is a single line on stdout. Fields are never renamed or removed; new fields may be added, so parse tolerantly. Times are RFC 3339 in UTC with milliseconds, such as `2026-10-19T08:30:00.123Z`. A uuid is the repository's hash of the artifact (40 hex characters for SHA1, 64 for SHA3-256).
`snapshot` prints the check-in uuid and a newline. With `--json`:
```json
{"uuid": "9f2c...", "changed": true}
```
`changed` is false when the bytes already matched the tip and `uuid` is that tip.
`log --json` prints an array, newest first:
```json
[{"uuid": "9f2c...", "time": "2026-10-19T08:30:00.123Z", "user": "dan",
"comment": "edit", "action": "modified", "file_uuid": "ab12...", "prev_name": ""}]
```
`action` is `added`, `modified`, `deleted` or `renamed`. `file_uuid` is absent for `deleted`; `prev_name` is present only for `renamed`. Without `--json`, each line is `UUID10 TIME USER ACTION COMMENT`.
`diff --json A B [PATH]` prints an array, empty when nothing differs:
```json
[{"name": "notes/x.md", "unified": "--- a/notes/x.md\n+++ b/notes/x.md\n@@ ..."}]
```
## Exit statuses
| Status | Meaning |
| --- | --- |
| 0 | Success, including a `snapshot` that changed nothing and a `diff` with no differences. |
| 1 | Any other failure: a bad `--path`, an unreadable repository, an ambiguous version prefix, an I/O error. The message is on stderr. |
| 2 | Not found: the version does not resolve, the file is not in that version (`cat`), the path is in neither version (`diff`), or the path has no history (`log`). Nothing is written to stdout. |
| 80 | Usage error: an unknown command or flag, or a missing required flag or argument. The command-line parser sets this status and writes the error and the command's usage to stderr; stdout stays empty. |
These are the statuses of the `libfossil` binary itself. `go run ./cmd/libfossil` reports any non-zero status as 1, so measure them on a built binary. A combination the parser accepts but the command refuses, such as `diff --json` without two versions, exits 1.
## Concurrency
Processes may snapshot into one repository at the same time. A snapshot takes SQLite's write lock before it reads the trunk tip and keeps it until its check-in commits, so concurrent snapshots run one after another, form one linear trunk, and none loses another's files. A snapshot waits for the lock up to the busy timeout (5 seconds) and then fails with exit 1. Keep the repository outside any synced folder, and open it from one machine at a time.
---
# Extension points
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/guides/extension-points/
libfossil exposes four surfaces for consumers that want to instrument, extend, or replace pieces of the stack:
1. **Observer interfaces** for sync and checkout lifecycle events (`SyncObserver`, `CheckoutObserver`).
2. **SQLite driver registration** (`db.Register`) — pick the SQLite backend at link time.
3. **The `db.DB` abstraction** — an opaque wrapper around `database/sql` with Fossil-specific helpers. Treat it as internal; it is less stable than the observer and driver surfaces.
4. **The `Transport` interface** — one method, `RoundTrip(ctx, payload) ([]byte, error)`, carries a sync round. `Repo.Sync` and `Clone` take any implementation, so sync can run over HTTP, WebSocket, NATS or an in-process pipe. `TransportFunc` adapts a plain function.
## Observer interfaces
Observers are the supported way to attach telemetry, logging, or metrics to a `Repo` without forking or wrapping the public API. Both interfaces are defined in the root `github.com/danmestas/go-libfossil` package and are context-free by design so that they can be reused across synchronous and asynchronous callers.
### `SyncObserver`
Source: `observer.go`.
```go
type SyncObserver interface {
Started(info SessionStart)
RoundStarted(round int)
RoundCompleted(round int, stats RoundStats)
Completed(info SessionEnd)
Error(err error)
HandleStarted(info HandleStart)
HandleCompleted(info HandleEnd)
TableSyncStarted(info TableSyncStart)
TableSyncCompleted(info TableSyncEnd)
}
```
| Method | Fires when |
|---|---|
| `Started` | A client-side sync or clone session begins, before any rounds run. |
| `RoundStarted` | A new sync round starts (there are typically multiple rounds per session). |
| `RoundCompleted` | A sync round finishes — `RoundStats` carries per-round totals. |
| `Completed` | The sync/clone session ends successfully. |
| `Error` | The sync machinery reports a per-error event (protocol or transport). |
| `HandleStarted` | Server-side: an inbound sync/clone HTTP request is accepted. |
| `HandleCompleted` | Server-side: an inbound sync/clone request finishes. |
| `TableSyncStarted` | An extension table sync (for example `peer_registry`) is about to run. |
| `TableSyncCompleted` | An extension table sync finishes. |
libfossil ships `NopSyncObserver()` and `StdoutSyncObserver()` (logs to stderr) as drop-in implementations.
### `CheckoutObserver`
Source: `observer.go`.
```go
type CheckoutObserver interface {
ExtractStarted(info ExtractStart)
ExtractFileCompleted(name string, change UpdateChange)
ExtractCompleted(info ExtractEnd)
ScanStarted(dir string)
ScanCompleted(info ScanEnd)
CommitStarted(info CommitStart)
CommitCompleted(info CommitEnd)
Error(err error)
}
```
| Method | Fires when |
|---|---|
| `ExtractStarted` | An extract or update begins writing files from a checkin into the working tree. |
| `ExtractFileCompleted` | One file has been written — `change` is `ChangeAdded`, `ChangeModified`, or `ChangeDeleted`. |
| `ExtractCompleted` | The extract/update finishes. |
| `ScanStarted` | A working-tree scan (status/change detection) begins for `dir`. |
| `ScanCompleted` | The working-tree scan finishes. |
| `CommitStarted` | A checkin commit begins after staging. |
| `CommitCompleted` | The commit lands — `info.UUID` and `info.RID` identify the new checkin. |
| `Error` | A per-error event fires from the checkout pipeline. |
### Minimal logging observer
```go
package main
import (
"log"
libfossil "github.com/danmestas/go-libfossil"
)
type logSync struct{ libfossil.SyncObserver } // embed nop for defaults
func (logSync) Started(info libfossil.SessionStart) {
log.Printf("sync start project=%s push=%v pull=%v", info.ProjectCode, info.Push, info.Pull)
}
func (logSync) RoundCompleted(round int, s libfossil.RoundStats) {
log.Printf("round %d sent=%d recv=%d", round, s.FilesSent, s.FilesRecvd)
}
func newLogSyncObserver() libfossil.SyncObserver {
return logSync{SyncObserver: libfossil.NopSyncObserver()}
}
```
The embedded `NopSyncObserver()` provides no-op implementations of the methods you do not override, so you only need to define the events you care about. The same pattern applies to `CheckoutObserver` via `NopCheckoutObserver()`; `StdoutCheckoutObserver()` is its stderr logger.
For a full real-world observer that emits spans and counters, see `observer/otel/otel.go`. It implements both interfaces and is the reference you should copy from when wiring up your own telemetry backend.
## Adding an OpenTelemetry (or other) observer
`observer/otel` lives in its own Go module (separate `go.mod`) so that the OpenTelemetry SDK dependency does not leak into consumers that do not want it. To use it, import the subpackage explicitly and hand the observer to each `Repo` operation that accepts one (`SyncOpts.Observer`, `CloneOpts.Observer`, `HandleOpts.Observer`, `CheckoutCreateOpts.Observer`, `CheckoutOpenOpts.Observer`).
```go
import (
libfossil "github.com/danmestas/go-libfossil"
"github.com/danmestas/go-libfossil/observer/otel"
)
obs := otel.NewSyncObserver() // implements libfossil.SyncObserver
_, err := repo.Sync(ctx, transport, libfossil.SyncOpts{
Pull: true,
Observer: obs,
})
```
`otel.NewCheckoutObserver()` returns the matching `CheckoutObserver`. Any third-party telemetry library (Prometheus, Datadog, zap, etc.) should be vendored the same way: a sibling module that imports `libfossil` and implements the observer interfaces.
## SQLite driver interface
Source: `db/register.go`, `db/db.go`, `db/doc.go`.
libfossil never imports a SQLite driver directly. Instead, the `db` package exposes a single registration hook that a driver package calls from its `init()`:
```go
// db/register.go
type DriverConfig struct {
Name string
BuildDSN func(path string, pragmas map[string]string) string
}
func Register(cfg DriverConfig)
```
Each `db.Open(path)` / `db.OpenWith(path, cfg)` call routes through the currently registered driver. Exactly one driver may be registered per process — `Register` panics if called twice, if `Name` is empty, or if `BuildDSN` is nil. Likewise, calling `Open` with no driver registered panics with a message pointing at the driver import paths.
The driver contract is intentionally small:
- **`Name`**: the name passed to `sql.Open`. The driver package is responsible for registering this name with `database/sql` (typically via a blank import of the underlying SQLite library).
- **`BuildDSN(path, pragmas)`**: return a DSN string that opens `path` and applies the given pragmas. libfossil calls `db.DefaultPragmas()` and merges any caller overrides before handing them to `BuildDSN`.
### Built-in drivers
| Driver | Import | When to use |
|---|---|---|
| `modernc` | `_ "github.com/danmestas/go-libfossil/db/driver/modernc"` | Default. Pure Go, no cgo, works on every `GOOS`/`GOARCH` libfossil supports. |
| `ncruces` | `_ "github.com/danmestas/go-libfossil/db/driver/ncruces"` | Uses `github.com/ncruces/go-sqlite3` (WASM-based SQLite). Pick this for WASM builds or environments where you need the ncruces feature set. |
### Registering a custom driver
Use this pattern when you want to wrap an instrumented SQLite build, redirect to a network-attached SQLite, or inject additional pragmas. Model the package after `db/driver/modernc/modernc.go`:
```go
package mydriver
import (
"fmt"
"strings"
"github.com/danmestas/go-libfossil/db"
_ "example.com/my/sqlite" // registers "mysqlite" with database/sql
)
func init() {
db.Register(db.DriverConfig{
Name: "mysqlite",
BuildDSN: buildDSN,
})
}
func buildDSN(path string, pragmas map[string]string) string {
var parts []string
for k, v := range pragmas {
parts = append(parts, fmt.Sprintf("_pragma=%s(%s)", k, v))
}
if len(parts) == 0 {
return path
}
return fmt.Sprintf("file:%s?%s", path, strings.Join(parts, "&"))
}
```
Consumers then blank-import your package (`_ "example.com/mydriver"`) exactly like they would for `modernc` or `ncruces`.
## The `DB` abstraction
`github.com/danmestas/go-libfossil/db` wraps `*sql.DB` with Fossil-aware helpers: DSN construction, default-pragma setup, WAL/nolock handling for WASM, transaction scoping via `DB.WithTx`, and the `Querier` interface (satisfied by both `DB` and `Tx`) so repository code can be written transaction-agnostic. The exported surface is stable for reading — `SqlDB()`, `Path()`, `Driver()`, `Exec`/`Query*` — but the struct itself is treated as internal: the public `Repo` API is the intended extension point. Prefer writing a custom driver (above) over implementing `DB` yourself. See `db/db.go`, `db/config.go`, and `db/scan.go` for the source of truth.
---
# Testing
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/guides/testing/
This guide describes libfossil's test strategy for contributors adding tests or
investigating failures. For the module layout referenced throughout, see
[Architecture](/docs/concepts/architecture/).
## Overview
libfossil's test suite has these layers:
1. **Unit tests** — standard `go test ./...` coverage for individual packages
under the root module and `internal/...`.
2. **Conformance tests** — `./conformance`, which checks the library against
the Fossil Internet-Drafts in `conformance/rfcs/`, requirement by
requirement, and against real `fossil` and `zit` binaries where they are
present. See [Conformance](/docs/reference/conformance/).
3. **DST simulation tests** — deterministic simulation scenarios under
`./dst/...` that drive multiple repos through a virtual clock with fault
injection and invariant checks.
4. **Fuzz targets** — Go native fuzz tests for the parsers and codecs.
5. **Dual-driver matrix** — the whole suite runs against two SQLite drivers
(`modernc` and `ncruces`) to catch driver-specific behavior.
## Running tests
The `Makefile` wraps the common targets:
- `make test` — runs `go test ./... -count=1 -timeout=120s` with the default
`modernc` driver.
- `make test-drivers` — runs the full suite twice:
`go test ./... -count=1 -timeout=120s` followed by
`go test -tags test_ncruces ./... -count=1 -timeout=120s`.
- `make test-otel` — runs the out-of-workspace OTel submodule with
`cd observer/otel && GOWORK=off go test ./... -count=1`.
- `make test-all` — chains `test-drivers`, `test-otel`, and `go build
./cmd/libfossil/`.
- `make ci` — mirrors `.github/workflows/test.yml`. It uses `GOWORK=off`,
runs the packages other than `dst` with a 120-second timeout, runs `./dst/...`
with a 300-second timeout, runs the driver submodule tests, `go vet` and
the CLI build, then repeats the tests under `-tags test_ncruces` and runs
the OTel submodule. Run it before opening a PR. `make release VERSION=vX.Y.Z`
runs it before it tags.
`make setup-hooks` points `core.hooksPath` at `.githooks/`. The installed
pre-commit hook runs both drivers with `-short`, `go vet`, the OTel submodule,
the CLI build, and a check that the generated SDK reference pages are in sync,
in roughly 45 seconds. Skip with `git commit --no-verify` only in emergencies.
Tests that compare against real binaries skip when the binary is missing.
`REQUIRE_FOSSIL_BIN=1` turns a missing `fossil` into a failure, and CI sets
it. `fossil` keeps a global config in `$HOME`, so run those tests with a
scratch `HOME` if you do not want your own touched.
## Fuzzing
There are seven fuzz targets. Each is also a regular test over its seed
corpus, so `go test` runs the seeds without `-fuzz`.
| Target | Package | Exercises |
|---|---|---|
| `FuzzUnified` | `internal/diff` | Unified diff output. |
| `FuzzTableSyncPayload` | `internal/sync` | Extension-table sync payload parsing. |
| `FuzzParseXDelete` | `internal/xfer` | The `xdelete` card parser. |
| `FuzzDecode` | `internal/xfer` | The xfer message decoder and its size limits. |
| `FuzzArtifactRoundTrip` | `conformance` | Artifact parse and re-encode. |
| `FuzzDeltaApply` | `conformance` | Applying arbitrary deltas. |
| `FuzzDeltaRoundTrip` | `conformance` | Delta create then apply. |
Run one with, for example:
```
go test ./conformance -run '^$' -fuzz FuzzDeltaApply -fuzztime 60s
```
CI does not run `-fuzz`, and no Makefile target or script runs the fuzz
targets or a seed sweep. They are run by hand.
## Corpus parity gate
`TestCloneCorpusParity` (root package, `clone_corpus_parity_test.go`) clones one
repository two ways — with the canonical `fossil` binary and with
`libfossil.Clone` — and compares what each derived. It is opt-in:
```
LIBFOSSIL_CORPUS_PARITY=1 go test -run TestCloneCorpusParity -v .
```
Without the variable it skips, so `make test` is unchanged in both runtime and
outcome. It also needs a fossil binary and skips without one (or fails, under
`REQUIRE_FOSSIL_BIN=1`). Budget roughly a second and under a megabyte of temp
space; everything lands in `t.TempDir()` and is removed on exit.
It exists because the per-package parity tests compare our derivation against
fossil's for fixtures built in-test, which only covers the shapes someone
thought to build. Issue #184 — a silent 100% loss of the ticket-change artifact
shape — passed that suite and was found by an external benchmark instead.
The gate asserts two things:
- **Per-`event`-type counts match**, not just the total. This distinction is the
point: on the real Fossil repository the totals read 23,396 vs 27,099, a 14%
shortfall easy to wave off, while the per-type breakdown put ticket events at
96 vs 3,808.
- **Row totals match for `blob`, `event`, `mlink`, `plink`, `tagxref` and
`leaf`**, reported in both directions. A surplus matters as much as a
shortfall — our `tagxref` ran +17,119 over fossil's on the real repository,
which is what writing a tag without finishing the work it marks looks like.
Failures name the table or type and the delta, e.g.
`event type "t": got 0, want 10 (-10)`.
The corpus is built by the canonical binary at test time rather than
downloaded: seconds and kilobytes instead of the ~10 minutes and ~1 GB the real
Fossil repository costs, no network dependency, and contents that are a
property of the test rather than of whatever a remote server holds today. It
covers check-ins (including a branch and a merge), ticket creations and
updates, a wiki page, a technote, an attachment, and a tag control artifact.
`requireCorpusShapes` classifies every blob by its card set and fails if any
shape falls below its expected floor. Without that check a corpus that quietly
stopped producing ticket artifacts would let the gate pass while covering
nothing — the exact blind spot it exists to remove.
Not covered: forum posts (`f` events), because fossil exposes no CLI to create
them; the scale-dependent behaviour of a large repository; and per-row content
comparison — this gate counts rows, and `internal/manifest` diffs them.
## Driver matrix
libfossil abstracts SQLite behind `db/` and two driver submodules. Both must
pass identical tests:
- **`modernc`** (default) — pure-Go port of SQLite. Used by every build
that doesn't set a tag. Registered via `internal/testdriver/modernc.go`
under `//go:build !test_ncruces && !test_mattn`.
- **`ncruces`** (wasm-capable) — selected with `-tags test_ncruces`, suitable
for WASM targets. Registered via `internal/testdriver/ncruces.go` under
`//go:build test_ncruces`.
GitHub Actions (`.github/workflows/test.yml`) runs `test`, `test-ncruces`,
`build-wasm`, `build-tinygo`, `test-otel` and `test-docs` as parallel jobs. The first two
install the pinned fossil 2.28, build every package, then run the unit tests,
DST and the driver submodule tests under modernc and ncruces respectively.
`build-wasm` compiles the packages for `js/wasm` with the ncruces tag,
`build-tinygo` builds `examples/tinygo` with TinyGo 0.43, and
`test-docs` builds this site with Hugo. For the canonical local invocation,
use `make test-drivers` or `make ci` rather than hand-rolling tag flags.
## DST (Deterministic Simulation Testing)
DST is inspired by FoundationDB and TigerBeetle: run many repos in a single
process under a simulated clock, inject faults, and replay a failing seed
deterministically. The harness lives in `dst/simulator.go` and
`dst/invariants.go`.
### Seeds and reproducibility
`dst.SimConfig.Seed` drives every random source used by the simulator: the
event queue, network fault decisions, and a `SeededBuggify` PRNG that gates
fault-injection sites. Any failure on seed N reproduces exactly on seed N.
`dst/scenario_test.go` exposes three flags driving `TestDST`:
```
go test ./dst -run 'TestDST$' -seed=42 -level=hostile -steps=10000
```
`TestDST` skips unless at least one flag is set. It builds a master repo
with 50 artifacts, 5 unversioned files and 3 private blobs, starts a
simulator with 3 leaves, and checks safety invariants as it runs.
- `-seed=` — seed passed to `SimConfig.Seed`. In `TestDST`, 0 means
seed 1; other scenarios use their own fixed seeds.
- `-level={normal,adversarial,hostile}` — picks a `severity` struct setting
`DropRate` and `Buggify`: `normal` is no drops and no BUGGIFY,
`adversarial` is 10% drops with BUGGIFY, `hostile` is 20% drops with
BUGGIFY. An empty or unknown level is `normal`.
- `-steps=` — caps the number of events processed (default 10000 in
`TestDST`).
Seed-parameterized scenarios (e.g. `TestCloneDSTSeedSweep` in
`dst/clone_test.go`, which runs seeds 0 to 19) loop over seeds via `t.Run(fmt.Sprintf("seed_%d", seed),
...)`. To reproduce a single sub-seed: `go test ./dst -run
TestCloneDSTSeedSweep/seed_7 -v`.
### Invariants
`dst/invariants.go` defines per-node and cross-node checks. `Simulator.CheckSafety`
runs the per-node set after every `SafetyCheckInterval` steps; convergence
checks run once the sim has quiesced:
- **Safety (anytime):** `CheckBlobIntegrity` (UUID matches hash of expanded
content), `CheckDeltaChains` (`delta.srcid` resolves to a blob),
`CheckNoOrphanPhantoms`, `CheckUVIntegrity`, `CheckTableSyncIntegrity`.
`CheckTagxrefIntegrity` is a separate check that scenarios call
directly.
- **Convergence (after fault-free sync):** `CheckConvergence` (leaves match
master blob-for-blob), `CheckSubsetOf`, `CheckUVConvergence`,
`CheckTableSyncConvergence`, `CheckTombstoneConvergence`.
Failures return a `*InvariantError{Invariant, NodeID, Detail}` naming the
failing invariant and the offending node. The scenarios add the seed and
level to the failure message, so a log line is enough to replay the run.
The checks run with BUGGIFY suspended, so they see what is persisted and
not faults injected into their own reads.
## BUGGIFY
BUGGIFY marks code points where faults can be injected — shrunk send
budgets, dropped or rejected cards, bad nonces, byte-flipped content. About
twenty sites exist in `internal/sync`, `internal/blob` and
`internal/content`. In production
the check is a single boolean read; under DST the seeded PRNG decides whether
to fire.
Two styles appear in the tree:
1. **Process-global, used outside the sync path.** `simio.Buggify(probability)`
consults a process-global state toggled by `simio.EnableBuggify(seed)` /
`DisableBuggify()` (see `simio/buggify.go`). Example from
`internal/content/content.go`:
```go
// BUGGIFY: flip a byte in expanded content to exercise the hash check
// below under DST.
if check && simio.Buggify(0.01) && len(content) > 0 {
corrupted := make([]byte, len(content))
copy(corrupted, content)
corrupted[0] ^= 0xFF
content = corrupted
verified = false
}
```
2. **Scoped, passed through session options.** `BuggifyChecker` is an interface
(`Check(site string, probability float64) bool`) defined in
`internal/sync/session.go` and re-exported from the root as
`libfossil.BuggifyChecker`. Sync code threads a checker through so each site
has a stable name:
```go
// BUGGIFY: drop the last file card to simulate partial send.
if s.opts.Buggify != nil && s.opts.Buggify.Check("sync.buildFileCards.skip", 0.05) && len(cards) > 0 {
cards = cards[:len(cards)-1]
}
```
`dst.SeededBuggify` in `dst/simulator.go` implements `BuggifyChecker` with a
seeded `*rand.Rand` (it ignores the site name). `Simulator.New` enables both styles when `SimConfig.Buggify`
is true, wiring independent PRNG streams into the client, the server-side
`MockFossil`, and the global `simio` state.
## Interop with fossil 2.28 and zit
Two other implementations check libfossil's output.
**Fossil.** CI downloads the official fossil 2.28 release tarball
(`FOSSIL_VERSION` in `.github/workflows/test.yml`) and verifies its SHA-256
and its reported version, so every run compares against the same build.
Tests in `conformance/`, `cli/` and the root package create repositories with
stock `fossil`, have it open and verify what libfossil wrote, and compare
the rows libfossil derives with the rows fossil derives. Locally they use
the `fossil` on `PATH`; `fossil version` should report 2.28.
**zit.** zit is a Zig reimplementation of Fossil. The `TestZit*` tests in
`conformance/*zit*_test.go` (20 of them) cover the newer drafts: sub-manifest
trees (`zit_interop_test.go`, `zit_writer_test.go`), sparse cones and sparse
stores, and horizons (serve, structural seeds, fallback, push check, cursor,
nodes, history URL and merge base). Each runs both ways where it can:
go-libfossil clones from and pushes to `zit serve`, and zit clones from and
pushes to go-libfossil's handler. They need a `zit` on `PATH` (or `ZIT_BIN`)
at a recent enough commit and skip otherwise. `REQUIRE_ZIT_BIN=1` makes a
missing or older zit a failure. CI has no zit, so there the bundle tests
(`TestZitTreeBundle`, `TestZitWriterBundle`) read recorded copies of what
zit wrote from `conformance/testdata/`. `ZIT_REGENERATE=1` rewrites those
copies from a live zit.
Signed check-ins are covered the same way: `TestSignedCheckin` has fossil
and zit clone and read libfossil's signed check-ins, and checks that a
check-in zit signs verifies in libfossil.
## Writing new tests
- Prefer table-driven tests in unit code; include success and failure rows.
- If you touch sync, merge, clone, or checkin, add a DST scenario under
`dst/` and run it across a seed range (see `TestCloneDSTSeedSweep` for the
pattern).
- Mark any new fault-sensitive code point with BUGGIFY. Use the scoped
`BuggifyChecker` style when the site lives inside the sync session; reserve
`simio.Buggify` for code that runs outside an explicit session scope.
- Assert invariants. If the new behavior warrants one, add it to
`dst/invariants.go` and wire it into `CheckSafety` or a convergence helper.
- Confirm tests pass under both drivers (`make test-drivers`) before
requesting review.
---
# Reference
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/reference/
Reference material, most of it generated from the source.
- [SDK](/docs/reference/sdk/): the Go API, one page per package, generated by gomarkdoc from doc comments.
- [CLI](/docs/reference/cli/): every `libfossil` command, generated from the binary's `--help` output.
- [Settings](/docs/reference/settings/): the repository settings go-libfossil reads.
- [Conformance](/docs/reference/conformance/): every requirement of every draft and its status, generated from `TestCoverage`.
---
# SDK
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/reference/sdk/
Auto-generated Go API reference. One page per package, generated by [gomarkdoc](https://github.com/princjef/gomarkdoc) from doc comments.
Run `make docs-gen-sdk` to regenerate. The pre-commit hook fails if the working tree drifts.
---
# cli
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/reference/sdk/cli/api/
# cli
```go
import "github.com/danmestas/go-libfossil/cli"
```
## Index
- [func Version\(\) string](<#Version>)
- [type Globals](<#Globals>)
- [func \(g \*Globals\) OpenRepo\(\) \(\*libfossil.Repo, error\)](<#Globals.OpenRepo>)
- [type RepoAddCmd](<#RepoAddCmd>)
- [func \(c \*RepoAddCmd\) Run\(g \*Globals\) \(err error\)](<#RepoAddCmd.Run>)
- [type RepoAnnotateCmd](<#RepoAnnotateCmd>)
- [func \(c \*RepoAnnotateCmd\) Run\(g \*Globals\) error](<#RepoAnnotateCmd.Run>)
- [type RepoBisectBadCmd](<#RepoBisectBadCmd>)
- [func \(c \*RepoBisectBadCmd\) Run\(g \*Globals\) error](<#RepoBisectBadCmd.Run>)
- [type RepoBisectCmd](<#RepoBisectCmd>)
- [type RepoBisectGoodCmd](<#RepoBisectGoodCmd>)
- [func \(c \*RepoBisectGoodCmd\) Run\(g \*Globals\) error](<#RepoBisectGoodCmd.Run>)
- [type RepoBisectLsCmd](<#RepoBisectLsCmd>)
- [func \(c \*RepoBisectLsCmd\) Run\(g \*Globals\) error](<#RepoBisectLsCmd.Run>)
- [type RepoBisectNextCmd](<#RepoBisectNextCmd>)
- [func \(c \*RepoBisectNextCmd\) Run\(g \*Globals\) error](<#RepoBisectNextCmd.Run>)
- [type RepoBisectResetCmd](<#RepoBisectResetCmd>)
- [func \(c \*RepoBisectResetCmd\) Run\(g \*Globals\) error](<#RepoBisectResetCmd.Run>)
- [type RepoBisectSkipCmd](<#RepoBisectSkipCmd>)
- [func \(c \*RepoBisectSkipCmd\) Run\(g \*Globals\) error](<#RepoBisectSkipCmd.Run>)
- [type RepoBisectStatusCmd](<#RepoBisectStatusCmd>)
- [func \(c \*RepoBisectStatusCmd\) Run\(g \*Globals\) error](<#RepoBisectStatusCmd.Run>)
- [type RepoBlameCmd](<#RepoBlameCmd>)
- [type RepoBranchCloseCmd](<#RepoBranchCloseCmd>)
- [func \(c \*RepoBranchCloseCmd\) Run\(g \*Globals\) error](<#RepoBranchCloseCmd.Run>)
- [type RepoBranchCmd](<#RepoBranchCmd>)
- [type RepoBranchLsCmd](<#RepoBranchLsCmd>)
- [func \(c \*RepoBranchLsCmd\) Run\(g \*Globals\) error](<#RepoBranchLsCmd.Run>)
- [type RepoBranchNewCmd](<#RepoBranchNewCmd>)
- [func \(c \*RepoBranchNewCmd\) Run\(g \*Globals\) error](<#RepoBranchNewCmd.Run>)
- [type RepoCatCmd](<#RepoCatCmd>)
- [func \(c \*RepoCatCmd\) Run\(g \*Globals\) error](<#RepoCatCmd.Run>)
- [type RepoCiCmd](<#RepoCiCmd>)
- [func \(c \*RepoCiCmd\) Run\(g \*Globals\) \(err error\)](<#RepoCiCmd.Run>)
- [type RepoCloneCmd](<#RepoCloneCmd>)
- [func \(c \*RepoCloneCmd\) Run\(g \*Globals\) error](<#RepoCloneCmd.Run>)
- [type RepoCmd](<#RepoCmd>)
- [type RepoCoCmd](<#RepoCoCmd>)
- [func \(c \*RepoCoCmd\) Run\(g \*Globals\) error](<#RepoCoCmd.Run>)
- [type RepoConfigCmd](<#RepoConfigCmd>)
- [type RepoConfigGetCmd](<#RepoConfigGetCmd>)
- [func \(c \*RepoConfigGetCmd\) Run\(g \*Globals\) error](<#RepoConfigGetCmd.Run>)
- [type RepoConfigLsCmd](<#RepoConfigLsCmd>)
- [func \(c \*RepoConfigLsCmd\) Run\(g \*Globals\) error](<#RepoConfigLsCmd.Run>)
- [type RepoConfigSetCmd](<#RepoConfigSetCmd>)
- [func \(c \*RepoConfigSetCmd\) Run\(g \*Globals\) error](<#RepoConfigSetCmd.Run>)
- [type RepoConflictsCmd](<#RepoConflictsCmd>)
- [type RepoConflictsExtractCmd](<#RepoConflictsExtractCmd>)
- [func \(c \*RepoConflictsExtractCmd\) Run\(g \*Globals\) error](<#RepoConflictsExtractCmd.Run>)
- [type RepoConflictsLsCmd](<#RepoConflictsLsCmd>)
- [func \(c \*RepoConflictsLsCmd\) Run\(g \*Globals\) error](<#RepoConflictsLsCmd.Run>)
- [type RepoConflictsMergeCmd](<#RepoConflictsMergeCmd>)
- [func \(c \*RepoConflictsMergeCmd\) Run\(g \*Globals\) error](<#RepoConflictsMergeCmd.Run>)
- [type RepoConflictsPickCmd](<#RepoConflictsPickCmd>)
- [func \(c \*RepoConflictsPickCmd\) Run\(g \*Globals\) error](<#RepoConflictsPickCmd.Run>)
- [type RepoConflictsShowCmd](<#RepoConflictsShowCmd>)
- [func \(c \*RepoConflictsShowCmd\) Run\(g \*Globals\) error](<#RepoConflictsShowCmd.Run>)
- [type RepoDeltaApplyCmd](<#RepoDeltaApplyCmd>)
- [func \(c \*RepoDeltaApplyCmd\) Run\(g \*Globals\) error](<#RepoDeltaApplyCmd.Run>)
- [type RepoDeltaCmd](<#RepoDeltaCmd>)
- [type RepoDeltaCreateCmd](<#RepoDeltaCreateCmd>)
- [func \(c \*RepoDeltaCreateCmd\) Run\(g \*Globals\) error](<#RepoDeltaCreateCmd.Run>)
- [type RepoDiffCmd](<#RepoDiffCmd>)
- [func \(c \*RepoDiffCmd\) Run\(g \*Globals\) error](<#RepoDiffCmd.Run>)
- [type RepoExtractCmd](<#RepoExtractCmd>)
- [func \(c \*RepoExtractCmd\) Run\(g \*Globals\) error](<#RepoExtractCmd.Run>)
- [type RepoHashCmd](<#RepoHashCmd>)
- [func \(c \*RepoHashCmd\) Run\(g \*Globals\) error](<#RepoHashCmd.Run>)
- [type RepoInfoCmd](<#RepoInfoCmd>)
- [func \(c \*RepoInfoCmd\) Run\(g \*Globals\) error](<#RepoInfoCmd.Run>)
- [type RepoInviteCmd](<#RepoInviteCmd>)
- [func \(c \*RepoInviteCmd\) Run\(g \*Globals\) error](<#RepoInviteCmd.Run>)
- [type RepoLsCmd](<#RepoLsCmd>)
- [func \(c \*RepoLsCmd\) Run\(g \*Globals\) error](<#RepoLsCmd.Run>)
- [type RepoMergeCmd](<#RepoMergeCmd>)
- [func \(c \*RepoMergeCmd\) Run\(g \*Globals\) \(err error\)](<#RepoMergeCmd.Run>)
- [type RepoMergeResolveCmd](<#RepoMergeResolveCmd>)
- [func \(c \*RepoMergeResolveCmd\) Run\(g \*Globals\) \(err error\)](<#RepoMergeResolveCmd.Run>)
- [type RepoNewCmd](<#RepoNewCmd>)
- [func \(c \*RepoNewCmd\) Run\(g \*Globals\) error](<#RepoNewCmd.Run>)
- [type RepoOpenCmd](<#RepoOpenCmd>)
- [func \(c \*RepoOpenCmd\) Run\(g \*Globals\) error](<#RepoOpenCmd.Run>)
- [type RepoQueryCmd](<#RepoQueryCmd>)
- [func \(c \*RepoQueryCmd\) Run\(g \*Globals\) error](<#RepoQueryCmd.Run>)
- [type RepoRedoCmd](<#RepoRedoCmd>)
- [func \(c \*RepoRedoCmd\) Run\(g \*Globals\) error](<#RepoRedoCmd.Run>)
- [type RepoRenameCmd](<#RepoRenameCmd>)
- [func \(c \*RepoRenameCmd\) Run\(g \*Globals\) \(err error\)](<#RepoRenameCmd.Run>)
- [type RepoResolveCmd](<#RepoResolveCmd>)
- [func \(c \*RepoResolveCmd\) Run\(g \*Globals\) error](<#RepoResolveCmd.Run>)
- [type RepoRevertCmd](<#RepoRevertCmd>)
- [func \(c \*RepoRevertCmd\) Run\(g \*Globals\) \(err error\)](<#RepoRevertCmd.Run>)
- [type RepoRmCmd](<#RepoRmCmd>)
- [func \(c \*RepoRmCmd\) Run\(g \*Globals\) \(err error\)](<#RepoRmCmd.Run>)
- [type RepoSchemaAddCmd](<#RepoSchemaAddCmd>)
- [func \(c \*RepoSchemaAddCmd\) Run\(g \*Globals\) error](<#RepoSchemaAddCmd.Run>)
- [type RepoSchemaCmd](<#RepoSchemaCmd>)
- [type RepoSchemaListCmd](<#RepoSchemaListCmd>)
- [func \(c \*RepoSchemaListCmd\) Run\(g \*Globals\) error](<#RepoSchemaListCmd.Run>)
- [type RepoSchemaRemoveCmd](<#RepoSchemaRemoveCmd>)
- [func \(c \*RepoSchemaRemoveCmd\) Run\(g \*Globals\) error](<#RepoSchemaRemoveCmd.Run>)
- [type RepoSchemaShowCmd](<#RepoSchemaShowCmd>)
- [func \(c \*RepoSchemaShowCmd\) Run\(g \*Globals\) error](<#RepoSchemaShowCmd.Run>)
- [type RepoServeCmd](<#RepoServeCmd>)
- [func \(c \*RepoServeCmd\) Run\(g \*Globals\) error](<#RepoServeCmd.Run>)
- [type RepoStashApplyCmd](<#RepoStashApplyCmd>)
- [func \(c \*RepoStashApplyCmd\) Run\(g \*Globals\) error](<#RepoStashApplyCmd.Run>)
- [type RepoStashClearCmd](<#RepoStashClearCmd>)
- [func \(c \*RepoStashClearCmd\) Run\(g \*Globals\) error](<#RepoStashClearCmd.Run>)
- [type RepoStashCmd](<#RepoStashCmd>)
- [type RepoStashDropCmd](<#RepoStashDropCmd>)
- [func \(c \*RepoStashDropCmd\) Run\(g \*Globals\) error](<#RepoStashDropCmd.Run>)
- [type RepoStashLsCmd](<#RepoStashLsCmd>)
- [func \(c \*RepoStashLsCmd\) Run\(g \*Globals\) error](<#RepoStashLsCmd.Run>)
- [type RepoStashPopCmd](<#RepoStashPopCmd>)
- [func \(c \*RepoStashPopCmd\) Run\(g \*Globals\) error](<#RepoStashPopCmd.Run>)
- [type RepoStashSaveCmd](<#RepoStashSaveCmd>)
- [func \(c \*RepoStashSaveCmd\) Run\(g \*Globals\) error](<#RepoStashSaveCmd.Run>)
- [type RepoStatusCmd](<#RepoStatusCmd>)
- [func \(c \*RepoStatusCmd\) Run\(g \*Globals\) error](<#RepoStatusCmd.Run>)
- [type RepoSubmanifestsCmd](<#RepoSubmanifestsCmd>)
- [type RepoSubmanifestsDisableCmd](<#RepoSubmanifestsDisableCmd>)
- [func \(c \*RepoSubmanifestsDisableCmd\) Run\(g \*Globals\) error](<#RepoSubmanifestsDisableCmd.Run>)
- [type RepoSubmanifestsEnableCmd](<#RepoSubmanifestsEnableCmd>)
- [func \(c \*RepoSubmanifestsEnableCmd\) Run\(g \*Globals\) error](<#RepoSubmanifestsEnableCmd.Run>)
- [type RepoSubmanifestsRetuneCmd](<#RepoSubmanifestsRetuneCmd>)
- [func \(c \*RepoSubmanifestsRetuneCmd\) Run\(g \*Globals\) error](<#RepoSubmanifestsRetuneCmd.Run>)
- [type RepoSubmanifestsStatusCmd](<#RepoSubmanifestsStatusCmd>)
- [func \(c \*RepoSubmanifestsStatusCmd\) Run\(g \*Globals\) error](<#RepoSubmanifestsStatusCmd.Run>)
- [type RepoTagAddCmd](<#RepoTagAddCmd>)
- [func \(c \*RepoTagAddCmd\) Run\(g \*Globals\) error](<#RepoTagAddCmd.Run>)
- [type RepoTagCmd](<#RepoTagCmd>)
- [type RepoTagLsCmd](<#RepoTagLsCmd>)
- [func \(c \*RepoTagLsCmd\) Run\(g \*Globals\) error](<#RepoTagLsCmd.Run>)
- [type RepoTimelineCmd](<#RepoTimelineCmd>)
- [func \(c \*RepoTimelineCmd\) Run\(g \*Globals\) error](<#RepoTimelineCmd.Run>)
- [type RepoUVCmd](<#RepoUVCmd>)
- [type RepoUVDeleteCmd](<#RepoUVDeleteCmd>)
- [func \(c \*RepoUVDeleteCmd\) Run\(g \*Globals\) error](<#RepoUVDeleteCmd.Run>)
- [type RepoUVGetCmd](<#RepoUVGetCmd>)
- [func \(c \*RepoUVGetCmd\) Run\(g \*Globals\) error](<#RepoUVGetCmd.Run>)
- [type RepoUVLsCmd](<#RepoUVLsCmd>)
- [func \(c \*RepoUVLsCmd\) Run\(g \*Globals\) error](<#RepoUVLsCmd.Run>)
- [type RepoUVPutCmd](<#RepoUVPutCmd>)
- [func \(c \*RepoUVPutCmd\) Run\(g \*Globals\) error](<#RepoUVPutCmd.Run>)
- [type RepoUndoCmd](<#RepoUndoCmd>)
- [func \(c \*RepoUndoCmd\) Run\(g \*Globals\) error](<#RepoUndoCmd.Run>)
- [type RepoUserCmd](<#RepoUserCmd>)
- [type RepoVerifyCmd](<#RepoVerifyCmd>)
- [func \(c \*RepoVerifyCmd\) Run\(g \*Globals\) error](<#RepoVerifyCmd.Run>)
- [type RepoWikiCmd](<#RepoWikiCmd>)
- [type RepoWikiExportCmd](<#RepoWikiExportCmd>)
- [func \(c \*RepoWikiExportCmd\) Run\(g \*Globals\) error](<#RepoWikiExportCmd.Run>)
- [type RepoWikiLsCmd](<#RepoWikiLsCmd>)
- [func \(c \*RepoWikiLsCmd\) Run\(g \*Globals\) error](<#RepoWikiLsCmd.Run>)
- [type UserAddCmd](<#UserAddCmd>)
- [func \(c \*UserAddCmd\) Run\(g \*Globals\) error](<#UserAddCmd.Run>)
- [type UserListCmd](<#UserListCmd>)
- [func \(c \*UserListCmd\) Run\(g \*Globals\) error](<#UserListCmd.Run>)
- [type UserPasswdCmd](<#UserPasswdCmd>)
- [func \(c \*UserPasswdCmd\) Run\(g \*Globals\) error](<#UserPasswdCmd.Run>)
- [type UserRmCmd](<#UserRmCmd>)
- [func \(c \*UserRmCmd\) Run\(g \*Globals\) error](<#UserRmCmd.Run>)
- [type UserUpdateCmd](<#UserUpdateCmd>)
- [func \(c \*UserUpdateCmd\) Run\(g \*Globals\) error](<#UserUpdateCmd.Run>)
- [type VersionCmd](<#VersionCmd>)
- [func \(c \*VersionCmd\) Run\(\) error](<#VersionCmd.Run>)
## func [Version]()
```go
func Version() string
```
Version returns a single\-line build identifier for this binary: the module version \(or a build\-time override\), the Go toolchain version, and the platform. The shape is fixed at four whitespace\-separated fields \-\- "libfossil \ \ \/\" \-\- regardless of which fields carried real data, so a caller can rely on strings.Fields\(Version\(\)\) unconditionally. This is the same string both \`libfossil version\` and \`libfossil \-\-version\` print.
## type [Globals]()
Globals holds flags shared by all CLI commands.
```go
type Globals struct {
Repo string `short:"R" help:"Path to repository file" type:"path"`
Verbose bool `short:"v" help:"Verbose output"`
}
```
### func \(\*Globals\) [OpenRepo]()
```go
func (g *Globals) OpenRepo() (*libfossil.Repo, error)
```
OpenRepo opens a Fossil repository using the handle API. If Repo is empty, it searches for a .fossil file or .fslckout checkout.
## type [RepoAddCmd]()
RepoAddCmd stages files for addition in the checkout database.
```go
type RepoAddCmd struct {
Files []string `arg:"" required:"" help:"Files to stage for addition"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoAddCmd\) [Run]()
```go
func (c *RepoAddCmd) Run(g *Globals) (err error)
```
## type [RepoAnnotateCmd]()
RepoAnnotateCmd annotates file lines with version history.
```go
type RepoAnnotateCmd struct {
File string `arg:"" required:"" help:"File to annotate"`
Version string `help:"Starting version (default: tip)"`
}
```
### func \(\*RepoAnnotateCmd\) [Run]()
```go
func (c *RepoAnnotateCmd) Run(g *Globals) error
```
## type [RepoBisectBadCmd]()
RepoBisectBadCmd marks a version as bad.
```go
type RepoBisectBadCmd struct {
Version string `arg:"" optional:"" help:"Version to mark (default: current checkout)"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoBisectBadCmd\) [Run]()
```go
func (c *RepoBisectBadCmd) Run(g *Globals) error
```
## type [RepoBisectCmd]()
RepoBisectCmd groups binary search operations.
```go
type RepoBisectCmd struct {
Good RepoBisectGoodCmd `cmd:"" help:"Mark version as good"`
Bad RepoBisectBadCmd `cmd:"" help:"Mark version as bad"`
Next RepoBisectNextCmd `cmd:"" help:"Check out midpoint version"`
Skip RepoBisectSkipCmd `cmd:"" help:"Skip current version"`
Reset RepoBisectResetCmd `cmd:"" help:"Clear bisect state"`
Ls RepoBisectLsCmd `cmd:"" help:"Show bisect path"`
Status RepoBisectStatusCmd `cmd:"" help:"Show bisect state"`
}
```
## type [RepoBisectGoodCmd]()
RepoBisectGoodCmd marks a version as good.
```go
type RepoBisectGoodCmd struct {
Version string `arg:"" optional:"" help:"Version to mark (default: current checkout)"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoBisectGoodCmd\) [Run]()
```go
func (c *RepoBisectGoodCmd) Run(g *Globals) error
```
## type [RepoBisectLsCmd]()
RepoBisectLsCmd shows the bisect path.
```go
type RepoBisectLsCmd struct {
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoBisectLsCmd\) [Run]()
```go
func (c *RepoBisectLsCmd) Run(g *Globals) error
```
## type [RepoBisectNextCmd]()
RepoBisectNextCmd checks out the bisect midpoint version.
```go
type RepoBisectNextCmd struct {
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoBisectNextCmd\) [Run]()
```go
func (c *RepoBisectNextCmd) Run(g *Globals) error
```
## type [RepoBisectResetCmd]()
RepoBisectResetCmd clears bisect state.
```go
type RepoBisectResetCmd struct {
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoBisectResetCmd\) [Run]()
```go
func (c *RepoBisectResetCmd) Run(g *Globals) error
```
## type [RepoBisectSkipCmd]()
RepoBisectSkipCmd skips the current version.
```go
type RepoBisectSkipCmd struct {
Version string `arg:"" optional:"" help:"Version to skip (default: current checkout)"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoBisectSkipCmd\) [Run]()
```go
func (c *RepoBisectSkipCmd) Run(g *Globals) error
```
## type [RepoBisectStatusCmd]()
RepoBisectStatusCmd shows bisect state.
```go
type RepoBisectStatusCmd struct {
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoBisectStatusCmd\) [Run]()
```go
func (c *RepoBisectStatusCmd) Run(g *Globals) error
```
## type [RepoBlameCmd]()
RepoBlameCmd is an alias for annotate.
```go
type RepoBlameCmd struct {
RepoAnnotateCmd
}
```
## type [RepoBranchCloseCmd]()
RepoBranchCloseCmd closes a branch.
```go
type RepoBranchCloseCmd struct {
Name string `arg:"" help:"Branch name to close"`
User string `help:"User for control artifacts (default: OS username)"`
}
```
### func \(\*RepoBranchCloseCmd\) [Run]()
```go
func (c *RepoBranchCloseCmd) Run(g *Globals) error
```
## type [RepoBranchCmd]()
RepoBranchCmd groups branch operations.
```go
type RepoBranchCmd struct {
Ls RepoBranchLsCmd `cmd:"" help:"List branches"`
New RepoBranchNewCmd `cmd:"" help:"Create new branch"`
Close RepoBranchCloseCmd `cmd:"" help:"Close a branch"`
}
```
## type [RepoBranchLsCmd]()
RepoBranchLsCmd lists branches.
```go
type RepoBranchLsCmd struct {
Closed bool `help:"Show only closed branches"`
All bool `help:"Show all branches (open and closed)"`
}
```
### func \(\*RepoBranchLsCmd\) [Run]()
```go
func (c *RepoBranchLsCmd) Run(g *Globals) error
```
## type [RepoBranchNewCmd]()
RepoBranchNewCmd creates a new branch.
```go
type RepoBranchNewCmd struct {
Name string `arg:"" help:"Branch name"`
From string `help:"Parent version (default: tip)"`
Message string `short:"m" help:"Checkin comment"`
User string `help:"Checkin user (default: OS username)"`
}
```
### func \(\*RepoBranchNewCmd\) [Run]()
```go
func (c *RepoBranchNewCmd) Run(g *Globals) error
```
## type [RepoCatCmd]()
RepoCatCmd outputs the content of an artifact.
```go
type RepoCatCmd struct {
Artifact string `arg:"" help:"Artifact UUID or prefix"`
Raw bool `help:"Output raw blob (no delta expansion)"`
}
```
### func \(\*RepoCatCmd\) [Run]()
```go
func (c *RepoCatCmd) Run(g *Globals) error
```
## type [RepoCiCmd]()
RepoCiCmd commits the checkout's changes, as fossil's commit command does: every change, or only the named files. A pending merge is committed with the merged\-in checkin as a merge parent.
```go
type RepoCiCmd struct {
Message string `short:"m" required:"" help:"Checkin comment"`
Files []string `arg:"" optional:"" help:"Files to commit (default: all changes)"`
User string `help:"Checkin user (default: OS username)"`
Branch string `help:"Branch name for this checkin"`
AllowConflict bool `help:"Commit files that still hold merge conflict markers"`
AllowEmpty bool `help:"Commit even when nothing has changed"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoCiCmd\) [Run]()
```go
func (c *RepoCiCmd) Run(g *Globals) (err error)
```
## type [RepoCloneCmd]()
RepoCloneCmd clones a remote Fossil repository.
```go
type RepoCloneCmd struct {
URL string `arg:"" optional:"" help:"Remote Fossil server URL"`
Path string `arg:"" optional:"" help:"Local path for new repository file"`
User string `short:"u" help:"Username for clone auth"`
Pass string `short:"p" help:"Password for clone auth"`
Invite string `help:"Invite token (from fossil invite)"`
Timeout time.Duration `default:"10m" help:"Abort the clone if it runs longer than this; 0 disables the deadline"`
}
```
### func \(\*RepoCloneCmd\) [Run]()
```go
func (c *RepoCloneCmd) Run(g *Globals) error
```
## type [RepoCmd]()
RepoCmd is the top\-level command group for repository operations. Embed this in your CLI struct alongside Globals for a complete fossil CLI.
```go
type RepoCmd struct {
New RepoNewCmd `cmd:"" help:"Create a new repository"`
Clone RepoCloneCmd `cmd:"" help:"Clone a remote repository"`
Ci RepoCiCmd `cmd:"" help:"Checkin file changes"`
Co RepoCoCmd `cmd:"" help:"Checkout a version"`
Ls RepoLsCmd `cmd:"" help:"List files in a version"`
Timeline RepoTimelineCmd `cmd:"" help:"Show repository history"`
Cat RepoCatCmd `cmd:"" help:"Output artifact content"`
Info RepoInfoCmd `cmd:"" help:"Repository statistics"`
Hash RepoHashCmd `cmd:"" help:"Hash files (SHA1 or SHA3)"`
Delta RepoDeltaCmd `cmd:"" help:"Delta create/apply operations"`
Config RepoConfigCmd `cmd:"" help:"Repository configuration"`
Query RepoQueryCmd `cmd:"" help:"Execute SQL against repository"`
Verify RepoVerifyCmd `cmd:"" help:"Verify repository integrity"`
Resolve RepoResolveCmd `cmd:"" help:"Resolve symbolic name to UUID"`
Extract RepoExtractCmd `cmd:"" help:"Extract files from a version"`
Wiki RepoWikiCmd `cmd:"" help:"Wiki page operations"`
Tag RepoTagCmd `cmd:"" help:"Tag operations"`
Open RepoOpenCmd `cmd:"" help:"Open a checkout in a directory"`
Status RepoStatusCmd `cmd:"" help:"Show working directory changes"`
Add RepoAddCmd `cmd:"" help:"Stage files for addition"`
Rm RepoRmCmd `cmd:"" help:"Stage files for removal"`
Rename RepoRenameCmd `cmd:"" help:"Rename a tracked file"`
Revert RepoRevertCmd `cmd:"" help:"Undo staging changes"`
Diff RepoDiffCmd `cmd:"" help:"Show changes vs a version"`
Merge RepoMergeCmd `cmd:"" help:"Merge a divergent version"`
Conflicts RepoConflictsCmd `cmd:"" help:"List/manage unresolved conflicts"`
MarkResolved RepoMergeResolveCmd `cmd:"" name:"mark-resolved" help:"Mark a conflict as resolved"`
Undo RepoUndoCmd `cmd:"" help:"Undo last operation"`
Redo RepoRedoCmd `cmd:"" help:"Redo undone operation"`
Stash RepoStashCmd `cmd:"" help:"Stash working changes"`
Bisect RepoBisectCmd `cmd:"" help:"Binary search for bugs"`
Annotate RepoAnnotateCmd `cmd:"" help:"Annotate file lines with version history"`
Blame RepoBlameCmd `cmd:"" help:"Alias for annotate"`
Branch RepoBranchCmd `cmd:"" help:"Branch operations"`
UV RepoUVCmd `cmd:"" name:"uv" help:"Unversioned file operations"`
Schema RepoSchemaCmd `cmd:"" help:"Synced table schema operations"`
User RepoUserCmd `cmd:"" help:"User management"`
Invite RepoInviteCmd `cmd:"" help:"Generate invite token for a user"`
Serve RepoServeCmd `cmd:"" help:"Serve repository over HTTP for sync/clone"`
Submanifests RepoSubmanifestsCmd `cmd:"" help:"Sub-manifest tree settings"`
}
```
## type [RepoCoCmd]()
RepoCoCmd checks out a version to the working directory.
```go
type RepoCoCmd struct {
Version string `arg:"" optional:"" help:"Version to checkout (default: tip)"`
Dir string `short:"d" help:"Output directory (default: current dir)" default:"."`
Force bool `help:"Overwrite existing files"`
}
```
### func \(\*RepoCoCmd\) [Run]()
```go
func (c *RepoCoCmd) Run(g *Globals) error
```
## type [RepoConfigCmd]()
RepoConfigCmd groups configuration operations.
```go
type RepoConfigCmd struct {
Ls RepoConfigLsCmd `cmd:"" help:"List all config entries"`
Get RepoConfigGetCmd `cmd:"" help:"Get a config value"`
Set RepoConfigSetCmd `cmd:"" help:"Set a config value"`
}
```
## type [RepoConfigGetCmd]()
RepoConfigGetCmd gets a single config value.
```go
type RepoConfigGetCmd struct {
Key string `arg:"" help:"Config key to get"`
}
```
### func \(\*RepoConfigGetCmd\) [Run]()
```go
func (c *RepoConfigGetCmd) Run(g *Globals) error
```
## type [RepoConfigLsCmd]()
RepoConfigLsCmd lists all config entries.
```go
type RepoConfigLsCmd struct{}
```
### func \(\*RepoConfigLsCmd\) [Run]()
```go
func (c *RepoConfigLsCmd) Run(g *Globals) error
```
## type [RepoConfigSetCmd]()
RepoConfigSetCmd sets a config value.
```go
type RepoConfigSetCmd struct {
Key string `arg:"" help:"Config key"`
Value string `arg:"" help:"Config value"`
}
```
### func \(\*RepoConfigSetCmd\) [Run]()
```go
func (c *RepoConfigSetCmd) Run(g *Globals) error
```
## type [RepoConflictsCmd]()
RepoConflictsCmd groups conflict management operations.
```go
type RepoConflictsCmd struct {
Ls RepoConflictsLsCmd `cmd:"" default:"1" help:"List all conflicts"`
Show RepoConflictsShowCmd `cmd:"" help:"Show all versions of a conflicted file"`
Pick RepoConflictsPickCmd `cmd:"" help:"Resolve by picking one version"`
Merge RepoConflictsMergeCmd `cmd:"" help:"Resolve by re-merging with a different strategy"`
Extract RepoConflictsExtractCmd `cmd:"" help:"Extract all versions to disk for manual editing"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
## type [RepoConflictsExtractCmd]()
RepoConflictsExtractCmd extracts all versions to disk for manual editing.
```go
type RepoConflictsExtractCmd struct {
File string `arg:"" help:"Conflicted file to extract"`
Dir string `short:"d" help:"Output directory" default:"."`
}
```
### func \(\*RepoConflictsExtractCmd\) [Run]()
```go
func (c *RepoConflictsExtractCmd) Run(g *Globals) error
```
## type [RepoConflictsLsCmd]()
RepoConflictsLsCmd lists all conflicts.
```go
type RepoConflictsLsCmd struct{}
```
### func \(\*RepoConflictsLsCmd\) [Run]()
```go
func (c *RepoConflictsLsCmd) Run(g *Globals) error
```
## type [RepoConflictsMergeCmd]()
RepoConflictsMergeCmd resolves a conflict by re\-merging with a specified strategy.
```go
type RepoConflictsMergeCmd struct {
File string `arg:"" help:"Conflicted file to re-merge"`
Strategy string `help:"Strategy to use" default:"three-way"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoConflictsMergeCmd\) [Run]()
```go
func (c *RepoConflictsMergeCmd) Run(g *Globals) error
```
## type [RepoConflictsPickCmd]()
RepoConflictsPickCmd resolves a conflict by picking one version.
```go
type RepoConflictsPickCmd struct {
File string `arg:"" help:"Conflicted file to resolve"`
Local bool `help:"Keep local version" xor:"version"`
Remote bool `help:"Keep remote version" xor:"version"`
Base bool `help:"Revert to base version" xor:"version"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoConflictsPickCmd\) [Run]()
```go
func (c *RepoConflictsPickCmd) Run(g *Globals) error
```
## type [RepoConflictsShowCmd]()
RepoConflictsShowCmd shows all versions of a conflicted file.
```go
type RepoConflictsShowCmd struct {
File string `arg:"" help:"Conflicted file to show"`
}
```
### func \(\*RepoConflictsShowCmd\) [Run]()
```go
func (c *RepoConflictsShowCmd) Run(g *Globals) error
```
## type [RepoDeltaApplyCmd]()
RepoDeltaApplyCmd applies a delta to a source file.
```go
type RepoDeltaApplyCmd struct {
Source string `arg:"" help:"Source (original) file"`
Delta string `arg:"" help:"Delta file"`
Output string `short:"o" help:"Output file (default: stdout)"`
}
```
### func \(\*RepoDeltaApplyCmd\) [Run]()
```go
func (c *RepoDeltaApplyCmd) Run(g *Globals) error
```
## type [RepoDeltaCmd]()
RepoDeltaCmd groups delta create/apply operations.
```go
type RepoDeltaCmd struct {
Create RepoDeltaCreateCmd `cmd:"" help:"Create a delta between two files"`
Apply RepoDeltaApplyCmd `cmd:"" help:"Apply a delta to a source file"`
}
```
## type [RepoDeltaCreateCmd]()
RepoDeltaCreateCmd creates a delta between two files.
```go
type RepoDeltaCreateCmd struct {
Source string `arg:"" help:"Source (original) file"`
Target string `arg:"" help:"Target (new) file"`
Output string `short:"o" help:"Output file (default: stdout)"`
}
```
### func \(\*RepoDeltaCreateCmd\) [Run]()
```go
func (c *RepoDeltaCreateCmd) Run(g *Globals) error
```
## type [RepoDiffCmd]()
RepoDiffCmd shows changes in the working directory vs a repository version.
```go
type RepoDiffCmd struct {
Version string `arg:"" optional:"" help:"Version to diff against (default: tip)"`
Dir string `short:"d" help:"Working directory to compare" default:"."`
Unified int `short:"U" help:"Lines of context" default:"3"`
}
```
### func \(\*RepoDiffCmd\) [Run]()
```go
func (c *RepoDiffCmd) Run(g *Globals) error
```
## type [RepoExtractCmd]()
RepoExtractCmd extracts files from a version.
```go
type RepoExtractCmd struct {
Version string `help:"Version to extract from (default: tip)"`
Files []string `arg:"" optional:"" help:"Files to extract (default: all)"`
Dir string `short:"d" help:"Output directory" default:"."`
}
```
### func \(\*RepoExtractCmd\) [Run]()
```go
func (c *RepoExtractCmd) Run(g *Globals) error
```
## type [RepoHashCmd]()
RepoHashCmd hashes files using SHA1 or SHA3\-256.
```go
type RepoHashCmd struct {
Files []string `arg:"" required:"" help:"Files to hash"`
SHA3 bool `name:"sha3" help:"Use SHA3-256 instead of SHA1"`
}
```
### func \(\*RepoHashCmd\) [Run]()
```go
func (c *RepoHashCmd) Run(g *Globals) error
```
## type [RepoInfoCmd]()
RepoInfoCmd shows repository statistics.
```go
type RepoInfoCmd struct{}
```
### func \(\*RepoInfoCmd\) [Run]()
```go
func (c *RepoInfoCmd) Run(g *Globals) error
```
## type [RepoInviteCmd]()
RepoInviteCmd generates an invite token for a new user.
```go
type RepoInviteCmd struct {
Login string `arg:"" help:"Username for the invitee"`
Cap string `help:"Capability string (e.g. oi)" required:""`
URL string `help:"Sync URL to embed in token" default:""`
TTL time.Duration `help:"Token time-to-live (e.g. 24h)" default:"0"`
}
```
### func \(\*RepoInviteCmd\) [Run]()
```go
func (c *RepoInviteCmd) Run(g *Globals) error
```
## type [RepoLsCmd]()
RepoLsCmd lists files in a version.
```go
type RepoLsCmd struct {
Version string `arg:"" optional:"" help:"Version to list (default: tip)"`
Long bool `short:"l" help:"Show sizes and hashes"`
}
```
### func \(\*RepoLsCmd\) [Run]()
```go
func (c *RepoLsCmd) Run(g *Globals) error
```
## type [RepoMergeCmd]()
RepoMergeCmd merges a divergent version into the current checkout.
```go
type RepoMergeCmd struct {
Version string `arg:"" help:"Version to merge into current checkout"`
Strategy string `help:"Override merge strategy for all files"`
DryRun bool `help:"Show what would be merged without writing"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoMergeCmd\) [Run]()
```go
func (c *RepoMergeCmd) Run(g *Globals) (err error)
```
## type [RepoMergeResolveCmd]()
RepoMergeResolveCmd marks a file conflict as resolved.
A merge conflict needs no marking: as in fossil, a file is in conflict while it holds conflict markers, and editing them out resolves it. So this command refuses a file that still holds markers, and resolves the conflict\-fork entries the conflict\-fork strategy records.
```go
type RepoMergeResolveCmd struct {
File string `arg:"" help:"File to mark as resolved"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoMergeResolveCmd\) [Run]()
```go
func (c *RepoMergeResolveCmd) Run(g *Globals) (err error)
```
## type [RepoNewCmd]()
RepoNewCmd creates a new Fossil repository.
```go
type RepoNewCmd struct {
Path string `arg:"" help:"Path for new repository file"`
User string `help:"Default user name" default:""`
}
```
### func \(\*RepoNewCmd\) [Run]()
```go
func (c *RepoNewCmd) Run(g *Globals) error
```
## type [RepoOpenCmd]()
RepoOpenCmd opens a checkout in a directory, creating the .fslckout database.
```go
type RepoOpenCmd struct {
Dir string `arg:"" optional:"" help:"Checkout directory (default: current dir)" default:"."`
}
```
### func \(\*RepoOpenCmd\) [Run]()
```go
func (c *RepoOpenCmd) Run(g *Globals) error
```
## type [RepoQueryCmd]()
RepoQueryCmd executes raw SQL against the repository database.
```go
type RepoQueryCmd struct {
SQL string `arg:"" help:"SQL query to execute"`
}
```
### func \(\*RepoQueryCmd\) [Run]()
```go
func (c *RepoQueryCmd) Run(g *Globals) error
```
## type [RepoRedoCmd]()
RepoRedoCmd re\-applies the last undone operation.
```go
type RepoRedoCmd struct {
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoRedoCmd\) [Run]()
```go
func (c *RepoRedoCmd) Run(g *Globals) error
```
## type [RepoRenameCmd]()
RepoRenameCmd renames a tracked file in the checkout and on disk.
```go
type RepoRenameCmd struct {
From string `arg:"" help:"Current file name"`
To string `arg:"" help:"New file name"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoRenameCmd\) [Run]()
```go
func (c *RepoRenameCmd) Run(g *Globals) (err error)
```
## type [RepoResolveCmd]()
RepoResolveCmd resolves a symbolic name to UUID and RID.
```go
type RepoResolveCmd struct {
Name string `arg:"" help:"Symbolic name, UUID, or prefix to resolve (e.g. trunk, tip, UUID prefix)"`
}
```
### func \(\*RepoResolveCmd\) [Run]()
```go
func (c *RepoResolveCmd) Run(g *Globals) error
```
## type [RepoRevertCmd]()
RepoRevertCmd restores files to their checked\-out version.
```go
type RepoRevertCmd struct {
Files []string `arg:"" optional:"" help:"Files to revert (default: all)"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoRevertCmd\) [Run]()
```go
func (c *RepoRevertCmd) Run(g *Globals) (err error)
```
## type [RepoRmCmd]()
RepoRmCmd stages files for removal from the checkout.
```go
type RepoRmCmd struct {
Files []string `arg:"" required:"" help:"Files to stage for removal"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoRmCmd\) [Run]()
```go
func (c *RepoRmCmd) Run(g *Globals) (err error)
```
## type [RepoSchemaAddCmd]()
RepoSchemaAddCmd registers a new synced table.
```go
type RepoSchemaAddCmd struct {
Name string `arg:"" help:"Table name (without x_ prefix)"`
Columns string `required:"" help:"Column defs: name:type[:pk],... (e.g. peer_id:text:pk,addr:text)"`
Conflict string `default:"mtime-wins" enum:"self-write,mtime-wins,owner-write" help:"Conflict strategy"`
}
```
### func \(\*RepoSchemaAddCmd\) [Run]()
```go
func (c *RepoSchemaAddCmd) Run(g *Globals) error
```
## type [RepoSchemaCmd]()
RepoSchemaCmd groups synced table schema operations.
```go
type RepoSchemaCmd struct {
Add RepoSchemaAddCmd `cmd:"" help:"Register a new synced table"`
List RepoSchemaListCmd `cmd:"" help:"List registered synced tables"`
Show RepoSchemaShowCmd `cmd:"" help:"Show table schema details"`
Remove RepoSchemaRemoveCmd `cmd:"" help:"Remove a synced table registration"`
}
```
## type [RepoSchemaListCmd]()
RepoSchemaListCmd lists registered synced tables.
```go
type RepoSchemaListCmd struct{}
```
### func \(\*RepoSchemaListCmd\) [Run]()
```go
func (c *RepoSchemaListCmd) Run(g *Globals) error
```
## type [RepoSchemaRemoveCmd]()
RepoSchemaRemoveCmd removes a synced table registration.
```go
type RepoSchemaRemoveCmd struct {
Name string `arg:"" help:"Table name (without x_ prefix)"`
}
```
### func \(\*RepoSchemaRemoveCmd\) [Run]()
```go
func (c *RepoSchemaRemoveCmd) Run(g *Globals) error
```
## type [RepoSchemaShowCmd]()
RepoSchemaShowCmd shows table schema details.
```go
type RepoSchemaShowCmd struct {
Name string `arg:"" help:"Table name (without x_ prefix)"`
}
```
### func \(\*RepoSchemaShowCmd\) [Run]()
```go
func (c *RepoSchemaShowCmd) Run(g *Globals) error
```
## type [RepoServeCmd]()
RepoServeCmd starts an HTTP xfer server for the repository so a remote fossil client \-\- the canonical binary or another instance of this tool \-\- can clone or sync against it. This is a thin wrapper: all serving logic lives in Repo.ServeHTTP; the command's job is to open the repo, resolve the bind address, and translate process interrupt into the context cancellation that ServeHTTP already honors. Access follows the user table, as with fossil serve: clients without credentials act as nobody, which may clone and pull a new repository but not push to it.
```go
type RepoServeCmd struct {
Addr string `short:"P" default:"127.0.0.1:8080" help:"Bind address as host:port"`
}
```
### func \(\*RepoServeCmd\) [Run]()
```go
func (c *RepoServeCmd) Run(g *Globals) error
```
## type [RepoStashApplyCmd]()
RepoStashApplyCmd applies a stash entry without removing it.
```go
type RepoStashApplyCmd struct {
ID int64 `arg:"" optional:"" help:"Stash ID to apply (default: latest)"`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoStashApplyCmd\) [Run]()
```go
func (c *RepoStashApplyCmd) Run(g *Globals) error
```
## type [RepoStashClearCmd]()
RepoStashClearCmd removes all stash entries.
```go
type RepoStashClearCmd struct{}
```
### func \(\*RepoStashClearCmd\) [Run]()
```go
func (c *RepoStashClearCmd) Run(g *Globals) error
```
## type [RepoStashCmd]()
RepoStashCmd groups stash operations.
```go
type RepoStashCmd struct {
Save RepoStashSaveCmd `cmd:"" help:"Stash working changes"`
Pop RepoStashPopCmd `cmd:"" help:"Apply top stash and drop it"`
Apply RepoStashApplyCmd `cmd:"" help:"Apply stash without dropping"`
Ls RepoStashLsCmd `cmd:"" help:"List stash entries"`
Drop RepoStashDropCmd `cmd:"" help:"Remove stash entry"`
Clear RepoStashClearCmd `cmd:"" help:"Remove all stash entries"`
}
```
## type [RepoStashDropCmd]()
RepoStashDropCmd removes a stash entry by ID.
```go
type RepoStashDropCmd struct {
ID int64 `arg:"" required:"" help:"Stash ID to drop"`
}
```
### func \(\*RepoStashDropCmd\) [Run]()
```go
func (c *RepoStashDropCmd) Run(g *Globals) error
```
## type [RepoStashLsCmd]()
RepoStashLsCmd lists stash entries.
```go
type RepoStashLsCmd struct{}
```
### func \(\*RepoStashLsCmd\) [Run]()
```go
func (c *RepoStashLsCmd) Run(g *Globals) error
```
## type [RepoStashPopCmd]()
RepoStashPopCmd pops the top stash entry.
```go
type RepoStashPopCmd struct {
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoStashPopCmd\) [Run]()
```go
func (c *RepoStashPopCmd) Run(g *Globals) error
```
## type [RepoStashSaveCmd]()
RepoStashSaveCmd saves working changes to the stash.
```go
type RepoStashSaveCmd struct {
Message string `short:"m" help:"Stash message" default:""`
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoStashSaveCmd\) [Run]()
```go
func (c *RepoStashSaveCmd) Run(g *Globals) error
```
## type [RepoStatusCmd]()
RepoStatusCmd shows working directory changes vs the tip version.
```go
type RepoStatusCmd struct {
Dir string `short:"d" help:"Checkout directory to scan" default:"."`
}
```
### func \(\*RepoStatusCmd\) [Run]()
```go
func (c *RepoStatusCmd) Run(g *Globals) error
```
## type [RepoSubmanifestsCmd]()
RepoSubmanifestsCmd groups the sub\-manifest settings \(draft\-fossil\-submanifests\-00\): whether new check\-ins are written as trees, and the partition parameter k they are built with.
```go
type RepoSubmanifestsCmd struct {
Enable RepoSubmanifestsEnableCmd `cmd:"" help:"Write new check-ins as sub-manifest trees"`
Retune RepoSubmanifestsRetuneCmd `cmd:"" help:"Re-pin the partition parameter for later commits"`
Disable RepoSubmanifestsDisableCmd `cmd:"" help:"Write new check-ins as flat manifests again"`
Status RepoSubmanifestsStatusCmd `cmd:"" help:"Show the sub-manifest settings and whether trees exist"`
}
```
## type [RepoSubmanifestsDisableCmd]()
RepoSubmanifestsDisableCmd turns tree emission off; existing trees stay.
```go
type RepoSubmanifestsDisableCmd struct{}
```
### func \(\*RepoSubmanifestsDisableCmd\) [Run]()
```go
func (c *RepoSubmanifestsDisableCmd) Run(g *Globals) error
```
## type [RepoSubmanifestsEnableCmd]()
RepoSubmanifestsEnableCmd turns tree emission on, pinning k the first time.
```go
type RepoSubmanifestsEnableCmd struct {
K *int `name:"k" help:"Partition parameter, 1..12 (default: selected from the main-branch tip)"`
}
```
### func \(\*RepoSubmanifestsEnableCmd\) [Run]()
```go
func (c *RepoSubmanifestsEnableCmd) Run(g *Globals) error
```
## type [RepoSubmanifestsRetuneCmd]()
RepoSubmanifestsRetuneCmd re\-pins k; a re\-tune is always explicit.
```go
type RepoSubmanifestsRetuneCmd struct {
K int `name:"k" required:"" help:"New partition parameter, 1..12"`
}
```
### func \(\*RepoSubmanifestsRetuneCmd\) [Run]()
```go
func (c *RepoSubmanifestsRetuneCmd) Run(g *Globals) error
```
## type [RepoSubmanifestsStatusCmd]()
RepoSubmanifestsStatusCmd prints the three settings and whether the repository holds any S\-bearing artifact.
```go
type RepoSubmanifestsStatusCmd struct{}
```
### func \(\*RepoSubmanifestsStatusCmd\) [Run]()
```go
func (c *RepoSubmanifestsStatusCmd) Run(g *Globals) error
```
## type [RepoTagAddCmd]()
RepoTagAddCmd adds a tag to an artifact.
```go
type RepoTagAddCmd struct {
Tag string `arg:"" help:"Tag name"`
Value string `arg:"" optional:"" help:"Tag value (optional)"`
Version string `help:"Version to tag (default: tip)"`
}
```
### func \(\*RepoTagAddCmd\) [Run]()
```go
func (c *RepoTagAddCmd) Run(g *Globals) error
```
## type [RepoTagCmd]()
RepoTagCmd groups tag operations.
```go
type RepoTagCmd struct {
Ls RepoTagLsCmd `cmd:"" help:"List tags on an artifact"`
Add RepoTagAddCmd `cmd:"" help:"Add a tag to an artifact"`
}
```
## type [RepoTagLsCmd]()
RepoTagLsCmd lists tags on an artifact.
```go
type RepoTagLsCmd struct {
Version string `arg:"" optional:"" help:"Version to list tags for (default: tip)"`
}
```
### func \(\*RepoTagLsCmd\) [Run]()
```go
func (c *RepoTagLsCmd) Run(g *Globals) error
```
## type [RepoTimelineCmd]()
RepoTimelineCmd shows repository timeline/history.
```go
type RepoTimelineCmd struct {
Limit int `short:"n" default:"20" help:"Number of entries"`
}
```
### func \(\*RepoTimelineCmd\) [Run]()
```go
func (c *RepoTimelineCmd) Run(g *Globals) error
```
## type [RepoUVCmd]()
RepoUVCmd groups unversioned file operations.
```go
type RepoUVCmd struct {
Ls RepoUVLsCmd `cmd:"" help:"List unversioned files"`
Put RepoUVPutCmd `cmd:"" help:"Add or update an unversioned file"`
Get RepoUVGetCmd `cmd:"" help:"Retrieve an unversioned file"`
Delete RepoUVDeleteCmd `cmd:"" help:"Delete an unversioned file (creates tombstone)"`
}
```
## type [RepoUVDeleteCmd]()
RepoUVDeleteCmd deletes an unversioned file \(creates a tombstone\).
```go
type RepoUVDeleteCmd struct {
Name string `arg:"" help:"Name of the unversioned file to delete"`
}
```
### func \(\*RepoUVDeleteCmd\) [Run]()
```go
func (c *RepoUVDeleteCmd) Run(g *Globals) error
```
## type [RepoUVGetCmd]()
RepoUVGetCmd retrieves an unversioned file.
```go
type RepoUVGetCmd struct {
Name string `arg:"" help:"Name of the unversioned file"`
Output string `short:"o" help:"Output file (default: stdout)"`
}
```
### func \(\*RepoUVGetCmd\) [Run]()
```go
func (c *RepoUVGetCmd) Run(g *Globals) error
```
## type [RepoUVLsCmd]()
RepoUVLsCmd lists unversioned files.
```go
type RepoUVLsCmd struct{}
```
### func \(\*RepoUVLsCmd\) [Run]()
```go
func (c *RepoUVLsCmd) Run(g *Globals) error
```
## type [RepoUVPutCmd]()
RepoUVPutCmd adds or updates an unversioned file.
```go
type RepoUVPutCmd struct {
Name string `arg:"" help:"Name of the unversioned file"`
File string `arg:"" help:"Local file to upload"`
}
```
### func \(\*RepoUVPutCmd\) [Run]()
```go
func (c *RepoUVPutCmd) Run(g *Globals) error
```
## type [RepoUndoCmd]()
RepoUndoCmd undoes the last checkout operation.
```go
type RepoUndoCmd struct {
Dir string `short:"d" help:"Checkout directory" default:"."`
}
```
### func \(\*RepoUndoCmd\) [Run]()
```go
func (c *RepoUndoCmd) Run(g *Globals) error
```
## type [RepoUserCmd]()
RepoUserCmd groups user management operations.
```go
type RepoUserCmd struct {
Add UserAddCmd `cmd:"" help:"Create a new user"`
List UserListCmd `cmd:"" help:"List all users"`
Update UserUpdateCmd `cmd:"" help:"Update user capabilities"`
Rm UserRmCmd `cmd:"" help:"Delete a user"`
Passwd UserPasswdCmd `cmd:"" help:"Reset user password"`
}
```
## type [RepoVerifyCmd]()
RepoVerifyCmd verifies repository integrity.
```go
type RepoVerifyCmd struct{}
```
### func \(\*RepoVerifyCmd\) [Run]()
```go
func (c *RepoVerifyCmd) Run(g *Globals) error
```
## type [RepoWikiCmd]()
RepoWikiCmd groups wiki operations.
```go
type RepoWikiCmd struct {
Ls RepoWikiLsCmd `cmd:"" help:"List wiki pages"`
Export RepoWikiExportCmd `cmd:"" help:"Export a wiki page"`
}
```
## type [RepoWikiExportCmd]()
RepoWikiExportCmd exports a wiki page.
```go
type RepoWikiExportCmd struct {
Page string `arg:"" help:"Wiki page name or artifact UUID"`
Output string `short:"o" help:"Output file (default: stdout)"`
}
```
### func \(\*RepoWikiExportCmd\) [Run]()
```go
func (c *RepoWikiExportCmd) Run(g *Globals) error
```
## type [RepoWikiLsCmd]()
RepoWikiLsCmd lists wiki pages.
```go
type RepoWikiLsCmd struct{}
```
### func \(\*RepoWikiLsCmd\) [Run]()
```go
func (c *RepoWikiLsCmd) Run(g *Globals) error
```
## type [UserAddCmd]()
UserAddCmd creates a new user.
```go
type UserAddCmd struct {
Login string `arg:"" help:"Username"`
Cap string `help:"Capability string (e.g. oi)" required:""`
}
```
### func \(\*UserAddCmd\) [Run]()
```go
func (c *UserAddCmd) Run(g *Globals) error
```
## type [UserListCmd]()
UserListCmd lists all users.
```go
type UserListCmd struct{}
```
### func \(\*UserListCmd\) [Run]()
```go
func (c *UserListCmd) Run(g *Globals) error
```
## type [UserPasswdCmd]()
UserPasswdCmd resets a user's password.
```go
type UserPasswdCmd struct {
Login string `arg:"" help:"Username"`
}
```
### func \(\*UserPasswdCmd\) [Run]()
```go
func (c *UserPasswdCmd) Run(g *Globals) error
```
## type [UserRmCmd]()
UserRmCmd deletes a user.
```go
type UserRmCmd struct {
Login string `arg:"" help:"Username"`
}
```
### func \(\*UserRmCmd\) [Run]()
```go
func (c *UserRmCmd) Run(g *Globals) error
```
## type [UserUpdateCmd]()
UserUpdateCmd updates user capabilities.
```go
type UserUpdateCmd struct {
Login string `arg:"" help:"Username"`
Cap string `help:"New capability string" required:""`
}
```
### func \(\*UserUpdateCmd\) [Run]()
```go
func (c *UserUpdateCmd) Run(g *Globals) error
```
## type [VersionCmd]()
VersionCmd prints a single\-line, stable, machine\-parseable build identifier and exits 0.
```go
type VersionCmd struct{}
```
### func \(\*VersionCmd\) [Run]()
```go
func (c *VersionCmd) Run() error
```
Run prints Version\(\) and always succeeds: there is nothing about the local environment that can make reporting the build identifier fail.
Generated by [gomarkdoc]()
---
# db/driver/modernc
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/reference/sdk/db/driver/modernc/
Pure-Go SQLite driver, the libfossil default.
```go
import _ "github.com/danmestas/go-libfossil/db/driver/modernc"
```
Importing this package registers a `modernc.org/sqlite` driver (driver name `"sqlite"`) with libfossil's `db` registry. The package exports no public API of its own — register-on-import is the contract.
Use this driver when you want a static, CGo-free build with no native dependencies.
See [db](/docs/reference/sdk/db/api/) for the registry interface.
---
# db/driver/ncruces
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/reference/sdk/db/driver/ncruces/
Wasm-friendly SQLite driver based on `ncruces/go-sqlite3`.
```go
import _ "github.com/danmestas/go-libfossil/db/driver/ncruces"
```
Importing this package registers an `ncruces` driver (driver name `"sqlite3"`) with libfossil's `db` registry. The package exports no public API of its own — register-on-import is the contract.
On `js/wasm` targets the package additionally imports `go-sqlite3-opfs`, routing the OPFS virtual filesystem through the same driver registration so browser-side builds get persistent storage automatically.
Use this driver when you need a smaller Wasm footprint than `modernc` provides, or for build configurations where `ncruces`'s tradeoffs are preferred.
See [db](/docs/reference/sdk/db/api/) for the registry interface.
---
# db
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/reference/sdk/db/api/
# db
```go
import "github.com/danmestas/go-libfossil/db"
```
Package db provides a SQLite database layer with pluggable drivers.
Two SQLite drivers ship with libfossil — modernc \(pure Go, default\) and ncruces \(WASM\-capable\). Select one at build time by importing its driver package:
```
import _ "github.com/danmestas/go-libfossil/db/driver/modernc"
```
[Open](<#Open>) and [OpenWith](<#OpenWith>) handle DSN construction, WAL/pragma setup, and WASM\-specific workarounds. Use [DB.WithTx](<#DB.WithTx>) for transaction scoping with automatic rollback on error.
The [Querier](<#Querier>) interface \(Exec, QueryRow, Query\) is satisfied by both [DB](<#DB>) and [Tx](<#Tx>), allowing callers to write transaction\-agnostic code.
## Index
- [func CreateRepoSchema\(d \*DB\) error](<#CreateRepoSchema>)
- [func DefaultPragmas\(\) map\[string\]string](<#DefaultPragmas>)
- [func OpenSQL\(path string, cfg OpenConfig, params map\[string\]string\) \(\*sql.DB, error\)](<#OpenSQL>)
- [func Register\(cfg DriverConfig\)](<#Register>)
- [func SQLDriverAndDSN\(path string, cfg OpenConfig, params map\[string\]string\) \(string, string\)](<#SQLDriverAndDSN>)
- [func ScanInt\(v any\) \(int, bool\)](<#ScanInt>)
- [func ScanJulianDay\(v any\) \(float64, bool\)](<#ScanJulianDay>)
- [func ScanTime\(v any\) \(time.Time, bool\)](<#ScanTime>)
- [func SeedConfig\(d \*DB, rng simio.Rand, projectCode string\) error](<#SeedConfig>)
- [func SeedNobody\(d \*DB, caps string\) error](<#SeedNobody>)
- [func SeedUser\(d \*DB, login string\) error](<#SeedUser>)
- [type CheckpointMode](<#CheckpointMode>)
- [func \(m CheckpointMode\) String\(\) string](<#CheckpointMode.String>)
- [type DB](<#DB>)
- [func Open\(path string\) \(\*DB, error\)](<#Open>)
- [func OpenWith\(path string, cfg OpenConfig\) \(\*DB, error\)](<#OpenWith>)
- [func \(d \*DB\) ApplicationID\(\) \(int32, error\)](<#DB.ApplicationID>)
- [func \(d \*DB\) Checkpoint\(mode CheckpointMode\) error](<#DB.Checkpoint>)
- [func \(d \*DB\) Close\(\) error](<#DB.Close>)
- [func \(d \*DB\) Driver\(\) string](<#DB.Driver>)
- [func \(d \*DB\) Exec\(query string, args ...any\) \(sql.Result, error\)](<#DB.Exec>)
- [func \(d \*DB\) Path\(\) string](<#DB.Path>)
- [func \(d \*DB\) Query\(query string, args ...any\) \(\*sql.Rows, error\)](<#DB.Query>)
- [func \(d \*DB\) QueryRow\(query string, args ...any\) \*sql.Row](<#DB.QueryRow>)
- [func \(d \*DB\) SetApplicationID\(id int32\) error](<#DB.SetApplicationID>)
- [func \(d \*DB\) SqlDB\(\) \*sql.DB](<#DB.SqlDB>)
- [func \(d \*DB\) WithTx\(fn func\(tx \*Tx\) error\) error](<#DB.WithTx>)
- [type DriverConfig](<#DriverConfig>)
- [func RegisteredDriver\(\) \*DriverConfig](<#RegisteredDriver>)
- [type OpenConfig](<#OpenConfig>)
- [type Querier](<#Querier>)
- [type Tx](<#Tx>)
- [func \(t \*Tx\) Exec\(query string, args ...any\) \(sql.Result, error\)](<#Tx.Exec>)
- [func \(t \*Tx\) Query\(query string, args ...any\) \(\*sql.Rows, error\)](<#Tx.Query>)
- [func \(t \*Tx\) QueryRow\(query string, args ...any\) \*sql.Row](<#Tx.QueryRow>)
## func [CreateRepoSchema]()
```go
func CreateRepoSchema(d *DB) error
```
## func [DefaultPragmas]()
```go
func DefaultPragmas() map[string]string
```
DefaultPragmas returns the default pragma settings.
## func [OpenSQL]()
```go
func OpenSQL(path string, cfg OpenConfig, params map[string]string) (*sql.DB, error)
```
OpenSQL opens a raw \*sql.DB using the registered driver and the same DSN construction as OpenWith. params are appended as URI query parameters after driver pragmas, so callers can request options such as mode=ro without constructing driver\-specific DSNs.
## func [Register]()
```go
func Register(cfg DriverConfig)
```
Register registers a SQLite driver for use by Open/OpenWith. Must be called exactly once \(typically from a driver package's init\(\)\). Panics if called more than once.
## func [SQLDriverAndDSN]()
```go
func SQLDriverAndDSN(path string, cfg OpenConfig, params map[string]string) (string, string)
```
SQLDriverAndDSN returns the driver name and DSN OpenWith would use, with optional URI query params appended after driver\-specific options.
## func [ScanInt]()
```go
func ScanInt(v any) (int, bool)
```
ScanInt converts a scanned value to int. SQLite drivers differ on BOOLEAN columns: modernc returns int64, ncruces returns bool. Scan the column into an \`any\` variable, then pass it here.
## func [ScanJulianDay]()
```go
func ScanJulianDay(v any) (float64, bool)
```
ScanJulianDay converts a scanned mtime value to a float64 Julian Day Number. SQLite drivers return mtime differently: modernc returns float64, ncruces returns time.Time for DATETIME/TIMESTAMP/DATE columns. Scan the column into an \`any\` variable, then pass it here.
## func [ScanTime]()
```go
func ScanTime(v any) (time.Time, bool)
```
ScanTime converts a scanned mtime value to time.Time. Same driver\-compatibility rationale as ScanJulianDay.
## func [SeedConfig]()
```go
func SeedConfig(d *DB, rng simio.Rand, projectCode string) error
```
SeedConfig seeds the config table with project\-code, server\-code, and schema markers. If projectCode is empty, a fresh 40\-char lowercase\-hex project\-code is generated from rng. If non\-empty, it must match ^\[0\-9a\-f\]\{40\}$; otherwise an error is returned and no rows are written. Server\-code is always generated.
## func [SeedNobody]()
```go
func SeedNobody(d *DB, caps string) error
```
SeedNobody inserts a "nobody" user with the given capabilities. This controls anonymous access policy for the repo.
## func [SeedUser]()
```go
func SeedUser(d *DB, login string) error
```
## type [CheckpointMode]()
CheckpointMode mirrors SQLite's PRAGMA wal\_checkpoint\(\\) argument.
```go
type CheckpointMode int
```
```go
const (
// CheckpointPassive checkpoints frames not held by readers; never blocks.
// Appropriate for periodic background checkpoints.
CheckpointPassive CheckpointMode = iota
// CheckpointFull blocks new writers until all frames are checkpointed.
CheckpointFull
// CheckpointRestart is FULL plus restarts the WAL file.
CheckpointRestart
// CheckpointTruncate is RESTART plus truncates the WAL file to zero bytes.
// Produces the most compact on-disk file and is what Close uses.
CheckpointTruncate
)
```
### func \(CheckpointMode\) [String]()
```go
func (m CheckpointMode) String() string
```
## type [DB]()
DB wraps a SQLite database connection.
```go
type DB struct {
// contains filtered or unexported fields
}
```
### func [Open]()
```go
func Open(path string) (*DB, error)
```
Open opens a SQLite database with the registered driver and default pragmas.
### func [OpenWith]()
```go
func OpenWith(path string, cfg OpenConfig) (*DB, error)
```
OpenWith opens a SQLite database with explicit configuration.
### func \(\*DB\) [ApplicationID]()
```go
func (d *DB) ApplicationID() (int32, error)
```
### func \(\*DB\) [Checkpoint]()
```go
func (d *DB) Checkpoint(mode CheckpointMode) error
```
Checkpoint runs PRAGMA wal\_checkpoint\(\\) against the database. Safe to call on a live database. PASSIVE never blocks; TRUNCATE produces the most compact on\-disk layout. On a non\-WAL database the underlying PRAGMA is a no\-op and returns nil.
### func \(\*DB\) [Close]()
```go
func (d *DB) Close() error
```
Close runs PRAGMA wal\_checkpoint\(TRUNCATE\) before closing the underlying connection so the on\-disk file is readable by external SQLite/fossil tooling. The WASM/WASI build path does not use WAL, so the checkpoint is skipped there. Checkpoint errors are joined with the close error so the connection is always closed regardless of checkpoint outcome.
### func \(\*DB\) [Driver]()
```go
func (d *DB) Driver() string
```
### func \(\*DB\) [Exec]()
```go
func (d *DB) Exec(query string, args ...any) (sql.Result, error)
```
### func \(\*DB\) [Path]()
```go
func (d *DB) Path() string
```
### func \(\*DB\) [Query]()
```go
func (d *DB) Query(query string, args ...any) (*sql.Rows, error)
```
### func \(\*DB\) [QueryRow]()
```go
func (d *DB) QueryRow(query string, args ...any) *sql.Row
```
### func \(\*DB\) [SetApplicationID]()
```go
func (d *DB) SetApplicationID(id int32) error
```
### func \(\*DB\) [SqlDB]()
```go
func (d *DB) SqlDB() *sql.DB
```
SqlDB returns the underlying \*sql.DB connection.
### func \(\*DB\) [WithTx]()
```go
func (d *DB) WithTx(fn func(tx *Tx) error) error
```
## type [DriverConfig]()
DriverConfig defines a SQLite driver's name and DSN builder.
```go
type DriverConfig struct {
Name string
BuildDSN func(path string, pragmas map[string]string) string
}
```
### func [RegisteredDriver]()
```go
func RegisteredDriver() *DriverConfig
```
RegisteredDriver returns a copy of the currently registered driver config, or nil if none.
## type [OpenConfig]()
OpenConfig allows callers to customize driver selection and pragmas.
```go
type OpenConfig struct {
Driver string // override driver name (empty = use registered driver)
Pragmas map[string]string // additional/override pragmas (merged with defaults)
}
```
## type [Querier]()
Querier is the common interface satisfied by both \*DB and \*Tx. Functions that need to work inside transactions accept Querier instead of \*DB.
```go
type Querier interface {
Exec(query string, args ...any) (sql.Result, error)
QueryRow(query string, args ...any) *sql.Row
Query(query string, args ...any) (*sql.Rows, error)
}
```
## type [Tx]()
```go
type Tx struct {
// contains filtered or unexported fields
}
```
### func \(\*Tx\) [Exec]()
```go
func (t *Tx) Exec(query string, args ...any) (sql.Result, error)
```
### func \(\*Tx\) [Query]()
```go
func (t *Tx) Query(query string, args ...any) (*sql.Rows, error)
```
### func \(\*Tx\) [QueryRow]()
```go
func (t *Tx) QueryRow(query string, args ...any) *sql.Row
```
Generated by [gomarkdoc]()
---
# dst
URL: https://libfossil-docs.daniel-mestas.workers.dev/docs/reference/sdk/dst/api/
# dst
```go
import "github.com/danmestas/go-libfossil/dst"
```
## Index
- [func AllBlobUUIDsSorted\(r \*repo.Repo\) \(\[\]string, error\)](<#AllBlobUUIDsSorted>)
- [func CheckBlobIntegrity\(nodeID string, r \*repo.Repo\) error](<#CheckBlobIntegrity>)
- [func CheckConvergence\(master \*repo.Repo, leaves map\[NodeID\]\*repo.Repo\) error](<#CheckConvergence>)
- [func CheckDeltaChains\(nodeID string, r \*repo.Repo\) error](<#CheckDeltaChains>)
- [func CheckNoOrphanPhantoms\(nodeID string, r \*repo.Repo\) error](<#CheckNoOrphanPhantoms>)
- [func CheckSubsetOf\(master \*repo.Repo, leaves map\[NodeID\]\*repo.Repo\) error](<#CheckSubsetOf>)
- [func CheckTableSyncConvergence\(repos map\[string\]\*repo.Repo\) error](<#CheckTableSyncConvergence>)
- [func CheckTableSyncIntegrity\(nodeID string, r \*repo.Repo\) error](<#CheckTableSyncIntegrity>)
- [func CheckTagxrefIntegrity\(nodeID string, r \*repo.Repo\) error](<#CheckTagxrefIntegrity>)
- [func CheckUVConvergence\(master \*repo.Repo, leaves map\[NodeID\]\*repo.Repo\) error](<#CheckUVConvergence>)
- [func CheckUVIntegrity\(nodeID string, r \*repo.Repo\) error](<#CheckUVIntegrity>)
- [func CountBlobs\(r \*repo.Repo\) \(int, error\)](<#CountBlobs>)
- [func HasBlob\(r \*repo.Repo, uuid string\) bool](<#HasBlob>)
- [type Action](<#Action>)
- [type ActionType](<#ActionType>)
- [type DefaultNode](<#DefaultNode>)
- [func NewDefaultNode\(r \*repo.Repo, t sync.Transport, projectCode, serverCode string, opts DefaultNodeOpts\) \*DefaultNode](<#NewDefaultNode>)
- [func \(n \*DefaultNode\) Repo\(\) \*repo.Repo](<#DefaultNode.Repo>)
- [func \(n \*DefaultNode\) Tick\(ctx context.Context, event EventType\) Action](<#DefaultNode.Tick>)
- [type DefaultNodeOpts](<#DefaultNodeOpts>)
- [type Event](<#Event>)
- [type EventQueue](<#EventQueue>)
- [func \(q EventQueue\) Len\(\) int](<#EventQueue.Len>)
- [func \(q EventQueue\) Less\(i, j int\) bool](<#EventQueue.Less>)
- [func \(q \*EventQueue\) Pop\(\) any](<#EventQueue.Pop>)
- [func \(q \*EventQueue\) PopEvent\(\) \*Event](<#EventQueue.PopEvent>)
- [func \(q \*EventQueue\) Push\(x any\)](<#EventQueue.Push>)
- [func \(q \*EventQueue\) PushEvent\(e \*Event\)](<#EventQueue.PushEvent>)
- [func \(q EventQueue\) Swap\(i, j int\)](<#EventQueue.Swap>)
- [type EventType](<#EventType>)
- [type InvariantError](<#InvariantError>)
- [func \(e \*InvariantError\) Error\(\) string](<#InvariantError.Error>)
- [type MockFossil](<#MockFossil>)
- [func NewMockFossil\(r \*repo.Repo\) \*MockFossil](<#NewMockFossil>)
- [func \(f \*MockFossil\) Exchange\(ctx context.Context, req \*xfer.Message\) \(\*xfer.Message, error\)](<#MockFossil.Exchange>)
- [func \(f \*MockFossil\) Repo\(\) \*repo.Repo](<#MockFossil.Repo>)
- [func \(f \*MockFossil\) SetBuggify\(b libsync.BuggifyChecker\)](<#MockFossil.SetBuggify>)
- [func \(f \*MockFossil\) StoreArtifact\(data \[\]byte\) \(string, error\)](<#MockFossil.StoreArtifact>)
- [type Node](<#Node>)
- [type NodeID](<#NodeID>)
- [type PeerNetwork](<#PeerNetwork>)
- [func NewPeerNetwork\(rng \*rand.Rand\) \*PeerNetwork](<#NewPeerNetwork>)
- [func \(n \*PeerNetwork\) AddPeer\(id NodeID, r \*repo.Repo\)](<#PeerNetwork.AddPeer>)
- [func \(n \*PeerNetwork\) Heal\(id NodeID\)](<#PeerNetwork.Heal>)
- [func \(n \*PeerNetwork\) HealAll\(\)](<#PeerNetwork.HealAll>)
- [func \(n \*PeerNetwork\) Partition\(id NodeID\)](<#PeerNetwork.Partition>)
- [func \(n \*PeerNetwork\) SetBuggify\(b libsync.BuggifyChecker\)](<#PeerNetwork.SetBuggify>)
- [func \(n \*PeerNetwork\) SetDropRate\(rate float64\)](<#PeerNetwork.SetDropRate>)
- [func \(n \*PeerNetwork\) Transport\(source, target NodeID\) \*PeerTransport](<#PeerNetwork.Transport>)
- [type PeerTransport](<#PeerTransport>)
- [func \(t \*PeerTransport\) Exchange\(ctx context.Context, req \*xfer.Message\) \(\*xfer.Message, error\)](<#PeerTransport.Exchange>)
- [type SeededBuggify](<#SeededBuggify>)
- [func \(b \*SeededBuggify\) Check\(\_ string, probability float64\) bool](<#SeededBuggify.Check>)
- [type SimConfig](<#SimConfig>)
- [type SimNetwork](<#SimNetwork>)
- [func NewSimNetwork\(rng \*rand.Rand, upstream libsync.Transport\) \*SimNetwork](<#NewSimNetwork>)
- [func \(n \*SimNetwork\) Heal\(id NodeID\)](<#SimNetwork.Heal>)
- [func \(n \*SimNetwork\) HealAll\(\)](<#SimNetwork.HealAll>)
- [func \(n \*SimNetwork\) Partition\(id NodeID\)](<#SimNetwork.Partition>)
- [func \(n \*SimNetwork\) SetDropRate\(rate float64\)](<#SimNetwork.SetDropRate>)
- [func \(n \*SimNetwork\) SetTruncateRate\(rate float64\)](<#SimNetwork.SetTruncateRate>)
- [func \(n \*SimNetwork\) Transport\(nodeID NodeID\) \*SimTransport](<#SimNetwork.Transport>)
- [type SimTransport](<#SimTransport>)
- [func \(t \*SimTransport\) Exchange\(ctx context.Context, req \*xfer.Message\) \(\*xfer.Message, error\)](<#SimTransport.Exchange>)
- [type Simulator](<#Simulator>)
- [func New\(cfg SimConfig\) \(\*Simulator, error\)](<#New>)
- [func \(s \*Simulator\) CheckAllConverged\(master \*repo.Repo\) error](<#Simulator.CheckAllConverged>)
- [func \(s \*Simulator\) CheckAllTableSyncConverged\(\) error](<#Simulator.CheckAllTableSyncConverged>)
- [func \(s \*Simulator\) CheckAllUVConverged\(master \*repo.Repo\) error](<#Simulator.CheckAllUVConverged>)
- [func \(s \*Simulator\) CheckSafety\(\) error](<#Simulator.CheckSafety>)
- [func \(s \*Simulator\) CheckTombstoneConvergence\(t \*testing.T, masterRepo \*repo.Repo, tableName string, def repo.TableDef\)](<#Simulator.CheckTombstoneConvergence>)
- [func \(s \*Simulator\) Clock\(\) \*simio.SimClock](<#Simulator.Clock>)
- [func \(s \*Simulator\) Close\(\) error](<#Simulator.Close>)
- [func \(s \*Simulator\) Leaf\(id NodeID\) Node](<#Simulator.Leaf>)
- [func \(s \*Simulator\) LeafIDs\(\) \[\]NodeID](<#Simulator.LeafIDs>)
- [func \(s \*Simulator\) Network\(\) \*SimNetwork](<#Simulator.Network>)
- [func \(s \*Simulator\) Run\(maxSteps int\) error](<#Simulator.Run>)
- [func \(s \*Simulator\) RunUntil\(deadline time.Time\) error](<#Simulator.RunUntil>)
- [func \(s \*Simulator\) ScheduleSyncNow\(id NodeID\)](<#Simulator.ScheduleSyncNow>)
- [func \(s \*Simulator\) ScheduleUVDelete\(id NodeID, at time.Time, name string, mtime int64\)](<#Simulator.ScheduleUVDelete>)
- [func \(s \*Simulator\) ScheduleUVWrite\(id NodeID, at time.Time, name string, data \[\]byte, mtime int64\)](<#Simulator.ScheduleUVWrite>)
- [func \(s \*Simulator\) SetMasterRepo\(r \*repo.Repo\)](<#Simulator.SetMasterRepo>)
- [func \(s \*Simulator\) Step\(\) \(bool, error\)](<#Simulator.Step>)
## func [AllBlobUUIDsSorted]()
```go
func AllBlobUUIDsSorted(r *repo.Repo) ([]string, error)
```
AllBlobUUIDsSorted returns a sorted slice of UUIDs for deterministic comparison.
## func [CheckBlobIntegrity]()
```go
func CheckBlobIntegrity(nodeID string, r *repo.Repo) error
```
CheckBlobIntegrity verifies that every blob in the repo has a UUID matching the hash of its expanded content. This catches corruption from buggify \(content.Expand byte\-flip\) or storage bugs.
## func [CheckConvergence]()
```go
func CheckConvergence(master *repo.Repo, leaves map[NodeID]*repo.Repo) error
```
CheckConvergence verifies that every leaf repo contains the same set of artifact UUIDs as the master repo.
## func [CheckDeltaChains]()
```go
func CheckDeltaChains(nodeID string, r *repo.Repo) error
```
CheckDeltaChains verifies that every delta's srcid points to an existing blob \(no dangling references\).
## func [CheckNoOrphanPhantoms]()
```go
func CheckNoOrphanPhantoms(nodeID string, r *repo.Repo) error
```
CheckNoOrphanPhantoms verifies that phantom entries reference blobs that actually exist in the blob table \(they're just missing content\).
## func [CheckSubsetOf]()
```go
func CheckSubsetOf(master *repo.Repo, leaves map[NodeID]*repo.Repo) error
```
CheckSubsetOf verifies that all artifacts in the master are present in every leaf. Unlike CheckConvergence, this allows leaves to have extra artifacts \(useful when only pull is being tested\).
## func [CheckTableSyncConvergence]()
```go
func CheckTableSyncConvergence(repos map[string]*repo.Repo) error
```
CheckTableSyncConvergence verifies that all repos have identical rows for every shared synced table. Tables present in some repos but not others are skipped \(schema may still be propagating\).
## func [CheckTableSyncIntegrity]()
```go
func CheckTableSyncIntegrity(nodeID string, r *repo.Repo) error
```
CheckTableSyncIntegrity verifies that: 1. Every row in every synced table has a valid PK hash. 2. Every row's mtime is positive. 3. The computed catalog hash is 40 hex chars.
## func [CheckTagxrefIntegrity]()
```go
func CheckTagxrefIntegrity(nodeID string, r *repo.Repo) error
```
CheckTagxrefIntegrity verifies that: 1. Every tagxref.rid references a valid blob 2. Every tagxref.tagid references a valid tag 3. Propagated entries \(srcid=0\) have tagtype=2 4. No tagxref.rid references a phantom blob
## func [CheckUVConvergence]()
```go
func CheckUVConvergence(master *repo.Repo, leaves map[NodeID]*repo.Repo) error
```
CheckUVConvergence verifies that all repos have identical UV content hashes.
## func [CheckUVIntegrity]()
```go
func CheckUVIntegrity(nodeID string, r *repo.Repo) error
```
CheckUVIntegrity verifies that every non\-tombstone entry in the unversioned table has a hash matching the SHA1 of its decompressed content, and sz matches the content length.
## func [CountBlobs]()
```go
func CountBlobs(r *repo.Repo) (int, error)
```
CountBlobs returns the number of non\-phantom blobs in the repo.
## func [HasBlob]()
```go
func HasBlob(r *repo.Repo, uuid string) bool
```
HasBlob checks if a specific artifact exists in the repo.
## type [Action]()
Action is the result of processing a single event via Tick.
```go
type Action struct {
Type ActionType
Result *sync.SyncResult // non-nil when Type == ActionSynced
Err error // non-nil on sync failure
}
```
## type [ActionType]()
ActionType classifies the result of processing an event.
```go
type ActionType int
```
```go
const (
ActionNone ActionType = iota
ActionSynced // a sync round was executed
)
```
## type [DefaultNode]()
DefaultNode is a simple Node that calls sync.Sync directly. No NATS, no HTTP — just a repo \+ transport.
```go
type DefaultNode struct {
// contains filtered or unexported fields
}
```
### func [NewDefaultNode]()
```go
func NewDefaultNode(r *repo.Repo, t sync.Transport, projectCode, serverCode string, opts DefaultNodeOpts) *DefaultNode
```
NewDefaultNode creates a Node backed by sync.Sync.
### func \(\*DefaultNode\) [Repo]()
```go
func (n *DefaultNode) Repo() *repo.Repo
```
Repo returns the node's Fossil repository.
### func \(\*DefaultNode\) [Tick]()
```go
func (n *DefaultNode) Tick(ctx context.Context, event EventType) Action
```
Tick executes a sync cycle for the given event.
## type [DefaultNodeOpts]()
DefaultNodeOpts configures a DefaultNode.
```go
type DefaultNodeOpts struct {
Push bool
Pull bool
UV bool
XTableSync bool
Private bool
Buggify sync.BuggifyChecker
}
```
## type [Event]()
Event is a scheduled occurrence in the simulation.
```go
type Event struct {
Time time.Time
Type EventType
NodeID NodeID
UVName string // for EvUVWrite/EvUVDelete: file name
UVData []byte // for EvUVWrite: file content
UVMTime int64 // for EvUVWrite/EvUVDelete: mtime
}
```
## type [EventQueue]()
EventQueue is a min\-heap of events ordered by time.
```go
type EventQueue []*Event
```
### func \(EventQueue\) [Len]()
```go
func (q EventQueue) Len() int
```
### func \(EventQueue\) [Less]()
```go
func (q EventQueue) Less(i, j int) bool
```