Working with Linked Worktrees
Git worktrees allow you to check out multiple branches simultaneously, each
in its own working directory. go-git supports creating and opening linked
worktrees through the experimental x/plumbing/worktree package.
Overview
A Git repository has one main worktree (created by git init or
git clone) and zero or more linked worktrees. Each linked worktree
has its own checked-out branch and working directory, while sharing the
same object database and refs with the main repository.
Common use cases:
- Running tests on one branch while developing on another
- Comparing behavior across branches side by side
- Building a release while continuing feature work
Creating a Linked Worktree
To create a linked worktree, you need:
- A
storage.Storerthat implementsWorktreeStorer(e.g.filesystem.Storage) - A
billy.Filesystemfor the new worktree's working directory - The commit hash to check out
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/go-git/go-billy/v6/osfs"
"github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
"github.com/go-git/go-git/v6/storage/filesystem"
xworktree "github.com/go-git/go-git/v6/x/plumbing/worktree"
)
func main() {
repoPath := "/path/to/repo"
worktreePath := "/path/to/new-worktree"
commitHash := "abc123..."
// Open the repository's .git storage
dotgitFs := osfs.New(filepath.Join(repoPath, ".git"), osfs.WithChrootOS())
store := filesystem.NewStorageWithOptions(dotgitFs, nil, filesystem.Options{})
// Create a worktree manager
wt, err := xworktree.New(store)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
// Create the linked worktree filesystem
worktreeFs := osfs.New(worktreePath)
name := filepath.Base(worktreePath)
// Add the worktree at the given commit
err = wt.Add(worktreeFs, name,
xworktree.WithCommit(plumbing.NewHash(commitHash)))
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Worktree %q created at %s\n", name, worktreePath)
}
Opening an Existing Linked Worktree
Once a linked worktree exists on disk, open it like any other repository
using git.Open with the shared storage and the worktree filesystem:
r, err := git.Open(store, worktreeFs)
if err != nil {
log.Fatal(err)
}
ref, err := r.Head()
if err != nil {
log.Fatal(err)
}
commit, err := r.CommitObject(ref.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(commit)
Worktree Configuration Extension
When extensions.worktreeConfig is enabled in the repository config,
each linked worktree can have its own config.worktree file at
.git/worktrees/<name>/config.worktree. go-git's filesystem storage
reads these files and overlays them on the shared repository config,
matching the behavior of upstream Git.
This is useful for per-worktree settings such as core.sparseCheckout
or custom configuration that should differ between worktrees.
Reference: git-worktree configuration file
Worktree Naming
Worktree names must match the pattern [a-zA-Z0-9\-]+. Typically the
name is derived from the base directory name of the worktree path (e.g.
/tmp/hotfix produces the name hotfix).
Limitations
- Only
addis fully supported for creating worktrees. Not all flags or subcommands fromgit worktreeare implemented. - The worktree package lives in
x/plumbing/worktree, meaning its API is experimental and may change without notice. - The storer must implement
WorktreeStorer— currently onlystorage/filesystemsatisfies this. - Worktree lock, move, and prune operations are not yet supported.
See the full example in the go-git repository.