This document covers all operations for working with the worktree (working directory) in go-git, including file management, staging, committing, and checkout operations.
The Worktree represents the working directory of a Git repository. It provides methods for:
type Worktree struct {
Filesystem billy.Filesystem
Excludes []gitignore.Pattern
// contains filtered or unexported fields
}func (r *Repository) Worktree() (*Worktree, error)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)
}
// Now you can perform worktree operations
_ = w
}Note: This will return ErrIsBareRepository for bare repositories.
func (w *Worktree) Status() (Status, error)
type Status map[string]*FileStatus
type FileStatus struct {
Staging StatusCode
Worktree StatusCode
Extra string
}
type StatusCode byteStatus Codes:
const (
Unmodified StatusCode = ' '
Untracked StatusCode = '?'
Modified StatusCode = 'M'
Added StatusCode = 'A'
Deleted StatusCode = 'D'
Renamed StatusCode = 'R'
Copied StatusCode = 'C'
UpdatedButUnmerged StatusCode = 'U'
)Example:
package main
import (
"fmt"
"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)
}
// Check if worktree is clean
if status.IsClean() {
fmt.Println("Worktree is clean")
} else {
fmt.Println("Modified files:")
for file, stat := range status {
if stat.Worktree != git.Unmodified {
fmt.Printf(" %s (worktree: %c, staging: %c)\n",
file, stat.Worktree, stat.Staging)
}
}
}
// Check specific file
fileStat := status.File("README.md")
if fileStat.Worktree == git.Modified {
fmt.Println("README.md is modified")
}
// Check if file is untracked
if status.IsUntracked("newfile.txt") {
fmt.Println("newfile.txt is untracked")
}
}func (s Status) IsClean() bool
func (s Status) File(path string) *FileStatus
func (s Status) IsUntracked(path string) bool
func (s Status) String() stringfunc (w *Worktree) Add(path string) (plumbing.Hash, error)Add a file to the staging area.
Example:
package main
import (
"fmt"
"os"
"path/filepath"
"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)
}
// Create a new file
filename := filepath.Join("/tmp/repo", "README.md")
err = os.WriteFile(filename, []byte("# My Project"), 0644)
if err != nil {
panic(err)
}
// Add file to staging area
hash, err := w.Add("README.md")
if err != nil {
panic(err)
}
fmt.Println("Added file with hash:", hash.String())
}func (w *Worktree) AddGlob(pattern string) errorAdd files matching a glob pattern.
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)
}
// Add all .go files
err = w.AddGlob("*.go")
if err != nil {
panic(err)
}
// Add all files in docs directory
err = w.AddGlob("docs/*")
if err != nil {
panic(err)
}
// Add all files recursively
err = w.AddGlob(".")
if err != nil {
panic(err)
}
}func (w *Worktree) AddWithOptions(opts *AddOptions) error
type AddOptions struct {
All bool
Path string
Glob string
SkipStatus bool
}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)
}
// Add all modified and new files (like git add -A)
err = w.AddWithOptions(&git.AddOptions{
All: true,
})
if err != nil {
panic(err)
}
// Add specific path
err = w.AddWithOptions(&git.AddOptions{
Path: "src/main.go",
})
if err != nil {
panic(err)
}
// Add by glob pattern
err = w.AddWithOptions(&git.AddOptions{
Glob: "*.md",
})
if err != nil {
panic(err)
}
}func (w *Worktree) Remove(path string) (plumbing.Hash, error)Remove a file from the worktree and staging area.
Example:
package main
import (
"fmt"
"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)
}
// Remove file
hash, err := w.Remove("oldfile.txt")
if err != nil {
panic(err)
}
fmt.Println("Removed file with hash:", hash.String())
}func (w *Worktree) RemoveGlob(pattern string) errorExample:
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)
}
// Remove all .tmp files
err = w.RemoveGlob("*.tmp")
if err != nil {
panic(err)
}
}func (w *Worktree) Move(from, to string) (plumbing.Hash, error)Example:
package main
import (
"fmt"
"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)
}
// Rename file
hash, err := w.Move("old-name.txt", "new-name.txt")
if err != nil {
panic(err)
}
fmt.Println("Moved file with hash:", hash.String())
}func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error)
type CommitOptions struct {
All bool
AllowEmptyCommits bool
Author *object.Signature
Committer *object.Signature
Parents []plumbing.Hash
SignKey *openpgp.Entity
Signer *Signer
Amend bool
}Example:
package main
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Create a file
filename := filepath.Join("/tmp/repo", "example.txt")
err = os.WriteFile(filename, []byte("Hello World"), 0644)
if err != nil {
panic(err)
}
// Add to staging
_, err = w.Add("example.txt")
if err != nil {
panic(err)
}
// Create commit
commit, err := w.Commit("Add example file", &git.CommitOptions{
Author: &object.Signature{
Name: "John Doe",
Email: "john@doe.org",
When: time.Now(),
},
})
if err != nil {
panic(err)
}
// Get commit object
obj, err := r.CommitObject(commit)
if err != nil {
panic(err)
}
fmt.Println(obj)
}Automatically stage all modified tracked files:
package main
import (
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Commit all modified files (like git commit -a)
commit, err := w.Commit("Update all files", &git.CommitOptions{
All: true,
Author: &object.Signature{
Name: "John Doe",
Email: "john@doe.org",
When: time.Now(),
},
})
if err != nil {
panic(err)
}
_ = commit
}package main
import (
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Create empty commit (like git commit --allow-empty)
commit, err := w.Commit("Empty commit", &git.CommitOptions{
AllowEmptyCommits: true,
Author: &object.Signature{
Name: "John Doe",
Email: "john@doe.org",
When: time.Now(),
},
})
if err != nil {
panic(err)
}
_ = commit
}package main
import (
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Amend the previous commit
commit, err := w.Commit("Updated commit message", &git.CommitOptions{
Amend: true,
Author: &object.Signature{
Name: "John Doe",
Email: "john@doe.org",
When: time.Now(),
},
})
if err != nil {
panic(err)
}
_ = commit
}func (w *Worktree) Checkout(opts *CheckoutOptions) error
type CheckoutOptions struct {
Hash plumbing.Hash
Branch plumbing.ReferenceName
Create bool
Force bool
Keep bool
SparseCheckoutDirectories []string
}Example - Checkout Branch:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Checkout existing branch
err = w.Checkout(&git.CheckoutOptions{
Branch: plumbing.ReferenceName("refs/heads/develop"),
})
if err != nil {
panic(err)
}
}Example - Create and Checkout New Branch:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Create and checkout new branch (like git checkout -b feature)
err = w.Checkout(&git.CheckoutOptions{
Branch: plumbing.ReferenceName("refs/heads/feature"),
Create: true,
})
if err != nil {
panic(err)
}
}Example - Checkout Specific Commit:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Checkout specific commit (detached HEAD)
err = w.Checkout(&git.CheckoutOptions{
Hash: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
})
if err != nil {
panic(err)
}
}Example - Force Checkout:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Force checkout, discarding local changes
err = w.Checkout(&git.CheckoutOptions{
Branch: plumbing.ReferenceName("refs/heads/main"),
Force: true,
})
if err != nil {
panic(err)
}
}Example - Sparse Checkout:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Sparse checkout - only checkout specific directories
err = w.Checkout(&git.CheckoutOptions{
Branch: plumbing.ReferenceName("refs/heads/main"),
SparseCheckoutDirectories: []string{"docs", "src"},
})
if err != nil {
panic(err)
}
}func (w *Worktree) Reset(opts *ResetOptions) error
type ResetOptions struct {
Commit plumbing.Hash
Mode ResetMode
Files []string
}
type ResetMode int
const (
MixedReset ResetMode = iota // Default
HardReset // Reset worktree and index
MergeReset // Keep worktree changes
SoftReset // Only move HEAD
)Example - Soft Reset:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Soft reset - move HEAD but keep changes staged
err = w.Reset(&git.ResetOptions{
Commit: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
Mode: git.SoftReset,
})
if err != nil {
panic(err)
}
}Example - Hard Reset:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
w, err := r.Worktree()
if err != nil {
panic(err)
}
// Hard reset - discard all changes
err = w.Reset(&git.ResetOptions{
Commit: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"),
Mode: git.HardReset,
})
if err != nil {
panic(err)
}
}Example - Reset Specific Files:
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)
}
// Reset specific files to HEAD
ref, err := r.Head()
if err != nil {
panic(err)
}
err = w.Reset(&git.ResetOptions{
Commit: ref.Hash(),
Files: []string{"README.md", "main.go"},
})
if err != nil {
panic(err)
}
}func (w *Worktree) Pull(o *PullOptions) error
func (w *Worktree) PullContext(ctx context.Context, o *PullOptions) errorSee Remote Operations for detailed pull documentation.
Quick 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)
}
// Pull latest changes
err = w.Pull(&git.PullOptions{RemoteName: "origin"})
if err != nil && err != git.NoErrAlreadyUpToDate {
panic(err)
}
}func (w *Worktree) Clean(opts *CleanOptions) error
type CleanOptions struct {
Dir bool
Flags CleanStatusFlags
}
type CleanStatusFlags uint
const (
CleanUntracked CleanStatusFlags = 1 << iota
CleanIgnored
)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)
}
// Clean untracked files
err = w.Clean(&git.CleanOptions{
Dir: true,
Flags: git.CleanUntracked,
})
if err != nil {
panic(err)
}
}func (w *Worktree) Restore(opts *RestoreOptions) error
type RestoreOptions struct {
Files []string
Hash plumbing.Hash
Branch plumbing.ReferenceName
Staged bool
Worktree bool
}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)
}
// Restore specific files from HEAD
err = w.Restore(&git.RestoreOptions{
Files: []string{"README.md"},
Worktree: true,
})
if err != nil {
panic(err)
}
}func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error)
type GrepOptions struct {
Patterns []*regexp.Regexp
InvertMatch bool
CommitHash plumbing.Hash
ReferenceName plumbing.ReferenceName
PathSpecs []string
}
type GrepResult struct {
FileName string
LineNumber int
Content string
TreeName string
}Example:
package main
import (
"fmt"
"regexp"
"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)
}
// Search for pattern
pattern := regexp.MustCompile("TODO")
results, err := w.Grep(&git.GrepOptions{
Patterns: []*regexp.Regexp{pattern},
})
if err != nil {
panic(err)
}
for _, result := range results {
fmt.Printf("%s:%d: %s\n", result.FileName, result.LineNumber, result.Content)
}
}func (w *Worktree) Submodule(name string) (*Submodule, error)
func (w *Worktree) Submodules() (Submodules, error)Example:
package main
import (
"fmt"
"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 all submodules
submodules, err := w.Submodules()
if err != nil {
panic(err)
}
// Initialize and update all submodules
err = submodules.Init()
if err != nil {
panic(err)
}
err = submodules.Update(&git.SubmoduleUpdateOptions{
Init: true,
})
if err != nil {
panic(err)
}
fmt.Printf("Updated %d submodules\n", len(submodules))
}var (
ErrUnableToAddFile = errors.New("unable to add file")
ErrUnableToStageFile = errors.New("unable to stage file")
ErrUnableToUnstageFile = errors.New("unable to unstage file")
ErrUnableToCheckout = errors.New("unable to checkout")
ErrUnstagedChanges = errors.New("worktree contains unstaged changes")
ErrGlobNoMatches = errors.New("glob pattern did not match any files")
ErrForceNeeded = errors.New("force is required")
ErrEmptyCommit = errors.New("empty commit")
ErrSubmoduleNotFound = errors.New("submodule not found")
)package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
w, _ := r.Worktree()
status, err := w.Status()
if err != nil {
panic(err)
}
if !status.IsClean() {
println("Warning: worktree has uncommitted changes")
}
}package main
import (
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
w, _ := r.Worktree()
// Automatically stage all modified files
_, _ = w.Commit("Quick update", &git.CommitOptions{
All: true,
Author: &object.Signature{
Name: "Auto Bot",
Email: "bot@example.com",
When: time.Now(),
},
})
}package main
import (
"github.com/go-git/go-git/v5"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
w, _ := r.Worktree()
err := w.Pull(&git.PullOptions{RemoteName: "origin"})
if err == git.NoErrAlreadyUpToDate {
println("Already up to date")
} else if err != nil {
panic(err)
}
}package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
w, _ := r.Worktree()
// Check for changes first
status, _ := w.Status()
if !status.IsClean() {
println("Warning: This will discard local changes")
}
// Force checkout
_ = w.Checkout(&git.CheckoutOptions{
Branch: plumbing.ReferenceName("refs/heads/main"),
Force: true,
})
}