Files

426 lines
9.1 KiB
Go

package plg_backend_git
import (
"fmt"
. "github.com/mickael-kerjean/filestash/server/common"
"golang.org/x/crypto/ssh"
"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/transport"
"gopkg.in/src-d/go-git.v4/plumbing/transport/http"
sshgit "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
"io"
"os"
"path/filepath"
"strings"
"time"
)
var git_cache AppCache
type Git struct {
git *GitLib
}
func init() {
Backend.Register("git", Git{})
git_cache = NewAppCache()
git_cache.OnEvict(func(key string, value interface{}) {
g := value.(*Git)
g.Close()
})
}
type GitParams struct {
repo string
username string
password string
passphrase string
commit string
branch string
authorName string
authorEmail string
committerName string
committerEmail string
basePath string
}
func (git Git) Init(params map[string]string, app *App) (IBackend, error) {
if obj := git_cache.Get(params); obj != nil {
return obj.(*Git), nil
}
g := &Git{
git: &GitLib{
params: &GitParams{
params["repo"],
params["username"],
params["password"],
params["passphrase"],
params["commit"],
params["branch"],
params["authorName"],
params["authorEmail"],
params["committerName"],
params["committerEmail"],
"",
},
},
}
p := g.git.params
if p.branch == "" {
p.branch = "master"
}
if p.commit == "" {
p.commit = "{action} ({filename}): {path}"
}
if p.authorName == "" {
p.authorName = "Filestash"
}
if p.authorEmail == "" {
p.authorEmail = "https://filestash.app"
}
if p.committerName == "" {
p.committerName = "Filestash"
}
if p.committerEmail == "" {
p.committerEmail = "https://filestash.app"
}
hash := GenerateID(params)
p.basePath = GetAbsolutePath(
TMP_PATH,
"git_"+hash,
) + "/"
repo, err := g.git.open(p, p.basePath)
g.git.repo = repo
if err != nil {
return g, err
}
git_cache.Set(params, g)
return g, nil
}
func (g Git) LoginForm() Form {
return Form{
Elmnts: []FormElement{
{
Name: "type",
Value: "git",
Type: "hidden",
},
{
Name: "repo",
Type: "text",
Placeholder: "Repository*",
},
{
Name: "username",
Type: "text",
Placeholder: "Username",
},
{
Name: "password",
Type: "long_password",
Placeholder: "Password",
},
{
Name: "advanced",
Type: "enable",
Placeholder: "Advanced",
Target: []string{
"git_path", "git_passphrase", "git_commit",
"git_branch", "git_author_email", "git_author_name",
"git_committer_email", "git_committer_name",
},
},
{
Id: "git_path",
Name: "path",
Type: "text",
Placeholder: "Path",
},
{
Id: "git_passphrase",
Name: "passphrase",
Type: "text",
Placeholder: "Passphrase",
},
{
Id: "git_commit",
Name: "commit",
Type: "text",
Placeholder: "Commit Format: default to \"{action}({filename}): {path}\"",
},
{
Id: "git_branch",
Name: "branch",
Type: "text",
Placeholder: "Branch: default to \"master\"",
},
{
Id: "git_author_email",
Name: "author_email",
Type: "text",
Placeholder: "Author email",
},
{
Id: "git_author_name",
Name: "author_name",
Type: "text",
Placeholder: "Author name",
},
{
Id: "git_committer_email",
Name: "committer_email",
Type: "text",
Placeholder: "Committer email",
},
{
Id: "git_committer_name",
Name: "committer_name",
Type: "text",
Placeholder: "Committer name",
},
},
}
}
func (g Git) Ls(path string) ([]os.FileInfo, error) {
g.git.refresh()
p, err := g.path(path)
if err != nil {
return nil, NewError(err.Error(), 403)
}
file, err := SafeOsOpenFile(p, os.O_RDONLY, os.ModePerm)
if err != nil {
return nil, err
}
return file.Readdir(0)
}
func (g Git) Cat(path string) (io.ReadCloser, error) {
p, err := g.path(path)
if err != nil {
return nil, NewError(err.Error(), 403)
}
return SafeOsOpenFile(p, os.O_RDONLY, os.ModePerm)
}
func (g Git) Mkdir(path string) error {
p, err := g.path(path)
if err != nil {
return NewError(err.Error(), 403)
}
return SafeOsMkdir(p, os.ModePerm)
}
func (g Git) Rm(path string) error {
p, err := g.path(path)
if err != nil {
return NewError(err.Error(), 403)
}
if err = SafeOsRemoveAll(p); err != nil {
return NewError(err.Error(), 403)
}
message := g.git.message("delete", path)
if err = g.git.save(message); err != nil {
return NewError(err.Error(), 403)
}
return nil
}
func (g Git) Mv(from string, to string) error {
fpath, err := g.path(from)
if err != nil {
return NewError(err.Error(), 403)
}
tpath, err := g.path(to)
if err != nil {
return NewError(err.Error(), 403)
}
if err = SafeOsRename(fpath, tpath); err != nil {
return NewError(err.Error(), 403)
}
message := g.git.message("move", from)
if err = g.git.save(message); err != nil {
return NewError(err.Error(), 403)
}
return nil
}
func (g Git) Touch(path string) error {
p, err := g.path(path)
if err != nil {
return NewError(err.Error(), 403)
}
file, err := SafeOsOpenFile(p, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
return NewError(err.Error(), 403)
}
file.Close()
message := g.git.message("create", path)
if err = g.git.save(message); err != nil {
return NewError(err.Error(), 403)
}
return nil
}
func (g Git) Save(path string, file io.Reader) error {
p, err := g.path(path)
if err != nil {
return NewError(err.Error(), 403)
}
fo, err := SafeOsOpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
return err
}
io.Copy(fo, file)
fo.Close()
message := g.git.message("save", path)
if err = g.git.save(message); err != nil {
return NewError(err.Error(), 403)
}
return nil
}
func (g Git) Close() error {
return os.RemoveAll(g.git.params.basePath)
}
func (g Git) path(path string) (string, error) {
if path == "" {
return "", ErrNotValid
}
basePath := filepath.Join(g.git.params.basePath, path)
if string(path[len(path)-1]) == "/" {
basePath += "/"
}
if strings.HasPrefix(basePath, g.git.params.basePath) == false {
return "", ErrNotFound
}
return basePath, nil
}
type GitLib struct {
repo *git.Repository
params *GitParams
}
func (g *GitLib) open(params *GitParams, path string) (*git.Repository, error) {
g.params = params
if _, err := os.Stat(g.params.basePath); os.IsNotExist(err) {
auth, err := g.auth()
if err != nil {
return nil, err
}
g, err := git.PlainClone(path, false, &git.CloneOptions{
URL: g.params.repo,
Depth: 1,
ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", g.params.branch)),
SingleBranch: true,
Auth: auth,
})
if err == transport.ErrEmptyRemoteRepository {
return g, nil
}
return g, err
}
return git.PlainOpen(g.params.basePath)
}
func (g *GitLib) save(message string) error {
w, err := g.repo.Worktree()
if err != nil {
return NewError(err.Error(), 500)
}
_, err = w.Add(".")
if err != nil {
return NewError(err.Error(), 500)
}
_, err = w.Commit(message, &git.CommitOptions{
All: true,
Author: &object.Signature{
Name: g.params.authorName,
Email: g.params.authorEmail,
When: time.Now(),
},
Committer: &object.Signature{
Name: g.params.committerName,
Email: g.params.committerEmail,
When: time.Now(),
},
})
if err != nil {
return err
}
auth, err := g.auth()
if err != nil {
return err
}
return g.repo.Push(&git.PushOptions{
Auth: auth,
})
}
func (g *GitLib) refresh() error {
w, err := g.repo.Worktree()
if err != nil {
return err
}
return w.Pull(&git.PullOptions{RemoteName: "origin"})
}
func (g *GitLib) auth() (transport.AuthMethod, error) {
if strings.HasPrefix(g.params.repo, "http") {
return &http.BasicAuth{
Username: g.params.username,
Password: g.params.password,
}, nil
}
isPrivateKey := func(pass string) bool {
if len(pass) > 1000 && strings.HasPrefix(pass, "-----") {
return true
}
return false
}
if isPrivateKey(g.params.password) {
signer, err := ssh.ParsePrivateKeyWithPassphrase([]byte(g.params.password), []byte(g.params.passphrase))
if err != nil {
return nil, err
}
return &sshgit.PublicKeys{
User: "git",
Signer: signer,
HostKeyCallbackHelper: sshgit.HostKeyCallbackHelper{
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
},
}, nil
}
return &sshgit.Password{
User: g.params.username,
Password: g.params.password,
HostKeyCallbackHelper: sshgit.HostKeyCallbackHelper{
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
},
}, nil
}
func (g *GitLib) message(action string, path string) string {
message := strings.Replace(g.params.commit, "{action}", "save", -1)
message = strings.Replace(message, "{filename}", filepath.Base(path), -1)
message = strings.Replace(message, "{path}", strings.Replace(path, g.params.basePath, "", -1), -1)
return message
}