This document covers all operations for creating, opening, and cloning Git repositories using go-git.
go-git provides two sets of repository functions:
type Repository struct {
Storer storage.Storer
// contains filtered or unexported fields
}The Repository type is the main entry point for all repository operations. It provides methods for:
Create a new repository with custom storage and filesystem.
func Init(s storage.Storer, worktree billy.Filesystem) (*Repository, error)Parameters:
s - Storage backend (e.g., memory.NewStorage(), filesystem.NewStorage())worktree - Filesystem abstraction for the working directory (nil for bare repositories)Example:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/go-git/go-billy/v5/memfs"
)
func main() {
// Create in-memory repository
storer := memory.NewStorage()
fs := memfs.New()
r, err := git.Init(storer, fs)
if err != nil {
panic(err)
}
// Repository is now initialized
ref, err := r.Head()
if err != nil {
// HEAD doesn't exist yet in empty repo
println("Empty repository")
}
}Create a new repository with initialization options.
func InitWithOptions(s storage.Storer, worktree billy.Filesystem, opts InitOptions) (*Repository, error)
type InitOptions struct {
DefaultBranch plumbing.ReferenceName
}Example:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/go-git/go-billy/v5/memfs"
)
func main() {
storer := memory.NewStorage()
fs := memfs.New()
// Initialize with 'main' as default branch
r, err := git.InitWithOptions(storer, fs, git.InitOptions{
DefaultBranch: plumbing.ReferenceName("refs/heads/main"),
})
if err != nil {
panic(err)
}
_ = r
}Create a new repository directly on the OS filesystem.
func PlainInit(path string, isBare bool) (*Repository, error)Parameters:
path - Directory path for the repositoryisBare - If true, creates a bare repository (no working directory)Example:
package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
// Create a regular repository with working directory
r, err := git.PlainInit("/tmp/my-repo", false)
if err != nil {
panic(err)
}
// Create a bare repository (no working directory)
bare, err := git.PlainInit("/tmp/bare-repo.git", true)
if err != nil {
panic(err)
}
_, _ = r, bare
}Create a new repository on the filesystem with options.
func PlainInitWithOptions(path string, opts *PlainInitOptions) (*Repository, error)
type PlainInitOptions struct {
DefaultBranch plumbing.ReferenceName
Bare bool
InitOptions InitOptions
}Example:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainInitWithOptions("/tmp/my-repo", &git.PlainInitOptions{
DefaultBranch: plumbing.ReferenceName("refs/heads/main"),
Bare: false,
})
if err != nil {
panic(err)
}
_ = r
}Open an existing repository with custom storage and filesystem.
func Open(s storage.Storer, worktree billy.Filesystem) (*Repository, error)Example:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/storage/filesystem"
"github.com/go-git/go-billy/v5/osfs"
)
func main() {
// Open repository using filesystem storage
fs := osfs.New("/tmp/my-repo")
storer := filesystem.NewStorage(fs, nil)
r, err := git.Open(storer, fs)
if err != nil {
panic(err)
}
// Access repository
ref, err := r.Head()
if err != nil {
panic(err)
}
println("HEAD:", ref.Hash().String())
}Open an existing repository directly from an OS filesystem path.
func PlainOpen(path string) (*Repository, error)Parameters:
path - Path to the repository (directory containing .git or bare repository)Example:
package main
import (
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
)
func main() {
// Open repository
r, err := git.PlainOpen("/path/to/repo")
if err != nil {
panic(err)
}
// Get HEAD reference
ref, err := r.Head()
if err != nil {
panic(err)
}
// Get commit history
cIter, err := r.Log(&git.LogOptions{From: ref.Hash()})
if err != nil {
panic(err)
}
// Count commits
var count int
err = cIter.ForEach(func(c *object.Commit) error {
count++
return nil
})
if err != nil {
panic(err)
}
fmt.Printf("Repository has %d commits\n", count)
}Open an existing repository with options.
func PlainOpenWithOptions(path string, opts *PlainOpenOptions) (*Repository, error)
type PlainOpenOptions struct {
DetectDotGit bool
EnableDotGitCommonDir bool
}Parameters:
DetectDotGit - Automatically detect .git directory in parent directoriesEnableDotGitCommonDir - Enable support for .git/commondir (worktree support)Example:
package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
// Open repository with automatic .git detection
r, err := git.PlainOpenWithOptions("/path/to/subdirectory", &git.PlainOpenOptions{
DetectDotGit: true,
EnableDotGitCommonDir: true,
})
if err != nil {
panic(err)
}
_ = r
}Clone a remote repository to custom storage.
func Clone(s storage.Storer, worktree billy.Filesystem, o *CloneOptions) (*Repository, error)Example:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/go-git/go-billy/v5/memfs"
)
func main() {
// Clone to in-memory storage
storer := memory.NewStorage()
fs := memfs.New()
r, err := git.Clone(storer, fs, &git.CloneOptions{
URL: "https://github.com/go-git/go-git",
})
if err != nil {
panic(err)
}
_ = r
}Clone a repository with context support for cancellation and timeout.
func CloneContext(ctx context.Context, s storage.Storer, worktree billy.Filesystem, o *CloneOptions) (*Repository, error)Example:
package main
import (
"context"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/go-git/go-billy/v5/memfs"
)
func main() {
// Clone with 30-second timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
storer := memory.NewStorage()
fs := memfs.New()
r, err := git.CloneContext(ctx, storer, fs, &git.CloneOptions{
URL: "https://github.com/go-git/go-git",
})
if err != nil {
panic(err)
}
_ = r
}Clone a repository to a filesystem directory.
func PlainClone(path string, isBare bool, o *CloneOptions) (*Repository, error)Parameters:
path - Destination directory pathisBare - If true, creates a bare clone (no working directory)o - Clone options (URL, authentication, progress, etc.)Example:
package main
import (
"os"
"github.com/go-git/go-git/v5"
)
func main() {
// Clone repository
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/go-git/go-git",
Progress: os.Stdout,
})
if err != nil {
panic(err)
}
// Get HEAD
ref, err := r.Head()
if err != nil {
panic(err)
}
commit, err := r.CommitObject(ref.Hash())
if err != nil {
panic(err)
}
println("Cloned to commit:", commit.Hash.String())
}Clone a repository with context support.
func PlainCloneContext(ctx context.Context, path string, isBare bool, o *CloneOptions) (*Repository, error)Example:
package main
import (
"context"
"os"
"time"
"github.com/go-git/go-git/v5"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
r, err := git.PlainCloneContext(ctx, "/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/go-git/go-git",
Progress: os.Stdout,
Depth: 1, // Shallow clone
})
if err != nil {
panic(err)
}
_ = r
}type CloneOptions struct {
URL string
Auth transport.AuthMethod
RemoteName string
ReferenceName plumbing.ReferenceName
SingleBranch bool
Mirror bool
NoCheckout bool
Depth int
RecurseSubmodules SubmoduleRescursivity
Progress sideband.Progress
Tags TagMode
InsecureSkipTLS bool
ClientCert []byte
ClientKey []byte
CABundle []byte
ProxyOptions transport.ProxyOptions
Shared bool
}Key Fields:
Example with Multiple Options:
package main
import (
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
func main() {
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/private/repo",
Auth: &http.BasicAuth{
Username: "user",
Password: "token",
},
ReferenceName: plumbing.ReferenceName("refs/heads/develop"),
SingleBranch: true,
Depth: 50,
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
Progress: os.Stdout,
Tags: git.NoTags,
})
if err != nil {
panic(err)
}
_ = r
}func (r *Repository) Config() (*config.Config, error)
func (r *Repository) SetConfig(cfg *config.Config) error
func (r *Repository) ConfigScoped(scope config.Scope) (*config.Config, error)Get and set repository configuration.
Example:
package main
import (
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Get configuration
cfg, err := r.Config()
if err != nil {
panic(err)
}
// Modify configuration
cfg.User.Name = "Jane Doe"
cfg.User.Email = "jane@example.com"
// Save configuration
err = r.SetConfig(cfg)
if err != nil {
panic(err)
}
fmt.Println("Configuration updated")
}func (r *Repository) Remote(name string) (*Remote, error)
func (r *Repository) Remotes() ([]*Remote, error)
func (r *Repository) CreateRemote(c *config.RemoteConfig) (*Remote, error)
func (r *Repository) DeleteRemote(name string) errorExample:
package main
import (
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// List remotes
remotes, err := r.Remotes()
if err != nil {
panic(err)
}
for _, remote := range remotes {
fmt.Println(remote.Config().Name, remote.Config().URLs)
}
// Create new remote
_, err = r.CreateRemote(&config.RemoteConfig{
Name: "upstream",
URLs: []string{"https://github.com/upstream/repo"},
})
if err != nil {
panic(err)
}
}func (r *Repository) Worktree() (*Worktree, error)Get the repository's worktree for file operations.
Example:
package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Get status
status, err := w.Status()
if err != nil {
panic(err)
}
println("Clean:", status.IsClean())
}var (
ErrRepositoryNotExists = errors.New("repository does not exist")
ErrRepositoryAlreadyExists = errors.New("repository already exists")
ErrRepositoryIncomplete = errors.New("repository is incomplete")
ErrIsBareRepository = errors.New("operation not supported on bare repository")
ErrWorktreeNotProvided = errors.New("worktree not provided")
)Error Handling Example:
package main
import (
"errors"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/nonexistent/repo")
if err != nil {
if errors.Is(err, git.ErrRepositoryNotExists) {
println("Repository doesn't exist")
// Maybe create it instead
r, err = git.PlainInit("/nonexistent/repo", false)
} else {
panic(err)
}
}
_ = r
}package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Check if bare before accessing worktree
_, err = r.Worktree()
if err == git.ErrIsBareRepository {
println("This is a bare repository")
// Handle accordingly
}
}package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
// Clone only the last 10 commits
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/large/repo",
Depth: 10,
})
if err != nil {
panic(err)
}
_ = r
}package main
import (
"os"
"github.com/go-git/go-git/v5"
)
func main() {
r, err := git.PlainClone("/tmp/repo", false, &git.CloneOptions{
URL: "https://github.com/go-git/go-git",
Progress: os.Stdout, // Print progress to stdout
})
if err != nil {
panic(err)
}
_ = r
}