Skip to content

Getting started

Clone a public repository into memory and read a file out of it. No disk, no credentials, no cleanup.

go get gitlab.com/phpboyscout/go/repo

A first repository

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/spf13/afero"
    "gitlab.com/phpboyscout/go/repo"
)

func main() {
    r, err := repo.NewRepo(repo.Settings{
        Forge: repo.ForgeGitLab,
        FS:    afero.NewOsFs(),
    })
    if err != nil {
        log.Fatal(err)
    }

    // Clone into RAM, shallow, since we only need the tip. The context lets a hung
    // remote be cancelled; every network-touching call takes one.
    ctx := context.Background()

    if _, _, err := r.OpenInMemory(
        ctx, "https://gitlab.com/phpboyscout/go/repo.git", "main",
        repo.WithShallowClone(1),
    ); err != nil {
        log.Fatal(err)
    }

    file, err := r.GetFile("go.mod")
    if err != nil {
        log.Fatal(err)
    }

    contents, err := file.Contents()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(contents)
}

Three things to notice, because they are the shape of the whole API:

No forge client anywhere. Forge is a string naming an authentication convention, not a GitHub or GitLab SDK. Cloning a repository does not drag a vendor client into your dependency graph. See why git needs no forge.

No credentials for a public repository. Add a Token for a private one; set Private: true and a missing token becomes a clear error instead of a confusing git failure. See authenticate to a forge.

OpenInMemory binds the repository to r. Later calls (GetFile, AddAll, Commit) need no handle. Call one before opening and you get ErrNoRepository or ErrNoWorktree back; never a panic.

Now write something

The worktree is an afero.Fs, and writes to it are the worktree. No sync step:

fs, err := r.WorkFS()
if err != nil {
    log.Fatal(err)
}

if err := afero.WriteFile(fs, "NOTES.md", []byte("hello\n"), 0o644); err != nil {
    log.Fatal(err)
}

if err := r.AddAll(); err != nil {
    log.Fatal(err)
}

hash, err := r.Commit(ctx, "docs: add notes", &git.CommitOptions{
    Author: &object.Signature{
        Name:  "You",
        Email: "you@example.com",
        When:  time.Now(),
    },
})

That needs three more imports: time, git "github.com/go-git/go-git/v5", and "github.com/go-git/go-git/v5/plumbing/object".

AddAll honours .gitignore, so build artefacts stay out of the commit. Because the repository is in memory, this commit exists only in your process, so there is nothing to clean up.

Why the author is spelled out

Commit also accepts nil options, and most examples use that. It works only where git is already configured: with nil, go-git resolves the author from user.name and user.email in your git configuration. Where none is set, which is the normal state inside a container or a CI runner, the commit fails with author field is required.

Naming the author explicitly makes the step work everywhere, which is why it is written that way here. This module supplies no identity of its own; see the errors reference.

Where next