Changelog Overhaul 2 #19

Merged
zeripath merged 25 commits from jolheiser/changelog:gitea-sdk into master 2020-01-24 16:30:19 +00:00
15 changed files with 477 additions and 197 deletions
Showing only changes of commit 110c9f6950 - Show all commits

View File

@ -10,8 +10,8 @@ import (
Review

copyright head ? @lunny

copyright head ? @lunny
const (
exampleFile = "changelog.example.yml"
writeFile = "config_default.go"
tmpl = `package main
writeFile = "config/config_default.go"
tmpl = `package config
func init() {
defaultConfig = []byte(` + "`" + `%s` + "`" + `)

View File

@ -1,5 +1,11 @@
# The full repository name
Review

copyright head ? @lunny

copyright head ? @lunny
repo: go-gitea/gitea
repo: gitea/tea
# Service type (gitea or github)
service: gitea
# Base URL for Gitea instance if using gitea service type
base-url: https://gitea.com

If the example base-url points to gitea.com, shouldn't the service match it with gitea instead of github?

If the example `base-url` points to gitea.com, shouldn't the service match it with `gitea` instead of `github`?

Should we really attempt to infer service type based on this URL?

Should we really attempt to infer service type based on this URL?

I didn't mean it like that. I thought this file would make more sense either like:

# Service type (gitea or github)
service: gitea

# Base URL for Gitea instance if using gitea service type
base-url: https://gitea.com

or

# Service type (gitea or github)
service: github

# Base URL for Gitea instance if using GitHub service type
base-url: https://github.com

Currently it looks mixed up: service says github but URL is gitea.com (if it's intentional, I'm missing the point)

I didn't mean it like that. I thought this file would make more sense either like: ``` # Service type (gitea or github) service: gitea # Base URL for Gitea instance if using gitea service type base-url: https://gitea.com ``` or ``` # Service type (gitea or github) service: github # Base URL for Gitea instance if using GitHub service type base-url: https://github.com ``` Currently it looks mixed up: service says `github` but URL is `gitea.com` (if it's intentional, I'm missing the point)

Ah, no that's okay.
The reason I did it this way is because https://gitea.com is the default gitea instance, yet currently we are still primarily using GitHub, so I left the service set as such.
No need to set a base-url for GitHub because it will never change.

Ah, no that's okay. The reason I did it this way is because `https://gitea.com` is the default gitea instance, yet currently we are still primarily using GitHub, so I left the service set as such. No need to set a base-url for GitHub because it will never change.

It's only a bit confusing, that's all. Being the example file...

It's only a bit confusing, that's all. Being the example file...

Yeah, fair enough. Do you think it would make more sense to leave base-url blank for the time being?

Yeah, fair enough. Do you think it would make more sense to leave `base-url` blank for the time being?

I'd use Gitea's for both parameters and add a comment about base-url not being required in the case of GitHub. Having said that, I would honor any base-url setting, even in the case of GitHub. We never know, maybe there's another repository hosting service that implements GitHub's API that we never knew about, or maybe GitHub allows private domains for corporate accounts, etc.

Ideally, I'd make base-url optional in both cases (GitHub and Gitea), but always honor its value if present and not empty.

I'd use Gitea's for both parameters and add a comment about `base-url` not being required in the case of GitHub. Having said that, I _would_ honor any `base-url` setting, even in the case of GitHub. We never know, maybe there's another repository hosting service that implements GitHub's API that we never knew about, or maybe GitHub allows private domains for corporate accounts, etc. Ideally, I'd make `base-url` optional in _both cases_ (GitHub and Gitea), but always honor its value if present and not empty.
# Changelog groups and which labeled PRs to add to each group
groups:

11
cmd/cmd.go Normal file
View File

@ -0,0 +1,11 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
var (
// CLI Flags
Milestone string
ConfigPath string
)

44
cmd/contributors.go Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"fmt"
"code.gitea.io/changelog/config"
"code.gitea.io/changelog/service"
"github.com/urfave/cli/v2"
)
var Contributors = &cli.Command{
Name: "contributors",
Usage: "generate contributors list",
Description: "generate contributors list",
Action: runContributors,
}
func runContributors(cmd *cli.Context) error {
cfg, err := config.Load(ConfigPath)
if err != nil {
return err
}
s, err := service.Parse(cfg.Service)
if err != nil {
return err
}
contributors, err := s.Contributors(cfg, Milestone)
if err != nil {
return err
}
for _, contributor := range contributors {
fmt.Printf("* [@%s](https://github.com/%s)\n", contributor, contributor)
}
return nil
}

52
cmd/generate.go Normal file
View File

@ -0,0 +1,52 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"fmt"
"code.gitea.io/changelog/config"
"code.gitea.io/changelog/service"
"github.com/urfave/cli/v2"
)
var Generate = &cli.Command{
Name: "generate",
Usage: "generate changelog",

If changelog here is a parameter, it's customary to use some indication (e.g. generate {changelog}). In *nix style, curly braces mean required parameters and square brackets mean optional. If you prefer, you could use another style like generate "changelog".

If `changelog` here is a parameter, it's customary to use some indication (e.g. `generate {changelog}`). In *nix style, curly braces mean required parameters and square brackets mean optional. If you prefer, you could use another style like `generate "changelog"`.

I can change it, this isn't a parameter.

I can change it, this isn't a parameter.

I was wondering about that. No need to change, then. ?

I was wondering about that. No need to change, then. ?
Description: "generate changelog",
Action: runGenerate,
}
func runGenerate(cmd *cli.Context) error {
cfg, err := config.Load(ConfigPath)
if err != nil {
return err
}
s, err := service.Parse(cfg.Service)
if err != nil {
return err
}
changelog, err := s.Changelog(cfg, Milestone)
if err != nil {
return err
}
fmt.Printf(changelog.TagURL)
for _, g := range cfg.Groups {
if len(changelog.Entries[g.Name]) == 0 {
continue
}
fmt.Println("* " + g.Name)
for _, entry := range changelog.Entries[g.Name] {
fmt.Printf(" * %s (#%d)\n", entry.Title, entry.Index)
}
}
return nil
}

View File

@ -2,10 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package main
//go:generate go run changelog.example.go
//go:generate go fmt ./...
package config
import (
"io/ioutil"
@ -16,18 +13,25 @@ import (
var defaultConfig []byte
// Group is a grouping of PRs
type Group struct {
Name string `yaml:"name"`
Labels []string `yaml:"labels"`
Default bool `yaml:"default"`
}
// Config is the changelog settings
type Config struct {
Repo string `yaml:"repo"`
Groups []struct {
Name string `yaml:"name"`
Labels []string `yaml:"labels"`
Default bool `yaml:"default"`
} `yaml:"groups"`
Repo string `yaml:"repo"`
Service string `yaml:"service"`
BaseURL string `yaml:"base-url"`
Groups []Group `yaml:"groups"`
SkipLabels string `yaml:"skip-labels"`
SkipRegex *regexp.Regexp `yaml:"-"`
}
func LoadConfig() (*Config, error) {
// Load a config from a path, defaulting to changelog.example.yml
func Load(configPath string) (*Config, error) {
var err error
var configContent []byte
if len(configPath) == 0 {

View File

@ -1,9 +1,15 @@
package main
Review

copyright head

copyright head
package config
func init() {
defaultConfig = []byte(`# The full repository name
repo: go-gitea/gitea
# Service type (gitea or github)
service: github
# Base URL for Gitea instance if using gitea service type
base-url: https://gitea.com
Review

Same concern here

Same concern here
# Changelog groups and which labeled PRs to add to each group
groups:
-

View File

@ -1,70 +0,0 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"fmt"
"log"
"sort"
"github.com/google/go-github/github"
"github.com/urfave/cli/v2"
)
var cmdContributors = &cli.Command{
Name: "contributors",
Usage: "generate contributors list",
Description: "generate contributors list",
Action: runContributors,
}
func runContributors(cmd *cli.Context) error {
config, err := LoadConfig()
if err != nil {
return err
}
client := github.NewClient(nil)
ctx := context.Background()
contributorsMap := make(map[string]bool)
query := fmt.Sprintf(`repo:%s is:merged milestone:"%s"`, config.Repo, milestone)
p := 1
perPage := 100
for {
result, _, err := client.Search.Issues(ctx, query, &github.SearchOptions{
ListOptions: github.ListOptions{
Page: p,
PerPage: perPage,
},
})
p++
if err != nil {
log.Fatal(err.Error())
}
for _, pr := range result.Issues {
contributorsMap[*pr.User.Login] = true
}
if len(result.Issues) != perPage {
break
}
}
contributors := make([]string, 0, len(contributorsMap))
for contributor, _ := range contributorsMap {
contributors = append(contributors, contributor)
}
sort.Strings(contributors)
for _, contributor := range contributors {
fmt.Printf("* [@%s](https://github.com/%s)\n", contributor, contributor)
}
return nil
}

View File

@ -1,103 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/google/go-github/github"
"github.com/urfave/cli/v2"
)
var cmdGenerate = &cli.Command{
Name: "generate",
Usage: "generate changelog",
Description: "generate changelog",
Action: runGenerate,
}
func runGenerate(cmd *cli.Context) error {
config, err := LoadConfig()
if err != nil {
return err
}
client := github.NewClient(nil)
ctx := context.Background()
labels := make(map[string]string)
changelogs := make(map[string][]github.Issue)
var defaultGroup string
for _, g := range config.Groups {
changelogs[g.Name] = []github.Issue{}
for _, l := range g.Labels {
labels[l] = g.Name
}
if g.Default {
defaultGroup = g.Name
}
}
if defaultGroup == "" {
defaultGroup = config.Groups[len(config.Groups)-1].Name
}
query := fmt.Sprintf(`repo:%s is:merged milestone:"%s"`, config.Repo, milestone)
p := 1
perPage := 100
for {
result, _, err := client.Search.Issues(ctx, query, &github.SearchOptions{
ListOptions: github.ListOptions{
Page: p,
PerPage: perPage,
},
})
p++
if err != nil {
log.Fatal(err.Error())
}
PRLoop: // labels in Go, let's get old school
for _, pr := range result.Issues {
var label string
for _, lb := range pr.Labels {
if config.SkipRegex != nil && config.SkipRegex.MatchString(lb.GetName()) {
continue PRLoop
}
if g, ok := labels[lb.GetName()]; ok && len(label) == 0 {
label = g
}
}
if len(label) > 0 {
changelogs[label] = append(changelogs[label], pr)
} else {
changelogs[defaultGroup] = append(changelogs[defaultGroup], pr)
}
}
if len(result.Issues) != perPage {
break
}
}
fmt.Printf("## [%s](https://github.com/%s/releases/tag/v%s) - %s\n", milestone, config.Repo, milestone, time.Now().Format("2006-01-02"))
for _, g := range config.Groups {
if len(changelogs[g.Name]) == 0 {
continue
}
fmt.Println("* " + g.Name)
for _, pr := range changelogs[g.Name] {
fmt.Printf(" * %s (#%d)\n", *pr.Title, *pr.Number)
}
}
return nil
}

1
go.mod
View File

@ -3,6 +3,7 @@ module code.gitea.io/changelog
go 1.13
require (
code.gitea.io/sdk/gitea v0.0.0-20200107034216-f0ac57ba0b2b
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/google/go-github v17.0.0+incompatible
github.com/google/go-querystring v1.0.0 // indirect

5
go.sum
View File

@ -1,3 +1,8 @@
code.gitea.io/sdk v0.0.0-20200107034216-f0ac57ba0b2b h1:ujbGMqEbr4d/gSfsOkJapGwx9h0g+cRgTuHlRovgLFI=
code.gitea.io/sdk/gitea v0.0.0-20200107034216-f0ac57ba0b2b h1:z5T6DxY2fqJz02qhXGnQQ2lXfPW5bTDM/0lqzFGNcyk=
code.gitea.io/sdk/gitea v0.0.0-20200107034216-f0ac57ba0b2b/go.mod h1:8IxkM1gyiwEjfO0m47bcmr3u3foR15+LoVub43hCHd0=
gitea.com/gitea/go-sdk v0.0.0-20200107034216-f0ac57ba0b2b h1:qAIFQDSrZFRzUHHWSXuotBA0BpB5m7Y978gl1Jp4Vro=
gitea.com/gitea/go-sdk v0.0.0-20200107034216-f0ac57ba0b2b/go.mod h1:r9S7LXuEumOEDhkZ9qHLhHSnTO6XNnKiBEbunPJJY+8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=

17
main.go
View File

@ -4,7 +4,11 @@
Review

copyright head ...

copyright head ...
Review

There is already a copyright here.

There is already a copyright here.
package main
//go:generate go run changelog.example.go
//go:generate go fmt ./...
import (
"code.gitea.io/changelog/cmd"
"fmt"
"os"
@ -16,11 +20,6 @@ const (
Version = "0.2"
)
var (
milestone string
configPath string
)
func main() {
app := &cli.App{
Name: "changelog",
@ -32,18 +31,18 @@ func main() {
Aliases: []string{"m"},
Usage: "Targeted milestone",
Required: true,
Destination: &milestone,
Destination: &cmd.Milestone,
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "Specify a config file",
Destination: &configPath,
Destination: &cmd.ConfigPath,
},
},
Commands: []*cli.Command{
cmdGenerate,
cmdContributors,
cmd.Generate,
cmd.Contributors,
},
}

147
service/gitea.go Normal file
View File

@ -0,0 +1,147 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package service
import (
"fmt"
"sort"
"strings"
"time"
"code.gitea.io/changelog/config"
"code.gitea.io/sdk/gitea"
)
// Gitea defines a Gitea service
type Gitea struct{}
// Changelog returns a Gitea changelog
func (ge Gitea) Changelog(cfg *config.Config, milestone string) (Changelog, error) {
changelog := Changelog{
TagURL: fmt.Sprintf("## [%s](%s/%s/src/tag/v%s) - %s\n", cfg.BaseURL, milestone, cfg.Repo, milestone, time.Now().Format("2006-01-02")),
}
client := gitea.NewClient(cfg.BaseURL, "")
labels := make(map[string]string)
entries := make(map[string][]Entry)
var defaultGroup string
for _, g := range cfg.Groups {
entries[g.Name] = []Entry{}
for _, l := range g.Labels {
labels[l] = g.Name
}
if g.Default {
defaultGroup = g.Name
}
Review

can you write the reason instead of linking to source code?

can you write the reason instead of linking to source code?
Review

Done.

Done.
}
if defaultGroup == "" {
defaultGroup = cfg.Groups[len(cfg.Groups)-1].Name
}
repoOwner := strings.Split(cfg.Repo, "/")
repo := repoOwner[0]
owner := repoOwner[1]
p := 1
perPage := 100
for {
// FIXME Once SDK is updated, search only by milestone
results, err := client.ListRepoPullRequests(repo, owner, gitea.ListPullRequestsOptions{
Page: p,
State: "closed",
})
p++
if err != nil {
return changelog, err
}
PRLoop: // labels in Go, let's get old school
for _, pr := range results {
if pr == nil || !pr.HasMerged || pr.Milestone == nil || pr.Milestone.Title != milestone {
continue
}
var label string
for _, lb := range pr.Labels {
if cfg.SkipRegex != nil && cfg.SkipRegex.MatchString(lb.Name) {
continue PRLoop
}
if g, ok := labels[lb.Name]; ok && len(label) == 0 {
label = g
}
}
if len(label) > 0 {
entries[label] = append(entries[label], Entry{
Title: pr.Title,
Index: pr.Index,
})
} else {
entries[defaultGroup] = append(entries[defaultGroup], Entry{
Title: pr.Title,
Index: pr.Index,
})
}
}
if len(results) != perPage {
break
}
}
changelog.Entries = entries
return changelog, nil
}
// Contributors returns a list of contributors from Gitea
func (ge Gitea) Contributors(cfg *config.Config, milestone string) ([]string, error) {
client := gitea.NewClient(cfg.BaseURL, "")
contributorsMap := make(map[string]bool)
repoOwner := strings.Split(cfg.Repo, "/")
repo := repoOwner[0]
owner := repoOwner[1]
p := 1
perPage := 100
for {
// FIXME Once SDK is updated, search only by milestone
results, err := client.ListRepoPullRequests(repo, owner, gitea.ListPullRequestsOptions{
Page: p,
State: "closed",
})
p++
if err != nil {
return nil, err
}
for _, pr := range results {
if !pr.HasMerged || pr.Milestone == nil {
continue
}
if pr.Milestone.Title != milestone {
continue
}
contributorsMap[pr.Poster.UserName] = true
}
if len(results) != perPage {
break
}
}
contributors := make([]string, 0, len(contributorsMap))
for contributor, _ := range contributorsMap {
contributors = append(contributors, contributor)
}
sort.Strings(contributors)
return contributors, nil
}

136
service/github.go Normal file
View File

@ -0,0 +1,136 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package service
import (
"context"
"fmt"
"sort"
"time"
"code.gitea.io/changelog/config"
"github.com/google/go-github/github"
)
// GitHub defines a GitHub service
type GitHub struct{}
// Changelog returns a GitHub changelog
func (gh GitHub) Changelog(cfg *config.Config, milestone string) (Changelog, error) {
changelog := Changelog{
TagURL: fmt.Sprintf("## [%s](https://github.com/%s/releases/tag/v%s) - %s\n", milestone, cfg.Repo, milestone, time.Now().Format("2006-01-02")),
}
client := github.NewClient(nil)
ctx := context.Background()
labels := make(map[string]string)
entries := make(map[string][]Entry)
var defaultGroup string
for _, g := range cfg.Groups {
entries[g.Name] = []Entry{}
for _, l := range g.Labels {
labels[l] = g.Name
}
if g.Default {
defaultGroup = g.Name
}
}
if defaultGroup == "" {
defaultGroup = cfg.Groups[len(cfg.Groups)-1].Name
}
query := fmt.Sprintf(`repo:%s is:merged milestone:"%s"`, cfg.Repo, milestone)
p := 1
perPage := 100
for {
result, _, err := client.Search.Issues(ctx, query, &github.SearchOptions{
ListOptions: github.ListOptions{
Page: p,
PerPage: perPage,
},
})
p++
if err != nil {
return changelog, err
}
PRLoop: // labels in Go, let's get old school
for _, pr := range result.Issues {
var label string
for _, lb := range pr.Labels {
if cfg.SkipRegex != nil && cfg.SkipRegex.MatchString(lb.GetName()) {
continue PRLoop
}
if g, ok := labels[lb.GetName()]; ok && len(label) == 0 {
label = g
}
}
if len(label) > 0 {
entries[label] = append(entries[label], Entry{
Title: *pr.Title,
Index: int64(*pr.Number),
})
} else {
entries[defaultGroup] = append(entries[defaultGroup], Entry{
Title: *pr.Title,
Index: int64(*pr.Number),
})
}
}
if len(result.Issues) != perPage {
break
}
}
changelog.Entries = entries
return changelog, nil
}
// Contributors returns a list of contributors from GitHub
func (gh GitHub) Contributors(cfg *config.Config, milestone string) ([]string, error) {
client := github.NewClient(nil)
ctx := context.Background()
contributorsMap := make(map[string]bool)
query := fmt.Sprintf(`repo:%s is:merged milestone:"%s"`, cfg.Repo, milestone)
p := 1
perPage := 100
for {
result, _, err := client.Search.Issues(ctx, query, &github.SearchOptions{
ListOptions: github.ListOptions{
Page: p,
PerPage: perPage,
},
})
p++
if err != nil {
return nil, err
}
for _, pr := range result.Issues {
contributorsMap[*pr.User.Login] = true
}
if len(result.Issues) != perPage {
break
}
}
contributors := make([]string, 0, len(contributorsMap))
for contributor, _ := range contributorsMap {
contributors = append(contributors, contributor)
}
sort.Strings(contributors)
return contributors, nil
}

42
service/service.go Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package service
import (
"errors"
"strings"
"code.gitea.io/changelog/config"
)
// Parse returns a service from a string
func Parse(s string) (Service, error) {
switch strings.ToLower(s) {
case "github":
return GitHub{}, nil
case "gitea":
return Gitea{}, nil
default:
return nil, errors.New("unknown service type")
}
}
// Service defines how a struct can be a Changelog Service
type Service interface {
Changelog(*config.Config, string) (Changelog, error)
Contributors(*config.Config, string) ([]string, error)
}
// Entry defines a changelog entry (PR title and index)
type Entry struct {
Title string
Index int64
}
// Changelog defines a full changelog
type Changelog struct {
TagURL string
Entries map[string][]Entry
}