@ e627440a1bc80b93c0b3cabede4f05680ad96ab9 | history
package core
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path"
"strings"
"github.com/dustin/go-humanize"
"github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
"github.com/go-git/go-git/v6/plumbing/object"
"github.com/go-git/go-git/v6/utils/binary"
)
const dotGit = ".git"
func List(dir string) ([]*Repo, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
var repos []*Repo
for _, e := range entries {
if e.Name() == dotGit {
continue
}
repo, err := Open(strings.TrimSuffix(e.Name(), dotGit))
if err == nil {
if _, err = repo.Worktree(); errors.Is(err, git.ErrIsBareRepository) {
repos = append(repos, repo)
}
}
}
if len(repos) == 0 {
return nil, errors.New("No repos found in " + dir)
}
return repos, nil
}
type Repo struct {
*git.Repository
Name string
}
func Open(repoName string) (*Repo, error) {
if _, err := os.Stat(repoName + dotGit); os.IsNotExist(err) {
return nil, err
}
repo, err := git.PlainOpen(repoName + dotGit)
if err != nil {
return nil, err
}
return &Repo{repo, repoName}, nil
}
func (repo *Repo) Load(entry *object.TreeEntry) (string, error) {
file, err := repo.BlobObject(entry.Hash)
if err != nil {
return "", err
}
r, err := file.Reader()
if err != nil {
return "", err
}
data, err := io.ReadAll(r)
if err != nil {
return "", err
}
defer r.Close()
binReader := bytes.NewReader(data)
isBin, err := binary.IsBinary(binReader)
if err != nil {
return "", err
}
if isBin {
return fmt.Sprintf("(%s binary file)", humanize.Bytes(uint64(file.Size))), nil
} else {
return string(data), nil
}
}
type Commit struct {
*object.Commit
Hash plumbing.Hash
RefName string
}
func (repo *Repo) ParseRoute(route string) (*Commit, string, error) {
if route == "" {
return nil, "", errors.New("empty route")
}
candidate := strings.TrimPrefix(route, "/")
for {
ref, err := repo.resolveRef(candidate)
var relPath string
if err == nil { // candidate is a tag or a branch name
if len(candidate) < len(route) {
relPath = strings.TrimPrefix(route[len(candidate):], "/")
}
refName := ref.Name().Short()
commit, _ := repo.resolveCommit(refName)
return &Commit{commit, commit.Hash, refName}, relPath, nil
}
commit, err := repo.resolveCommit(candidate)
if err == nil { // candidate is a commit hash
if len(candidate) < len(route) {
relPath = strings.TrimPrefix(route[len(candidate):], "/")
}
return &Commit{commit, commit.Hash, ""}, relPath, nil
}
parent := path.Dir(candidate)
if parent == "." || parent == candidate {
break
}
candidate = parent
}
return nil, "", errors.New("no commit found")
}
func (repo *Repo) resolveCommit(name string) (*object.Commit, error) {
hash, err := repo.ResolveRevision(plumbing.Revision(name))
if err != nil {
return nil, err
}
return repo.CommitObject(*hash)
}
func (repo *Repo) resolveRef(name string) (*plumbing.Reference, error) {
// first check for branches with the given name
branchRef := plumbing.ReferenceName(path.Join("refs", "heads", name))
ref, err := repo.Reference(branchRef, true)
if err == nil {
return ref, nil
}
// then tags
tagRef := plumbing.ReferenceName(path.Join("refs", "tags", name))
ref, err = repo.Reference(tagRef, true)
if err != nil {
return nil, err
}
obj, err := repo.Object(plumbing.AnyObject, ref.Hash())
if err != nil {
return nil, err
}
// if it's an annotated tag, extract the target
if tagObj, ok := obj.(*object.Tag); ok {
ref = plumbing.NewHashReference(ref.Name(), tagObj.Target)
}
return ref, nil
}