2019-11-07 10:00:24 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2020-03-20 13:58:11 +00:00
|
|
|
"github.com/go-git/go-git/v5"
|
|
|
|
"github.com/go-git/go-git/v5/plumbing/object"
|
|
|
|
gitHttp "github.com/go-git/go-git/v5/plumbing/transport/http"
|
2019-11-14 17:46:49 +00:00
|
|
|
"io/ioutil"
|
2020-03-20 13:58:11 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
2020-04-20 18:53:45 +00:00
|
|
|
"sync"
|
2020-03-20 13:58:11 +00:00
|
|
|
"time"
|
2019-11-07 10:00:24 +00:00
|
|
|
)
|
|
|
|
|
2019-12-22 20:17:11 +00:00
|
|
|
type Storage interface {
|
|
|
|
CreateFile(path string, file string, message string) (err error)
|
|
|
|
UpdateFile(path string, file string, message string) (err error)
|
2020-03-26 17:22:42 +00:00
|
|
|
DeleteFile(path string, message string) (err error)
|
2019-12-22 20:17:11 +00:00
|
|
|
}
|
|
|
|
|
2020-03-20 13:58:11 +00:00
|
|
|
type Git struct {
|
|
|
|
filepath string
|
|
|
|
url string
|
|
|
|
username string
|
|
|
|
password string
|
|
|
|
name string
|
|
|
|
email string
|
2020-04-20 18:53:45 +00:00
|
|
|
lock *sync.Mutex
|
2019-12-22 20:17:11 +00:00
|
|
|
}
|
|
|
|
|
2020-04-20 18:53:45 +00:00
|
|
|
func (g *Git) init() (r *git.Repository, w *git.Worktree, err error) {
|
2020-03-20 13:58:11 +00:00
|
|
|
// Open repo
|
|
|
|
r, err = git.PlainOpen(g.filepath)
|
|
|
|
if err == nil {
|
|
|
|
// Try to get work tree
|
|
|
|
w, err = r.Worktree()
|
|
|
|
if err == nil {
|
|
|
|
// Try to pull
|
|
|
|
err = w.Pull(&git.PullOptions{
|
|
|
|
Auth: &gitHttp.BasicAuth{
|
|
|
|
Username: g.username,
|
|
|
|
Password: g.password,
|
|
|
|
},
|
|
|
|
SingleBranch: true,
|
|
|
|
})
|
|
|
|
if err == git.NoErrAlreadyUpToDate {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
// Delete old things
|
2020-03-20 14:32:36 +00:00
|
|
|
g.destroyRepo()
|
2020-03-20 13:58:11 +00:00
|
|
|
// Clone
|
|
|
|
r, err = git.PlainClone(g.filepath, false, &git.CloneOptions{
|
|
|
|
Auth: &gitHttp.BasicAuth{
|
|
|
|
Username: g.username,
|
|
|
|
Password: g.password,
|
|
|
|
},
|
|
|
|
URL: g.url,
|
|
|
|
Depth: 1,
|
|
|
|
SingleBranch: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
err = errors.New("failed to clone repo")
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
w, err = r.Worktree()
|
|
|
|
if err != nil {
|
|
|
|
err = errors.New("failed to get work tree")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
2019-12-12 15:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-04-20 18:53:45 +00:00
|
|
|
func (g *Git) destroyRepo() {
|
2020-03-20 14:32:36 +00:00
|
|
|
_ = os.RemoveAll(g.filepath)
|
|
|
|
}
|
|
|
|
|
2020-04-20 18:53:45 +00:00
|
|
|
func (g *Git) push(repository *git.Repository) error {
|
2020-03-20 13:58:11 +00:00
|
|
|
err := repository.Push(&git.PushOptions{
|
|
|
|
Auth: &gitHttp.BasicAuth{
|
|
|
|
Username: g.username,
|
|
|
|
Password: g.password,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err == nil || err == git.NoErrAlreadyUpToDate {
|
|
|
|
return nil
|
|
|
|
} else {
|
2020-03-20 14:32:36 +00:00
|
|
|
// Destroy repo to prevent errors when trying to create same post again
|
|
|
|
g.destroyRepo()
|
2020-03-20 13:58:11 +00:00
|
|
|
return errors.New("failed to push to remote")
|
|
|
|
}
|
2019-12-12 15:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-04-20 18:53:45 +00:00
|
|
|
func (g *Git) CreateFile(filepath string, file string, message string) (err error) {
|
|
|
|
g.lock.Lock()
|
|
|
|
defer g.lock.Unlock()
|
2020-03-20 13:58:11 +00:00
|
|
|
_, _, err = g.init()
|
2019-11-07 10:00:24 +00:00
|
|
|
if err != nil {
|
2020-03-20 13:58:11 +00:00
|
|
|
err = errors.New("failed to initialize repo")
|
|
|
|
return
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
2020-03-20 13:58:11 +00:00
|
|
|
joinedPath := path.Join(g.filepath, filepath)
|
|
|
|
if _, e := os.Stat(joinedPath); e == nil {
|
|
|
|
return errors.New("file already exists")
|
|
|
|
} else {
|
2020-04-20 18:53:45 +00:00
|
|
|
return g.unsafeUpdateFile(filepath, file, message)
|
2019-11-07 10:00:24 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-14 17:46:49 +00:00
|
|
|
|
2020-04-20 18:53:45 +00:00
|
|
|
func (g *Git) UpdateFile(filepath string, file string, message string) error {
|
|
|
|
g.lock.Lock()
|
|
|
|
defer g.lock.Unlock()
|
|
|
|
return g.unsafeUpdateFile(filepath, file, message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Git) unsafeUpdateFile(filepath string, file string, message string) error {
|
2020-03-20 13:58:11 +00:00
|
|
|
repo, w, err := g.init()
|
2019-12-12 15:29:26 +00:00
|
|
|
if err != nil {
|
2020-03-20 13:58:11 +00:00
|
|
|
return errors.New("failed to initialize repo")
|
2019-12-12 15:29:26 +00:00
|
|
|
}
|
2020-03-20 13:58:11 +00:00
|
|
|
joinedPath := path.Join(g.filepath, filepath)
|
|
|
|
_ = os.MkdirAll(path.Dir(joinedPath), 0755)
|
|
|
|
err = ioutil.WriteFile(joinedPath, []byte(file), 0644)
|
2019-12-12 15:29:26 +00:00
|
|
|
if err != nil {
|
2020-03-20 13:58:11 +00:00
|
|
|
return errors.New("failed to write to file")
|
2019-12-12 15:29:26 +00:00
|
|
|
}
|
2020-04-20 19:46:59 +00:00
|
|
|
status, err := w.Status()
|
|
|
|
if err == nil && status.IsClean() {
|
|
|
|
// No file changes, prevent empty commit
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
err = nil
|
|
|
|
}
|
2020-03-20 13:58:11 +00:00
|
|
|
_, err = w.Add(filepath)
|
2019-12-12 15:29:26 +00:00
|
|
|
if err != nil {
|
2020-03-20 13:58:11 +00:00
|
|
|
return errors.New("failed to stage file")
|
|
|
|
}
|
|
|
|
if len(message) == 0 {
|
|
|
|
message = "Add " + filepath
|
2019-12-12 15:29:26 +00:00
|
|
|
}
|
2020-03-20 13:58:11 +00:00
|
|
|
_, err = w.Commit(message, &git.CommitOptions{
|
|
|
|
Author: &object.Signature{
|
|
|
|
Name: g.name,
|
|
|
|
Email: g.email,
|
|
|
|
When: time.Now(),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("failed to commit file")
|
2019-12-12 15:29:26 +00:00
|
|
|
}
|
2020-03-20 13:58:11 +00:00
|
|
|
return g.push(repo)
|
2020-03-26 17:22:42 +00:00
|
|
|
}
|
|
|
|
|
2020-04-20 18:53:45 +00:00
|
|
|
func (g *Git) DeleteFile(filepath string, message string) (err error) {
|
|
|
|
g.lock.Lock()
|
|
|
|
defer g.lock.Unlock()
|
2020-03-26 17:22:42 +00:00
|
|
|
repo, w, err := g.init()
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("failed to initialize repo")
|
|
|
|
}
|
|
|
|
joinedPath := path.Join(g.filepath, filepath)
|
|
|
|
err = os.Remove(joinedPath)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("failed to delete file")
|
|
|
|
}
|
|
|
|
_, err = w.Add(filepath)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("failed to stage deletion")
|
|
|
|
}
|
|
|
|
_, err = w.Commit(message, &git.CommitOptions{
|
|
|
|
Author: &object.Signature{
|
|
|
|
Name: g.name,
|
|
|
|
Email: g.email,
|
|
|
|
When: time.Now(),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("failed to commit deletion")
|
|
|
|
}
|
|
|
|
return g.push(repo)
|
|
|
|
}
|