From 8b3bd3ca2779a8a0925817938cd6d0a8b03729c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Fri, 30 Aug 2024 23:12:05 +0200 Subject: [PATCH] refactor: move forge to package --- cmd/rp/cmd/run.go | 16 +- internal/forge/forge.go | 61 +++++++ forge.go => internal/forge/github/github.go | 178 ++++++-------------- internal/forge/gitlab/gitlab.go | 31 ++++ internal/pointer/pointer.go | 5 + releaserpleaser.go | 5 +- 6 files changed, 160 insertions(+), 136 deletions(-) create mode 100644 internal/forge/forge.go rename forge.go => internal/forge/github/github.go (70%) create mode 100644 internal/forge/gitlab/gitlab.go create mode 100644 internal/pointer/pointer.go diff --git a/cmd/rp/cmd/run.go b/cmd/rp/cmd/run.go index 7d5862b..6729c46 100644 --- a/cmd/rp/cmd/run.go +++ b/cmd/rp/cmd/run.go @@ -6,6 +6,8 @@ import ( "github.com/spf13/cobra" rp "github.com/apricote/releaser-pleaser" + "github.com/apricote/releaser-pleaser/internal/forge" + "github.com/apricote/releaser-pleaser/internal/forge/github" ) var runCmd = &cobra.Command{ @@ -41,9 +43,9 @@ func run(cmd *cobra.Command, _ []string) error { "repo", flagRepo, ) - var forge rp.Forge + var f forge.Forge - forgeOptions := rp.ForgeOptions{ + forgeOptions := forge.Options{ Repository: flagRepo, BaseBranch: flagBranch, } @@ -53,17 +55,17 @@ func run(cmd *cobra.Command, _ []string) error { // f = rp.NewGitLab(forgeOptions) case "github": logger.DebugContext(ctx, "using forge GitHub") - forge = rp.NewGitHub(logger, &rp.GitHubOptions{ - ForgeOptions: forgeOptions, - Owner: flagOwner, - Repo: flagRepo, + f = github.New(logger, &github.Options{ + Options: forgeOptions, + Owner: flagOwner, + Repo: flagRepo, }) } extraFiles := parseExtraFiles(flagExtraFiles) releaserPleaser := rp.New( - forge, + f, logger, flagBranch, rp.NewConventionalCommitsParser(), diff --git a/internal/forge/forge.go b/internal/forge/forge.go new file mode 100644 index 0000000..5636c93 --- /dev/null +++ b/internal/forge/forge.go @@ -0,0 +1,61 @@ +package forge + +import ( + "context" + + "github.com/go-git/go-git/v5/plumbing/transport" + + "github.com/apricote/releaser-pleaser" + "github.com/apricote/releaser-pleaser/internal/git" +) + +type Forge interface { + RepoURL() string + CloneURL() string + ReleaseURL(version string) string + + GitAuth() transport.AuthMethod + + // LatestTags returns the last stable tag created on the main branch. If there is a more recent pre-release tag, + // that is also returned. If no tag is found, it returns nil. + LatestTags(context.Context) (rp.Releases, error) + + // CommitsSince returns all commits to main branch after the Tag. The tag can be `nil`, in which case this + // function should return all commits. + CommitsSince(context.Context, *git.Tag) ([]git.Commit, error) + + // EnsureLabelsExist verifies that all desired labels are available on the repository. If labels are missing, they + // are created them. + EnsureLabelsExist(context.Context, []rp.Label) error + + // PullRequestForBranch returns the open pull request between the branch and Options.BaseBranch. If no open PR + // exists, it returns nil. + PullRequestForBranch(context.Context, string) (*rp.ReleasePullRequest, error) + + // CreatePullRequest opens a new pull/merge request for the ReleasePullRequest. + CreatePullRequest(context.Context, *rp.ReleasePullRequest) error + + // UpdatePullRequest updates the pull/merge request identified through the ID of + // the ReleasePullRequest to the current description and title. + UpdatePullRequest(context.Context, *rp.ReleasePullRequest) error + + // SetPullRequestLabels updates the pull/merge request identified through the ID of + // the ReleasePullRequest to the current labels. + SetPullRequestLabels(ctx context.Context, pr *rp.ReleasePullRequest, remove, add []rp.Label) error + + // ClosePullRequest closes the pull/merge request identified through the ID of + // the ReleasePullRequest, as it is no longer required. + ClosePullRequest(context.Context, *rp.ReleasePullRequest) error + + // PendingReleases returns a list of ReleasePullRequest. The list should contain all pull/merge requests that are + // merged and have the matching label. + PendingReleases(context.Context, rp.Label) ([]*rp.ReleasePullRequest, error) + + // CreateRelease creates a release on the Forge, pointing at the commit with the passed in details. + CreateRelease(ctx context.Context, commit git.Commit, title, changelog string, prerelease, latest bool) error +} + +type Options struct { + Repository string + BaseBranch string +} diff --git a/forge.go b/internal/forge/github/github.go similarity index 70% rename from forge.go rename to internal/forge/github/github.go index cef40cf..8021135 100644 --- a/forge.go +++ b/internal/forge/github/github.go @@ -1,4 +1,4 @@ -package rp +package github import ( "context" @@ -14,76 +14,26 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/google/go-github/v63/github" + "github.com/apricote/releaser-pleaser" + "github.com/apricote/releaser-pleaser/internal/forge" "github.com/apricote/releaser-pleaser/internal/git" + "github.com/apricote/releaser-pleaser/internal/pointer" ) const ( - GitHubPerPageMax = 100 - GitHubPRStateOpen = "open" - GitHubPRStateClosed = "closed" - GitHubEnvAPIToken = "GITHUB_TOKEN" // nolint:gosec // Not actually a hardcoded credential - GitHubEnvUsername = "GITHUB_USER" - GitHubEnvRepository = "GITHUB_REPOSITORY" - GitHubLabelColor = "dedede" + PerPageMax = 100 + PRStateOpen = "open" + PRStateClosed = "closed" + EnvAPIToken = "GITHUB_TOKEN" // nolint:gosec // Not actually a hardcoded credential + EnvUsername = "GITHUB_USER" + EnvRepository = "GITHUB_REPOSITORY" + LabelColor = "dedede" ) -type Forge interface { - RepoURL() string - CloneURL() string - ReleaseURL(version string) string - - GitAuth() transport.AuthMethod - - // LatestTags returns the last stable tag created on the main branch. If there is a more recent pre-release tag, - // that is also returned. If no tag is found, it returns nil. - LatestTags(context.Context) (Releases, error) - - // CommitsSince returns all commits to main branch after the Tag. The tag can be `nil`, in which case this - // function should return all commits. - CommitsSince(context.Context, *git.Tag) ([]git.Commit, error) - - // EnsureLabelsExist verifies that all desired labels are available on the repository. If labels are missing, they - // are created them. - EnsureLabelsExist(context.Context, []Label) error - - // PullRequestForBranch returns the open pull request between the branch and ForgeOptions.BaseBranch. If no open PR - // exists, it returns nil. - PullRequestForBranch(context.Context, string) (*ReleasePullRequest, error) - - // CreatePullRequest opens a new pull/merge request for the ReleasePullRequest. - CreatePullRequest(context.Context, *ReleasePullRequest) error - - // UpdatePullRequest updates the pull/merge request identified through the ID of - // the ReleasePullRequest to the current description and title. - UpdatePullRequest(context.Context, *ReleasePullRequest) error - - // SetPullRequestLabels updates the pull/merge request identified through the ID of - // the ReleasePullRequest to the current labels. - SetPullRequestLabels(ctx context.Context, pr *ReleasePullRequest, remove, add []Label) error - - // ClosePullRequest closes the pull/merge request identified through the ID of - // the ReleasePullRequest, as it is no longer required. - ClosePullRequest(context.Context, *ReleasePullRequest) error - - // PendingReleases returns a list of ReleasePullRequest. The list should contain all pull/merge requests that are - // merged and have the matching label. - PendingReleases(context.Context, Label) ([]*ReleasePullRequest, error) - - // CreateRelease creates a release on the Forge, pointing at the commit with the passed in details. - CreateRelease(ctx context.Context, commit git.Commit, title, changelog string, prerelease, latest bool) error -} - -type ForgeOptions struct { - Repository string - BaseBranch string -} - -var _ Forge = &GitHub{} - -// var _ Forge = &GitLab{} +var _ forge.Forge = &GitHub{} type GitHub struct { - options *GitHubOptions + options *Options client *github.Client log *slog.Logger @@ -108,20 +58,20 @@ func (g *GitHub) GitAuth() transport.AuthMethod { } } -func (g *GitHub) LatestTags(ctx context.Context) (Releases, error) { +func (g *GitHub) LatestTags(ctx context.Context) (rp.Releases, error) { g.log.DebugContext(ctx, "listing all tags in github repository") page := 1 - var releases Releases + var releases rp.Releases for { tags, resp, err := g.client.Repositories.ListTags( ctx, g.options.Owner, g.options.Repo, - &github.ListOptions{Page: page, PerPage: GitHubPerPageMax}, + &github.ListOptions{Page: page, PerPage: PerPageMax}, ) if err != nil { - return Releases{}, err + return rp.Releases{}, err } for _, ghTag := range tags { @@ -206,7 +156,7 @@ func (g *GitHub) commitsSinceTag(ctx context.Context, tag *git.Tag) ([]*github.R ctx, g.options.Owner, g.options.Repo, tag.Hash, head, &github.ListOptions{ Page: page, - PerPage: GitHubPerPageMax, + PerPage: PerPageMax, }) if err != nil { return nil, err @@ -246,7 +196,7 @@ func (g *GitHub) commitsSinceInit(ctx context.Context) ([]*github.RepositoryComm SHA: head, ListOptions: github.ListOptions{ Page: page, - PerPage: GitHubPerPageMax, + PerPage: PerPageMax, }, }) if err != nil { @@ -256,7 +206,7 @@ func (g *GitHub) commitsSinceInit(ctx context.Context) ([]*github.RepositoryComm if repositoryCommits == nil && resp.LastPage > 0 { // Pre-initialize slice on first request log.Debug("found commits", "pages", resp.LastPage) - repositoryCommits = make([]*github.RepositoryCommit, 0, resp.LastPage*GitHubPerPageMax) + repositoryCommits = make([]*github.RepositoryCommit, 0, resp.LastPage*PerPageMax) } repositoryCommits = append(repositoryCommits, commits...) @@ -287,7 +237,7 @@ func (g *GitHub) prForCommit(ctx context.Context, commit git.Commit) (*git.PullR ctx, g.options.Owner, g.options.Repo, commit.Hash, &github.ListOptions{ Page: page, - PerPage: GitHubPerPageMax, + PerPage: PerPageMax, }) if err != nil { return nil, err @@ -316,7 +266,7 @@ func (g *GitHub) prForCommit(ctx context.Context, commit git.Commit) (*git.PullR return gitHubPRToPullRequest(pullrequest), nil } -func (g *GitHub) EnsureLabelsExist(ctx context.Context, labels []Label) error { +func (g *GitHub) EnsureLabelsExist(ctx context.Context, labels []rp.Label) error { existingLabels := make([]string, 0, len(labels)) page := 1 @@ -327,7 +277,7 @@ func (g *GitHub) EnsureLabelsExist(ctx context.Context, labels []Label) error { ctx, g.options.Owner, g.options.Repo, &github.ListOptions{ Page: page, - PerPage: GitHubPerPageMax, + PerPage: PerPageMax, }) if err != nil { return err @@ -349,8 +299,8 @@ func (g *GitHub) EnsureLabelsExist(ctx context.Context, labels []Label) error { _, _, err := g.client.Issues.CreateLabel( ctx, g.options.Owner, g.options.Repo, &github.Label{ - Name: Pointer(string(label)), - Color: Pointer(GitHubLabelColor), + Name: pointer.Pointer(string(label)), + Color: pointer.Pointer(LabelColor), }, ) if err != nil { @@ -362,13 +312,13 @@ func (g *GitHub) EnsureLabelsExist(ctx context.Context, labels []Label) error { return nil } -func (g *GitHub) PullRequestForBranch(ctx context.Context, branch string) (*ReleasePullRequest, error) { +func (g *GitHub) PullRequestForBranch(ctx context.Context, branch string) (*rp.ReleasePullRequest, error) { page := 1 for { prs, resp, err := g.client.PullRequests.ListPullRequestsWithCommit(ctx, g.options.Owner, g.options.Repo, branch, &github.ListOptions{ Page: page, - PerPage: GitHubPerPageMax, + PerPage: PerPageMax, }) if err != nil { var ghErr *github.ErrorResponse @@ -381,7 +331,7 @@ func (g *GitHub) PullRequestForBranch(ctx context.Context, branch string) (*Rele } for _, pr := range prs { - if pr.GetBase().GetRef() == g.options.BaseBranch && pr.GetHead().GetRef() == branch && pr.GetState() == GitHubPRStateOpen { + if pr.GetBase().GetRef() == g.options.BaseBranch && pr.GetHead().GetRef() == branch && pr.GetState() == PRStateOpen { return gitHubPRToReleasePullRequest(pr), nil } } @@ -395,7 +345,7 @@ func (g *GitHub) PullRequestForBranch(ctx context.Context, branch string) (*Rele return nil, nil } -func (g *GitHub) CreatePullRequest(ctx context.Context, pr *ReleasePullRequest) error { +func (g *GitHub) CreatePullRequest(ctx context.Context, pr *rp.ReleasePullRequest) error { ghPR, _, err := g.client.PullRequests.Create( ctx, g.options.Owner, g.options.Repo, &github.NewPullRequest{ @@ -412,7 +362,7 @@ func (g *GitHub) CreatePullRequest(ctx context.Context, pr *ReleasePullRequest) // TODO: String ID? pr.ID = ghPR.GetNumber() - err = g.SetPullRequestLabels(ctx, pr, []Label{}, pr.Labels) + err = g.SetPullRequestLabels(ctx, pr, []rp.Label{}, pr.Labels) if err != nil { return err } @@ -420,7 +370,7 @@ func (g *GitHub) CreatePullRequest(ctx context.Context, pr *ReleasePullRequest) return nil } -func (g *GitHub) UpdatePullRequest(ctx context.Context, pr *ReleasePullRequest) error { +func (g *GitHub) UpdatePullRequest(ctx context.Context, pr *rp.ReleasePullRequest) error { _, _, err := g.client.PullRequests.Edit( ctx, g.options.Owner, g.options.Repo, pr.ID, &github.PullRequest{ @@ -435,7 +385,7 @@ func (g *GitHub) UpdatePullRequest(ctx context.Context, pr *ReleasePullRequest) return nil } -func (g *GitHub) SetPullRequestLabels(ctx context.Context, pr *ReleasePullRequest, remove, add []Label) error { +func (g *GitHub) SetPullRequestLabels(ctx context.Context, pr *rp.ReleasePullRequest, remove, add []rp.Label) error { for _, label := range remove { _, err := g.client.Issues.RemoveLabelForIssue( ctx, g.options.Owner, g.options.Repo, @@ -462,11 +412,11 @@ func (g *GitHub) SetPullRequestLabels(ctx context.Context, pr *ReleasePullReques return nil } -func (g *GitHub) ClosePullRequest(ctx context.Context, pr *ReleasePullRequest) error { +func (g *GitHub) ClosePullRequest(ctx context.Context, pr *rp.ReleasePullRequest) error { _, _, err := g.client.PullRequests.Edit( ctx, g.options.Owner, g.options.Repo, pr.ID, &github.PullRequest{ - State: Pointer(GitHubPRStateClosed), + State: pointer.Pointer(PRStateClosed), }, ) if err != nil { @@ -476,20 +426,20 @@ func (g *GitHub) ClosePullRequest(ctx context.Context, pr *ReleasePullRequest) e return nil } -func (g *GitHub) PendingReleases(ctx context.Context, pendingLabel Label) ([]*ReleasePullRequest, error) { +func (g *GitHub) PendingReleases(ctx context.Context, pendingLabel rp.Label) ([]*rp.ReleasePullRequest, error) { page := 1 - var prs []*ReleasePullRequest + var prs []*rp.ReleasePullRequest for { ghPRs, resp, err := g.client.PullRequests.List( ctx, g.options.Owner, g.options.Repo, &github.PullRequestListOptions{ - State: GitHubPRStateClosed, + State: PRStateClosed, Base: g.options.BaseBranch, ListOptions: github.ListOptions{ Page: page, - PerPage: GitHubPerPageMax, + PerPage: PerPageMax, }, }) if err != nil { @@ -499,7 +449,7 @@ func (g *GitHub) PendingReleases(ctx context.Context, pendingLabel Label) ([]*Re if prs == nil && resp.LastPage > 0 { // Pre-initialize slice on first request g.log.Debug("found pending releases", "pages", resp.LastPage) - prs = make([]*ReleasePullRequest, 0, (resp.LastPage-1)*GitHubPerPageMax) + prs = make([]*rp.ReleasePullRequest, 0, (resp.LastPage-1)*PerPageMax) } for _, pr := range ghPRs { @@ -561,11 +511,11 @@ func gitHubPRToPullRequest(pr *github.PullRequest) *git.PullRequest { } } -func gitHubPRToReleasePullRequest(pr *github.PullRequest) *ReleasePullRequest { - labels := make([]Label, 0, len(pr.Labels)) +func gitHubPRToReleasePullRequest(pr *github.PullRequest) *rp.ReleasePullRequest { + labels := make([]rp.Label, 0, len(pr.Labels)) for _, label := range pr.Labels { - labelName := Label(label.GetName()) - if slices.Contains(KnownLabels, Label(label.GetName())) { + labelName := rp.Label(label.GetName()) + if slices.Contains(rp.KnownLabels, rp.Label(label.GetName())) { labels = append(labels, labelName) } } @@ -575,7 +525,7 @@ func gitHubPRToReleasePullRequest(pr *github.PullRequest) *ReleasePullRequest { releaseCommit = &git.Commit{Hash: pr.GetMergeCommitSHA()} } - return &ReleasePullRequest{ + return &rp.ReleasePullRequest{ ID: pr.GetNumber(), Title: pr.GetTitle(), Description: pr.GetBody(), @@ -586,16 +536,16 @@ func gitHubPRToReleasePullRequest(pr *github.PullRequest) *ReleasePullRequest { } } -func (g *GitHubOptions) autodiscover() { - if apiToken := os.Getenv(GitHubEnvAPIToken); apiToken != "" { +func (g *Options) autodiscover() { + if apiToken := os.Getenv(EnvAPIToken); apiToken != "" { g.APIToken = apiToken } // TODO: Check if there is a better solution for cloning/pushing locally - if username := os.Getenv(GitHubEnvUsername); username != "" { + if username := os.Getenv(EnvUsername); username != "" { g.Username = username } - if envRepository := os.Getenv(GitHubEnvRepository); envRepository != "" { + if envRepository := os.Getenv(EnvRepository); envRepository != "" { // GITHUB_REPOSITORY=apricote/releaser-pleaser parts := strings.Split(envRepository, "/") if len(parts) == 2 { @@ -606,8 +556,8 @@ func (g *GitHubOptions) autodiscover() { } } -type GitHubOptions struct { - ForgeOptions +type Options struct { + forge.Options Owner string Repo string @@ -616,7 +566,7 @@ type GitHubOptions struct { Username string } -func NewGitHub(log *slog.Logger, options *GitHubOptions) *GitHub { +func New(log *slog.Logger, options *Options) *GitHub { options.autodiscover() client := github.NewClient(nil) @@ -633,29 +583,3 @@ func NewGitHub(log *slog.Logger, options *GitHubOptions) *GitHub { return gh } - -type GitLab struct { - options ForgeOptions -} - -func (g *GitLab) autodiscover() { - // Read settings from GitLab-CI env vars -} - -func NewGitLab(options ForgeOptions) *GitLab { - gl := &GitLab{ - options: options, - } - - gl.autodiscover() - - return gl -} - -func (g *GitLab) RepoURL() string { - return fmt.Sprintf("https://gitlab.com/%s", g.options.Repository) -} - -func Pointer[T any](value T) *T { - return &value -} diff --git a/internal/forge/gitlab/gitlab.go b/internal/forge/gitlab/gitlab.go new file mode 100644 index 0000000..98a4252 --- /dev/null +++ b/internal/forge/gitlab/gitlab.go @@ -0,0 +1,31 @@ +package gitlab + +import ( + "fmt" + + "github.com/apricote/releaser-pleaser/internal/forge" +) + +// var _ forge.Forge = &GitLab{} + +type GitLab struct { + options forge.Options +} + +func (g *GitLab) autodiscover() { + // Read settings from GitLab-CI env vars +} + +func New(options forge.Options) *GitLab { + gl := &GitLab{ + options: options, + } + + gl.autodiscover() + + return gl +} + +func (g *GitLab) RepoURL() string { + return fmt.Sprintf("https://gitlab.com/%s", g.options.Repository) +} diff --git a/internal/pointer/pointer.go b/internal/pointer/pointer.go new file mode 100644 index 0000000..1c62f74 --- /dev/null +++ b/internal/pointer/pointer.go @@ -0,0 +1,5 @@ +package pointer + +func Pointer[T any](value T) *T { + return &value +} diff --git a/releaserpleaser.go b/releaserpleaser.go index 6c557dc..a047790 100644 --- a/releaserpleaser.go +++ b/releaserpleaser.go @@ -11,6 +11,7 @@ import ( "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" + "github.com/apricote/releaser-pleaser/internal/forge" git2 "github.com/apricote/releaser-pleaser/internal/git" ) @@ -19,7 +20,7 @@ const ( ) type ReleaserPleaser struct { - forge Forge + forge forge.Forge logger *slog.Logger targetBranch string commitParser CommitParser @@ -28,7 +29,7 @@ type ReleaserPleaser struct { updaters []Updater } -func New(forge Forge, logger *slog.Logger, targetBranch string, commitParser CommitParser, versioningStrategy VersioningStrategy, extraFiles []string, updaters []Updater) *ReleaserPleaser { +func New(forge forge.Forge, logger *slog.Logger, targetBranch string, commitParser CommitParser, versioningStrategy VersioningStrategy, extraFiles []string, updaters []Updater) *ReleaserPleaser { return &ReleaserPleaser{ forge: forge, logger: logger,