Skip to content

Commit

Permalink
Fix test
Browse files Browse the repository at this point in the history
  • Loading branch information
knanao committed Mar 3, 2022
1 parent 8f5c889 commit 8d17ec5
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 69 deletions.
55 changes: 28 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,57 @@ An action that enables operating GitHub release via pull request. You send a pul
- Adding a RELEASE file to the repository. You can also have multiple RELEASE files in case of monorepo style. Its content looks like this:

``` yaml
tag: v0.1.0 # The tag number will be created. Required.
tag: v0.1.0 # The tag number will be created. Required.

# # Optional fields:
#
# name: string # The release name. Default is empty.
# title: string # The release title. Default is "Release ${tag}".
# targetCommitish: string # The release commitish. Default is the merged commit.
# releaseNote: string # The release body. Default is the auto-generated release note.
# prerelease: bool # True if this is a prerelease. Default is false.
# name: string # The release name. Default is empty.
# title: string # The release title. Default is "Release ${tag}".
# targetCommitish: string # The release commitish. Default is the merged commit.
# releaseNote: string # The release body. Default is the auto-generated release note.
# prerelease: bool # True if this is a prerelease. Default is false.
#
#
# # If specified, all matching commits will be excluded from release. Empty means excluding nothing.
#
# commitExclude:
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# prefixes: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# contains: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# prefixes: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# contains: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
#
#
# # If specified, all matching commits will be included to release. Empty means including alls.
#
# commitInclude:
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# prefixes: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# contains: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# prefixes: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# contains: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
#
#
# # List of categories and how to decide which category a commit should belong to.
#
# commitCategories:
# - title: string # Category title.
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# contains: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# prefixes: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
# - title: string # Category title.
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# contains: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# prefixes: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
#
#
# # Config used while generating release note.
#
# releaseNoteGenerator:
# showAbbrevHash: bool # Whether to include abbreviated hash value in release note. Default is false.
# showCommitter: bool # Whether to include committer in release note. Default is true.
# useReleaseNoteBlock: bool # Whether to use release note block instead of commit message. Default is false.
# commitExclude: # Additional excludes applied while generating release note.
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# prefixes: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# contains: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
# commitInclude: # Additional includes applied while generating release note.
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# prefixes: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# contains: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
# showAbbrevHash: bool # Whether to include abbreviated hash value in release note. Default is false.
# showCommitter: bool # Whether to include committer in release note. Default is true.
# useReleaseNoteBlock: bool # Whether to use release note block instead of commit message. Default is false.
# usePullRequestMetadata: bool # Whether to use pull request metadata instead of commit message when using merge-commit. If useReleaseNoteBlock is also true, release note block of pull request is used. Otherwise pull request title is used. If this option is set, showAbbrevHash and showCommitter is ignored. Default is false.
# commitExclude: # Additional excludes applied while generating release note.
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# prefixes: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# contains: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
# commitInclude: # Additional includes applied while generating release note.
# parentOfMergeCommit: bool # True is whether the commit is the parent commit of the matching merge commit. Default is false.
# prefixes: []string # Matches if commit's subject is prefixed by one of the given values. Default is emtpy.
# contains: []string # Matches if commit's body is containing one of the given values. Default is emtpy.
```

- Adding a new workflow (eg: `.github/workflows/gh-release.yaml`) with the content as below:
Expand Down
18 changes: 18 additions & 0 deletions git.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ func (c Commit) IsMerge() bool {
return len(c.ParentHashes) == 2
}

func (c Commit) PullRequestNumber() (int, bool) {
if !c.IsMerge() {
return 0, false
}

subs := defaultMergeCommitRegex.FindStringSubmatch(c.Subject)
if len(subs) != 2 {
return 0, false
}

prNumber, err := strconv.Atoi(subs[1])
if err != nil {
return 0, false
}

return prNumber, true
}

