Skip to content

Work in memory

The same API drives a real checkout or a repository that exists only in RAM. The in-memory backend uses go-git's memfs and memory.Storage, so nothing touches disk.

ctx below is a context.Context you supply; every network-touching call takes one. See context and cancellation.

gitRepo, worktree, err := r.OpenInMemory(ctx, url, "main")

Why you'd want it

  • No cleanup. There are no temporary directories to create, track, or delete, and none left behind when a process dies mid-run.
  • Speed. All I/O stays in memory, which is markedly faster for small and medium repositories.
  • Security. Nothing sensitive is written to shared disk. That matters on CI runners and multi-tenant hosts, where a temp directory may outlive your job or be readable by others.

Typical uses: temporary analysis, code generation, and CI pipelines where filesystem artefacts are undesirable.

Choosing a backend

// explicitly in memory
gitRepo, worktree, err := r.OpenInMemory(ctx, url, "main")

// explicitly on disk
gitRepo, worktree, err := r.OpenLocal(ctx, path)

// caller decides by value
gitRepo, worktree, err := r.Open(ctx, repo.InMemoryRepo, url, "main",
    repo.WithShallowClone(1),
    repo.WithSingleBranch("main"),
)

Open takes a RepoType so a program can select the backend from configuration or a flag without branching. SourceIs reports which one is in use.

Clone options

Option Effect
WithShallowClone(depth int) Fetch only the last depth commits
WithSingleBranch(branch string) Limit the fetch to one branch
WithNoTags() Skip fetching tags
WithRecurseSubmodules() Initialise submodules after cloning

These matter more in memory than on disk, because everything you fetch stays resident, tags included, and go-git's clone fetches all of those unless you say otherwise. Defaults and exact effects are in the options reference.

Open(ctx, repo.LocalRepo, …) silently discards both the clone options and the branch argument; only the in-memory path forwards them.

Mind the memory ceiling

A large repository, especially one with heavy binary history, can consume all available RAM. Past a few hundred megabytes, prefer a local shallow clone (WithShallowClone(1)) over an in-memory one.

In-memory repositories make excellent test fixtures

Because it is a real repository, code under test runs against real git behaviour, with no mock to drift from reality, while staying hermetic and fast:

r, _ := repo.NewRepo(repo.Settings{FS: afero.NewMemMapFs()})
_, _, err := r.OpenInMemory(ctx, url, "main")

fs, _ := r.WorkFS()
_ = afero.WriteFile(fs, "main.go", []byte("package main"), 0o644)
_ = r.AddAll()
_, _ = r.Commit(ctx, "initial", nil)

Combined with the worktree filesystem, an afero-based "commit-on-save" routine runs verbatim over an in-memory repository, on the same code path as production. See testing for when to prefer this over a mock.

What in memory does not give you

An in-memory repository is not a persistence layer. There is no export and no flush: a commit made in memory exists until the process drops the reference, and Push is the only way to get it anywhere else.

Settings.FS does not select the backend either. It is read only when loading an SSH key, so passing afero.NewMemMapFs() will not keep a Clone off disk. See what this module does not do.