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