grafana-backuper/pkg/git/project.go

215 lines
4.2 KiB
Go

package git
import (
"context"
"errors"
"fmt"
"io"
"strings"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/storage/memory"
)
type ProjectOption func(*Project)
func WithBasicAuth(user, pass string) ProjectOption {
return func(p *Project) {
p.auth = &http.BasicAuth{
Username: user,
Password: pass,
}
}
}
func WithBranch(branch string) ProjectOption {
return func(p *Project) {
p.Branch = branch
}
}
func WithOutputWriter(o io.Writer) ProjectOption {
return func(p *Project) {
p.writer = o
}
}
type Project struct {
Branch string
Force bool
RepoURL string
auth transport.AuthMethod
fs billy.Filesystem
storer *memory.Storage
repository *git.Repository
worktree *git.Worktree
writer io.Writer
}
func NewProject(url string, options ...ProjectOption) *Project {
project := &Project{
RepoURL: url,
fs: memfs.New(),
storer: memory.NewStorage(),
repository: nil,
}
for _, option := range options {
option(project)
}
return project
}
func (p *Project) Checkout() error {
branchRef := plumbing.NewBranchReferenceName(p.Branch)
_, err := p.repository.Reference(branchRef, true)
if errors.Is(err, plumbing.ErrReferenceNotFound) {
var headRef *plumbing.Reference
headRef, err = p.repository.Head()
if err != nil {
return err
}
ref := plumbing.NewHashReference(branchRef, headRef.Hash())
if err = p.repository.Storer.SetReference(ref); err != nil {
return err
}
} else if err != nil {
return err
}
p.worktree, err = p.repository.Worktree()
if err != nil {
return err
}
checkoutOpts := git.CheckoutOptions{
Branch: branchRef,
Create: false,
}
if err = checkoutOpts.Validate(); err != nil {
return err
}
return p.worktree.Checkout(&checkoutOpts)
}
func (p *Project) Clone(ctx context.Context) error {
cloneOpts := git.CloneOptions{
URL: p.RepoURL,
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
}
if p.auth != nil {
cloneOpts.Auth = p.auth
}
if p.writer != nil {
cloneOpts.Progress = p.writer
}
if err := cloneOpts.Validate(); err != nil {
return err
}
var err error
p.repository, err = git.CloneContext(
ctx,
p.storer,
p.fs,
&cloneOpts,
)
if err != nil {
return err
}
return nil
}
func (p *Project) CommitExists(uid string, id uint) bool {
commitIter, err := p.repository.Log(&git.LogOptions{})
if err != nil {
return false
}
err = commitIter.ForEach(func(commit *object.Commit) error {
if strings.Contains(commit.Message, fmt.Sprintf("Update %s", uid)) &&
strings.Contains(commit.Message, fmt.Sprintf("version %d", id)) {
return errors.New("version already committed")
}
return nil
})
return err != nil
}
func (p *Project) HasChanges() bool {
remoteBranchRef := plumbing.NewRemoteReferenceName("origin", p.Branch)
remoteBranch, err := p.repository.Reference(remoteBranchRef, true)
if errors.Is(err, plumbing.ErrReferenceNotFound) {
return true
} else if err != nil {
return false
}
localBranchRef, err := p.repository.Reference(plumbing.NewBranchReferenceName(p.Branch), true)
if err != nil {
return false
}
if localBranchRef.Hash() != remoteBranch.Hash() {
return true
}
return false
}
func (p *Project) Pull(ctx context.Context) error {
pullOpts := git.PullOptions{
ReferenceName: plumbing.NewBranchReferenceName(p.Branch),
}
if p.writer != nil {
pullOpts.Progress = p.writer
}
if err := pullOpts.Validate(); err != nil {
return err
}
err := p.worktree.PullContext(ctx, &pullOpts)
if !errors.Is(err, plumbing.ErrReferenceNotFound) && err != nil {
return err
}
return nil
}
func (p *Project) Push(ctx context.Context) error {
pushOpts := git.PushOptions{
RemoteName: "origin",
}
if p.auth != nil {
pushOpts.Auth = p.auth
}
if p.writer != nil {
pushOpts.Progress = p.writer
}
if err := pushOpts.Validate(); err != nil {
return err
}
return p.repository.PushContext(ctx, &pushOpts)
}