From 73be67a0fda57ebb915a5e56a9583018c79c3923 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Wed, 21 Sep 2022 21:29:44 +0200 Subject: [PATCH] with moved commands --- cmd/kraken/commands/root.go | 14 - cmd/kraken/kraken.go | 18 - cmd/octopush/commands/process.go | 51 +++ cmd/octopush/commands/root.go | 18 + .../commands/server}/process.go | 2 +- cmd/octopush/commands/server/server.go | 16 + cmd/octopush/octopush.go | 28 ++ internal/actions/action_creator.go | 4 +- internal/cli/cli.go | 26 ++ internal/commands/process_repos.go | 8 +- internal/serverdeps/server_deps.go | 2 +- internal/services/providers/git.go | 338 ----------------- internal/services/providers/gogit.go | 339 ++++++++++++++++++ 13 files changed, 486 insertions(+), 378 deletions(-) delete mode 100644 cmd/kraken/commands/root.go delete mode 100644 cmd/kraken/kraken.go create mode 100644 cmd/octopush/commands/process.go create mode 100644 cmd/octopush/commands/root.go rename cmd/{kraken/commands => octopush/commands/server}/process.go (98%) create mode 100644 cmd/octopush/commands/server/server.go create mode 100644 cmd/octopush/octopush.go create mode 100644 internal/cli/cli.go create mode 100644 internal/services/providers/gogit.go diff --git a/cmd/kraken/commands/root.go b/cmd/kraken/commands/root.go deleted file mode 100644 index 69aee00..0000000 --- a/cmd/kraken/commands/root.go +++ /dev/null @@ -1,14 +0,0 @@ -package commands - -import "github.com/spf13/cobra" - -func CreateOctopushCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "octopush", - // Run: func(cmd *cobra.Command, args []string) { }, - } - - cmd.AddCommand(CreateOctopushProcessCmd()) - - return cmd -} diff --git a/cmd/kraken/kraken.go b/cmd/kraken/kraken.go deleted file mode 100644 index 494b764..0000000 --- a/cmd/kraken/kraken.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "os" - - "git.front.kjuulh.io/kjuulh/octopush/cmd/octopush/commands" -) - -func main() { - Execute() -} - -func Execute() { - err := commands.CreateOctopushCmd().Execute() - if err != nil { - os.Exit(1) - } -} diff --git a/cmd/octopush/commands/process.go b/cmd/octopush/commands/process.go new file mode 100644 index 0000000..35ad362 --- /dev/null +++ b/cmd/octopush/commands/process.go @@ -0,0 +1,51 @@ +package commands + +import ( + "git.front.kjuulh.io/kjuulh/octopush/internal/cli" + "git.front.kjuulh.io/kjuulh/octopush/internal/commands" + "github.com/spf13/cobra" + "go.uber.org/zap" +) + +func CreateOctopushProcessCmd(logger *zap.Logger) *cobra.Command { + + var ( + actionsRepo string + branch string + path string + ) + cmd := &cobra.Command{ + Use: "process", + RunE: func(cmd *cobra.Command, args []string) error { + if err := cmd.ParseFlags(args); err != nil { + return err + } + + ctx := cmd.Context() + + deps, err := cli.Start(ctx, logger) + if err != nil { + return err + } + + err = commands. + NewProcessRepos(logger, deps). + Process(ctx, actionsRepo, branch, path) + if err != nil { + return err + } + + return nil + }, + } + + pf := cmd.PersistentFlags() + + pf.StringVar(&actionsRepo, "actions-repo", "", "actions repo is the location of your actions, not where to apply the actions themselves, that should be self contained") + cmd.MarkPersistentFlagRequired("actions-repo") + pf.StringVar(&branch, "branch", "main", "which branch to look for actions in, will default to main") + pf.StringVar(&path, "path", "", "the location of the path inside the repository") + cmd.MarkPersistentFlagRequired("path") + + return cmd +} diff --git a/cmd/octopush/commands/root.go b/cmd/octopush/commands/root.go new file mode 100644 index 0000000..3a6654f --- /dev/null +++ b/cmd/octopush/commands/root.go @@ -0,0 +1,18 @@ +package commands + +import ( + "git.front.kjuulh.io/kjuulh/octopush/cmd/octopush/commands/server" + "github.com/spf13/cobra" + "go.uber.org/zap" +) + +func CreateOctopushCmd(logger *zap.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "octopush", + } + + cmd.AddCommand(CreateOctopushProcessCmd(logger)) + cmd.AddCommand(server.CreateOctopushServerCmd(logger)) + + return cmd +} diff --git a/cmd/kraken/commands/process.go b/cmd/octopush/commands/server/process.go similarity index 98% rename from cmd/kraken/commands/process.go rename to cmd/octopush/commands/server/process.go index 22ed98e..04f2041 100644 --- a/cmd/kraken/commands/process.go +++ b/cmd/octopush/commands/server/process.go @@ -1,4 +1,4 @@ -package commands +package server import ( "bytes" diff --git a/cmd/octopush/commands/server/server.go b/cmd/octopush/commands/server/server.go new file mode 100644 index 0000000..90287a6 --- /dev/null +++ b/cmd/octopush/commands/server/server.go @@ -0,0 +1,16 @@ +package server + +import ( + "github.com/spf13/cobra" + "go.uber.org/zap" +) + +func CreateOctopushServerCmd(logger *zap.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "server", + } + + cmd.AddCommand(CreateOctopushProcessCmd()) + + return cmd +} diff --git a/cmd/octopush/octopush.go b/cmd/octopush/octopush.go new file mode 100644 index 0000000..f75223c --- /dev/null +++ b/cmd/octopush/octopush.go @@ -0,0 +1,28 @@ +package main + +import ( + "os" + + "git.front.kjuulh.io/kjuulh/octopush/cmd/octopush/commands" + "git.front.kjuulh.io/kjuulh/octopush/internal/logger" + "go.uber.org/zap" +) + +func main() { + logger, err := logger.New() + if err != nil { + panic(err) + } + _ = logger.Sync() + + zap.ReplaceGlobals(logger) + + Execute(logger) +} + +func Execute(logger *zap.Logger) { + err := commands.CreateOctopushCmd(logger).Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/internal/actions/action_creator.go b/internal/actions/action_creator.go index 80b71e0..7fe4ef0 100644 --- a/internal/actions/action_creator.go +++ b/internal/actions/action_creator.go @@ -23,12 +23,12 @@ type ( ActionCreator struct { logger *zap.Logger storage *storage.Service - git *providers.Git + git *providers.GoGit } ActionCreatorDeps interface { GetStorageService() *storage.Service - GetGitProvider() *providers.Git + GetGitProvider() *providers.GoGit } ) diff --git a/internal/cli/cli.go b/internal/cli/cli.go new file mode 100644 index 0000000..009bd93 --- /dev/null +++ b/internal/cli/cli.go @@ -0,0 +1,26 @@ +package cli + +import ( + "context" + + "git.front.kjuulh.io/kjuulh/curre" + "git.front.kjuulh.io/kjuulh/octopush/internal/server" + "git.front.kjuulh.io/kjuulh/octopush/internal/serverdeps" + "git.front.kjuulh.io/kjuulh/octopush/internal/services/signer" + "go.uber.org/zap" +) + +func Start(ctx context.Context, logger *zap.Logger) (*serverdeps.ServerDeps, error) { + deps := serverdeps.NewServerDeps(logger) + + err := curre.NewManager(). + Register( + server.NewStorageServer(logger.With(zap.Namespace("storage")), deps), + ). + Register( + signer.NewOpenPGPApp(deps.GetOpenPGP()), + ). + RunNonBlocking(ctx) + + return deps, err +} diff --git a/internal/commands/process_repos.go b/internal/commands/process_repos.go index 6ae32c1..fdf949b 100644 --- a/internal/commands/process_repos.go +++ b/internal/commands/process_repos.go @@ -20,14 +20,14 @@ type ( ProcessRepos struct { logger *zap.Logger storage *storage.Service - git *providers.Git + git *providers.GoGit actionCreator *actions.ActionCreator gitea *gitproviders.Gitea } ProcessReposDeps interface { GetStorageService() *storage.Service - GetGitProvider() *providers.Git + GetGitProvider() *providers.GoGit GetActionCreator() *actions.ActionCreator GetGitea() *gitproviders.Gitea } @@ -161,7 +161,7 @@ func (pr *ProcessRepos) prepareAction( return cleanupfunc, area, nil } -func (pr *ProcessRepos) clone(ctx context.Context, area *storage.Area, repoUrl string) (*providers.GitRepo, error) { +func (pr *ProcessRepos) clone(ctx context.Context, area *storage.Area, repoUrl string) (*providers.GoGitRepo, error) { pr.logger.Debug("Cloning repo", zap.String("path", area.Path), zap.String("repoUrl", repoUrl)) cloneCtx, _ := context.WithTimeout(ctx, time.Second*5) repo, err := pr.git.Clone(cloneCtx, area, repoUrl) @@ -177,7 +177,7 @@ func (pr *ProcessRepos) clone(ctx context.Context, area *storage.Area, repoUrl s return repo, nil } -func (pr *ProcessRepos) commit(ctx context.Context, area *storage.Area, repo *providers.GitRepo, repoUrl string) error { +func (pr *ProcessRepos) commit(ctx context.Context, area *storage.Area, repo *providers.GoGitRepo, repoUrl string) error { wt, err := pr.git.Add(ctx, area, repo) if err != nil { return fmt.Errorf("could not add file: %w", err) diff --git a/internal/serverdeps/server_deps.go b/internal/serverdeps/server_deps.go index a712e45..47e446f 100644 --- a/internal/serverdeps/server_deps.go +++ b/internal/serverdeps/server_deps.go @@ -53,7 +53,7 @@ func (deps *ServerDeps) GetStorageService() *storage.Service { return storage.NewService(deps.logger.With(zap.Namespace("storage")), deps.storageConfig) } -func (deps *ServerDeps) GetGitProvider() *providers.Git { +func (deps *ServerDeps) GetGitProvider() *providers.GoGit { return providers.NewGit(deps.logger.With(zap.Namespace("gitProvider")), deps.gitCfg, deps.openPGP) } diff --git a/internal/services/providers/git.go b/internal/services/providers/git.go index 27253de..cf2a3ba 100644 --- a/internal/services/providers/git.go +++ b/internal/services/providers/git.go @@ -1,339 +1 @@ package providers - -import ( - "context" - "errors" - "fmt" - "time" - - "git.front.kjuulh.io/kjuulh/octopush/internal/services/signer" - "git.front.kjuulh.io/kjuulh/octopush/internal/services/storage" - "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/transport" - "github.com/go-git/go-git/v5/plumbing/transport/http" - "github.com/go-git/go-git/v5/plumbing/transport/ssh" - "go.uber.org/zap" - "go.uber.org/zap/zapio" -) - -// Git is a native git provider, it can clone, pull -// , push and as in abstraction on native git operations -type Git struct { - logger *zap.Logger - gitConfig *GitConfig - openPGP *signer.OpenPGP -} - -type GitRepo struct { - repo *git.Repository -} - -func (gr *GitRepo) GetHEAD() (string, error) { - head, err := gr.repo.Head() - if err != nil { - return "", err - } - - return head.Name().Short(), nil -} - -type GitAuth string - -const ( - GIT_AUTH_SSH GitAuth = "ssh" - GIT_AUTH_USERNAME_PASSWORD GitAuth = "username_password" - GIT_AUTH_ACCESS_TOKEN GitAuth = "access_token" - GIT_AUTH_ANONYMOUS GitAuth = "anonymous" - GIT_AUTH_SSH_AGENT GitAuth = "ssh_agent" -) - -type GitConfig struct { - AuthOption GitAuth - User string - Password string - AccessToken string - SshPublicKeyFilePath string - SshPrivateKeyPassword string -} - -func NewGit(logger *zap.Logger, gitConfig *GitConfig, openPGP *signer.OpenPGP) *Git { - return &Git{logger: logger, gitConfig: gitConfig, openPGP: openPGP} -} - -func (g *Git) GetOriginHEADForRepo(ctx context.Context, gitRepo *GitRepo) (string, error) { - auth, err := g.GetAuth() - if err != nil { - return "", err - } - - remote, err := gitRepo.repo.Remote("origin") - if err != nil { - return "", err - } - - refs, err := remote.ListContext(ctx, &git.ListOptions{ - Auth: auth, - }) - if err != nil { - return "", err - } - - headRef := "" - for _, ref := range refs { - //g.logger.Debug(ref.String()) - if !ref.Name().IsBranch() { - headRef = ref.Target().Short() - } - } - - if headRef == "" { - return "", errors.New("no upstream HEAD branch could be found") - } - - return headRef, nil -} - -func (g *Git) CloneBranch(ctx context.Context, storageArea *storage.Area, repoUrl string, branch string) (*GitRepo, error) { - g.logger.Debug( - "cloning repository", - zap.String("repoUrl", repoUrl), - zap.String("path", storageArea.Path), - ) - - auth, err := g.GetAuth() - if err != nil { - return nil, err - } - - cloneOptions := git.CloneOptions{ - URL: repoUrl, - Auth: auth, - RemoteName: "origin", - ReferenceName: plumbing.NewBranchReferenceName(branch), - SingleBranch: false, - NoCheckout: false, - Depth: 1, - RecurseSubmodules: 1, - Progress: g.getProgressWriter(), - Tags: 0, - InsecureSkipTLS: false, - CABundle: []byte{}, - } - - repo, err := git.PlainCloneContext(ctx, storageArea.Path, false, &cloneOptions) - if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { - return nil, err - } - - g.logger.Debug("done cloning repo") - - return &GitRepo{repo: repo}, nil -} - -func (g *Git) Clone(ctx context.Context, storageArea *storage.Area, repoUrl string) (*GitRepo, error) { - g.logger.Debug( - "cloning repository", - zap.String("repoUrl", repoUrl), - zap.String("path", storageArea.Path), - ) - - auth, err := g.GetAuth() - if err != nil { - return nil, err - } - - cloneOptions := git.CloneOptions{ - URL: repoUrl, - Auth: auth, - RemoteName: "origin", - ReferenceName: "refs/heads/main", - SingleBranch: false, - NoCheckout: false, - Depth: 1, - RecurseSubmodules: 1, - Progress: g.getProgressWriter(), - Tags: 0, - InsecureSkipTLS: false, - CABundle: []byte{}, - } - - repo, err := git.PlainCloneContext(ctx, storageArea.Path, false, &cloneOptions) - if err != nil { - return nil, err - } - - g.logger.Debug("done cloning repo") - - return &GitRepo{repo: repo}, nil -} - -func (g *Git) getProgressWriter() *zapio.Writer { - return &zapio.Writer{ - Log: g.logger.With(zap.String("process", "go-git")), - Level: zap.DebugLevel, - } -} - -func (g *Git) Add(ctx context.Context, storageArea *storage.Area, gitRepo *GitRepo) (*git.Worktree, error) { - worktree, err := gitRepo.repo.Worktree() - if err != nil { - return nil, err - } - - err = worktree.AddWithOptions(&git.AddOptions{ - All: true, - }) - if err != nil { - return nil, err - } - - status, err := worktree.Status() - if err != nil { - return nil, err - } - - g.logger.Debug("git status", zap.String("status", status.String())) - - return worktree, nil -} - -func (g *Git) CreateBranch(ctx context.Context, gitRepo *GitRepo) error { - worktree, err := gitRepo.repo.Worktree() - if err != nil { - return err - } - - refSpec := plumbing.NewBranchReferenceName("octopush-apply") - err = gitRepo.repo.CreateBranch(&config.Branch{ - Name: "octopush-apply", - Remote: "origin", - Merge: refSpec, - Rebase: "", - }) - if err != nil { - return fmt.Errorf("could not create branch: %w", err) - } - - err = worktree.Checkout(&git.CheckoutOptions{ - Branch: plumbing.ReferenceName(refSpec.String()), - Create: true, - Force: false, - Keep: false, - }) - if err != nil { - return fmt.Errorf("could not checkout branch: %w", err) - } - - remoteRef := plumbing.NewRemoteReferenceName("origin", "octopush-apply") - ref := plumbing.NewSymbolicReference(refSpec, remoteRef) - err = gitRepo.repo.Storer.SetReference(ref) - if err != nil { - return fmt.Errorf("could not set reference: %w", err) - } - - auth, err := g.GetAuth() - if err != nil { - return err - } - - err = worktree.PullContext(ctx, &git.PullOptions{ - RemoteName: "origin", - ReferenceName: "refs/heads/main", - SingleBranch: false, - Depth: 1, - Auth: auth, - RecurseSubmodules: 1, - Progress: g.getProgressWriter(), - Force: true, - InsecureSkipTLS: false, - CABundle: []byte{}, - }) - if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { - return fmt.Errorf("could not pull from origin: %w", err) - } - - g.logger.Debug("done creating branches") - - return nil -} - -func (g *Git) Commit(ctx context.Context, gitRepo *GitRepo) error { - worktree, err := gitRepo.repo.Worktree() - if err != nil { - return err - } - - _, err = worktree.Commit("some-commit", &git.CommitOptions{ - All: true, - Author: &object.Signature{Name: "octopush", Email: "octopush@kasperhermansen.com", When: time.Now()}, - Committer: &object.Signature{Name: "octopush", Email: "octopush@kasperhermansen.com", When: time.Now()}, - SignKey: g.openPGP.SigningKey, - }) - if err != nil { - return err - } - - g.logger.Debug("done commiting objects") - - return nil -} - -func (g *Git) Push(ctx context.Context, gitRepo *GitRepo) error { - auth, err := g.GetAuth() - if err != nil { - return err - } - - err = gitRepo.repo.PushContext(ctx, &git.PushOptions{ - RemoteName: "origin", - RefSpecs: []config.RefSpec{}, - Auth: auth, - Progress: g.getProgressWriter(), - Prune: false, - Force: true, - InsecureSkipTLS: false, - CABundle: []byte{}, - RequireRemoteRefs: []config.RefSpec{}, - }) - if err != nil { - return err - } - - g.logger.Debug("done pushing branch") - - return nil -} - -func (g *Git) GetAuth() (transport.AuthMethod, error) { - switch g.gitConfig.AuthOption { - case GIT_AUTH_SSH: - sshKey, err := ssh.NewPublicKeysFromFile( - g.gitConfig.User, - g.gitConfig.SshPublicKeyFilePath, - g.gitConfig.SshPrivateKeyPassword, - ) - if err != nil { - return nil, err - } - return sshKey, nil - case GIT_AUTH_USERNAME_PASSWORD: - return &http.BasicAuth{ - Username: g.gitConfig.User, - Password: g.gitConfig.Password, - }, nil - case GIT_AUTH_ACCESS_TOKEN: - return &http.BasicAuth{ - Username: "required-username", - Password: g.gitConfig.AccessToken, - }, nil - case GIT_AUTH_ANONYMOUS: - return nil, nil - case GIT_AUTH_SSH_AGENT: - return ssh.NewSSHAgentAuth(g.gitConfig.User) - default: - return nil, nil - } -} diff --git a/internal/services/providers/gogit.go b/internal/services/providers/gogit.go new file mode 100644 index 0000000..edff30a --- /dev/null +++ b/internal/services/providers/gogit.go @@ -0,0 +1,339 @@ +package providers + +import ( + "context" + "errors" + "fmt" + "time" + + "git.front.kjuulh.io/kjuulh/octopush/internal/services/signer" + "git.front.kjuulh.io/kjuulh/octopush/internal/services/storage" + "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/transport" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" + "go.uber.org/zap" + "go.uber.org/zap/zapio" +) + +// GoGit is a native git provider, it can clone, pull +// , push and as in abstraction on native git operations +type GoGit struct { + logger *zap.Logger + gitConfig *GitConfig + openPGP *signer.OpenPGP +} + +type GoGitRepo struct { + repo *git.Repository +} + +func (gr *GoGitRepo) GetHEAD() (string, error) { + head, err := gr.repo.Head() + if err != nil { + return "", err + } + + return head.Name().Short(), nil +} + +type GitAuth string + +const ( + GIT_AUTH_SSH GitAuth = "ssh" + GIT_AUTH_USERNAME_PASSWORD GitAuth = "username_password" + GIT_AUTH_ACCESS_TOKEN GitAuth = "access_token" + GIT_AUTH_ANONYMOUS GitAuth = "anonymous" + GIT_AUTH_SSH_AGENT GitAuth = "ssh_agent" +) + +type GitConfig struct { + AuthOption GitAuth + User string + Password string + AccessToken string + SshPublicKeyFilePath string + SshPrivateKeyPassword string +} + +func NewGit(logger *zap.Logger, gitConfig *GitConfig, openPGP *signer.OpenPGP) *GoGit { + return &GoGit{logger: logger, gitConfig: gitConfig, openPGP: openPGP} +} + +func (g *GoGit) GetOriginHEADForRepo(ctx context.Context, gitRepo *GoGitRepo) (string, error) { + auth, err := g.GetAuth() + if err != nil { + return "", err + } + + remote, err := gitRepo.repo.Remote("origin") + if err != nil { + return "", err + } + + refs, err := remote.ListContext(ctx, &git.ListOptions{ + Auth: auth, + }) + if err != nil { + return "", err + } + + headRef := "" + for _, ref := range refs { + //g.logger.Debug(ref.String()) + if !ref.Name().IsBranch() { + headRef = ref.Target().Short() + } + } + + if headRef == "" { + return "", errors.New("no upstream HEAD branch could be found") + } + + return headRef, nil +} + +func (g *GoGit) CloneBranch(ctx context.Context, storageArea *storage.Area, repoUrl string, branch string) (*GoGitRepo, error) { + g.logger.Debug( + "cloning repository", + zap.String("repoUrl", repoUrl), + zap.String("path", storageArea.Path), + ) + + auth, err := g.GetAuth() + if err != nil { + return nil, err + } + + cloneOptions := git.CloneOptions{ + URL: repoUrl, + Auth: auth, + RemoteName: "origin", + ReferenceName: plumbing.NewBranchReferenceName(branch), + SingleBranch: false, + NoCheckout: false, + Depth: 1, + RecurseSubmodules: 1, + Progress: g.getProgressWriter(), + Tags: 0, + InsecureSkipTLS: false, + CABundle: []byte{}, + } + + repo, err := git.PlainCloneContext(ctx, storageArea.Path, false, &cloneOptions) + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { + return nil, err + } + + g.logger.Debug("done cloning repo") + + return &GoGitRepo{repo: repo}, nil +} + +func (g *GoGit) Clone(ctx context.Context, storageArea *storage.Area, repoUrl string) (*GoGitRepo, error) { + g.logger.Debug( + "cloning repository", + zap.String("repoUrl", repoUrl), + zap.String("path", storageArea.Path), + ) + + auth, err := g.GetAuth() + if err != nil { + return nil, err + } + + cloneOptions := git.CloneOptions{ + URL: repoUrl, + Auth: auth, + RemoteName: "origin", + ReferenceName: "refs/heads/main", + SingleBranch: false, + NoCheckout: false, + Depth: 1, + RecurseSubmodules: 1, + Progress: g.getProgressWriter(), + Tags: 0, + InsecureSkipTLS: false, + CABundle: []byte{}, + } + + repo, err := git.PlainCloneContext(ctx, storageArea.Path, false, &cloneOptions) + if err != nil { + return nil, err + } + + g.logger.Debug("done cloning repo") + + return &GoGitRepo{repo: repo}, nil +} + +func (g *GoGit) getProgressWriter() *zapio.Writer { + return &zapio.Writer{ + Log: g.logger.With(zap.String("process", "go-git")), + Level: zap.DebugLevel, + } +} + +func (g *GoGit) Add(ctx context.Context, storageArea *storage.Area, gitRepo *GoGitRepo) (*git.Worktree, error) { + worktree, err := gitRepo.repo.Worktree() + if err != nil { + return nil, err + } + + err = worktree.AddWithOptions(&git.AddOptions{ + All: true, + }) + if err != nil { + return nil, err + } + + status, err := worktree.Status() + if err != nil { + return nil, err + } + + g.logger.Debug("git status", zap.String("status", status.String())) + + return worktree, nil +} + +func (g *GoGit) CreateBranch(ctx context.Context, gitRepo *GoGitRepo) error { + worktree, err := gitRepo.repo.Worktree() + if err != nil { + return err + } + + refSpec := plumbing.NewBranchReferenceName("octopush-apply") + err = gitRepo.repo.CreateBranch(&config.Branch{ + Name: "octopush-apply", + Remote: "origin", + Merge: refSpec, + Rebase: "", + }) + if err != nil { + return fmt.Errorf("could not create branch: %w", err) + } + + err = worktree.Checkout(&git.CheckoutOptions{ + Branch: plumbing.ReferenceName(refSpec.String()), + Create: true, + Force: false, + Keep: false, + }) + if err != nil { + return fmt.Errorf("could not checkout branch: %w", err) + } + + remoteRef := plumbing.NewRemoteReferenceName("origin", "octopush-apply") + ref := plumbing.NewSymbolicReference(refSpec, remoteRef) + err = gitRepo.repo.Storer.SetReference(ref) + if err != nil { + return fmt.Errorf("could not set reference: %w", err) + } + + auth, err := g.GetAuth() + if err != nil { + return err + } + + err = worktree.PullContext(ctx, &git.PullOptions{ + RemoteName: "origin", + ReferenceName: "refs/heads/main", + SingleBranch: false, + Depth: 1, + Auth: auth, + RecurseSubmodules: 1, + Progress: g.getProgressWriter(), + Force: true, + InsecureSkipTLS: false, + CABundle: []byte{}, + }) + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { + return fmt.Errorf("could not pull from origin: %w", err) + } + + g.logger.Debug("done creating branches") + + return nil +} + +func (g *GoGit) Commit(ctx context.Context, gitRepo *GoGitRepo) error { + worktree, err := gitRepo.repo.Worktree() + if err != nil { + return err + } + + _, err = worktree.Commit("some-commit", &git.CommitOptions{ + All: true, + Author: &object.Signature{Name: "octopush", Email: "octopush@kasperhermansen.com", When: time.Now()}, + Committer: &object.Signature{Name: "octopush", Email: "octopush@kasperhermansen.com", When: time.Now()}, + SignKey: g.openPGP.SigningKey, + }) + if err != nil { + return err + } + + g.logger.Debug("done commiting objects") + + return nil +} + +func (g *GoGit) Push(ctx context.Context, gitRepo *GoGitRepo) error { + auth, err := g.GetAuth() + if err != nil { + return err + } + + err = gitRepo.repo.PushContext(ctx, &git.PushOptions{ + RemoteName: "origin", + RefSpecs: []config.RefSpec{}, + Auth: auth, + Progress: g.getProgressWriter(), + Prune: false, + Force: true, + InsecureSkipTLS: false, + CABundle: []byte{}, + RequireRemoteRefs: []config.RefSpec{}, + }) + if err != nil { + return err + } + + g.logger.Debug("done pushing branch") + + return nil +} + +func (g *GoGit) GetAuth() (transport.AuthMethod, error) { + switch g.gitConfig.AuthOption { + case GIT_AUTH_SSH: + sshKey, err := ssh.NewPublicKeysFromFile( + g.gitConfig.User, + g.gitConfig.SshPublicKeyFilePath, + g.gitConfig.SshPrivateKeyPassword, + ) + if err != nil { + return nil, err + } + return sshKey, nil + case GIT_AUTH_USERNAME_PASSWORD: + return &http.BasicAuth{ + Username: g.gitConfig.User, + Password: g.gitConfig.Password, + }, nil + case GIT_AUTH_ACCESS_TOKEN: + return &http.BasicAuth{ + Username: "required-username", + Password: g.gitConfig.AccessToken, + }, nil + case GIT_AUTH_ANONYMOUS: + return nil, nil + case GIT_AUTH_SSH_AGENT: + return ssh.NewSSHAgentAuth(g.gitConfig.User) + default: + return nil, nil + } +}