# 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.