Changelog Overhaul 2 #19
@ -1,3 +1,7 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
// +build ignore
|
// +build ignore
|
||||||
|
|
||||||
package main
|
package main
|
||||||
@ -10,8 +14,12 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
exampleFile = "changelog.example.yml"
|
exampleFile = "changelog.example.yml"
|
||||||
writeFile = "config_default.go"
|
writeFile = "config/config_default.go"
|
||||||
tmpl = `package main
|
tmpl = `// 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 config
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
defaultConfig = []byte(` + "`" + `%s` + "`" + `)
|
defaultConfig = []byte(` + "`" + `%s` + "`" + `)
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
# The full repository name
|
# The full repository name
|
||||||
|
|||||||
repo: go-gitea/gitea
|
repo: go-gitea/gitea
|
||||||
|
|
||||||
|
# Service type (gitea or github)
|
||||||
|
service: github
|
||||||
|
|
||||||
|
# Base URL for Gitea instance if using gitea service type (optional)
|
||||||
|
# Default: 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.
|
|||||||
|
base-url:
|
||||||
|
|
||||||
# Changelog groups and which labeled PRs to add to each group
|
# Changelog groups and which labeled PRs to add to each group
|
||||||
groups:
|
groups:
|
||||||
-
|
-
|
||||||
|
13
cmd/cmd.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// 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 (
|
||||||
|
MilestoneFlag string
|
||||||
|
ConfigPathFlag string
|
||||||
|
TokenFlag string
|
||||||
|
DetailsFlag bool
|
||||||
|
AfterFlag int64
|
||||||
|
)
|
46
cmd/contributors.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// 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"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"code.gitea.io/changelog/config"
|
||||||
|
"code.gitea.io/changelog/service"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Contributors = &cli.Command{
|
||||||
|
Name: "contributors",
|
||||||
|
Usage: "Generates a contributors list",
|
||||||
|
Action: runContributors,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runContributors(cmd *cli.Context) error {
|
||||||
|
cfg, err := config.New(ConfigPathFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := service.New(cfg.Service, cfg.Repo, cfg.BaseURL, MilestoneFlag, TokenFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
contributors, err := s.Contributors()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(contributors)
|
||||||
|
|
||||||
|
for _, contributor := range contributors {
|
||||||
|
fmt.Printf("* [@%s](%s)\n", contributor.Name, contributor.Profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
101
cmd/generate.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// 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: "Generates a 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. ?
|
|||||||
|
Action: runGenerate,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runGenerate(cmd *cli.Context) error {
|
||||||
|
cfg, err := config.New(ConfigPathFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := make(map[string]string)
|
||||||
|
entries := make(map[string][]service.PullRequest)
|
||||||
|
var defaultGroup string
|
||||||
|
for _, g := range cfg.Groups {
|
||||||
|
entries[g.Name] = []service.PullRequest{}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := service.New(cfg.Service, cfg.Repo, cfg.BaseURL, MilestoneFlag, TokenFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
title, prs, err := s.Generate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
PRLoop: // labels in Go, let's get old school
|
||||||
|
for _, pr := range prs {
|
||||||
|
if pr.Index < AfterFlag {
|
||||||
|
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], pr)
|
||||||
|
} else {
|
||||||
|
entries[defaultGroup] = append(entries[defaultGroup], pr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(title)
|
||||||
|
for _, g := range cfg.Groups {
|
||||||
|
if len(entries[g.Name]) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if DetailsFlag {
|
||||||
|
fmt.Println("<details><summary>" + g.Name + "</summary>")
|
||||||
|
fmt.Println()
|
||||||
|
for _, entry := range entries[g.Name] {
|
||||||
|
fmt.Printf("* %s (#%d)\n", entry.Title, entry.Index)
|
||||||
|
}
|
||||||
|
fmt.Println("</details>")
|
||||||
|
} else {
|
||||||
|
fmt.Println("* " + g.Name)
|
||||||
|
for _, entry := range entries[g.Name] {
|
||||||
|
fmt.Printf(" * %s (#%d)\n", entry.Title, entry.Index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
54
config.go
@ -1,54 +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
|
|
||||||
|
|
||||||
//go:generate go run changelog.example.go
|
|
||||||
//go:generate go fmt ./...
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultConfig []byte
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Repo string `yaml:"repo"`
|
|
||||||
Groups []struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Labels []string `yaml:"labels"`
|
|
||||||
Default bool `yaml:"default"`
|
|
||||||
} `yaml:"groups"`
|
|
||||||
SkipLabels string `yaml:"skip-labels"`
|
|
||||||
SkipRegex *regexp.Regexp `yaml:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadConfig() (*Config, error) {
|
|
||||||
var err error
|
|
||||||
var configContent []byte
|
|
||||||
if len(configPath) == 0 {
|
|
||||||
configContent = defaultConfig
|
|
||||||
} else {
|
|
||||||
configContent, err = ioutil.ReadFile(configPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var config *Config
|
|
||||||
if err = yaml.Unmarshal(configContent, &config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.SkipLabels) > 0 {
|
|
||||||
if config.SkipRegex, err = regexp.Compile(config.SkipLabels); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
58
config/config.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
Service string `yaml:"service"`
|
||||||
|
BaseURL string `yaml:"base-url"`
|
||||||
|
Groups []Group `yaml:"groups"`
|
||||||
|
SkipLabels string `yaml:"skip-labels"`
|
||||||
|
SkipRegex *regexp.Regexp `yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a config from a path, defaulting to changelog.example.yml
|
||||||
|
func New(configPath string) (*Config, error) {
|
||||||
|
var err error
|
||||||
|
var configContent []byte
|
||||||
|
if len(configPath) == 0 {
|
||||||
|
configContent = defaultConfig
|
||||||
|
} else {
|
||||||
|
configContent, err = ioutil.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg *Config
|
||||||
|
if err = yaml.Unmarshal(configContent, &cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.SkipLabels) > 0 {
|
||||||
|
if cfg.SkipRegex, err = regexp.Compile(cfg.SkipLabels); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
@ -1,9 +1,20 @@
|
|||||||
package main
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
6543
commented
copyright head copyright head
|
|||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
defaultConfig = []byte(`# The full repository name
|
defaultConfig = []byte(`# The full repository name
|
||||||
repo: go-gitea/gitea
|
repo: go-gitea/gitea
|
||||||
|
|
||||||
|
# Service type (gitea or github)
|
||||||
guillep2k
commented
Same concern here Same concern here
|
|||||||
|
service: github
|
||||||
|
|
||||||
|
# Base URL for Gitea instance if using gitea service type (optional)
|
||||||
|
# Default: https://gitea.com
|
||||||
|
base-url:
|
||||||
|
|
||||||
# Changelog groups and which labeled PRs to add to each group
|
# Changelog groups and which labeled PRs to add to each group
|
||||||
groups:
|
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
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
code.gitea.io/sdk/gitea v0.0.0-20200116035226-b24cfd841cda
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
github.com/google/go-github v17.0.0+incompatible
|
github.com/google/go-github v17.0.0+incompatible
|
||||||
github.com/google/go-querystring v1.0.0 // indirect
|
github.com/google/go-querystring v1.0.0 // indirect
|
||||||
|
7
go.sum
@ -1,7 +1,11 @@
|
|||||||
|
code.gitea.io/sdk/gitea v0.0.0-20200116035226-b24cfd841cda h1:J+qDCjmjcewNcPNfHIex5z726cgv/URXK0MnXHTIo1U=
|
||||||
|
code.gitea.io/sdk/gitea v0.0.0-20200116035226-b24cfd841cda/go.mod h1:SXOCD/+QP5txLJQ2bPkgHGSQs1YQ4s1ep1ZpI6ItO4A=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
@ -12,6 +16,9 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
|
|||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
||||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
35
main.go
@ -4,10 +4,14 @@
|
|||||||
|
|
||||||
6543
commented
copyright head ... copyright head ...
jolheiser
commented
There is already a copyright here. There is already a copyright here.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
//go:generate go run changelog.example.go
|
||||||
|
//go:generate go fmt ./...
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"code.gitea.io/changelog/cmd"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,11 +20,6 @@ const (
|
|||||||
Version = "0.2"
|
Version = "0.2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
milestone string
|
|
||||||
configPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
Name: "changelog",
|
Name: "changelog",
|
||||||
@ -32,18 +31,36 @@ func main() {
|
|||||||
Aliases: []string{"m"},
|
Aliases: []string{"m"},
|
||||||
Usage: "Targeted milestone",
|
Usage: "Targeted milestone",
|
||||||
Required: true,
|
Required: true,
|
||||||
Destination: &milestone,
|
Destination: &cmd.MilestoneFlag,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
Usage: "Specify a config file",
|
Usage: "Specify a config file",
|
||||||
Destination: &configPath,
|
Destination: &cmd.ConfigPathFlag,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "token",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Access token for private repositories/instances",
|
||||||
|
Destination: &cmd.TokenFlag,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "details",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Generate detail lists instead of long lists",
|
||||||
|
Destination: &cmd.DetailsFlag,
|
||||||
|
},
|
||||||
|
&cli.Int64Flag{
|
||||||
|
Name: "after",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "Only select PRs after a given index (continuing a previous changelog)",
|
||||||
|
Destination: &cmd.AfterFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
cmdGenerate,
|
cmd.Generate,
|
||||||
cmdContributors,
|
cmd.Contributors,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
138
service/gitea.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// 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"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gitea defines a Gitea service
|
||||||
|
type Gitea struct {
|
||||||
|
Milestone string
|
||||||
|
Token string
|
||||||
|
BaseURL string
|
||||||
|
Owner string
|
||||||
|
Repo string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate returns a Gitea changelog
|
||||||
|
func (ge *Gitea) Generate() (string, []PullRequest, error) {
|
||||||
|
client := gitea.NewClient(ge.BaseURL, ge.Token)
|
||||||
|
|
||||||
|
prs := make([]PullRequest, 0)
|
||||||
|
|
||||||
|
milestoneID, err := ge.milestoneID(client)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tagURL := fmt.Sprintf("## [%s](%s/%s/%s/pulls?q=&type=all&state=closed&milestone=%d) - %s", ge.Milestone, ge.BaseURL, ge.Owner, ge.Repo, milestoneID, time.Now().Format("2006-01-02"))
|
||||||
|
|
||||||
|
p := 1
|
||||||
|
// https://github.com/go-gitea/gitea/blob/d92781bf941972761177ac9e07441f8893758fd3/models/repo.go#L63
|
||||||
|
// https://github.com/go-gitea/gitea/blob/e3c3b33ea7a5a223e22688c3f0eb2d3dab9f991c/models/pull_list.go#L104
|
||||||
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.
|
|||||||
|
// FIXME Gitea has this hard-coded at 40
|
||||||
|
perPage := 40
|
||||||
|
for {
|
||||||
|
results, err := client.ListRepoPullRequests(ge.Owner, ge.Repo, gitea.ListPullRequestsOptions{
|
||||||
|
Page: p,
|
||||||
|
State: "closed",
|
||||||
|
Milestone: milestoneID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
p++
|
||||||
|
|
||||||
|
for _, pr := range results {
|
||||||
|
if pr != nil && pr.HasMerged {
|
||||||
|
p := PullRequest{
|
||||||
|
Title: pr.Title,
|
||||||
|
Index: pr.Index,
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := make([]Label, len(pr.Labels))
|
||||||
|
for idx, lbl := range pr.Labels {
|
||||||
|
labels[idx] = Label{
|
||||||
|
Name: lbl.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Labels = labels
|
||||||
|
|
||||||
|
prs = append(prs, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) != perPage {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagURL, prs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contributors returns a list of contributors from Gitea
|
||||||
|
func (ge *Gitea) Contributors() (ContributorList, error) {
|
||||||
|
client := gitea.NewClient(ge.BaseURL, ge.Token)
|
||||||
|
|
||||||
|
contributorsMap := make(map[string]bool)
|
||||||
|
|
||||||
|
milestoneID, err := ge.milestoneID(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := 1
|
||||||
|
perPage := 100
|
||||||
|
for {
|
||||||
|
results, err := client.ListRepoPullRequests(ge.Owner, ge.Repo, gitea.ListPullRequestsOptions{
|
||||||
|
Page: p,
|
||||||
|
State: "closed",
|
||||||
|
Milestone: milestoneID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p++
|
||||||
|
|
||||||
|
for _, pr := range results {
|
||||||
|
if pr != nil && pr.HasMerged {
|
||||||
|
contributorsMap[pr.Poster.UserName] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) != perPage {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contributors := make(ContributorList, 0, len(contributorsMap))
|
||||||
|
for contributor, _ := range contributorsMap {
|
||||||
|
contributors = append(contributors, Contributor{
|
||||||
|
Name: contributor,
|
||||||
|
Profile: fmt.Sprintf("%s/%s", ge.BaseURL, contributor),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return contributors, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ge *Gitea) milestoneID(client *gitea.Client) (int64, error) {
|
||||||
|
milestones, err := client.ListRepoMilestones(ge.Owner, ge.Repo)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ms := range milestones {
|
||||||
|
if ms.Title == ge.Milestone {
|
||||||
|
return ms.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("no milestone found for %s", ge.Milestone)
|
||||||
|
}
|
112
service/github.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// 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"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-github/github"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GitHub defines a GitHub service
|
||||||
|
type GitHub struct {
|
||||||
|
Milestone string
|
||||||
|
Token string
|
||||||
|
Repo string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate returns a GitHub changelog
|
||||||
|
func (gh *GitHub) Generate() (string, []PullRequest, error) {
|
||||||
|
tagURL := fmt.Sprintf("## [%s](https://github.com/%s/releases/tag/v%s) - %s", gh.Milestone, gh.Repo, gh.Milestone, time.Now().Format("2006-01-02"))
|
||||||
|
|
||||||
|
client := github.NewClient(nil)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
prs := make([]PullRequest, 0)
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`repo:%s is:merged milestone:"%s"`, gh.Repo, gh.Milestone)
|
||||||
|
p := 1
|
||||||
|
perPage := 100
|
||||||
|
for {
|
||||||
|
result, _, err := client.Search.Issues(ctx, query, &github.SearchOptions{
|
||||||
|
ListOptions: github.ListOptions{
|
||||||
|
Page: p,
|
||||||
|
PerPage: perPage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
p++
|
||||||
|
|
||||||
|
for _, pr := range result.Issues {
|
||||||
|
if pr.IsPullRequest() {
|
||||||
|
p := PullRequest{
|
||||||
|
Title: pr.GetTitle(),
|
||||||
|
Index: int64(pr.GetNumber()),
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := make([]Label, len(pr.Labels))
|
||||||
|
for idx, lbl := range pr.Labels {
|
||||||
|
labels[idx] = Label{
|
||||||
|
Name: lbl.GetName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Labels = labels
|
||||||
|
|
||||||
|
prs = append(prs, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Issues) != perPage {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagURL, prs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contributors returns a list of contributors from GitHub
|
||||||
|
func (gh *GitHub) Contributors() (ContributorList, error) {
|
||||||
|
client := github.NewClient(nil)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
contributorsMap := make(map[string]bool)
|
||||||
|
query := fmt.Sprintf(`repo:%s is:merged milestone:"%s"`, gh.Repo, gh.Milestone)
|
||||||
|
p := 1
|
||||||
|
perPage := 100
|
||||||
|
for {
|
||||||
|
result, _, err := client.Search.Issues(ctx, query, &github.SearchOptions{
|
||||||
|
ListOptions: github.ListOptions{
|
||||||
|
Page: p,
|
||||||
|
PerPage: perPage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p++
|
||||||
|
|
||||||
|
for _, pr := range result.Issues {
|
||||||
|
contributorsMap[pr.GetUser().GetLogin()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Issues) != perPage {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contributors := make(ContributorList, 0, len(contributorsMap))
|
||||||
|
for contributor, _ := range contributorsMap {
|
||||||
|
contributors = append(contributors, Contributor{
|
||||||
|
Name: contributor,
|
||||||
|
Profile: fmt.Sprintf("https://github.com/%s", contributor),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return contributors, nil
|
||||||
|
}
|
38
service/github_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
lunny
commented
copyright head copyright head
|
|||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package service
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var gh = &GitHub{
|
||||||
|
Milestone: "1.1.0", // https://github.com/go-gitea/test_repo/milestone/2?closed=1
|
||||||
|
Repo: "go-gitea/test_repo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitHubGenerate(t *testing.T) {
|
||||||
|
_, entries, err := gh.Generate()
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(entries) != 1 {
|
||||||
|
t.Logf("Expected 1 changelog entry, but got %d", len(entries))
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitHubContributors(t *testing.T) {
|
||||||
|
contributors, err := gh.Contributors()
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contributors) != 1 {
|
||||||
|
t.Logf("Expected 1 contributor, but got %d", len(contributors))
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
80
service/service.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultGitea = "https://gitea.com"
|
||||||
|
|
||||||
|
// Load returns a service from a string
|
||||||
|
func New(serviceType, repo, baseURL, milestone, token string) (Service, error) {
|
||||||
|
switch strings.ToLower(serviceType) {
|
||||||
|
case "github":
|
||||||
|
return &GitHub{
|
||||||
|
Milestone: milestone,
|
||||||
|
Token: token,
|
||||||
|
Repo: repo,
|
||||||
|
}, nil
|
||||||
|
case "gitea":
|
||||||
|
ownerRepo := strings.Split(repo, "/")
|
||||||
|
if strings.TrimSpace(baseURL) == "" {
|
||||||
|
baseURL = defaultGitea
|
||||||
|
}
|
||||||
|
return &Gitea{
|
||||||
|
Milestone: milestone,
|
||||||
|
Token: token,
|
||||||
|
BaseURL: baseURL,
|
||||||
|
Owner: ownerRepo[0],
|
||||||
|
Repo: ownerRepo[1],
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown service type %s", serviceType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service defines how a struct can be a Changelog Service
|
||||||
|
type Service interface {
|
||||||
|
Generate() (string, []PullRequest, error)
|
||||||
|
Contributors() (ContributorList, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label is the minimum information needed for a PR label
|
||||||
|
type Label struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullRequest is the minimum information needed to make a changelog entry
|
||||||
|
type PullRequest struct {
|
||||||
|
Title string
|
||||||
|
Index int64
|
||||||
|
Labels []Label
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contributor is a project contributor
|
||||||
|
type Contributor struct {
|
||||||
|
Name string
|
||||||
|
Profile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContributorList is a slice of Contributors that can be sorted
|
||||||
|
type ContributorList []Contributor
|
||||||
|
|
||||||
|
// Len is the length of the ContributorList
|
||||||
|
func (cl ContributorList) Len() int {
|
||||||
|
return len(cl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less determines whether a Contributor comes before another Contributor
|
||||||
|
func (cl ContributorList) Less(i, j int) bool {
|
||||||
|
return cl[i].Name < cl[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps Contributors in a ContributorList
|
||||||
|
func (cl ContributorList) Swap(i, j int) {
|
||||||
|
cl[i], cl[j] = cl[j], cl[i]
|
||||||
|
}
|
14
service/service_test.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
lunny
commented
copyright head copyright head
|
|||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
copyright head ? @lunny