# 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
```sh
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](/install/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.
```go
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.
// 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:
```go
// ref := "file:///tmp/store@"
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
| Function | Signature | Notes |
| --- | --- | --- |
| `ID` | `ID(ctx, path string, opts *ManifestOptions) (string, error)` | 64-hex BLAKE3 snapshot ID |
| `Manifest` | `Manifest(ctx, path string, opts *ManifestOptions) (*ManifestResult, error)` | Full entry list + raw text |
| `IDFromManifest` | `IDFromManifest(m *ManifestResult) (string, error)` | Sync, pure — same result as `ID()` |
| `Push` | `Push(ctx, path, storeURI string) (string, error)` | Upload to store; returns snapshot ID |
| `Pull` | `Pull(ctx, snapshotID, storeURI, dest string) error` | Download + materialize |
| `Fetch` | `Fetch(ctx, snapshotID, storeURI string) error` | Download to local cache |
| `Diff` | `Diff(ctx, fromURI, toURI string) ([]DiffEntry, error)` | Compare two stores |
| `Version` | `Version() string` | snapdir-api crate version string |
## 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:
```go
_, 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:
| Code | Meaning |
| --- | --- |
| `IO_ERROR` | A filesystem read/write failed. |
| `HASH_MISMATCH` | Retrieved content did not match its expected checksum. |
| `STORE_ERROR` | The object store rejected or failed the operation. |
| `IN_FLUX` | A file changed while it was being read. |
| `CATALOG_ERROR` | The store catalog is missing or malformed. |
| `INVALID_ID` | The snapshot ID is not a valid 64-hex string. |
| `INVALID_STORE` | The store URI is malformed or unsupported. |
| `CONFLICT` | A concurrent operation conflicted with this one. |
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.
| Binding | Linux glibc | Linux musl (Alpine) | macOS | Windows |
| --- | --- | --- | --- | --- |
| Go | yes (x64 + arm64) | yes (x64 + arm64) | yes | no |
All bindings share this matrix except Java, which is glibc-only today — musl support is planned for 1.11.1.