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.
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.Fsincluding the optional symlink interfaces, with realafero.Filehandles 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
FsandFileoperation is serialised through async.Lockeryou 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
Fsexposes 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¶
- Getting started — bridge an in-memory billy filesystem in a few lines.
- Work with a go-git worktree — the flagship use case, read and write live.
- Make a handle concurrency-safe —
what
WithLockerguarantees, and the two footguns. - billy vs afero semantics — every place the two interfaces disagree, and what the adapter does about it.
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.Fsandafero.Filemethod, what it maps onto, and what it returns on failure. - billy backend support — what
memfs,osfsandWithBoundOSeach 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

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.