Work with a go-git worktree¶
go-git exposes a repository's worktree as a
billy.Filesystem. If the rest of your code speaks afero — as most Go application and
test code does — you have three options: rewrite it against billy, copy the tree out and
sync it back, or bridge. This is the bridge.
Read and write a worktree live¶
repo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: "https://gitlab.com/phpboyscout/go/aferobilly.git",
})
if err != nil {
return err
}
wt, err := repo.Worktree()
if err != nil {
return err
}
fs := aferobilly.New(wt.Filesystem)
// ordinary afero code, operating on the real worktree
data, err := afero.ReadFile(fs, "go.mod")
if err != nil {
return err
}
if err := afero.WriteFile(fs, "VERSION", []byte("v1.2.3\n"), 0o644); err != nil {
return err
}
There is no copy and no sync step — the worktree is the single source of truth. A
write through the afero handle is a write to the worktree, so wt.Add(...) and
wt.Commit(...) see it immediately:
In-memory repositories¶
The combination that makes tests fast: an in-memory git repository, bridged to afero, so test code touches no disk at all.
repo, _ := git.Init(memory.NewStorage(), memfs.New())
wt, _ := repo.Worktree()
fs := aferobilly.New(wt.Filesystem)
// build a fixture tree with plain afero calls
_ = afero.WriteFile(fs, "cmd/main.go", []byte("package main"), 0o644)
_ = afero.WriteFile(fs, "README.md", []byte("# demo"), 0o644)
Because it's a real afero.Fs, code under test that takes an afero.Fs needs no
test-specific branching — it gets the same interface it gets in production.
Concurrent access to a worktree¶
go-git worktrees are not safe for concurrent use, so if the handle escapes to another goroutine, pass the mutex that guards the repository:
Every operation then serialises through the same lock the repository uses. Read make a handle concurrency-safe first — there are two footguns (sequences are not atomic, and the locker is not reentrant).
Which bridge do you actually want?¶
Two different jobs are easy to confuse:
| You want | Use |
|---|---|
| A live read/write view of the worktree | this adapter |
| A snapshot of a commit or tree copied into a separate filesystem | a tree-extraction helper, not this |
This adapter is a view: nothing is copied, and changes go straight through. If what you need is "give me the contents of commit X in a scratch filesystem", copy the tree out instead — a live view of a checked-out worktree is the wrong tool for that.