Changelog Overhaul 2 #19
|
@ -10,8 +10,8 @@ import (
|
|||
|
||||
|
||||
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` + "`" + `)
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# The full repository name
|
||||
6543
commented
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
|
||||
guillep2k
commented
If the example If the example `base-url` points to gitea.com, shouldn't the service match it with `gitea` instead of `github`?
jolheiser
commented
Should we really attempt to infer service type based on this URL? Should we really attempt to infer service type based on this URL?
guillep2k
commented
I didn't mean it like that. I thought this file would make more sense either like:
or
Currently it looks mixed up: service says 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)
jolheiser
commented
Ah, no that's okay. 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.
guillep2k
commented
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...
jolheiser
commented
Yeah, fair enough. Do you think it would make more sense to leave Yeah, fair enough. Do you think it would make more sense to leave `base-url` blank for the time being?
guillep2k
commented
I'd use Gitea's for both parameters and add a comment about Ideally, I'd make 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
|
@ -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
|
@ -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
|
@ -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",
|
||||
guillep2k
commented
If 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"`.
jolheiser
commented
I can change it, this isn't a parameter. I can change it, this isn't a parameter.
guillep2k
commented
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
|
||||
}
|
|
@ -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 {
|
|
@ -1,9 +1,15 @@
|
|||
package main
|
||||
6543
commented
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
|
||||
guillep2k
commented
Same concern here Same concern here
|
||||
|
||||
# Changelog groups and which labeled PRs to add to each group
|
||||
groups:
|
||||
-
|
|
@ -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
|
||||
}
|
103
generate.go
|
@ -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
|
@ -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
|
@ -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
|
@ -4,7 +4,11 @@
|
|||
|
||||
6543
commented
copyright head ... copyright head ...
jolheiser
commented
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
|
@ -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
|
||||
}
|
||||
6543
commented
can you write the reason instead of linking to source code? can you write the reason instead of linking to source code?
jolheiser
commented
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
|
@ -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
|
@ -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
|
||||
}
|
copyright head ? @lunny