mirror of
https://github.com/apricote/releaser-pleaser.git
synced 2026-01-13 21:21:03 +00:00
refactor: move things to packages
This commit is contained in:
parent
44184a77f9
commit
11063b2d2c
32 changed files with 923 additions and 892 deletions
227
internal/git/git.go
Normal file
227
internal/git/git.go
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"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/apricote/releaser-pleaser/internal/updater"
|
||||
)
|
||||
|
||||
const (
|
||||
remoteName = "origin"
|
||||
)
|
||||
|
||||
type Commit struct {
|
||||
Hash string
|
||||
Message string
|
||||
|
||||
PullRequest *PullRequest
|
||||
}
|
||||
|
||||
type PullRequest struct {
|
||||
ID int
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Hash string
|
||||
Name string
|
||||
}
|
||||
|
||||
type Releases struct {
|
||||
Latest *Tag
|
||||
Stable *Tag
|
||||
}
|
||||
|
||||
func CloneRepo(ctx context.Context, logger *slog.Logger, cloneURL, branch string, auth transport.AuthMethod) (*Repository, error) {
|
||||
dir, err := os.MkdirTemp("", "releaser-pleaser.*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temporary directory for repo clone: %w", err)
|
||||
}
|
||||
|
||||
repo, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
|
||||
URL: cloneURL,
|
||||
RemoteName: remoteName,
|
||||
ReferenceName: plumbing.NewBranchReferenceName(branch),
|
||||
SingleBranch: false,
|
||||
Auth: auth,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
|
||||
return &Repository{r: repo, logger: logger, auth: auth}, nil
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
r *git.Repository
|
||||
logger *slog.Logger
|
||||
auth transport.AuthMethod
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteBranch(ctx context.Context, branch string) error {
|
||||
if b, _ := r.r.Branch(branch); b != nil {
|
||||
r.logger.DebugContext(ctx, "deleting local branch", "branch.name", branch)
|
||||
if err := r.r.DeleteBranch(branch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) Checkout(_ context.Context, branch string) error {
|
||||
worktree, err := r.r.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = worktree.Checkout(&git.CheckoutOptions{
|
||||
Branch: plumbing.NewBranchReferenceName(branch),
|
||||
Create: true,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to check out branch: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateFile(_ context.Context, path string, updaters []updater.Updater) error {
|
||||
worktree, err := r.r.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := worktree.Filesystem.OpenFile(path, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedContent := string(content)
|
||||
|
||||
for _, update := range updaters {
|
||||
updatedContent, err = update(updatedContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run updater on file %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
err = file.Truncate(0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to replace file content: %w", err)
|
||||
}
|
||||
_, err = file.Seek(0, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to replace file content: %w", err)
|
||||
}
|
||||
_, err = file.Write([]byte(updatedContent))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to replace file content: %w", err)
|
||||
}
|
||||
|
||||
_, err = worktree.Add(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add updated file to git worktree: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) Commit(_ context.Context, message string) (Commit, error) {
|
||||
worktree, err := r.r.Worktree()
|
||||
if err != nil {
|
||||
return Commit{}, err
|
||||
}
|
||||
|
||||
releaseCommitHash, err := worktree.Commit(message, &git.CommitOptions{
|
||||
Author: signature(),
|
||||
Committer: signature(),
|
||||
})
|
||||
if err != nil {
|
||||
return Commit{}, fmt.Errorf("failed to commit changes: %w", err)
|
||||
}
|
||||
|
||||
return Commit{
|
||||
Hash: releaseCommitHash.String(),
|
||||
Message: message,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) HasChangesWithRemote(ctx context.Context, branch string) (bool, error) {
|
||||
remoteRef, err := r.r.Reference(plumbing.NewRemoteReferenceName(remoteName, branch), false)
|
||||
if err != nil {
|
||||
if err.Error() == "reference not found" {
|
||||
// No remote branch means that there are changes
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
remoteCommit, err := r.r.CommitObject(remoteRef.Hash())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
localRef, err := r.r.Reference(plumbing.NewBranchReferenceName(branch), false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
localCommit, err := r.r.CommitObject(localRef.Hash())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
diff, err := localCommit.PatchContext(ctx, remoteCommit)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
hasChanges := len(diff.FilePatches()) > 0
|
||||
|
||||
return hasChanges, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ForcePush(ctx context.Context, branch string) error {
|
||||
pushRefSpec := config.RefSpec(fmt.Sprintf(
|
||||
"+%s:%s",
|
||||
plumbing.NewBranchReferenceName(branch),
|
||||
// This needs to be the local branch name, not the remotes/origin ref
|
||||
// See https://stackoverflow.com/a/75727620
|
||||
plumbing.NewBranchReferenceName(branch),
|
||||
))
|
||||
|
||||
r.logger.DebugContext(ctx, "pushing branch", "branch.name", branch, "refspec", pushRefSpec.String())
|
||||
return r.r.PushContext(ctx, &git.PushOptions{
|
||||
RemoteName: remoteName,
|
||||
RefSpecs: []config.RefSpec{pushRefSpec},
|
||||
Force: true,
|
||||
Auth: r.auth,
|
||||
})
|
||||
}
|
||||
|
||||
func signature() *object.Signature {
|
||||
return &object.Signature{
|
||||
Name: "releaser-pleaser",
|
||||
Email: "",
|
||||
When: time.Now(),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue