mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-10-28 04:05:21 +08:00
426 lines
9.1 KiB
Go
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
|
|
}
|