Skip to content

Commit f361487

Browse files
author
Dhia Ayachi
authored
Merge pull request hashicorp#1 from mikemorris/changelog-gen-cleanup
minor cleanup of changelog-gen command, rename to changelog-entry
2 parents 75d4493 + 48b2648 commit f361487

File tree

6 files changed

+277
-239
lines changed

6 files changed

+277
-239
lines changed

cmd/changelog-entry/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# changelog-entry
2+
3+
`changelog-entry` is a command that will generate a changelog entry based on the information passed and the information retrieved from the Github repository.
4+
5+
The default changelog entry template is embedded from [`changelog-entry.tmpl`](changelog-entry.tmpl) but a path to a custom template can also can be passed as parameter.
6+
7+
The type parameter can be one of the following:
8+
* bug
9+
* note
10+
* enhancement
11+
* new-resource
12+
* new-datasource
13+
* deprecation
14+
* breaking-change
15+
* feature
16+
17+
## Usage
18+
19+
```sh
20+
$ changelog-entry -type improvement -subcategory monitoring -description "optimize the monitoring endpoint to avoid losing logs when under high load"
21+
```
22+
23+
If parameters are missing the command will prompt to fill them, the pull request number is optional and if not provided the command will try to guess it based on the current branch name and remote if the current directory is in a git repository.
24+
25+
## Output
26+
27+
``````markdown
28+
```release-note:improvement
29+
monitoring: optimize the monitoring endpoint to avoid losing logs when under high load
30+
```
31+
``````
32+
33+
Any failures will be logged to stderr. The entry will be written to a file named `{PR_NUMBER}.txt`, in the current directory unless an output directory is specified.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:{{.Type}}
2+
{{if ne .Subcategory ""}}{{.Subcategory}}: {{end}}{{.Description}}{{if ne .URL ""}} [GH-{{.PR}}]({{.URL}}){{end}}
3+
```

cmd/changelog-entry/main.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"context"
6+
_ "embed"
7+
"errors"
8+
"flag"
9+
"fmt"
10+
"github.com/go-git/go-git/v5"
11+
"github.com/google/go-github/github"
12+
"github.com/hashicorp/go-changelog"
13+
"github.com/manifoldco/promptui"
14+
"os"
15+
"path"
16+
"regexp"
17+
"strings"
18+
"text/template"
19+
)
20+
21+
//go:embed changelog-entry.tmpl
22+
var changelogTmplDefault string
23+
24+
type Note struct {
25+
// service or area of codebase the pull request changes
26+
Subcategory string
27+
// release note type (Bug...)
28+
Type string
29+
// release note text
30+
Description string
31+
// pull request number
32+
PR int
33+
// URL of the pull request
34+
URL string
35+
}
36+
37+
func main() {
38+
pwd, err := os.Getwd()
39+
if err != nil {
40+
fmt.Fprintln(os.Stderr, err)
41+
os.Exit(1)
42+
}
43+
var subcategory, changeType, description, changelogTmpl, dir, url string
44+
var pr int
45+
var Url bool
46+
flag.BoolVar(&Url, "add-url", false, "add GitHub issue URL (omitted by default due to formatting in changelog-build)")
47+
flag.IntVar(&pr, "pr", -1, "pull request number")
48+
flag.StringVar(&subcategory, "subcategory", "", "the service or area of the codebase the pull request changes (optional)")
49+
flag.StringVar(&changeType, "type", "", "the type of change")
50+
flag.StringVar(&description, "description", "", "the changelog entry content")
51+
flag.StringVar(&changelogTmpl, "changelog-template", "", "the path of the file holding the template to use for the changelog entries")
52+
flag.StringVar(&dir, "dir", "", "the relative path from the current directory of where the changelog entry file should be written")
53+
flag.Parse()
54+
55+
if pr == -1 {
56+
pr, url, err = getPrNumberFromGithub(pwd)
57+
if err != nil {
58+
fmt.Fprintln(os.Stderr, "Must specify pull request number or run in a git repo with a GitHub remote origin:", err)
59+
fmt.Fprintln(os.Stderr, "")
60+
flag.Usage()
61+
os.Exit(1)
62+
}
63+
}
64+
fmt.Fprintln(os.Stderr, "Found matching pull request:", url)
65+
66+
if changeType == "" {
67+
prompt := promptui.Select{
68+
Label: "Select a change type",
69+
Items: changelog.TypeValues,
70+
}
71+
72+
_, changeType, err = prompt.Run()
73+
74+
if err != nil {
75+
fmt.Fprintln(os.Stderr, "Must specify the change type")
76+
fmt.Fprintln(os.Stderr, "")
77+
flag.Usage()
78+
os.Exit(1)
79+
}
80+
} else {
81+
if !changelog.TypeValid(changeType) {
82+
fmt.Fprintln(os.Stderr, "Must specify a valid type")
83+
fmt.Fprintln(os.Stderr, "")
84+
flag.Usage()
85+
os.Exit(1)
86+
}
87+
}
88+
89+
if subcategory == "" {
90+
prompt := promptui.Prompt{Label: "Subcategory (optional)"}
91+
subcategory, err = prompt.Run()
92+
}
93+
94+
if description == "" {
95+
prompt := promptui.Prompt{Label: "Description"}
96+
description, err = prompt.Run()
97+
if err != nil {
98+
fmt.Fprintln(os.Stderr, "Must specify the change description")
99+
fmt.Fprintln(os.Stderr, "")
100+
flag.Usage()
101+
os.Exit(1)
102+
}
103+
}
104+
105+
var tmpl *template.Template
106+
if changelogTmpl != "" {
107+
file, err := os.ReadFile(changelogTmpl)
108+
if err != nil {
109+
os.Exit(1)
110+
}
111+
tmpl, err = template.New("").Parse(string(file))
112+
if err != nil {
113+
os.Exit(1)
114+
}
115+
} else {
116+
tmpl, err = template.New("").Parse(changelogTmplDefault)
117+
if err != nil {
118+
os.Exit(1)
119+
}
120+
}
121+
122+
if !Url {
123+
url = ""
124+
}
125+
n := Note{Type: changeType, Description: description, Subcategory: subcategory, PR: pr, URL: url}
126+
127+
var buf bytes.Buffer
128+
err = tmpl.Execute(&buf, n)
129+
fmt.Printf("\n%s\n", buf.String())
130+
if err != nil {
131+
os.Exit(1)
132+
}
133+
filename := fmt.Sprintf("%d.txt", pr)
134+
filepath := path.Join(pwd, dir, filename)
135+
err = os.WriteFile(filepath, buf.Bytes(), 0644)
136+
if err != nil {
137+
os.Exit(1)
138+
}
139+
fmt.Fprintln(os.Stderr, "Created changelog entry at", filepath)
140+
}
141+
142+
func OpenGit(path string) (*git.Repository, error) {
143+
r, err := git.PlainOpen(path)
144+
if err != nil {
145+
if path == "/" {
146+
return r, err
147+
} else {
148+
return OpenGit(path[:strings.LastIndex(path, "/")])
149+
}
150+
}
151+
return r, err
152+
}
153+
154+
func getPrNumberFromGithub(path string) (int, string, error) {
155+
r, err := OpenGit(path)
156+
if err != nil {
157+
return -1, "", err
158+
}
159+
160+
ref, err := r.Head()
161+
if err != nil {
162+
return -1, "", err
163+
}
164+
165+
localBranch, err := r.Branch(ref.Name().Short())
166+
if err != nil {
167+
return -1, "", err
168+
}
169+
170+
remote, err := r.Remote("origin")
171+
if err != nil {
172+
return -1, "", err
173+
}
174+
175+
if len(remote.Config().URLs) <= 0 {
176+
return -1, "", errors.New("not able to parse repo and org")
177+
}
178+
remoteUrl := remote.Config().URLs[0]
179+
180+
re := regexp.MustCompile(`.*github\.com:(.*)/(.*)\.git`)
181+
m := re.FindStringSubmatch(remoteUrl)
182+
if len(m) < 3 {
183+
return -1, "", errors.New("not able to parse repo and org")
184+
}
185+
186+
cli := github.NewClient(nil)
187+
188+
ctx := context.Background()
189+
190+
githubOrg := m[1]
191+
githubRepo := m[2]
192+
193+
opt := &github.PullRequestListOptions{
194+
ListOptions: github.ListOptions{PerPage: 200},
195+
Sort: "updated",
196+
Direction: "desc",
197+
}
198+
199+
list, _, err := cli.PullRequests.List(ctx, githubOrg, githubRepo, opt)
200+
if err != nil {
201+
return -1, "", err
202+
}
203+
204+
for _, pr := range list {
205+
head := pr.GetHead()
206+
if head == nil {
207+
continue
208+
}
209+
210+
branch := head.GetRef()
211+
if branch == "" {
212+
continue
213+
}
214+
215+
repo := head.GetRepo()
216+
if repo == nil {
217+
continue
218+
}
219+
220+
// Allow finding PRs from forks - localBranch.Remote will return the
221+
// remote name for branches of origin, but the remote URL for forks
222+
var gitRemote string
223+
remote, err := r.Remote(localBranch.Remote)
224+
if err != nil {
225+
gitRemote = localBranch.Remote
226+
} else {
227+
gitRemote = remote.Config().URLs[0]
228+
}
229+
230+
if (gitRemote == *repo.SSHURL || gitRemote == *repo.CloneURL) &&
231+
localBranch.Name == branch {
232+
n := pr.GetNumber()
233+
234+
if n != 0 {
235+
return n, pr.GetHTMLURL(), nil
236+
}
237+
}
238+
}
239+
240+
return -1, "", errors.New("no match found")
241+
}

cmd/changelog-gen/README.md

Lines changed: 0 additions & 33 deletions
This file was deleted.

cmd/changelog-gen/changelog.tmpl

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)