func parseCommits(log string) ([]Commit, error) {
lines := strings.Split(log, separator)
if len(lines) < 1 {
Expand Down
77 changes: 77 additions & 0 deletions github.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ import (
"io/ioutil"
"net/http"
"os"
"regexp"

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

var (
defaultMergeCommitRegex = regexp.MustCompile(`Merge pull request #([0-9]+) from .+`)
)

type githubEvent struct {
Name string

Expand Down Expand Up @@ -174,3 +179,75 @@ func (g *githubClient) getPullRequest(ctx context.Context, owner, repo string, n
pr, _, err := g.restClient.PullRequests.Get(ctx, owner, repo, number)
return pr, err
}

type (
PullRequestState string
PullRequestSort string
PullRequestDirection string
)

const (
// PullRequestState
PullRequestStateOpen PullRequestState = "open"
PullRequestStateClosed PullRequestState = "closed"
PullRequestStateAll PullRequestState = "all"

// PullRequestSort
PullRequestSortCreated PullRequestSort = "created"
PullRequestSortUpdated PullRequestSort = "updated"
PullRequestSortPopularity PullRequestSort = "popularity"
PullRequestSortLongRunning PullRequestSort = "long-running"

// PullRequestDirection
PullRequestDirectionAsc PullRequestDirection = "asc"
PullRequestDirectionDesc PullRequestDirection = "desc"
)

func (p PullRequestState) String() string {
return string(p)
}

func (p PullRequestSort) String() string {
return string(p)
}

func (p PullRequestDirection) String() string {
return string(p)
}

type ListPullRequestOptions struct {
State PullRequestState
Sort PullRequestSort
Direction PullRequestDirection
Limit int
}

func (g *githubClient) listPullRequests(ctx context.Context, owner, repo string, opt *ListPullRequestOptions) ([]*github.PullRequest, error) {
const perPage = 100
listOpts := github.ListOptions{PerPage: perPage}
opts := &github.PullRequestListOptions{
State: opt.State.String(),
Sort: opt.Sort.String(),
Direction: opt.Direction.String(),
ListOptions: listOpts,
}
ret := make([]*github.PullRequest, 0, perPage*10)
count := opt.Limit / perPage
for i := 0; i <= count; i++ {
prs, resp, err := g.restClient.PullRequests.List(ctx, owner, repo, opts)
if err != nil {
return nil, err
}
for _, pr := range prs {
if len(ret) == opt.Limit {
break
}
ret = append(ret, pr)
}
if resp.NextPage == 0 || len(ret) == opt.Limit {
break
}
opts.Page = resp.NextPage
}
return ret, nil
}
100 changes: 59 additions & 41 deletions release.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,15 @@ import (
"fmt"
"log"
"regexp"
"strconv"
"strings"

"github.com/creasty/defaults"
"github.com/google/go-github/v39/github"
"sigs.k8s.io/yaml"
)

var (
releaseNoteBlockRegex = regexp.MustCompile(`(?s)(?:Release note\*\*:\s*(?:<!--[^<>]*-->\s*)?` + "```(?:release-note)?|```release-note)(.+?)```")
defaultMergeCommitRegex = regexp.MustCompile(`Merge pull request #([0-9]+) from .+`)
releaseNoteBlockRegex = regexp.MustCompile(`(?s)(?:Release note\*\*:\s*(?:<!--[^<>]*-->\s*)?` + "```(?:release-note)?|```release-note)(.+?)```")
)

type ReleaseConfig struct {
Expand All @@ -54,12 +53,12 @@ type ReleaseCommitCategoryConfig struct {
}

type ReleaseNoteGeneratorConfig struct {
ShowAbbrevHash bool `json:"showAbbrevHash,omitempty" default:"false"`
ShowCommitter bool `json:"showCommitter,omitempty" default:"true"`
UseReleaseNoteBlock bool `json:"useReleaseNoteBlock,omitempty" default:"false"`
UseDefaultMergeCommitParser bool `json:"useDefaultMergeCommitParser,omitempty" default:"false"`
CommitInclude ReleaseCommitMatcherConfig `json:"commitInclude,omitempty"`
CommitExclude ReleaseCommitMatcherConfig `json:"commitExclude,omitempty"`
ShowAbbrevHash bool `json:"showAbbrevHash,omitempty" default:"false"`
ShowCommitter bool `json:"showCommitter,omitempty" default:"true"`
UseReleaseNoteBlock bool `json:"useReleaseNoteBlock,omitempty" default:"false"`
UsePullRequestMetadata bool `json:"usePullRequestMetadata,omitempty" default:"false"`
CommitInclude ReleaseCommitMatcherConfig `json:"commitInclude,omitempty"`
CommitExclude ReleaseCommitMatcherConfig `json:"commitExclude,omitempty"`
}

type ReleaseCommitMatcherConfig struct {
Expand Down Expand Up @@ -142,8 +141,9 @@ type ReleaseProposal struct {

type ReleaseCommit struct {
Commit
ReleaseNote string `json:"releaseNote,omitempty"`
CategoryName string `json:"categoryName,omitempty"`
ReleaseNote string `json:"releaseNote,omitempty"`
CategoryName string `json:"categoryName,omitempty"`
PullRequestNumber int `json:"pullRequestNumber,omitempty"`
}

func buildReleaseProposal(ctx context.Context, ghClient *githubClient, releaseFile string, gitExecPath, repoDir string, event *githubEvent) (*ReleaseProposal, error) {
Expand Down Expand Up @@ -172,7 +172,10 @@ func buildReleaseProposal(ctx context.Context, ghClient *githubClient, releaseFi
return nil, err
}

releaseCommits := buildReleaseCommits(ctx, ghClient, commits, *headCfg, event)
releaseCommits, err := buildReleaseCommits(ctx, ghClient, commits, *headCfg, event)
if err != nil {
return nil, err
}
p := ReleaseProposal{
Tag: headCfg.Tag,
Name: headCfg.Name,
Expand Down Expand Up @@ -202,7 +205,7 @@ func buildReleaseProposal(ctx context.Context, ghClient *githubClient, releaseFi
return &p, nil
}

func buildReleaseCommits(ctx context.Context, ghClient *githubClient, commits []Commit, cfg ReleaseConfig, event *githubEvent) []ReleaseCommit {
func buildReleaseCommits(ctx context.Context, ghClient *githubClient, commits []Commit, cfg ReleaseConfig, event *githubEvent) ([]ReleaseCommit, error) {
hashes := make(map[string]Commit, len(commits))
for _, commit := range commits {
hashes[commit.Hash] = commit
Expand Down Expand Up @@ -231,6 +234,26 @@ func buildReleaseCommits(ctx context.Context, ghClient *githubClient, commits []
}
}

gen, limit := cfg.ReleaseNoteGenerator, 1000
prs := make(map[int]*github.PullRequest, limit)
if gen.UsePullRequestMetadata {
opts := &ListPullRequestOptions{
State: PullRequestStateClosed,
Sort: PullRequestSortUpdated,
Direction: PullRequestDirectionDesc,
Limit: limit,
}
v, err := ghClient.listPullRequests(ctx, event.Owner, event.Repo, opts)
if err != nil {
log.Fatalf("Failed to list pull requests: %v\n", err)
return nil, err
}
for i := range v {
number := *v[i].Number
prs[number] = v[i]
}
}

out := make([]ReleaseCommit, 0, len(commits))
for _, commit := range commits {

Expand All @@ -244,42 +267,36 @@ func buildReleaseCommits(ctx context.Context, ghClient *githubClient, commits []
continue
}

releaseNote, err := determineReleaseNote(ctx, ghClient, commit, cfg.ReleaseNoteGenerator, event)
if err != nil {
log.Fatalf("Failed to determine release note: %v\n", err)
continue
}

c := ReleaseCommit{
Commit: commit,
ReleaseNote: releaseNote,
ReleaseNote: extractReleaseNote(commit.Subject, commit.Body, gen.UseReleaseNoteBlock),
CategoryName: determineCommitCategory(commit, mergeCommits[commit.Hash], cfg.CommitCategories),
}
out = append(out, c)
}
return out
}

func determineReleaseNote(ctx context.Context, ghClient *githubClient, c Commit, cfg ReleaseNoteGeneratorConfig, event *githubEvent) (string, error) {
if !cfg.UseDefaultMergeCommitParser {
return extractReleaseNote(c.Subject, c.Body, cfg.UseReleaseNoteBlock), nil
}
if gen.UsePullRequestMetadata {
prNumber, ok := commit.PullRequestNumber()
if !ok {
continue
}

subs := defaultMergeCommitRegex.FindStringSubmatch(c.Subject)
if len(subs) != 2 {
return c.Subject, nil
}
if pr, ok := prs[prNumber]; ok {
c.ReleaseNote = extractReleaseNote(pr.GetTitle(), pr.GetBody(), gen.UseReleaseNoteBlock)
c.PullRequestNumber = prNumber
continue
}

prNumber, err := strconv.Atoi(subs[1])
if err != nil {
return "", err
}
pr, err := ghClient.getPullRequest(ctx, event.Owner, event.Repo, prNumber)
if err != nil {
log.Fatalf("Failed to get pull request: %v\n", err)
continue
}
c.ReleaseNote = extractReleaseNote(pr.GetTitle(), pr.GetBody(), gen.UseReleaseNoteBlock)
c.PullRequestNumber = prNumber
}

pr, err := ghClient.getPullRequest(ctx, event.Owner, event.Repo, prNumber)
if err != nil {
return "", err
out = append(out, c)
}
return extractReleaseNote(pr.GetTitle(), pr.GetBody(), cfg.UseReleaseNoteBlock), nil
return out, nil
}

func extractReleaseNote(def, body string, useReleaseNoteBlock bool) string {
Expand Down Expand Up @@ -316,7 +333,8 @@ func renderReleaseNote(p ReleaseProposal, cfg ReleaseConfig) []byte {
gen := cfg.ReleaseNoteGenerator
renderCommit := func(c ReleaseCommit) {
b.WriteString(fmt.Sprintf("* %s", c.ReleaseNote))
if gen.UseDefaultMergeCommitParser {
if gen.UsePullRequestMetadata && c.PullRequestNumber != 0 {
b.WriteString(fmt.Sprintf(" ([#%d](https://github.com/%s/%s/pull/%d))", c.PullRequestNumber, p.Owner, p.Repo, c.PullRequestNumber))
b.WriteString("\n")
return
}
Expand Down
Loading

0 comments on commit 8d17ec5

Please sign in to comment.