bidet / internal / core / repo.go

@ c362fc5ff73740244ceaacba31cdae3e6098ac04 | history


package core

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"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"
)

type Repo struct {
	*git.Repository
	Name string
}

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 (c *Commit) RefLabel() string {
	var label string
	if c.RefName != "" {
		label = c.RefName
	} else {
		label = c.Hash.String()
	}
	return label
}

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
}