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 — but the behaviour underneath them is where the questions are.

  • API reference — every symbol, its default, and what happens when you get it wrong.
  • Filesystem operations — every afero.Fs and afero.File method, what it maps onto, and what it returns on failure.
  • billy backend support — what memfs, osfs and WithBoundOS each actually honour.

The generated Go documentation is on pkg.go.dev.

What this does not do

It bridges one direction only, it is not a sandbox, Sync never flushes, and Chown and Chtimes do nothing on any backend. Each limit is deliberate and each has a reason — what this does not do sets them out.

Further reading

The blog carries a curated route through this subject: Building a command-line tool in Go collects everything written about it, ordered so you can start at the beginning rather than newest-first.

Ask phpbotscout

phpbotscout

He answers questions about the projects over on the Discord, citing the docs where they already cover it, and offering to raise an issue where they don't. Bring a bug, an idea, or a questionable engineering decision.

Join the Discord