Skip to content

Commit

Permalink
feat: PRs contain version numbers and labels of the bump type (#153)
Browse files Browse the repository at this point in the history
* chore: PR titles now contain target version numbers + bump type

* chore: and labels
  • Loading branch information
ThomasRooney authored Jul 23, 2024
1 parent 5fb4160 commit 6e557ff
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 31 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ require (
github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.11.0
github.com/google/go-github/v54 v54.0.0
github.com/google/go-github/v63 v63.0.0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-version v1.6.0
github.com/pb33f/libopenapi v0.14.0
github.com/speakeasy-api/git-diff-parser v0.0.3
github.com/speakeasy-api/sdk-gen-config v1.7.4
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.7.1
github.com/speakeasy-api/versioning-reports v0.4.0
github.com/speakeasy-api/versioning-reports v0.6.0
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20231127185646-65229373498e
golang.org/x/oauth2 v0.11.0
Expand Down
12 changes: 4 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v54 v54.0.0 h1:OZdXwow4EAD5jEo5qg+dGFH2DpkyZvVsAehjvJuUL/c=
github.com/google/go-github/v54 v54.0.0/go.mod h1:Sw1LXWHhXRZtzJ9LI5fyJg9wbQzYvFhW8W5P2yaAQ7s=
github.com/google/go-github/v63 v63.0.0 h1:13xwK/wk9alSokujB9lJkuzdmQuVn2QCPeck76wR3nE=
github.com/google/go-github/v63 v63.0.0/go.mod h1:IqbcrgUmIcEaioWrGYei/09o+ge5vhffGOcxrO0AfmA=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
Expand Down Expand Up @@ -140,14 +142,8 @@ github.com/speakeasy-api/sdk-gen-config v1.7.4 h1:hk3GaKiL6zxx3SulnxdunvU2V7bUSQ
github.com/speakeasy-api/sdk-gen-config v1.7.4/go.mod h1:4R+8FTyM6UdLHltOVAigIoR5D2UfPsGMmEFzPOP1yCs=
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.7.1 h1:8cfPRFXn9a7IMBAQFhQ0N4rKCHMqWclVBGTEmXKXrZ4=
github.com/speakeasy-api/speakeasy-client-sdk-go/v3 v3.7.1/go.mod h1:b4fiZ1Wid0JHwwiYqhaPifDwjmC15uiN7A8Cmid+9kw=
github.com/speakeasy-api/versioning-reports v0.2.0 h1:ha0B8q8rY+KD6ETTOKXkfs/p3Tb3xIN3l9/LUoG34rY=
github.com/speakeasy-api/versioning-reports v0.2.0/go.mod h1:e3cvYTwPB3RJdgAPbf7Gc6BDfBM63bF3V+In2yKbHWs=
github.com/speakeasy-api/versioning-reports v0.2.1 h1:bf6PayXjF2vpvy8St7XTUcQA2Jd0rPvxsrimOrn/ASg=
github.com/speakeasy-api/versioning-reports v0.2.1/go.mod h1:e3cvYTwPB3RJdgAPbf7Gc6BDfBM63bF3V+In2yKbHWs=
github.com/speakeasy-api/versioning-reports v0.3.1 h1:AtynRZBTAc1BcHWU984yHNBLoyBrf0M6F0xUWEIWFHI=
github.com/speakeasy-api/versioning-reports v0.3.1/go.mod h1:LW5FABrvi5SBbeiD3HJYw0JZYe6Rw2Xna59pFJ2BmLI=
github.com/speakeasy-api/versioning-reports v0.4.0 h1:B1AxSUZ7iB2iM7yz5D3fKR4u1UtLw1o5+VQucuLg0Yg=
github.com/speakeasy-api/versioning-reports v0.4.0/go.mod h1:LW5FABrvi5SBbeiD3HJYw0JZYe6Rw2Xna59pFJ2BmLI=
github.com/speakeasy-api/versioning-reports v0.6.0 h1:oLokEQ7xnDXqWAQk60Sk+ifwYaRbq3BrLX2KyT8gWxE=
github.com/speakeasy-api/versioning-reports v0.6.0/go.mod h1:LW5FABrvi5SBbeiD3HJYw0JZYe6Rw2Xna59pFJ2BmLI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
Expand Down
2 changes: 1 addition & 1 deletion internal/actions/runWorkflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"
"strings"

"github.com/google/go-github/v54/github"
"github.com/google/go-github/v63/github"
"github.com/speakeasy-api/versioning-reports/versioning"

"github.com/speakeasy-api/sdk-generation-action/internal/configuration"
Expand Down
209 changes: 190 additions & 19 deletions internal/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"github.com/speakeasy-api/sdk-generation-action/internal/logging"
"github.com/speakeasy-api/sdk-generation-action/pkg/releases"

"github.com/google/go-github/v54/github"
"github.com/google/go-github/v63/github"
"golang.org/x/oauth2"
)

Expand Down Expand Up @@ -177,20 +177,20 @@ func (g *Git) FindExistingPR(branchName string, action environment.Action, sourc

var prTitle string
if action == environment.ActionRunWorkflow || action == environment.ActionFinalize {
prTitle = getGenPRTitle()
prTitle = getGenPRTitlePrefix()
if sourceGeneration {
prTitle = getGenSourcesTitle()
prTitle = getGenSourcesTitlePrefix()
}
} else if action == environment.ActionFinalize || action == environment.ActionFinalizeSuggestion {
prTitle = getSuggestPRTitle()
prTitle = getSuggestPRTitlePrefix()
}

if environment.IsDocsGeneration() {
prTitle = getDocsPRTitle()
prTitle = getDocsPRTitlePrefix()
}

for _, p := range prs {
if strings.Compare(p.GetTitle(), prTitle) == 0 {
if strings.HasPrefix(p.GetTitle(), prTitle) {
logging.Info("Found existing PR %s", *p.Title)

if branchName != "" && p.GetHead().GetRef() != branchName {
Expand Down Expand Up @@ -427,6 +427,8 @@ func (g *Git) CreateOrUpdatePR(info PRInfo) error {
var changelog string
var err error

labelTypes := g.UpsertLabelTypes(context.Background())

var previousGenVersions []string

if info.PreviousGenVersion != "" {
Expand Down Expand Up @@ -537,25 +539,28 @@ Based on:
if len(body) > maxBodyLength {
body = body[:maxBodyLength-3] + "..."
}
title := getGenPRTitlePrefix()
if environment.IsDocsGeneration() {
title = getDocsPRTitlePrefix()
} else if info.SourceGeneration {
title = getGenSourcesTitlePrefix()
}
suffix, labels := PRMetadata(info.VersioningReport, labelTypes)
title += suffix

if info.PR != nil {
logging.Info("Updating PR")

info.PR.Body = github.String(body)
info.PR.Title = &title
info.PR, _, err = g.client.PullRequests.Edit(context.Background(), os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), info.PR.GetNumber(), info.PR)
g.setPRLabels(context.Background(), os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), info.PR.GetNumber(), labelTypes, info.PR.Labels, labels)
if err != nil {
return fmt.Errorf("failed to update PR: %w", err)
}
} else {
logging.Info("Creating PR")

title := getGenPRTitle()
if environment.IsDocsGeneration() {
title = getDocsPRTitle()
} else if info.SourceGeneration {
title = getGenSourcesTitle()
}

info.PR, _, err = g.client.PullRequests.Create(context.Background(), os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), &github.NewPullRequest{
Title: github.String(title),
Body: github.String(body),
Expand All @@ -569,6 +574,8 @@ Based on:
messageSuffix += "\nNavigate to Settings > Actions > Workflow permissions and ensure that allow GitHub Actions to create and approve pull requests is checked. For more information see https://www.speakeasyapi.dev/docs/advanced-setup/github-setup"
}
return fmt.Errorf("failed to create PR: %w%s", err, messageSuffix)
} else if info.PR != nil && len(labels) > 0 {
g.setPRLabels(context.Background(), os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), info.PR.GetNumber(), labelTypes, info.PR.Labels, labels)
}
}

Expand All @@ -582,6 +589,84 @@ Based on:
return nil
}

func (g *Git) setPRLabels(background context.Context, owner string, repo string, issueNumber int, labelTypes map[string]github.Label, actualLabels, desiredLabels []*github.Label) {
shouldRemove := []string{}
shouldAdd := []string{}
for _, label := range actualLabels {
foundInDesired := false
for _, desired := range desiredLabels {
if label.GetName() == desired.GetName() {
foundInDesired = true
break
}
if _, ok := labelTypes[label.GetName()]; !ok {
foundInDesired = true
continue
}
break
}
if !foundInDesired {
shouldRemove = append(shouldRemove, label.GetName())
}
}
for _, desired := range desiredLabels {
foundInActual := false
for _, label := range actualLabels {
if label.GetName() == desired.GetName() {
foundInActual = true
break
}
}
if !foundInActual {
shouldAdd = append(shouldAdd, desired.GetName())
}
}
if len(shouldAdd) > 0 {
_, _, err := g.client.Issues.AddLabelsToIssue(background, owner, repo, issueNumber, shouldAdd)
if err != nil {
logging.Info("failed to add labels %v: %s", shouldAdd, err.Error())
}
}
if len(shouldRemove) > 0 {
for _, label := range shouldRemove {
_, err := g.client.Issues.RemoveLabelForIssue(background, owner, repo, issueNumber, label)
if err != nil {
logging.Info("failed to remove labels %s: %s", label, err.Error())
}
}
}
}

func notEquivalent(desired []*github.Label, actual []*github.Label) bool {
desiredByName := make(map[string]bool)
for _, label := range desired {
desiredByName[label.GetName()] = true
}
if len(desiredByName) != len(actual) {
return true
}
for _, label := range actual {
_, ok := desiredByName[label.GetName()]
if !ok {
return true
}
}
return false
}

func reapplySuffix(title *string, suffix string) *string {
if title == nil {
return nil
}
split := strings.Split(*title, "🐝")
if len(split) < 2 {
return title
}
// take first two sections
*title = strings.Join(split[:2], "🐝") + suffix
return title
}

func stripCodes(str string) string {
const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
var re = regexp.MustCompile(ansi)
Expand Down Expand Up @@ -614,7 +699,7 @@ Based on:
logging.Info("Creating PR")

pr, _, err = g.client.PullRequests.Create(context.Background(), os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), &github.NewPullRequest{
Title: github.String(getDocsPRTitle()),
Title: github.String(getDocsPRTitlePrefix()),
Body: github.String(body),
Head: github.String(branchName),
Base: github.String(environment.GetRef()),
Expand All @@ -635,13 +720,53 @@ Based on:
return nil
}

func PRMetadata(m *versioning.MergedVersionReport, labelTypes map[string]github.Label) (string, []*github.Label) {
if m == nil {
return "", []*github.Label{}
}
labels := []*github.Label{}
skipBumpType := false
skipVersionNumber := false
singleBumpType := ""
singleNewVersion := ""
for _, report := range m.Reports {
if len(report.BumpType) > 0 && report.BumpType != versioning.BumpNone && report.BumpType != versioning.BumpCustom {
if len(singleBumpType) > 0 {
skipBumpType = true
}
singleBumpType = string(report.BumpType)
}
if len(report.NewVersion) > 0 {
if len(singleNewVersion) > 0 {
skipVersionNumber = true
}
singleNewVersion = report.NewVersion
}
}
var builder []string
if !skipVersionNumber {
builder = append(builder, singleNewVersion)
}
if !skipBumpType {
if matched, ok := labelTypes[singleBumpType]; ok {
labels = append(labels, &matched)
}
}
// Add an extra " " at front
if len(builder) > 0 {
builder = append([]string{""}, builder...)
}
return strings.Join(builder, " "), labels
}


func (g *Git) CreateSuggestionPR(branchName, output string) (*int, string, error) {
body := fmt.Sprintf(`Generated OpenAPI Suggestions by Speakeasy CLI.
Outputs changes to *%s*.`, output)

logging.Info("Creating PR")

fmt.Println(body, branchName, getSuggestPRTitle(), environment.GetRef())
fmt.Println(body, branchName, getSuggestPRTitlePrefix(), environment.GetRef())

pr, _, err := g.client.PullRequests.Create(context.Background(), os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), &github.NewPullRequest{
Title: github.String("Speakeasy OpenAPI Suggestions -" + environment.GetWorkflowName()),
Expand Down Expand Up @@ -894,6 +1019,52 @@ func (g *Git) CreateTag(tag string, hash string) error {
return nil
}

func (g *Git) UpsertLabelTypes(ctx context.Context) map[string]github.Label {
desiredLabels := map[string]github.Label{}
addGitHubLabel := func(name, description string) {
desiredLabels[name] = github.Label{
Name: &name,
Description: &description,
}
}
addGitHubLabel(string(versioning.BumpMajor), "Major version bump")
addGitHubLabel(string(versioning.BumpMinor), "Minor version bump")
addGitHubLabel(string(versioning.BumpPatch), "Patch version bump")
addGitHubLabel(string(versioning.BumpGraduate), "Graduate prerelease to stable")
addGitHubLabel(string(versioning.BumpPrerelease), "Bump by a prerelease version")
actualLabels := make(map[string]github.Label)
allLabels, _, err := g.client.Issues.ListLabels(ctx, os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), nil)
if err != nil {
return actualLabels
}
for _, label := range allLabels {
actualLabels[*label.Name] = *label
}


for _, label := range desiredLabels {
foundLabel, ok := actualLabels[*label.Name]
if ok {
if *foundLabel.Description != *label.Description {
_, _, err = g.client.Issues.EditLabel(ctx, os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), *label.Name, &github.Label{
Name: label.Name,
Description: label.Description,
})
if err != nil {
return actualLabels
}
}
} else {
_, _, err = g.client.Issues.CreateLabel(ctx, os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), &label)
if err != nil {
return actualLabels
}
}
actualLabels[*label.Name] = label
}
return actualLabels
}

func getGithubAuth(accessToken string) *gitHttp.BasicAuth {
return &gitHttp.BasicAuth{
Username: "gen",
Expand All @@ -914,23 +1085,23 @@ const (
speakeasyDocsPRTitle = "chore: 🐝 Update SDK Docs - "
)

func getGenPRTitle() string {
func getGenPRTitlePrefix() string {
title := speakeasyGenPRTitle + environment.GetWorkflowName()
if environment.SpecifiedTarget() != "" && !strings.Contains(title, strings.ToUpper(environment.SpecifiedTarget())) {
title += " " + strings.ToUpper(environment.SpecifiedTarget())
}
return title
}

func getGenSourcesTitle() string {
func getGenSourcesTitlePrefix() string {
return speakeasyGenSpecsTitle + environment.GetWorkflowName()
}

func getDocsPRTitle() string {
func getDocsPRTitlePrefix() string {
return speakeasyDocsPRTitle + environment.GetWorkflowName()
}

func getSuggestPRTitle() string {
func getSuggestPRTitlePrefix() string {
return speakeasySuggestPRTitle + environment.GetWorkflowName()
}

Expand Down
2 changes: 1 addition & 1 deletion internal/git/releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"path/filepath"
"strings"

"github.com/google/go-github/v54/github"
"github.com/google/go-github/v63/github"
"github.com/speakeasy-api/sdk-generation-action/internal/environment"
"github.com/speakeasy-api/sdk-generation-action/internal/telemetry"
"github.com/speakeasy-api/sdk-generation-action/pkg/releases"
Expand Down
2 changes: 1 addition & 1 deletion internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"path/filepath"
"strings"

"github.com/google/go-github/v54/github"
"github.com/google/go-github/v63/github"
"github.com/speakeasy-api/versioning-reports/versioning"

"github.com/speakeasy-api/sdk-gen-config/workflow"
Expand Down

0 comments on commit 6e557ff

Please sign in to comment.