Skip to content

aferobilly

Go has two filesystem abstractions and they don't speak to each other. This bridges them in one direction: give it a go-billy filesystem, get an afero.Fs.

go get gitlab.com/phpboyscout/go/aferobilly
fs := aferobilly.New(worktree.Filesystem)

data, err := afero.ReadFile(fs, "go.mod") // afero code, billy filesystem

The usual reason you need this: go-git exposes a worktree as billy.Filesystem, while most application code — and most test helpers — are written against afero.Fs. Rather than copying the tree out and syncing it back, work against it live.

Why

  • A complete adapter, not a subset. A full afero.Fs including the optional symlink interfaces, with real afero.File handles for files and directories. The awkward corners where billy and afero disagree are resolved deliberately — see billy vs afero semantics.
  • Optional locking that actually holds. Every Fs and File operation is serialised through a sync.Locker you supply. Hand it the mutex guarding whatever produced the filesystem and a handle stays safe even after the reference escapes.
  • The lock boundary can't be bypassed. The returned Fs exposes no accessor for the underlying billy object — by design, so there is no way to forget to lock.
  • No VCS dependency. go-git is the common source of a billy filesystem, but this module never imports it and works with memfs, osfs, chroot, or your own. A dependency guard keeps it that way.

Where next

Reference

The API is three symbols — New, Option, WithLocker — documented on pkg.go.dev.