Skip to content

Commit

Permalink
feat: allow to PR homebrew taps (#3903)
Browse files Browse the repository at this point in the history
closes #3485

also fixed a bug in file creation for github: it was always searching
for the file in the default branch

also, we don't need to create the file first, update does both create
and update.

TODO: implement the for krew, scoop, etc...

---------

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
  • Loading branch information
caarlos0 committed Apr 7, 2023
1 parent ed2e378 commit 8b1c4ea
Show file tree
Hide file tree
Showing 18 changed files with 651 additions and 118 deletions.
17 changes: 10 additions & 7 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,19 @@ type Client interface {
ReleaseURLTemplate(ctx *context.Context) (string, error)
CreateFile(ctx *context.Context, commitAuthor config.CommitAuthor, repo Repo, content []byte, path, message string) (err error)
Upload(ctx *context.Context, releaseID string, artifact *artifact.Artifact, file *os.File) (err error)
GetDefaultBranch(ctx *context.Context, repo Repo) (string, error)
Changelog(ctx *context.Context, repo Repo, prev, current string) (string, error)
}

// GitHubClient is the client with GitHub-only features.
type GitHubClient interface {
Client
// ReleaseNotesGenerator can generate release notes.
type ReleaseNotesGenerator interface {
GenerateReleaseNotes(ctx *context.Context, repo Repo, prev, current string) (string, error)
}

// PullRequestOpener can open pull requests.
type PullRequestOpener interface {
OpenPullRequest(ctx *context.Context, repo Repo, head, title string) error
}

// New creates a new client depending on the token type.
func New(ctx *context.Context) (Client, error) {
return newWithToken(ctx, ctx.Token)
Expand All @@ -68,11 +71,11 @@ func newWithToken(ctx *context.Context, token string) (Client, error) {
log.WithField("type", ctx.TokenType).Debug("token type")
switch ctx.TokenType {
case context.TokenTypeGitHub:
return NewGitHub(ctx, token)
return newGitHub(ctx, token)
case context.TokenTypeGitLab:
return NewGitLab(ctx, token)
return newGitLab(ctx, token)
case context.TokenTypeGitea:
return NewGitea(ctx, token)
return newGitea(ctx, token)
default:
return nil, fmt.Errorf("invalid client token type: %q", ctx.TokenType)
}
Expand Down
9 changes: 5 additions & 4 deletions internal/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ func TemplateRef(apply func(s string) (string, error), ref config.RepoRef) (conf
return ref, err
}
return config.RepoRef{
Owner: owner,
Name: name,
Token: ref.Token,
Branch: branch,
Owner: owner,
Name: name,
Token: ref.Token,
Branch: branch,
PullRequest: ref.PullRequest,
}, nil
}
10 changes: 6 additions & 4 deletions internal/client/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type giteaClient struct {
client *gitea.Client
}

var _ Client = &giteaClient{}

func getInstanceURL(ctx *context.Context) (string, error) {
apiURL, err := tmpl.New(ctx).Apply(ctx.Config.GiteaURLs.API)
if err != nil {
Expand All @@ -39,8 +41,8 @@ func getInstanceURL(ctx *context.Context) (string, error) {
return rawurl, nil
}

// NewGitea returns a gitea client implementation.
func NewGitea(ctx *context.Context, token string) (Client, error) {
// newGitea returns a gitea client implementation.
func newGitea(ctx *context.Context, token string) (*giteaClient, error) {
instanceURL, err := getInstanceURL(ctx)
if err != nil {
return nil, err
Expand Down Expand Up @@ -87,7 +89,7 @@ func (c *giteaClient) CloseMilestone(_ *context.Context, repo Repo, title string
return err
}

func (c *giteaClient) GetDefaultBranch(_ *context.Context, repo Repo) (string, error) {
func (c *giteaClient) getDefaultBranch(_ *context.Context, repo Repo) (string, error) {
projectID := repo.String()
p, res, err := c.client.GetRepo(repo.Owner, repo.Name)
if err != nil {
Expand Down Expand Up @@ -117,7 +119,7 @@ func (c *giteaClient) CreateFile(
if repo.Branch != "" {
branch = repo.Branch
} else {
branch, err = c.GetDefaultBranch(ctx, repo)
branch, err = c.getDefaultBranch(ctx, repo)
if err != nil {
// Fall back to 'master' 😭
log.WithFields(log.Fields{
Expand Down
12 changes: 6 additions & 6 deletions internal/client/gitea_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ func TestGiteaReleaseURLTemplate(t *testing.T) {
},
},
})
client, err := NewGitea(ctx, ctx.Token)
client, err := newGitea(ctx, ctx.Token)
require.NoError(t, err)

urlTpl, err := client.ReleaseURLTemplate(ctx)
Expand Down Expand Up @@ -556,15 +556,15 @@ func TestGiteaGetDefaultBranch(t *testing.T) {
API: srv.URL,
},
})
client, err := NewGitea(ctx, "test-token")
client, err := newGitea(ctx, "test-token")
require.NoError(t, err)
repo := Repo{
Owner: "someone",
Name: "something",
Branch: "somebranch",
}

_, err = client.GetDefaultBranch(ctx, repo)
_, err = client.getDefaultBranch(ctx, repo)
require.NoError(t, err)
require.Equal(t, 2, totalRequests)
}
Expand All @@ -587,15 +587,15 @@ func TestGiteaGetDefaultBranchErr(t *testing.T) {
API: srv.URL,
},
})
client, err := NewGitea(ctx, "test-token")
client, err := newGitea(ctx, "test-token")
require.NoError(t, err)
repo := Repo{
Owner: "someone",
Name: "something",
Branch: "somebranch",
}

_, err = client.GetDefaultBranch(ctx, repo)
_, err = client.getDefaultBranch(ctx, repo)
require.Error(t, err)
}

Expand All @@ -614,7 +614,7 @@ func TestGiteaChangelog(t *testing.T) {
API: srv.URL,
},
})
client, err := NewGitea(ctx, "test-token")
client, err := newGitea(ctx, "test-token")
require.NoError(t, err)
repo := Repo{
Owner: "someone",
Expand Down
122 changes: 87 additions & 35 deletions internal/client/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,24 @@ import (

const DefaultGitHubDownloadURL = "https://github.com"

var (
_ Client = &githubClient{}
_ ReleaseNotesGenerator = &githubClient{}
_ PullRequestOpener = &githubClient{}
)

type githubClient struct {
client *github.Client
}

// NewGitHub returns a github client implementation.
func NewGitHub(ctx *context.Context, token string) (GitHubClient, error) {
// NewGitHubReleaseNotesGenerator returns a GitHub client that can generate
// changelogs.
func NewGitHubReleaseNotesGenerator(ctx *context.Context, token string) (ReleaseNotesGenerator, error) {
return newGitHub(ctx, token)
}

// newGitHub returns a github client implementation.
func newGitHub(ctx *context.Context, token string) (*githubClient, error) {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
Expand Down Expand Up @@ -89,8 +101,8 @@ func (c *githubClient) Changelog(ctx *context.Context, repo Repo, prev, current
return strings.Join(log, "\n"), nil
}

// GetDefaultBranch returns the default branch of a github repo
func (c *githubClient) GetDefaultBranch(ctx *context.Context, repo Repo) (string, error) {
// getDefaultBranch returns the default branch of a github repo
func (c *githubClient) getDefaultBranch(ctx *context.Context, repo Repo) (string, error) {
p, res, err := c.client.Repositories.Get(ctx, repo.Owner, repo.Name)
if err != nil {
log.WithFields(log.Fields{
Expand Down Expand Up @@ -128,6 +140,35 @@ func (c *githubClient) CloseMilestone(ctx *context.Context, repo Repo, title str
return err
}

func (c *githubClient) OpenPullRequest(
ctx *context.Context,
repo Repo,
base, title string,
) error {
if base == "" {
def, err := c.getDefaultBranch(ctx, repo)
if err != nil {
return err
}
base = def
}
pr, res, err := c.client.PullRequests.Create(ctx, repo.Owner, repo.Name, &github.NewPullRequest{
Title: github.String(title),
Head: github.String(repo.Branch),
Base: github.String(base),
Body: github.String("Automatically generated by [GoReleaser](https://goreleaser.com)"),
})
if err != nil {
if res.StatusCode == 422 {
log.Warn("PR already exists, doing nothing...")
return nil
}
return fmt.Errorf("could not create pull request: %w", err)
}
log.WithField("url", pr.GetHTMLURL()).Info("opened")
return nil
}

func (c *githubClient) CreateFile(
ctx *context.Context,
commitAuthor config.CommitAuthor,
Expand All @@ -136,22 +177,16 @@ func (c *githubClient) CreateFile(
path,
message string,
) error {
var branch string
var err error
if repo.Branch != "" {
branch = repo.Branch
} else {
branch, err = c.GetDefaultBranch(ctx, repo)
if err != nil {
// Fall back to sdk default
log.WithFields(log.Fields{
"fileName": path,
"projectID": repo.String(),
"requestedBranch": branch,
"err": err.Error(),
}).Warn("error checking for default branch, using master")
}
defBranch, err := c.getDefaultBranch(ctx, repo)
if err != nil {
return fmt.Errorf("could not get default branch: %w", err)
}

branch := repo.Branch
if branch == "" {
branch = defBranch
}

options := &github.RepositoryContentFileOptions{
Committer: &github.CommitAuthor{
Name: github.String(commitAuthor.Name),
Expand All @@ -167,36 +202,53 @@ func (c *githubClient) CreateFile(
options.Branch = &branch
}

if defBranch != branch && branch != "" {
_, res, err := c.client.Repositories.GetBranch(ctx, repo.Owner, repo.Name, branch, true)
if err != nil && (res == nil || res.StatusCode != 404) {
return fmt.Errorf("could not get branch %q: %w", branch, err)
}

if res.StatusCode == 404 {
defRef, _, err := c.client.Git.GetRef(ctx, repo.Owner, repo.Name, "refs/heads/"+defBranch)
if err != nil {
return fmt.Errorf("could not get ref %q: %w", "refs/heads/"+defBranch, err)
}

if _, _, err := c.client.Git.CreateRef(ctx, repo.Owner, repo.Name, &github.Reference{
Ref: github.String("refs/heads/" + branch),
Object: &github.GitObject{
SHA: defRef.Object.SHA,
},
}); err != nil {
return fmt.Errorf("could not create ref %q from %q: %w", "refs/heads/"+branch, defRef.Object.GetSHA(), err)
}
}
}

file, _, res, err := c.client.Repositories.GetContents(
ctx,
repo.Owner,
repo.Name,
path,
&github.RepositoryContentGetOptions{},
&github.RepositoryContentGetOptions{
Ref: branch,
},
)
if err != nil && (res == nil || res.StatusCode != 404) {
return err
return fmt.Errorf("could not get %q: %w", path, err)
}

if res.StatusCode == 404 {
_, _, err = c.client.Repositories.CreateFile(
ctx,
repo.Owner,
repo.Name,
path,
options,
)
return err
}
options.SHA = file.SHA
_, _, err = c.client.Repositories.UpdateFile(
options.SHA = github.String(file.GetSHA())
if _, _, err := c.client.Repositories.UpdateFile(
ctx,
repo.Owner,
repo.Name,
path,
options,
)
return err
); err != nil {
return fmt.Errorf("could not update %q: %w", path, err)
}
return nil
}

func (c *githubClient) CreateRelease(ctx *context.Context, body string) (string, error) {
Expand Down

0 comments on commit 8b1c4ea

Please sign in to comment.