Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add git_trailers input
Browse files Browse the repository at this point in the history
peaceiris committed Dec 24, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 4180e4b commit 99f3d1f
Showing 5 changed files with 155 additions and 46 deletions.
38 changes: 27 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# github-actions-merger
github-actions-merger is github actions that merges pull request with commit message including pull request labels and release-note block.

github-actions-merger is a custom GitHub Action that merges pull request with metadata (commit message, pull request labels, release-note block, and git trailers).

## Usage
1. Write your workflow file.
```

Write your workflow file.

```yaml
- name: merge
uses: abema/github-actions-merger@main
with:
@@ -14,29 +17,37 @@ github-actions-merger is github actions that merges pull request with commit mes
"comment": ${{ github.event.comment.body }}
"mergers": 'na-ga,0daryo'
```
https://github.com/abema/github-actions-merger/blob/main/.github/workflows/github-actions-merger.yaml
2. comment ```/merge``` on github pull request comment.
PullRequest body can include release-note block.
Post a comment with ```/merge``` on a GitHub pull request.

Pull-request body can include release-note block.

e.g.

e.g.
```release-note
Breaking change!
```

3. pull request is merged, and commit message includes labels and release-note block.
```
3. The pull request will be merged, and commit message includes labels and release-note block.

~~~md
fix: readme
---
Labels:
* documentation
* enhancement
release-note:
* Breaking change!
```release-note
Breaking change!
```
~~~

## Parameters

