From 4a8abf660ef05ca69d8ec021bac95f0c973744e3 Mon Sep 17 00:00:00 2001 From: Clara Fu Date: Thu, 30 Jul 2020 10:09:01 -0400 Subject: [PATCH] add support for generating release note from branch Previously, the generate command would only work for generating a release note using the latest release created from the repository. This was changed so that release branches that create older releases can still generate release notes from the release branch. For example, if the latest release is v6.0.0 and I have a v5.5.x release branch with the latest release on that branch being v5.5.5, I can generate a release note for that release branch for my next release of v5.5.6. It achieves this by grabbing all the last 50 releases made to the repository and then getting the commit SHAs that are tagged for those releases. It then goes through all the commits on the branch and tries to match it to one of the 50 commit release SHAs. If it finds one, it will fetch all the prs made after that commit. Also changed some formatting of the release note template so that it will not bold all the text for each pr. Signed-off-by: Clara Fu Co-authored-by: Alex Suraci --- cmd/generate.go | 9 ++- cmd/validate.go | 5 +- generate/generate.go | 4 +- generate/template.go | 2 +- github/github.go | 149 ++++++++++++++++++++++++------------------- 5 files changed, 97 insertions(+), 72 deletions(-) diff --git a/cmd/generate.go b/cmd/generate.go index 554793b..3f2d8ef 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -30,13 +30,18 @@ func generateReleaseNote(cmd *cobra.Command, args []string) { githubOwner, _ := cmd.Flags().GetString("github-owner") githubRepo, _ := cmd.Flags().GetString("github-repo") - commitSHA, err := client.FetchLatestReleaseCommitSHA(githubOwner, githubRepo) + releaseSHAs, err := client.FetchCommitsFromReleases(githubOwner, githubRepo) if err != nil { - failf("failed to fetch latest release commit SHA from github: %s", err) + failf("failed to fetch release commit SHAs from github: %s", err) } githubBranch, _ := cmd.Flags().GetString("github-branch") + commitSHA, err := client.FetchLatestReleaseCommitFromBranch(githubOwner, githubRepo, githubBranch, releaseSHAs) + if err != nil { + failf("failed to fetch latest release commit from branch: %s", err) + } + pullRequests, err := client.FetchPullRequestsAfterCommit(githubOwner, githubRepo, githubBranch, commitSHA) if err != nil { failf("failed to fetch pull requests: %s", err) diff --git a/cmd/validate.go b/cmd/validate.go index 07a21dd..c09650a 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -4,8 +4,8 @@ import ( "fmt" "strconv" - "github.com/clarafu/release-me/github" "github.com/clarafu/release-me/generate" + "github.com/clarafu/release-me/github" "github.com/spf13/cobra" ) @@ -15,7 +15,7 @@ var validateCmd = &cobra.Command{ Long: `Ensures that the pull request given has at least one of the labels required to properly generate a release note using the "generate" command.`, - Run: validate, + Run: validate, } func init() { @@ -49,4 +49,3 @@ func validate(cmd *cobra.Command, args []string) { fmt.Printf("pull request #%d has valid labels\n", prNumber) } - diff --git a/generate/generate.go b/generate/generate.go index 6293f69..d12c159 100644 --- a/generate/generate.go +++ b/generate/generate.go @@ -122,7 +122,7 @@ func Validate(labels []string) bool { for _, validLabel := range ValidLabels { validLabelsMap[validLabel] = true } - + for _, label := range labels { if _, exists := validLabelsMap[label]; exists { return true @@ -130,4 +130,4 @@ func Validate(labels []string) bool { } return false -} \ No newline at end of file +} diff --git a/generate/template.go b/generate/template.go index df69dfd..896d85c 100644 --- a/generate/template.go +++ b/generate/template.go @@ -31,7 +31,7 @@ const rawTemplate = ` ## {{$section.Icon}} {{$section.Title}} {{ range $pr := $section.PRs }} -* **{{$pr.Title}} (#{{$pr.Number}})** @{{$pr.Author}} :link: +* {{$pr.Title}} (#{{$pr.Number}}) @{{$pr.Author}} :link: {{ $pr.ReleaseNote | indent 2 }} {{end}} {{end}} diff --git a/github/github.go b/github/github.go index fbc9e70..2ba57f6 100644 --- a/github/github.go +++ b/github/github.go @@ -42,40 +42,8 @@ func New(token string) GitHub { } } -func (g GitHub) FetchLabelsForPullRequest(owner, repo string, pullRequestNumber int) ([]string, error) { - var PullRequestlabelsQuery struct { - Repository struct { - PullRequest struct { - Labels struct { - Nodes []struct { - Name string - } - } `graphql:"labels(first: 10)"` - } `graphql:"pullRequest(number: $prNumber)"` - } `graphql:"repository(owner: $owner, name: $name)"` - } - - PRVariables := map[string]interface{}{ - "owner": githubv4.String(owner), - "name": githubv4.String(repo), - "prNumber": githubv4.Int(pullRequestNumber), - } - - err := g.client.Query(context.Background(), &PullRequestlabelsQuery, PRVariables) - if err != nil { - return nil, err - } - - var labels []string - for _, node := range PullRequestlabelsQuery.Repository.PullRequest.Labels.Nodes { - labels = append(labels, node.Name) - } - - return labels, nil -} - -func (g GitHub) FetchLatestReleaseCommitSHA(owner, repo string) (string, error) { - var releaseSHAQuery struct { +func (g GitHub) FetchCommitsFromReleases(owner, repo string) (map[string]bool, error) { + var releaseSHAsQuery struct { Repository struct { Releases struct { Nodes []struct { @@ -85,60 +53,81 @@ func (g GitHub) FetchLatestReleaseCommitSHA(owner, repo string) (string, error) } } } - } `graphql:"releases(last: 1, orderBy:{direction: ASC, field: NAME})"` + } `graphql:"releases(first: 50, orderBy: {direction: DESC, field: CREATED_AT})"` } `graphql:"repository(owner: $owner, name: $name)"` } - releaseSHAVariables := map[string]interface{}{ + releaseSHAsVariables := map[string]interface{}{ "owner": githubv4.String(owner), "name": githubv4.String(repo), } - err := g.client.Query(context.Background(), &releaseSHAQuery, releaseSHAVariables) + err := g.client.Query(context.Background(), &releaseSHAsQuery, releaseSHAsVariables) if err != nil { - return "", err + return nil, err } - // If the repository does not have a release, return an empty string - var releaseSHA string - if len(releaseSHAQuery.Repository.Releases.Nodes) > 0 { - releaseSHA = releaseSHAQuery.Repository.Releases.Nodes[0].Tag.Target.Oid + releaseSHAs := map[string]bool{} + for _, release := range releaseSHAsQuery.Repository.Releases.Nodes { + releaseSHAs[release.Tag.Target.Oid] = true } - return releaseSHA, nil + return releaseSHAs, nil } -func (g GitHub) FetchCommitFromTag(owner, repo, tag string) (string, error) { - var releaseSHAQuery struct { +func (g GitHub) FetchLatestReleaseCommitFromBranch(owner, repo, branch string, releaseSHAs map[string]bool) (string, error) { + var commitsQuery struct { Repository struct { - Refs struct { - Nodes []struct { - Target struct { - Oid string - } + Ref struct { + Target struct { + Commit struct { + History struct { + Nodes []struct { + Oid string + } + PageInfo struct { + EndCursor githubv4.String + HasNextPage bool + } + } `graphql:"history(first: 100, after: $commitCursor)"` + } `graphql:"... on Commit"` } - } `graphql:"refs(refPrefix: "refs/tags/", first: 1, query: $tag)"` + } `graphql:"ref(qualifiedName: $branch)"` } `graphql:"repository(owner: $owner, name: $name)"` } - releaseSHAVariables := map[string]interface{}{ - "owner": githubv4.String(owner), - "name": githubv4.String(repo), - "tag": githubv4.String(tag), + commitsVariables := map[string]interface{}{ + "owner": githubv4.String(owner), + "name": githubv4.String(repo), + "branch": githubv4.String(branch), + "commitCursor": (*githubv4.String)(nil), } - err := g.client.Query(context.Background(), &releaseSHAQuery, releaseSHAVariables) - if err != nil { - return "", err - } + var lastCommit string + for { + err := g.client.Query(context.Background(), &commitsQuery, commitsVariables) + if err != nil { + return "", fmt.Errorf("failed to fetch commits from github: %w", err) + } + + history := commitsQuery.Repository.Ref.Target.Commit.History + for _, commit := range history.Nodes { + lastCommit = commit.Oid + + if _, found := releaseSHAs[commit.Oid]; found { + return commit.Oid, nil + } + } + + if !history.PageInfo.HasNextPage { + fmt.Printf("could not find a commit from the latest release, generating release note using all commits in branch %s\n", branch) + break + } - // If the repository does not have a release, return an empty string - var releaseSHA string - if len(releaseSHAQuery.Repository.Refs.Nodes) > 0 { - releaseSHA = releaseSHAQuery.Repository.Refs.Nodes[0].Target.Oid + commitsVariables["commitCursor"] = history.PageInfo.EndCursor } - return releaseSHA, nil + return lastCommit, nil } func (g GitHub) FetchPullRequestsAfterCommit(owner, repo, branch, commitSHA string) ([]PullRequest, error) { @@ -241,3 +230,35 @@ func (g GitHub) FetchPullRequestsAfterCommit(owner, repo, branch, commitSHA stri } return pullRequests, nil } + +func (g GitHub) FetchLabelsForPullRequest(owner, repo string, pullRequestNumber int) ([]string, error) { + var PullRequestlabelsQuery struct { + Repository struct { + PullRequest struct { + Labels struct { + Nodes []struct { + Name string + } + } `graphql:"labels(first: 10)"` + } `graphql:"pullRequest(number: $prNumber)"` + } `graphql:"repository(owner: $owner, name: $name)"` + } + + PRVariables := map[string]interface{}{ + "owner": githubv4.String(owner), + "name": githubv4.String(repo), + "prNumber": githubv4.Int(pullRequestNumber), + } + + err := g.client.Query(context.Background(), &PullRequestlabelsQuery, PRVariables) + if err != nil { + return nil, err + } + + var labels []string + for _, node := range PullRequestlabelsQuery.Repository.PullRequest.Labels.Nodes { + labels = append(labels, node.Name) + } + + return labels, nil +}