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 }