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 full
afero.Fs, not a subset. That includes the optional symlink interfaces, with realafero.Filehandles for files and directories.ChownandChtimesare deliberate no-ops. The awkward corners where billy and afero disagree are resolved deliberately; see billy vs afero semantics. - Optional locking that holds after the handle escapes. Every
FsandFileoperation that reaches the filesystem 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
Fsand its files expose no accessor for the underlying billy object, 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.