refactor(git): split / improve clone function

This commit is contained in:
Tom Neuber 2024-07-27 18:52:47 +02:00
parent 55c59e0164
commit 4aad919153
Signed by: tom
GPG key ID: F17EFE4272D89FF6
3 changed files with 189 additions and 88 deletions

View file

@ -2,15 +2,13 @@ package git
import ( import (
"errors" "errors"
"os" "fmt"
"path/filepath"
"strings"
"time" "time"
"github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"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/object"
) )
@ -36,10 +34,16 @@ func WithCommitter(name, email string) CommitOption {
} }
} }
func WithFileContent(content []byte, file billy.File) CommitOption { func WithFileContent(content []byte, filename, folder string) CommitOption {
return func(c *Commit) { return func(c *Commit) {
c.Content = content c.Content = content
c.File = file c.Filename = filepath.Join(folder, fmt.Sprintf("%s.json", filename))
}
}
func WithSigner(signKey SignKey) CommitOption {
return func(c *Commit) {
c.signKey = signKey.entity
} }
} }
@ -47,9 +51,11 @@ type Commit struct {
Author *object.Signature Author *object.Signature
Committer *object.Signature Committer *object.Signature
Content []byte Content []byte
File billy.File Filename string
KeyFile string
project *Project project *Project
signKey *openpgp.Entity
} }
func (p *Project) NewCommit(options ...CommitOption) *Commit { func (p *Project) NewCommit(options ...CommitOption) *Commit {
@ -63,64 +69,11 @@ func (p *Project) NewCommit(options ...CommitOption) *Commit {
} }
func (c *Commit) Create(msg string) error { func (c *Commit) Create(msg string) error {
var ( if err := c.addContent(); err != nil {
signer *openpgp.Entity
)
if c.project.KeyFile != "" {
file, err := os.Open(c.project.KeyFile)
if err != nil {
return err
}
defer file.Close()
block, err := armor.Decode(file)
if err != nil {
return err return err
} }
entityList, err := openpgp.ReadKeyRing(block.Body) if _, err := c.project.worktree.Add(c.Filename); err != nil {
if err != nil || len(entityList) < 1 {
return err
}
signer = entityList[0]
}
worktree, err := c.project.repository.Worktree()
if err != nil {
return err
}
_, err = c.project.repository.Branch(c.project.Branch)
if errors.Is(err, git.ErrBranchNotFound) {
err = c.project.repository.CreateBranch(
&config.Branch{Name: c.project.Branch, Remote: c.project.Branch},
)
if err != nil {
return err
}
}
if err != nil {
return err
}
err = worktree.Checkout(&git.CheckoutOptions{
Branch: plumbing.ReferenceName(c.project.Branch),
})
if err != nil {
return err
}
if _, err = c.File.Write(c.Content); err != nil {
return err
}
if err = c.File.Close(); err != nil {
return err
}
if _, err = worktree.Add(c.File.Name()); err != nil {
return err return err
} }
@ -130,25 +83,41 @@ func (c *Commit) Create(msg string) error {
commitOpts.Committer = c.Committer commitOpts.Committer = c.Committer
} }
if signer != nil { if c.signKey != nil {
commitOpts.SignKey = signer commitOpts.SignKey = c.signKey
} }
_, err = worktree.Commit(msg, &commitOpts) _, err := c.project.worktree.Commit(msg, &commitOpts)
return err
}
func (c *Commit) Push() error {
origin, err := c.project.repository.Remote("origin")
if err != nil { if err != nil {
return err return err
} }
pushOpts := git.PushOptions{} return nil
if c.project.auth != nil {
pushOpts.Auth = c.project.auth
} }
return origin.Push(&pushOpts) func (c *Commit) Exists(uid string, id uint) bool {
commitIter, err := c.project.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 (c *Commit) addContent() error {
file, err := c.project.worktree.Filesystem.Create(c.Filename)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(c.Content)
return err
} }

View file

@ -1,9 +1,13 @@
package git package git
import ( import (
"context"
"errors"
"github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport" "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/plumbing/transport/http"
"github.com/go-git/go-git/v5/storage/memory" "github.com/go-git/go-git/v5/storage/memory"
@ -26,28 +30,21 @@ func WithBranch(branch string) ProjectOption {
} }
} }
func WithKey(path string) ProjectOption {
return func(p *Project) {
p.KeyFile = path
}
}
type Project struct { type Project struct {
Branch string Branch string
Force bool
RepoURL string RepoURL string
KeyFile string
auth transport.AuthMethod auth transport.AuthMethod
fs billy.Filesystem fs billy.Filesystem
storer *memory.Storage storer *memory.Storage
repository *git.Repository repository *git.Repository
worktree *git.Worktree
} }
func NewProject(url string, options ...ProjectOption) *Project { func NewProject(url string, options ...ProjectOption) *Project {
project := &Project{ project := &Project{
Branch: "",
RepoURL: url, RepoURL: url,
KeyFile: "",
fs: memfs.New(), fs: memfs.New(),
storer: memory.NewStorage(), storer: memory.NewStorage(),
repository: nil, repository: nil,
@ -60,20 +57,120 @@ func NewProject(url string, options ...ProjectOption) *Project {
return project return project
} }
func (p *Project) Clone() error { 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{ cloneOpts := git.CloneOptions{
URL: p.RepoURL, URL: p.RepoURL,
RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
} }
if p.auth != nil { if p.auth != nil {
cloneOpts.Auth = p.auth cloneOpts.Auth = p.auth
} }
if err := cloneOpts.Validate(); err != nil {
return err
}
var err error var err error
p.repository, err = git.Clone( p.repository, err = git.CloneContext(
ctx,
p.storer, p.storer,
p.fs, p.fs,
&cloneOpts, &cloneOpts,
) )
if err != nil {
return err return err
} }
return 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 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 err := pushOpts.Validate(); err != nil {
return err
}
return p.repository.PushContext(ctx, &pushOpts)
}

35
pkg/git/sign_key.go Normal file
View file

@ -0,0 +1,35 @@
package git
import (
"os"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
)
type SignKey struct {
KeyFile string
entity *openpgp.Entity
}
func (s *SignKey) ReadKeyFile() error {
file, err := os.Open(s.KeyFile)
if err != nil {
return err
}
defer file.Close()
block, err := armor.Decode(file)
if err != nil {
return err
}
entityList, err := openpgp.ReadKeyRing(block.Body)
if err != nil || len(entityList) < 1 {
return err
}
s.entity = entityList[0]
return nil
}