280 lines
6.1 KiB
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
|
|
}
|