or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

config.mdindex.mdobjects.mdplumbing.mdreferences.mdremote-operations.mdrepository-operations.mdstorage.mdtransport.mdworktree-operations.md
tile.json

references.mddocs/

References

This document covers working with Git references (branches, tags, HEAD) and hashes in go-git.

Overview

Git references are pointers to commits. go-git supports:

  • Direct references - Point directly to a commit hash
  • Symbolic references - Point to another reference (like HEAD)
  • Branches - refs/heads/* references
  • Tags - refs/tags/* references (lightweight)
  • Remote branches - refs/remotes/* references

Hash Type

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() string

Constants:

var ZeroHash Hash

Example:

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")
    }
}

ReferenceName Type

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() string

Constants:

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) ReferenceName

Example:

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)
}

Reference Type

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() string

Example - 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())
}

Getting References

Get HEAD

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())
    }
}

Get Specific Reference

func (r *Repository) Reference(name plumbing.ReferenceName, resolved bool) (*plumbing.Reference, error)

Parameters:

  • name - Reference name to get
  • resolved - If true, resolve symbolic references to their target

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)
    }

    // 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())
}

Listing References

Iterate All References

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()
}

Iterate Branches

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()
}

Iterate Tags

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())
    }
}

Iterate Notes

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()
}

Managing Branches

Create Branch

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)
}

Delete Branch

func (r *Repository) DeleteBranch(name string) 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)
    }

    // Delete branch
    err = r.DeleteBranch("old-feature")
    if err != nil {
        panic(err)
    }
}

Managing Tags

Create Tag

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())
}

Delete Tag

func (r *Repository) DeleteTag(name string) 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)
    }

    // Delete tag
    err = r.DeleteTag("v0.9.0")
    if err != nil {
        panic(err)
    }
}

Resolving Revisions

ResolveRevision

func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, error)

type Revision string

Resolve 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())
        }
    }
}

Common Patterns

Check if Branch Exists

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")
    }
}

Get Current Branch Name

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())
    }
}

List All Local and Remote Branches

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
    })
}

Find Tags Pointing to Commit

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
    })
}

Reference Storage Operations

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.

Common Errors

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")
)

Best Practices

1. Always Resolve Symbolic References

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())
}

2. Close Iterators

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
    })
}

3. Validate Reference Names

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())
    }
}

See Also