Refactor entire project #16
3 changed files with 189 additions and 88 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
35
pkg/git/sign_key.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue