src.dualinventive.com/go/devsim/repository/git/manager.go

280 lines
6.1 KiB
Go

package git
import (
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"sync"
git "gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"gopkg.in/src-d/go-git.v4/plumbing/storer"
"gopkg.in/src-d/go-git.v4/plumbing/transport"
"src.dualinventive.com/go/devsim"
"src.dualinventive.com/go/devsim/repository"
)
// manager contains helper functions to git
type manager struct {
mx sync.Mutex
uri string
path string
authMethod transport.AuthMethod
repository *git.Repository
}
// newManager creates a new manager. The uri is the location of the remote repository. The path is the location to
// clone the repository in. AuthMethod is the authorization that is used for GIT.
func newManager(uri string, path string, authMethod transport.AuthMethod) (*manager, error) {
m := manager{
uri: uri,
path: path,
authMethod: authMethod,
}
// return the real repository path
repPath := m.repositoryPath()
var r *git.Repository
var err error
// Path exist. Try to open the repository
if _, err = os.Stat(repPath); err == nil {
r, err = git.PlainOpen(repPath)
} else {
r, err = git.PlainClone(repPath, false, &git.CloneOptions{
URL: uri,
Auth: authMethod,
Tags: git.AllTags,
})
}
// The repository was not cloned or the current repository was not an GIT repository.
if err != nil {
fmt.Println("clone err", err)
return nil, err
}
fmt.Println("clone ok")
m.repository = r
return &m, nil
}
// hashPath returns the location of the commit hash template
func (m *manager) hashPath(hash plumbing.Hash) string {
return path.Join(m.path, hash.String())
}
// repositoryPath returns the location of the GIT repository
func (m *manager) repositoryPath() string {
return path.Join(m.path, repositoryDirName)
}
// clone clones the commit hash into the right folder
func (m *manager) clone(hash plumbing.Hash) error {
dest := m.hashPath(hash)
// Path exist. No need for clone
if _, err := os.Stat(dest); err == nil {
return nil
}
m.mx.Lock()
defer m.mx.Unlock()
// Checkout the hash and copy the content to location
wtree, err := m.repository.Worktree()
if err != nil {
return err
}
err = wtree.Checkout(&git.CheckoutOptions{
Force: true,
Hash: hash,
})
if err != nil {
return err
}
src := m.repositoryPath()
info, err := os.Stat(src)
if err != nil {
return err
}
return gitcopy(src, dest, info)
}
// versions return the known versions
func (m *manager) versions() ([]string, error) {
var tmp []string
itr, err := m.repository.TagObjects()
if err != nil {
return nil, err
}
err = itr.ForEach(func(tag *object.Tag) error {
tmp = append(tmp, tag.Name)
return nil
})
// TODO add branches
if err != nil {
return nil, err
}
return tmp, nil
}
// key returns the key based on the given version. When the version is not found ErrNotFound is returned.
func (m *manager) key(version string) (string, error) {
itr, err := m.repository.TagObjects()
if err != nil {
return "", err
}
var key string
err = itr.ForEach(func(tag *object.Tag) error {
if tag.Name == version {
key = tag.Target.String()
return storer.ErrStop
}
return nil
})
if err != nil {
return "", err
}
if key == "" {
return "", devsim.ErrNotFound
}
return key, nil
}
// template returns the template based on the given key. When the key is not found ErrNotFound is returned.
func (m *manager) template(key string) (repository.Template, error) {
itr, err := m.repository.TagObjects()
if err != nil {
return nil, err
}
var t *Template
err = itr.ForEach(func(tag *object.Tag) error {
if tag.Target.String() == key {
t = &Template{
author: tag.Tagger.Name,
authorEmail: tag.Tagger.Email,
key: tag.Target,
name: tag.Name,
time: tag.Tagger.When,
tagHash: &tag.Hash,
manager: m,
}
return storer.ErrStop
}
return nil
})
if err != nil {
return nil, err
}
if t != nil {
return t, nil
}
// Tag is not found. Try based on the commit
c, err := m.repository.CommitObject(plumbing.NewHash(key))
if err == plumbing.ErrObjectNotFound {
return nil, devsim.ErrNotFound
}
if err != nil {
return nil, err
}
return &Template{
author: c.Author.Name,
authorEmail: c.Author.Email,
key: c.Hash,
name: key,
time: c.Author.When,
tagHash: nil,
manager: m,
}, nil
}
// fetch fetches the new tags (versions)
func (m *manager) fetch() error {
err := m.repository.Fetch(&git.FetchOptions{
Force: true,
Tags: git.AllTags,
Auth: m.authMethod,
})
// NoErrAlreadyUpToDate is not an error so ignore that one.
if err != nil && err != git.NoErrAlreadyUpToDate {
return err
}
return nil
}
// versionByTagHash returns the version using the tag hash.
// When the given hash is still a tag object the hash version is
// returned else ErrNotFound is returned
func (m *manager) versionByTagHash(hash plumbing.Hash) (string, error) {
t, err := m.repository.TagObject(hash)
if err != nil {
return "", devsim.ErrNotFound
}
return t.Name, nil
}
// gitcopy copies the files and directories
func gitcopy(src, dest string, info os.FileInfo) error {
if info.IsDir() {
return gitdcopy(src, dest, info)
}
return gitfcopy(src, dest, info)
}
// gitfcopy copies the file
func gitfcopy(src, dest string, info os.FileInfo) error {
f, err := os.Create(dest)
if err != nil {
return err
}
defer f.Close()
if err = os.Chmod(f.Name(), info.Mode()); err != nil {
return err
}
// #nosec as we allow git file open from arbitrary locations
s, err := os.Open(src)
if err != nil {
return err
}
defer s.Close()
_, err = io.Copy(f, s)
return err
}
// gitdcopy copies the directory. The git folder is ignored.
func gitdcopy(src, dest string, info os.FileInfo) error {
// Ignore the git folder
if info.Name() == ".git" {
return nil
}
if err := os.MkdirAll(dest, info.Mode()); err != nil {
return err
}
infos, err := ioutil.ReadDir(src)
if err != nil {
return err
}
for _, info := range infos {
if err := gitcopy(
filepath.Join(src, info.Name()),
filepath.Join(dest, info.Name()),
info,
); err != nil {
return err
}
}
return nil
}