snapdir for Go
The Go binding is a CGo package that links the native libsnapdir_ffi static library, wrapping the same Rust core as the snapdir CLI. It walks a directory tree, hashes every file with BLAKE3, and produces a stable snapshot ID you can push to and pull from object stores. Manifests and snapshot IDs are bit-identical to the CLI and every other binding.
Install
go get github.com/snapdir/snapdir/bindings/go@v1.11.0
This is a build-from-source binding. The module is not self-contained: you must supply the native libsnapdir_ffi.a and snapdir.h, built from the published snapdir-ffi crate (crates.io) with a Rust toolchain and cbindgen, then dropped into the module's lib/ and include/ directories (both gitignored, shipped by neither). Set CGO_ENABLED=1. The full recipe is in Install snapdir for Go.
Usage
Every operation takes a context.Context and honours cancellation. The store URI is always an argument — file://$PWD/store for a no-setup local store, gs://bucket/prefix for Google Cloud, s3://… for S3-compatible stores.
package main
import (
"context"
"fmt"
"log"
snapdir "github.com/snapdir/snapdir/bindings/go"
)
func main() {
ctx := context.Background()
store := "file://" + "/path/to/store" // or gs://bucket/prefix
// Snapshot ID for a directory — 64-char lowercase hex BLAKE3.
id, err := snapdir.ID(ctx, "./my-dir", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("id:", id) // e.g. <snapshot-id>
// Full manifest: per-entry type, permissions, checksum, size.
m, err := snapdir.Manifest(ctx, "./my-dir", &snapdir.ManifestOptions{
NoFollow: true, // record symlinks as links
Exclude: []string{`\.git$`, `\.DS_Store`}, // extended-regex patterns
})
if err != nil {
log.Fatal(err)
}
for _, e := range m.Entries {
fmt.Printf("%c %04o %s %d %s\n",
e.PathType, e.Permissions, e.Checksum, e.Size, e.Path)
}
// Push a directory to a store; returns the snapshot ID.
sid, err := snapdir.Push(ctx, "./my-dir", store)
if err != nil {
log.Fatal(err)
}
// Pull a snapshot from a store and materialize it at dest.
if err := snapdir.Pull(ctx, sid, store, "./restored"); err != nil {
log.Fatal(err)
}
// Fetch downloads a snapshot into the local cache without materializing.
if err := snapdir.Fetch(ctx, sid, store); err != nil {
log.Fatal(err)
}
// Diff two stores — statuses are A / D / M / = (byte-identical to the CLI).
entries, err := snapdir.Diff(ctx, "file:///tmp/store-a", "file:///tmp/store-b")
if err != nil {
log.Fatal(err)
}
for _, e := range entries {
fmt.Printf("%c\t%s\n", e.Status, e.Path)
}
}
To diff two pinned snapshots by reference, use the store@id convention — the last @ splits the store URI from the 64-hex snapshot ID:
// ref := "file:///tmp/store@<snapshot-id>"
at := strings.LastIndex(ref, "@")
store, id := ref[:at], ref[at+1:]
The binding's Diff compares two whole stores; to compare two pinned snapshots you pull each into a temporary directory and push each to its own temporary file:// store, then diff those two stores.
API surface
Errors
Every failure surfaces as a *snapdir.SnapdirError, whose .Code field is one of eight stable SCREAMING_SNAKE_CASE codes. Use errors.As to inspect it:
_, err := snapdir.Push(ctx, "./missing", store)
var se *snapdir.SnapdirError
if errors.As(err, &se) {
fmt.Println("code: ", se.Code) // e.g. "IO_ERROR"
fmt.Println("message:", se.Message)
}
The taxonomy is shared across every binding:
Context cancellation propagates too: a Push/Pull on a cancelled or timed-out context returns an error you can match with errors.Is(err, context.DeadlineExceeded).
Platform support
Every binding is CI-verified against its live public registry before release. The Go binding runs on Linux (glibc and Alpine/musl, x64 and arm64) and macOS. Windows is unsupported — snapdir is Unix-only.
All bindings share this matrix except Java, which is glibc-only today — musl support is planned for 1.11.1.