hugo-micropub/storage.go

178 lines
3.9 KiB
Go

package main
import (
"errors"
"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"
"io/ioutil"
"os"
"path"
"sync"
"time"
)
type Storage interface {
CreateFile(path string, file string, message string) (err error)
UpdateFile(path string, file string, message string) (err error)
DeleteFile(path string, message string) (err error)
}
type Git struct {
filepath string
url string
username string
password string
name string
email string
lock *sync.Mutex
}
func (g *Git) init() (r *git.Repository, w *git.Worktree, err error) {
// 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
g.destroyRepo()
// 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
}
func (g *Git) destroyRepo() {
_ = os.RemoveAll(g.filepath)
}
func (g *Git) push(repository *git.Repository) error {
err := repository.Push(&git.PushOptions{
Auth: &gitHttp.BasicAuth{
Username: g.username,
Password: g.password,
},
})
if err == nil || err == git.NoErrAlreadyUpToDate {
return nil
} else {
// Destroy repo to prevent errors when trying to create same post again
g.destroyRepo()
return errors.New("failed to push to remote")
}
}
func (g *Git) CreateFile(filepath string, file string, message string) (err error) {
g.lock.Lock()
defer g.lock.Unlock()
_, _, err = g.init()
if err != nil {
err = errors.New("failed to initialize repo")
return
}
joinedPath := path.Join(g.filepath, filepath)
if _, e := os.Stat(joinedPath); e == nil {
return errors.New("file already exists")
} else {
return g.unsafeUpdateFile(filepath, file, message)
}
}
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 {
repo, w, err := g.init()
if err != nil {
return errors.New("failed to initialize repo")
}
joinedPath := path.Join(g.filepath, filepath)
_ = os.MkdirAll(path.Dir(joinedPath), 0755)
err = ioutil.WriteFile(joinedPath, []byte(file), 0644)
if err != nil {
return errors.New("failed to write to file")
}
_, err = w.Add(filepath)
if err != nil {
return errors.New("failed to stage file")
}
if len(message) == 0 {
message = "Add " + filepath
}
_, 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")
}
return g.push(repo)
}
func (g *Git) DeleteFile(filepath string, message string) (err error) {
g.lock.Lock()
defer g.lock.Unlock()
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)
}