This document covers working with Git references (branches, tags, HEAD) and hashes in go-git.
Git references are pointers to commits. go-git supports:
type Hash [20]byte
func NewHash(s string) Hash
func ComputeHash(t ObjectType, data []byte) Hash
func IsHash(s string) bool
func (h Hash) IsZero() bool
func (h Hash) String() stringConstants:
var ZeroHash HashExample:
package main
import (
"fmt"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
// Create hash from string
hash := plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")
fmt.Println("Hash:", hash.String())
// Check if zero
if hash.IsZero() {
fmt.Println("Zero hash")
}
// Zero hash constant
zero := plumbing.ZeroHash
fmt.Println("Zero:", zero.String())
// Validate hash string
if plumbing.IsHash("abc123") {
fmt.Println("Valid hash")
}
}type ReferenceName string
func (r ReferenceName) IsBranch() bool
func (r ReferenceName) IsNote() bool
func (r ReferenceName) IsRemote() bool
func (r ReferenceName) IsTag() bool
func (r ReferenceName) Short() string
func (r ReferenceName) Validate() error
func (r ReferenceName) String() stringConstants:
const (
HEAD ReferenceName = "HEAD"
Master ReferenceName = "refs/heads/master"
Main ReferenceName = "refs/heads/main"
)Constructor Functions:
func NewBranchReferenceName(name string) ReferenceName
func NewTagReferenceName(name string) ReferenceName
func NewRemoteReferenceName(remote, branch string) ReferenceName
func NewRemoteHEADReferenceName(remote string) ReferenceName
func NewNoteReferenceName(namespace string) ReferenceNameExample:
package main
import (
"fmt"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
// Create branch reference
branch := plumbing.NewBranchReferenceName("develop")
fmt.Println("Branch:", branch) // refs/heads/develop
fmt.Println("Short:", branch.Short()) // develop
fmt.Println("Is branch:", branch.IsBranch())
// Create tag reference
tag := plumbing.NewTagReferenceName("v1.0.0")
fmt.Println("Tag:", tag) // refs/tags/v1.0.0
fmt.Println("Is tag:", tag.IsTag())
// Create remote branch reference
remote := plumbing.NewRemoteReferenceName("origin", "main")
fmt.Println("Remote:", remote) // refs/remotes/origin/main
fmt.Println("Is remote:", remote.IsRemote())
// HEAD constant
fmt.Println("HEAD:", plumbing.HEAD)
}type Reference struct {
// contains filtered or unexported fields
}
type ReferenceType int
const (
InvalidReference ReferenceType = 0
HashReference ReferenceType = 1
SymbolicReference ReferenceType = 2
)
func NewHashReference(name ReferenceName, h Hash) *Reference
func NewSymbolicReference(name, target ReferenceName) *Reference
func NewReferenceFromStrings(name, target string) *Reference
func (r *Reference) Type() ReferenceType
func (r *Reference) Name() ReferenceName
func (r *Reference) Hash() Hash
func (r *Reference) Target() ReferenceName
func (r *Reference) Strings() [2]string
func (r *Reference) String() stringExample - Hash Reference:
package main
import (
"fmt"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
// Create hash reference (points to commit)
hash := plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")
ref := plumbing.NewHashReference(
plumbing.NewBranchReferenceName("main"),
hash,
)
fmt.Println("Type:", ref.Type())
fmt.Println("Name:", ref.Name())
fmt.Println("Hash:", ref.Hash())
}Example - Symbolic Reference:
package main
import (
"fmt"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
// Create symbolic reference (points to another reference)
ref := plumbing.NewSymbolicReference(
plumbing.HEAD,
plumbing.NewBranchReferenceName("main"),
)
fmt.Println("Type:", ref.Type())
fmt.Println("Name:", ref.Name())
fmt.Println("Target:", ref.Target())
}func (r *Repository) Head() (*plumbing.Reference, 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)
}
// Get HEAD reference
head, err := r.Head()
if err != nil {
panic(err)
}
fmt.Println("HEAD type:", head.Type())
fmt.Println("HEAD name:", head.Name())
if head.Type() == plumbing.SymbolicReference {
fmt.Println("HEAD -> ", head.Target())
} else {
fmt.Println("Detached HEAD:", head.Hash())
}
}func (r *Repository) Reference(name plumbing.ReferenceName, resolved bool) (*plumbing.Reference, error)Parameters:
name - Reference name to getresolved - If true, resolve symbolic references to their targetExample:
package main
import (
"fmt"
"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)
}
// Get branch reference
ref, err := r.Reference(plumbing.NewBranchReferenceName("main"), false)
if err != nil {
panic(err)
}
fmt.Println("main ->", ref.Hash())
// Get HEAD (symbolic)
head, err := r.Reference(plumbing.HEAD, false)
if err != nil {
panic(err)
}
fmt.Println("HEAD ->", head.Target())
// Get HEAD (resolved)
resolved, err := r.Reference(plumbing.HEAD, true)
if err != nil {
panic(err)
}
fmt.Println("HEAD resolves to:", resolved.Hash())
}func (r *Repository) References() (storer.ReferenceIter, error)Example:
package main
import (
"fmt"
"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)
}
// Iterate all references
refs, err := r.References()
if err != nil {
panic(err)
}
err = refs.ForEach(func(ref *plumbing.Reference) error {
fmt.Printf("%s -> %s\n", ref.Name().Short(), ref.Hash())
return nil
})
if err != nil {
panic(err)
}
refs.Close()
}func (r *Repository) Branches() (storer.ReferenceIter, error)Example:
package main
import (
"fmt"
"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)
}
// Iterate branches (refs/heads/*)
branches, err := r.Branches()
if err != nil {
panic(err)
}
fmt.Println("Local branches:")
err = branches.ForEach(func(ref *plumbing.Reference) error {
fmt.Printf(" %s\n", ref.Name().Short())
return nil
})
if err != nil {
panic(err)
}
branches.Close()
}func (r *Repository) Tags() (storer.ReferenceIter, error)
func (r *Repository) Tag(name string) (*plumbing.Reference, error)Example:
package main
import (
"fmt"
"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)
}
// Iterate tags (refs/tags/*)
tags, err := r.Tags()
if err != nil {
panic(err)
}
fmt.Println("Tags:")
err = tags.ForEach(func(ref *plumbing.Reference) error {
fmt.Printf(" %s -> %s\n", ref.Name().Short(), ref.Hash())
return nil
})
if err != nil {
panic(err)
}
tags.Close()
// Get specific tag
tagRef, err := r.Tag("v1.0.0")
if err == nil {
fmt.Println("\nTag v1.0.0:", tagRef.Hash())
}
}func (r *Repository) Notes() (storer.ReferenceIter, error)Example:
package main
import (
"fmt"
"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)
}
// Iterate notes (refs/notes/*)
notes, err := r.Notes()
if err != nil {
panic(err)
}
err = notes.ForEach(func(ref *plumbing.Reference) error {
fmt.Printf("%s\n", ref.Name())
return nil
})
if err != nil {
panic(err)
}
notes.Close()
}func (r *Repository) CreateBranch(c *config.Branch) error
func (r *Repository) Branch(name string) (*config.Branch, error)Example:
package main
import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, err := git.PlainOpen("/tmp/repo")
if err != nil {
panic(err)
}
// Create branch configuration
err = r.CreateBranch(&config.Branch{
Name: "feature",
Remote: "origin",
Merge: plumbing.NewBranchReferenceName("feature"),
})
if err != nil {
panic(err)
}
// Get branch config
branch, err := r.Branch("feature")
if err != nil {
panic(err)
}
println("Branch:", branch.Name)
println("Remote:", branch.Remote)
}func (r *Repository) DeleteBranch(name 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)
}
// Delete branch
err = r.DeleteBranch("old-feature")
if err != nil {
panic(err)
}
}func (r *Repository) CreateTag(name string, hash plumbing.Hash, opts *CreateTagOptions) (*plumbing.Reference, error)
type CreateTagOptions struct {
Tagger *object.Signature
Message string
SignKey *openpgp.Entity
}Example - Lightweight Tag:
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)
}
// Get current HEAD
ref, err := r.Head()
if err != nil {
panic(err)
}
// Create lightweight tag
tagRef, err := r.CreateTag("v1.0.0", ref.Hash(), nil)
if err != nil {
panic(err)
}
println("Created tag:", tagRef.Name().String())
}Example - Annotated Tag:
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")
ref, _ := r.Head()
// Create annotated tag
tagRef, err := r.CreateTag("v1.0.0", ref.Hash(), &git.CreateTagOptions{
Tagger: &object.Signature{
Name: "John Doe",
Email: "john@doe.org",
When: time.Now(),
},
Message: "Release v1.0.0",
})
if err != nil {
panic(err)
}
println("Created tag:", tagRef.Name().String())
}func (r *Repository) DeleteTag(name 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)
}
// Delete tag
err = r.DeleteTag("v0.9.0")
if err != nil {
panic(err)
}
}func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error)
type Revision stringResolve Git revision strings like "HEAD1", "main^", "v1.0.03".
Example:
package main
import (
"fmt"
"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)
}
// Resolve various revision strings
revisions := []plumbing.Revision{
"HEAD",
"HEAD~1",
"HEAD~5",
"main",
"main~2",
"v1.0.0",
"v1.0.0^",
}
for _, rev := range revisions {
hash, err := r.ResolveRevision(rev)
if err != nil {
fmt.Printf("%s: error - %v\n", rev, err)
} else {
fmt.Printf("%s -> %s\n", rev, hash.String())
}
}
}package main
import (
"errors"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
branchName := plumbing.NewBranchReferenceName("feature")
_, err := r.Reference(branchName, false)
if err == plumbing.ErrReferenceNotFound {
println("Branch doesn't exist")
} else if err != nil {
panic(err)
} else {
println("Branch exists")
}
}package main
import (
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
head, err := r.Head()
if err != nil {
panic(err)
}
if head.Type() == plumbing.SymbolicReference {
fmt.Println("Current branch:", head.Target().Short())
} else {
fmt.Println("Detached HEAD at:", head.Hash())
}
}package main
import (
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
refs, _ := r.References()
defer refs.Close()
fmt.Println("Local branches:")
refs.ForEach(func(ref *plumbing.Reference) error {
if ref.Name().IsBranch() {
fmt.Printf(" %s\n", ref.Name().Short())
}
return nil
})
// Reset iterator
refs, _ = r.References()
defer refs.Close()
fmt.Println("\nRemote branches:")
refs.ForEach(func(ref *plumbing.Reference) error {
if ref.Name().IsRemote() {
fmt.Printf(" %s\n", ref.Name().Short())
}
return nil
})
}package main
import (
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
r, _ := git.PlainOpen("/tmp/repo")
ref, _ := r.Head()
targetHash := ref.Hash()
tags, _ := r.Tags()
defer tags.Close()
fmt.Println("Tags pointing to HEAD:")
tags.ForEach(func(ref *plumbing.Reference) error {
if ref.Hash() == targetHash {
fmt.Printf(" %s\n", ref.Name().Short())
}
return nil
})
}For low-level reference manipulation, see the storer interfaces:
type ReferenceStorer interface {
SetReference(*plumbing.Reference) error
CheckAndSetReference(*plumbing.Reference, *plumbing.Reference) error
Reference(plumbing.ReferenceName) (*plumbing.Reference, error)
IterReferences() (ReferenceIter, error)
RemoveReference(plumbing.ReferenceName) error
CountLooseRefs() (int, error)
PackRefs() error
}See Storage for more details on storage operations.
var (
ErrReferenceNotFound = errors.New("reference not found")
ErrInvalidReferenceName = errors.New("invalid reference name")
ErrInvalidReference = errors.New("invalid reference")
ErrBranchExists = errors.New("branch already exists")
ErrBranchNotFound = errors.New("branch not found")
ErrTagExists = errors.New("tag already exists")
ErrTagNotFound = errors.New("tag not found")
)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")
// Get HEAD resolved to actual commit
head, _ := r.Reference(plumbing.HEAD, true)
println("HEAD commit:", head.Hash().String())
}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")
refs, _ := r.References()
defer refs.Close() // Always close
refs.ForEach(func(ref *plumbing.Reference) error {
// Process reference
return nil
})
}package main
import (
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
refName := plumbing.ReferenceName("refs/heads/my-branch")
if err := refName.Validate(); err != nil {
println("Invalid reference name:", err.Error())
}
}