Getting started¶
This tutorial bridges an in-memory billy filesystem to afero and writes to it with ordinary afero calls — enough to see the whole API, which is three symbols.
Prerequisites¶
- Go 1.26 or newer.
1. Create a module¶
mkdir bridgedemo && cd bridgedemo
go mod init bridgedemo
go get gitlab.com/phpboyscout/go/aferobilly github.com/go-git/go-billy/v5 github.com/spf13/afero
2. Bridge a billy filesystem¶
memfs gives you a billy filesystem with no setup. Wrap it and you have an afero.Fs:
package main
import (
"fmt"
"github.com/go-git/go-billy/v5/memfs"
"github.com/spf13/afero"
"gitlab.com/phpboyscout/go/aferobilly"
)
func main() {
bfs := memfs.New() // a billy filesystem
fs := aferobilly.New(bfs) // ...now an afero.Fs
if err := afero.WriteFile(fs, "notes/hello.txt", []byte("hi"), 0o644); err != nil {
panic(err)
}
data, err := afero.ReadFile(fs, "notes/hello.txt")
if err != nil {
panic(err)
}
fmt.Println(string(data))
// hi
}
Note that notes/ was created implicitly — afero's WriteFile calls Mkdir, which maps
onto billy's MkdirAll. That's one of a handful of deliberate semantic choices; see
billy vs afero semantics.
3. Walk it like any afero filesystem¶
Because it's a genuine afero.Fs, the whole afero toolbox works — including helpers
that expect real directory handles:
_ = afero.WriteFile(fs, "notes/a.txt", []byte("a"), 0o644)
_ = afero.WriteFile(fs, "notes/b.txt", []byte("b"), 0o644)
entries, _ := afero.ReadDir(fs, "notes")
for _, e := range entries {
fmt.Println(e.Name(), e.Size())
}
_ = afero.Walk(fs, "/", func(path string, info os.FileInfo, err error) error {
fmt.Println(path)
return nil
})
billy can't Open a directory, so the adapter synthesises a read-only directory handle
whose Readdir/Stat behave the way os and afero callers expect.
4. Add locking when a handle escapes¶
If the handle is used from more than one goroutine — or outlives the function that made it — give it the mutex guarding whatever produced the filesystem:
Now every operation serialises through mu. See
make a handle concurrency-safe for what that does and does not
guarantee — there are two footguns worth knowing before you rely on it.
Where next¶
- Work with a go-git worktree — the real reason this module exists.
- Make a handle concurrency-safe
- billy vs afero semantics — where the two interfaces disagree.