Read and write the worktree¶
go-git exposes a worktree as a billy.Filesystem; most Go code is written against
afero.Fs. WorkFS() bridges them, so you can read and write a checked-out tree with
ordinary afero code — live, with no copy and no sync step.
fs, err := r.WorkFS()
if err != nil {
return err // ErrNoWorktree if nothing is open yet
}
_ = afero.WriteFile(fs, "VERSION", []byte("v1.2.3\n"), 0o644)
Files written through that filesystem are the worktree, so go-git stages and commits them with no further step:
One source of truth. This works identically for an in-memory repository and a real checkout.
Atomic sequences¶
WorkFS() gives a handle whose operations are individually atomic. When a sequence
must be atomic relative to Commit/Add — write a coherent set of files, then commit —
use the callback form, which holds the lock for its whole duration:
err := r.WithWorkFS(func(fs afero.Fs) error {
if err := afero.WriteFile(fs, "go.mod", gomod, 0o644); err != nil {
return err
}
return afero.WriteFile(fs, "go.sum", gosum, 0o644)
})
Which to use:
| Situation | Use |
|---|---|
Handing an afero.Fs to a library you don't control (templating, workspace tooling) |
WorkFS() |
| Long-lived or streaming I/O where interleaving is tolerable | WorkFS() |
| A set of writes that must land together, before any commit sees a partial state | WithWorkFS() |
Inside the callback, don't touch the repository
fn must not retain the afero.Fs past return, call any other method on the same
repository, or use a WorkFS() handle. The lock is already held and is not
reentrant — all three deadlock. Keep fn short: it blocks every other repository
operation.
Live view vs snapshot¶
There are two ways to get repository content into an afero.Fs, and they are not
interchangeable:
WorkFS() / WithWorkFS() |
AddToFS() |
|
|---|---|---|
| What | A live view of the worktree | A copy into a separate filesystem |
| Writes | Go straight to the worktree | Go to your filesystem only |
| Use for | Continuous read/write, edit-then-commit | One-shot extraction from any commit |
AddToFS copies a file out of the git object model into a filesystem you supply — ideal
for hydrating a virtual filesystem with content from any point in history:
Note that it skips a file that already exists in the target rather than overwriting.
The trap is using AddToFS when you meant the live view: you then have two sources of
truth, and a commit only sees your edits after an explicit write-back that is easy to
forget.
Filesystem semantics¶
The bridge is provided by aferobilly, and a few
places where billy and afero disagree are resolved deliberately — Mkdir creates parents,
Chmod/Chown/Chtimes are no-ops, directories get a synthesised read-only handle.
Those are documented in full on
that module's semantics page.