You need to set parameters in workflow.
```

```yaml
github_token: ${{ secrets.GITHUB_TOKEN }}
owner: ${{ github.event.repository.owner.login }}
repo: ${{ github.event.repository.name }}
@@ -45,15 +56,20 @@ comment: ${{ github.event.comment.body }}
merge_method: 'merge'
mergers: 'comma separeted github usernames. every user is allowed if not specified'
enable_auto_merge: true
git_trailers: 'Co-authored-by=abema,Co-authored-by=actions'
```


## Options

### Enable Auto Merge

- [About auto merge](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request#about-auto-merge)
- You can use the auto merge when `enable_auto_merge` is true.
- Default is `false`.
- For more information about enabling auto merge to see the Note: [Enabling auto-merge](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request#about-auto-merge).


## Note

**Setting Branch protection rules is recommended to avoid unexpected merge of pull requests.**
5 changes: 4 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: 'github-actions-merger'
description: 'merge pull request with labels'
description: 'Merge pull-requests with metadata'
author: 'abema'
branding:
icon: 'git-merge'
@@ -33,3 +33,6 @@ inputs:
enable_auto_merge:
description: 'enable auto merge'
required: false
git_trailers:
description: 'git trailers'
required: false
78 changes: 55 additions & 23 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"bytes"
"context"
"fmt"
"os"
@@ -27,6 +26,7 @@ type env struct {
Mergers []string `envconfig:"MERGERS"`
Actor string `envconfig:"GITHUB_ACTOR"` // github user who initiated the workflow.
EnableAutoMerge bool `envconfig:"ENABLE_AUTO_MERGE" default:"false"`
GitTrailers []string `envconfig:"GIT_TRAILERS"`
}

const (
@@ -58,7 +58,7 @@ func main() {
fmt.Printf("failed to validate env: %v", err)
panic(err.Error())
}
if err := client.merge(ctx, e.Owner, e.Repo, e.PRNumber, e.MergeMethod, e.EnableAutoMerge); err != nil {
if err := client.merge(ctx, e.Owner, e.Repo, e.PRNumber, e.MergeMethod, e.EnableAutoMerge, e.GitTrailers); err != nil {
if serr := client.sendMsg(ctx, e.Owner, e.Repo, e.PRNumber, errMsg(err)); serr != nil {
fmt.Printf("failed to send message: %v original: %v", serr, err)
panic(serr.Error())
@@ -111,12 +111,14 @@ func newGHClient(token string) *ghClient {
}
}

func (gh *ghClient) merge(ctx context.Context, owner, repo string, prNumber int, mergeMethod string, enableAutoMerge bool) error {
func (gh *ghClient) merge(
ctx context.Context, owner, repo string, prNumber int, mergeMethod string, enableAutoMerge bool, gitTrailers []string,
) error {
pr, _, err := gh.client.PullRequests.Get(ctx, owner, repo, prNumber)
if err != nil {
return fmt.Errorf("failed to get pull request: %w", err)
}
commitMsg, err := generateCommitBody(pr)
commitMsg, err := generateCommitBody(pr, gitTrailers)
if err != nil {
return fmt.Errorf("failed to generate template: %w", err)
}
@@ -140,13 +142,13 @@ func generateCommitSubject(pr *github.PullRequest) string {
return fmt.Sprintf("%s (#%d)", pr.GetTitle(), pr.GetNumber())
}

func generateCommitBody(pr *github.PullRequest) (string, error) {
body := newCommitBody(pr)
o := new(bytes.Buffer)
if err := bodyTpl.Execute(o, body); err != nil {
func generateCommitBody(pr *github.PullRequest, gitTrailers []string) (string, error) {
body := newCommitBody(pr, gitTrailers)
t, err := getTemplate(body)
if err != nil {
return "", err
}
return o.String(), nil
return t, nil
}

func (gh *ghClient) sendMsg(ctx context.Context, owner, repo string, prNumber int, msg string) error {
@@ -159,38 +161,68 @@ func (gh *ghClient) sendMsg(ctx context.Context, owner, repo string, prNumber in
return nil
}

func newCommitBody(pr *github.PullRequest) commitBody {
func newCommitBody(pr *github.PullRequest, gitTrailersInput []string) commitBody {
labels := make([]string, 0, len(pr.Labels))
for _, l := range pr.Labels {
labels = append(labels, l.GetName())
}

description, releaseNote := splitReleaseNote(pr.GetBody())

gitTrailers := make([]gitTrailer, 0, len(gitTrailersInput))
for _, i := range gitTrailersInput {
kv := strings.SplitN(i, "=", 2)
if len(kv) != 2 {
continue
}
gitTrailers = append(gitTrailers, gitTrailer{Key: kv[0], Value: kv[1]})
}

return commitBody{
Message: description,
Labels: labels,
ReleaseNote: releaseNote,
GitTrailers: gitTrailers,
}
}

type commitBody struct {
Labels []string
Message string
ReleaseNote string
GitTrailers []gitTrailer
}

type gitTrailer struct {
Key string
Value string
}

var bodyTpl = template.Must(template.New("commit").Parse(`
{{- if .Message }}
{{ .Message }}
{{- end }}
{{if .Labels}}
Labels:
{{- range .Labels }}
* {{ . }}
{{- end -}}
{{- end -}}
` +
"\n\n```release-note\n* {{ .ReleaseNote }}\n```",
))
func getTemplate(commitBody commitBody) (string, error) {
tmpl, err := template.ParseFiles("template/commit.tmpl")
if err != nil {
return "", err
}

data := struct {
Labels []string
Message string
ReleaseNote string
GitTrailers []gitTrailer
}{
Labels: commitBody.Labels,
Message: commitBody.Message,
ReleaseNote: commitBody.ReleaseNote,
GitTrailers: commitBody.GitTrailers,
}

var b strings.Builder
if err := tmpl.Execute(&b, data); err != nil {
return "", err
}

return b.String(), nil
}

var (
needApproveRegexp = regexp.MustCompile("At least ([0-9]+) approving review is required by reviewers with write access")
58 changes: 47 additions & 11 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -9,7 +9,8 @@ import (

func Test_ghClient_generateCommitBody(t *testing.T) {
type args struct {
pr *github.PullRequest
pr *github.PullRequest
gitTrailers []string
}
tests := []struct {
name string
@@ -32,14 +33,16 @@ func Test_ghClient_generateCommitBody(t *testing.T) {
},
},
},
want: `
pull request body
want: `---
Labels:
* label1
* label2` +
* label2
---
pull request body
---
` +
"```release-note\n" +
"* NONE\n" +
"NONE\n" +
"```",
wantErr: false,
},
@@ -51,8 +54,13 @@ Labels:
},
},
want: `
---
pull request body
` + "```release-note\n* NONE\n```",
---
` +
"```release-note\n" +
"NONE\n" +
"```",
wantErr: false,
},
{
@@ -63,21 +71,49 @@ pull request body
},
},
want: `
---
pull request body
---
` +
"```release-note\n" +
"This is greate a release!!!\n" +
"```",
wantErr: false,
},
{
name: "with git trailers",
args: args{
pr: &github.PullRequest{
Body: github.String("pull request body"),
},
gitTrailers: []string{
"Co-authored-by=abema",
"Co-authored-by=actions",
},
},
want: `Co-authored-by: abema
Co-authored-by: actions
---
pull request body
---
` +
"\n```release-note\n* This is greate a release!!!\n```",
"```release-note\n" +
"NONE\n" +
"```",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := generateCommitBody(tt.args.pr)
got, err := generateCommitBody(tt.args.pr, tt.args.gitTrailers)
if (err != nil) != tt.wantErr {
t.Errorf("ghClient.generateCommitMessage() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("err = %v, wantErr = %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ghClient.generateCommitMessage() = %v, want %v", got, tt.want)
t.Errorf("got = %v, want = %v", got, tt.want)
}
})
}
22 changes: 22 additions & 0 deletions template/commit.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{{- if .GitTrailers -}}
{{- range .GitTrailers -}}
{{ .Key }}: {{ .Value }}
{{ end -}}
{{- end -}}
{{ if .Labels -}}
---
Labels:
{{- range .Labels }}
* {{ . }}
{{- end -}}
{{- end -}}
{{ if .Message }}
---
{{ .Message }}
{{ end }}
{{- if .ReleaseNote -}}
---
```release-note
{{ .ReleaseNote }}
```
{{- end -}}

0 comments on commit 99f3d1f

Please sign in to comment.