package gitx
import (
"context"
"sort"
"time"
"github.com/go-git/go-git/v5/plumbing"
)
// Ref is a named git reference paired with the object id it points at (the tag
// object for annotated tags, matching git for-each-ref's %(objectname)).
type Ref struct {
Name string
SHA string
}
// Refs lists the repository's branches and tags. Branches are returned with the
// default (HEAD) branch first and the remainder alphabetical; tags are ordered
// newest-first by creator date (tagger date for annotated tags, committer date
// for lightweight tags).
func (r *Repo) Refs(ctx context.Context) (branches, tags []Ref, err error) {
_, cancel := r.withTimeout(ctx)
defer cancel()
bIter, err := r.repo.Branches()
if err != nil {
return nil, nil, err
}
err = bIter.ForEach(func(ref *plumbing.Reference) error {
branches = append(branches, Ref{Name: ref.Name().Short(), SHA: ref.Hash().String()})
return nil
})
if err != nil {
return nil, nil, err
}
def, _ := r.DefaultBranch(ctx)
sortBranches(branches, def)
type tagRef struct {
ref Ref
when time.Time
}
var trefs []tagRef
tIter, err := r.repo.Tags()
if err != nil {
return nil, nil, err
}
err = tIter.ForEach(func(ref *plumbing.Reference) error {
trefs = append(trefs, tagRef{
ref: Ref{Name: ref.Name().Short(), SHA: ref.Hash().String()},
when: r.tagWhen(ref.Hash()),
})
return nil
})
if err != nil {
return nil, nil, err
}
sort.SliceStable(trefs, func(i, j int) bool {
if !trefs[i].when.Equal(trefs[j].when) {
return trefs[i].when.After(trefs[j].when)
}
return trefs[i].ref.Name > trefs[j].ref.Name
})
for _, t := range trefs {
tags = append(tags, t.ref)
}
return branches, tags, nil
}
// DefaultBranch returns the short name of the branch HEAD points at (e.g.
// "main"). It fails if HEAD is detached (not a symbolic reference).
func (r *Repo) DefaultBranch(ctx context.Context) (string, error) {
_, cancel := r.withTimeout(ctx)
defer cancel()
ref, err := r.repo.Reference(plumbing.HEAD, false)
if err != nil {
return "", err
}
if ref.Type() != plumbing.SymbolicReference {
return "", plumbing.ErrReferenceNotFound
}
return ref.Target().Short(), nil
}
// tagWhen returns the creation time of a tag reference: the tagger time for an
// annotated tag, else the committer time of the pointed-at commit. A zero time
// is returned when neither can be resolved.
func (r *Repo) tagWhen(h plumbing.Hash) time.Time {
if t, err := r.repo.TagObject(h); err == nil {
return t.Tagger.When
}
if c, err := r.repo.CommitObject(h); err == nil {
return c.Committer.When
}
return time.Time{}
}
// sortBranches orders refs alphabetically but floats the default branch to the
// front.
func sortBranches(refs []Ref, defaultBranch string) {
sort.SliceStable(refs, func(i, j int) bool {
if refs[i].Name == defaultBranch {
return refs[j].Name != defaultBranch
}
if refs[j].Name == defaultBranch {
return false
}
return refs[i].Name < refs[j].Name
})
}