2024-06-23 16:25:55 +02:00
|
|
|
package git
|
|
|
|
|
|
|
|
import (
|
2024-07-27 18:52:47 +02:00
|
|
|
"context"
|
|
|
|
"errors"
|
2024-07-27 19:16:55 +02:00
|
|
|
"io"
|
2024-07-27 18:52:47 +02:00
|
|
|
|
2024-06-23 16:25:55 +02:00
|
|
|
"github.com/go-git/go-billy/v5"
|
|
|
|
"github.com/go-git/go-billy/v5/memfs"
|
|
|
|
"github.com/go-git/go-git/v5"
|
2024-07-27 18:52:47 +02:00
|
|
|
"github.com/go-git/go-git/v5/plumbing"
|
2024-07-30 20:41:16 +02:00
|
|
|
"github.com/go-git/go-git/v5/plumbing/object"
|
2024-06-23 16:25:55 +02:00
|
|
|
"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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-27 19:16:55 +02:00
|
|
|
func WithOutputWriter(o io.Writer) ProjectOption {
|
|
|
|
return func(p *Project) {
|
|
|
|
p.writer = o
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-23 16:25:55 +02:00
|
|
|
type Project struct {
|
2024-08-19 14:10:46 +02:00
|
|
|
Branch string
|
|
|
|
Force bool
|
|
|
|
RepoURL string
|
|
|
|
CommitLogs map[string]*object.Commit
|
2024-06-23 16:25:55 +02:00
|
|
|
|
|
|
|
auth transport.AuthMethod
|
|
|
|
fs billy.Filesystem
|
|
|
|
storer *memory.Storage
|
|
|
|
repository *git.Repository
|
2024-07-27 18:52:47 +02:00
|
|
|
worktree *git.Worktree
|
2024-07-27 19:16:55 +02:00
|
|
|
writer io.Writer
|
2024-06-23 16:25:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewProject(url string, options ...ProjectOption) *Project {
|
|
|
|
project := &Project{
|
|
|
|
RepoURL: url,
|
2024-08-19 14:10:46 +02:00
|
|
|
CommitLogs: make(map[string]*object.Commit),
|
2024-06-23 16:25:55 +02:00
|
|
|
fs: memfs.New(),
|
|
|
|
storer: memory.NewStorage(),
|
|
|
|
repository: nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, option := range options {
|
|
|
|
option(project)
|
|
|
|
}
|
|
|
|
|
|
|
|
return project
|
|
|
|
}
|
|
|
|
|
2024-07-27 18:52:47 +02:00
|
|
|
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 {
|
2024-06-23 16:25:55 +02:00
|
|
|
cloneOpts := git.CloneOptions{
|
2024-07-27 18:52:47 +02:00
|
|
|
URL: p.RepoURL,
|
|
|
|
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
|
2024-06-23 16:25:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if p.auth != nil {
|
|
|
|
cloneOpts.Auth = p.auth
|
|
|
|
}
|
|
|
|
|
2024-07-27 19:16:55 +02:00
|
|
|
if p.writer != nil {
|
|
|
|
cloneOpts.Progress = p.writer
|
|
|
|
}
|
|
|
|
|
2024-07-27 18:52:47 +02:00
|
|
|
if err := cloneOpts.Validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-06-23 16:25:55 +02:00
|
|
|
var err error
|
2024-07-27 18:52:47 +02:00
|
|
|
p.repository, err = git.CloneContext(
|
|
|
|
ctx,
|
2024-06-23 16:25:55 +02:00
|
|
|
p.storer,
|
|
|
|
p.fs,
|
|
|
|
&cloneOpts,
|
|
|
|
)
|
2024-07-27 18:52:47 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-19 14:10:46 +02:00
|
|
|
func (p *Project) LoadLogs() error {
|
2024-07-30 20:41:16 +02:00
|
|
|
commitIter, err := p.repository.Log(&git.LogOptions{})
|
|
|
|
if err != nil {
|
2024-08-19 14:10:46 +02:00
|
|
|
return err
|
2024-07-30 20:41:16 +02:00
|
|
|
}
|
|
|
|
|
2024-08-19 14:10:46 +02:00
|
|
|
return commitIter.ForEach(func(c *object.Commit) error {
|
|
|
|
p.CommitLogs[c.Message] = c
|
2024-07-30 20:41:16 +02:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-08-19 14:10:46 +02:00
|
|
|
func (p *Project) CommitExists(commitmsg string) bool {
|
|
|
|
if _, ok := p.CommitLogs[commitmsg]; ok {
|
2024-07-27 18:52:47 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-08-19 14:10:46 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Project) HasChanges() bool {
|
|
|
|
localBranchRef, err := p.repository.Head()
|
2024-07-27 18:52:47 +02:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-08-19 14:10:46 +02:00
|
|
|
remoteBranchRef, err := p.repository.Reference(plumbing.NewRemoteReferenceName("origin", p.Branch), true)
|
|
|
|
if errors.Is(err, plumbing.ErrReferenceNotFound) {
|
2024-07-27 18:52:47 +02:00
|
|
|
return true
|
2024-08-19 14:10:46 +02:00
|
|
|
} else if err != nil {
|
|
|
|
return false
|
2024-07-27 18:52:47 +02:00
|
|
|
}
|
|
|
|
|
2024-08-19 14:10:46 +02:00
|
|
|
return localBranchRef.Hash() != remoteBranchRef.Hash()
|
2024-07-27 18:52:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Project) Pull(ctx context.Context) error {
|
|
|
|
pullOpts := git.PullOptions{
|
|
|
|
ReferenceName: plumbing.NewBranchReferenceName(p.Branch),
|
|
|
|
}
|
|
|
|
|
2024-07-27 19:16:55 +02:00
|
|
|
if p.writer != nil {
|
|
|
|
pullOpts.Progress = p.writer
|
|
|
|
}
|
|
|
|
|
2024-07-27 18:52:47 +02:00
|
|
|
if err := pullOpts.Validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err := p.worktree.PullContext(ctx, &pullOpts)
|
2024-08-19 14:10:46 +02:00
|
|
|
if !errors.Is(err, plumbing.ErrReferenceNotFound) &&
|
|
|
|
!errors.Is(err, git.NoErrAlreadyUpToDate) &&
|
|
|
|
err != nil {
|
2024-07-27 18:52:47 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-07-27 19:16:55 +02:00
|
|
|
if p.writer != nil {
|
|
|
|
pushOpts.Progress = p.writer
|
|
|
|
}
|
|
|
|
|
2024-07-27 18:52:47 +02:00
|
|
|
if err := pushOpts.Validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return p.repository.PushContext(ctx, &pushOpts)
|
2024-06-23 16:25:55 +02:00
|
|
|
}
|