diff --git a/nupkg/push_packages.ps1 b/nupkg/push_packages.ps1 new file mode 100644 index 00000000000..816977df77e --- /dev/null +++ b/nupkg/push_packages.ps1 @@ -0,0 +1,14 @@ +. ".\common.ps1" + +# Get the version +[xml]$commonPropsXml = Get-Content (Join-Path $rootFolder "common.props") +$version = $commonPropsXml.Project.PropertyGroup.Version + +# Publish all packages +foreach($project in $projects) { + $projectName = $project.Substring($project.LastIndexOf("/") + 1) + & dotnet nuget push ($projectName + "." + $version + ".nupkg") -s "https://nuget.abp.io/API_KEY/v3/index.json" +} + +# Go back to the pack folder +Set-Location $packFolder diff --git a/tools/github-changelog-generator/README.md b/tools/github-changelog-generator/README.md new file mode 100644 index 00000000000..2efa72e7655 --- /dev/null +++ b/tools/github-changelog-generator/README.md @@ -0,0 +1,36 @@ +# GitHub Change Log Generator + +## Configuration + +Edit the config.yaml file to customize the generated output. + +## Usage + +``` +.\generator.exe generate [flags] > changelog.md +``` + +### Options + +``` +Flags: + -h, --help help for generate + -m, --milestone string milestone title to get issues and pull requests for + -r, --repo string repository name to generate the Changelog for, in the form user/repo + --since-tag string issues and pull requests since tag + -s, --state string state of the issues and pull requests to get (open,closed or all) + -t, --token string personal access token + --until-tag string issues and pull requests until tag + +Global Flags: + --config string config file (default is config.yaml) +``` + +## Example + +Generate change logs for the ABP repository for the 0.19 milestone: + +```` +.\generator.exe generate -r abpframework/abp -m "0.19" > changelog.md +```` + diff --git a/tools/github-changelog-generator/changelog.md b/tools/github-changelog-generator/changelog.md new file mode 100644 index 00000000000..e753f610c65 Binary files /dev/null and b/tools/github-changelog-generator/changelog.md differ diff --git a/tools/github-changelog-generator/config.yaml b/tools/github-changelog-generator/config.yaml new file mode 100644 index 00000000000..9ca41ef1665 --- /dev/null +++ b/tools/github-changelog-generator/config.yaml @@ -0,0 +1,54 @@ +--- +repo: abpframework/abp +# token: +milestone: 0.18.1 +# since-tag: 0.18.0 +# until-tag: 0.17.0 +state: closed +groups: + - labels: + - breaking change + title: "Breaking Changes" + + - labels: + - feature + title: "Features" + + - labels: + - enhancement + title: "Enhancements" + + - labels: + - bug + title: "Bug Fixes" + + - labels: + title: "Others" + +#template: |- +# {{if .Milestone}}## {{.Milestone.GetTitle}} ({{.Milestone.GetClosedAt.Format "2006-01-02"}}){{end -}} +# {{if .IssuesByMilestone}} +# {{range .IssuesByMilestone}} +# ### {{.Title}} +# {{range .Issues}} +# {{if .IsPullRequest -}} +# - PR [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} (by [{{.GetUser.GetLogin}}]({{.GetUser.GetHTMLURL}})) +# {{- else -}} +# - ISSUE [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} +# {{- end -}} +# {{end}} +# {{end}} +# {{end -}} +# {{if .SinceTagCommit}}## {{.SinceTag}}{{if .UntilTagCommit}} - {{.UntilTag}}{{end}}{{end -}} +# {{if .IssuesByTag}} +# {{range .IssuesByTag}} +# ### {{.Title}} +# {{range .Issues}} +# {{if .IsPullRequest -}} +# - PR [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} (by [{{.GetUser.GetLogin}}]({{.GetUser.GetHTMLURL}})) +# {{- else -}} +# - ISSUE [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} +# {{- end -}} +# {{end}} +# {{end}} +# {{- end -}} \ No newline at end of file diff --git a/tools/github-changelog-generator/generator.exe b/tools/github-changelog-generator/generator.exe new file mode 100644 index 00000000000..38739279bea Binary files /dev/null and b/tools/github-changelog-generator/generator.exe differ diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/generate.go b/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/generate.go new file mode 100644 index 00000000000..0287bf424c3 --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/generate.go @@ -0,0 +1,162 @@ +package cmd + +import ( + "GithubChangelogGenerator/internal" + "GithubChangelogGenerator/pkg" + "fmt" + "io" + "os" + + "github.com/google/go-github/github" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// generateCmd represents the generate command +var generateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate changelog", + Long: `Generates changelog based on a milestone or tag(s) +Edit the config.yaml file to customize the generated output.`, + Run: func(cmd *cobra.Command, args []string) { + c := &internal.Config{} + err := viper.Unmarshal(c) + if err != nil { + er(err) + } + + repo, err := pkg.NewRepo(c.Repo, c.Token) + if err != nil { + er(err) + } + + issuesByMilestone, err := repo.IssuesByMilestone(c.Milestone, c.State) + if err != nil { + er(err) + } + milestone, err := repo.Milestone(c.Milestone) + if err != nil { + er(err) + } + sinceTagCommit, err := repo.TagCommit(c.SinceTag) + if err != nil { + er(err) + } + untilTagCommit, err := repo.TagCommit(c.UntilTag) + if err != nil { + er(err) + } + + var issuesByTag []*github.Issue + switch { + case sinceTagCommit != nil: + issuesByTag, err = repo.IssuesSince(sinceTagCommit.Committer.GetDate()) + if err != nil { + er(err) + } + if c.UntilTag != "" { + issuesByTag = pkg.FilterUntil(issuesByTag, untilTagCommit.Committer.GetDate()) + } + case untilTagCommit != nil: + issuesByTag, err = repo.AllIssues(c.State) + if err != nil { + er(err) + } + issuesByTag = pkg.FilterUntil(issuesByTag, untilTagCommit.Committer.GetDate()) + } + + groupedIssuesByMilestone := internal.GroupIssues(c.Groups, issuesByMilestone) + if err != nil { + er(err) + } + + groupedIssuesByTag := internal.GroupIssues(c.Groups, issuesByTag) + if err != nil { + er(err) + } + + err = writeChangelog(os.Stdout, &TemplateData{ + Repository: repo.Repository(), + IssuesByMilestone: groupedIssuesByMilestone, + IssuesByTag: groupedIssuesByTag, + Milestone: milestone, + SinceTag: c.SinceTag, + SinceTagCommit: sinceTagCommit, + UntilTag: c.UntilTag, + UntilTagCommit: untilTagCommit, + }) + if err != nil { + er(err) + } + }, +} + +func init() { + rootCmd.AddCommand(generateCmd) + generateCmd.Flags().StringP("repo", "r", "", "repository name to generate the Changelog for, in the form user/repo") + generateCmd.Flags().StringP("token", "t", "", "personal access token") + generateCmd.Flags().StringP("milestone", "m", "", "milestone title to get issues and pull requests for") + generateCmd.Flags().String("since-tag", "", "issues and pull requests since tag") + generateCmd.Flags().String("until-tag", "", "issues and pull requests until tag") + generateCmd.Flags().StringP("state", "s", "", "state of the issues and pull requests to get (open,closed or all)") + + err := viper.BindPFlags(generateCmd.Flags()) + if err != nil { + er(err) + } +} + +type TemplateData struct { + Repository *github.Repository + IssuesByMilestone []*internal.GroupedIssues + IssuesByTag []*internal.GroupedIssues + Milestone *github.Milestone + SinceTag string + SinceTagCommit *github.Commit + UntilTag string + UntilTagCommit *github.Commit +} + +func writeChangelog(w io.WriteCloser, td *TemplateData) error { + template := viper.GetString("template") + if template == "" { + template = `{{if .Milestone}}## {{.Milestone.GetTitle}} ({{.Milestone.GetClosedAt.Format "2006-01-02"}}){{end -}} +{{if .IssuesByMilestone}} +{{range .IssuesByMilestone}} +### {{.Title}} +{{range .Issues}} +{{if .IsPullRequest -}} +- PR [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} (by [{{.GetUser.GetLogin}}]({{.GetUser.GetHTMLURL}})) +{{- else -}} +- ISSUE [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} +{{- end -}} +{{end}} +{{end}} +{{end -}} +{{if .SinceTagCommit}}## {{.SinceTag}}{{if .UntilTagCommit}} - {{.UntilTag}}{{end}}{{end -}} +{{if .IssuesByTag}} +{{range .IssuesByTag}} +### {{.Title}} +{{range .Issues}} +{{if .IsPullRequest -}} +- PR [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} (by [{{.GetUser.GetLogin}}]({{.GetUser.GetHTMLURL}})) +{{- else -}} +- ISSUE [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} +{{- end -}} +{{end}} +{{end}} +{{- end -}} +` + } + + result, err := executeTemplate(template, td) + if err != nil { + return err + } + _, err = fmt.Fprintf(w, "%s", result) + if err != nil { + return err + } + + return w.Close() +} diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/helpers.go b/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/helpers.go new file mode 100644 index 00000000000..e69a0d57064 --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/helpers.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "bytes" + "fmt" + "os" + "text/template" +) + +func executeTemplate(tmplStr string, data interface{}) ([]byte, error) { + tmpl, err := template.New("").Parse(tmplStr) + if err != nil { + return nil, err + } + + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, data) + return buf.Bytes(), err +} + +func er(msg interface{}) { + fmt.Println("Error:", msg) + os.Exit(1) +} diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/root.go b/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/root.go new file mode 100644 index 00000000000..2f9c97ad331 --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/root.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "GithubChangelogGenerator", + Short: "Changelog generator", + Long: `This application is a tool to generate changelog from a Github milestone.`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is config.yaml)") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + viper.AddConfigPath(".") + viper.SetConfigName("config") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + //fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/version.go b/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/version.go new file mode 100644 index 00000000000..d46b992ed3d --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/cmd/version.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +const version = "0.2.0" + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number.", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version) + }, +} + +func init() { + rootCmd.AddCommand(versionCmd) +} diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/config/config.yaml b/tools/github-changelog-generator/src/GithubChangelogGenerator/config/config.yaml new file mode 100644 index 00000000000..0c3fd18bc6b --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/config/config.yaml @@ -0,0 +1,54 @@ +--- +repo: abpframework/abp +# token: +milestone: 0.17 +# since-tag: 0.15.0 +# until-tag: 0.16.0 +state: closed +groups: + - labels: + - breaking change + title: "Breaking Changes" + + - labels: + - feature + title: "Features" + + - labels: + - enhancement + title: "Enhancements" + + - labels: + - bug + title: "Bug Fixes" + + - labels: + title: "Others" + +#template: |- +# {{if .Milestone}}## {{.Milestone.GetTitle}} ({{.Milestone.GetClosedAt.Format "2006-01-02"}}){{end -}} +# {{if .IssuesByMilestone}} +# {{range .IssuesByMilestone}} +# ### {{.Title}} +# {{range .Issues}} +# {{if .IsPullRequest -}} +# - PR [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} (by [{{.GetUser.GetLogin}}]({{.GetUser.GetHTMLURL}})) +# {{- else -}} +# - ISSUE [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} +# {{- end -}} +# {{end}} +# {{end}} +# {{end -}} +# {{if .SinceTagCommit}}## {{.SinceTag}}{{if .UntilTagCommit}} - {{.UntilTag}}{{end}}{{end -}} +# {{if .IssuesByTag}} +# {{range .IssuesByTag}} +# ### {{.Title}} +# {{range .Issues}} +# {{if .IsPullRequest -}} +# - PR [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} (by [{{.GetUser.GetLogin}}]({{.GetUser.GetHTMLURL}})) +# {{- else -}} +# - ISSUE [\#{{.GetNumber}}]({{.GetHTMLURL}}): {{.GetTitle}} +# {{- end -}} +# {{end}} +# {{end}} +# {{- end -}} \ No newline at end of file diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/go.mod b/tools/github-changelog-generator/src/GithubChangelogGenerator/go.mod new file mode 100644 index 00000000000..faa470d983d --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/go.mod @@ -0,0 +1,13 @@ +module GithubChangelogGenerator + +go 1.12 + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/google/go-github v17.0.0+incompatible + github.com/google/go-querystring v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/spf13/cobra v0.0.3 + github.com/spf13/viper v1.3.2 + golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a +) diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/go.sum b/tools/github-changelog-generator/src/GithubChangelogGenerator/go.sum new file mode 100644 index 00000000000..5493026cc48 --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/go.sum @@ -0,0 +1,63 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/internal/config.go b/tools/github-changelog-generator/src/GithubChangelogGenerator/internal/config.go new file mode 100644 index 00000000000..bb23dfcb8be --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/internal/config.go @@ -0,0 +1,28 @@ +package internal + +type Config struct { + Repo string `mapstructure:"repo"` + Token string `mapstructure:"token"` + Milestone string `mapstructure:"milestone"` + SinceTag string `mapstructure:"since-tag"` + UntilTag string `mapstructure:"until-tag"` + State string `mapstructure:"state"` + Groups []*Group `mapstructure:"groups"` + Template string `mapstructure:"template"` +} + +type Group struct { + Labels []string `mapstructure:"labels"` + Title string `mapstructure:"title"` +} + +func AllLabels(groups []*Group) map[int][]string { + allLabels := make(map[int][]string) + for i, group := range groups { + if allLabels[i] == nil { + allLabels[i] = make([]string, 0) + } + allLabels[i] = append(allLabels[i], group.Labels...) + } + return allLabels +} diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/internal/issues.go b/tools/github-changelog-generator/src/GithubChangelogGenerator/internal/issues.go new file mode 100644 index 00000000000..4353811a0a1 --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/internal/issues.go @@ -0,0 +1,70 @@ +package internal + +import ( + "fmt" + "regexp" + "sort" + + "github.com/google/go-github/github" +) + +type GroupedIssues struct { + Title string + Issues []*github.Issue +} + +func GroupIssues(groups []*Group, issues []*github.Issue) []*GroupedIssues { + if issues == nil { + return nil + } + var result []*GroupedIssues + grouped := make(map[string][]*github.Issue) + + for _, issue := range issues { + if i, ok := containsAny(issue.Labels, AllLabels(groups)); ok { + grouped[groups[i].Title] = append(grouped[groups[i].Title], issue) + } else { + grouped["no_label"] = append(grouped["no_label"], issue) + } + } + + for _, group := range groups { + if len(group.Labels) == 0 { + result = append(result, &GroupedIssues{Title: group.Title, Issues: grouped["no_label"]}) + continue + } + result = append(result, &GroupedIssues{Title: group.Title, Issues: grouped[group.Title]}) + } + + return result +} + +func containsAny(gls []github.Label, cls map[int][]string) (int, bool) { + var keys []int + for k := range cls { + keys = append(keys, k) + } + sort.Ints(keys) + + for _, gl := range gls { + for _, k := range keys { + for _, l := range cls[k] { + if match(gl.GetName(), l) { + return k, true + } + } + } + } + return 0, false +} + +func match(a, rx string) bool { + if a == rx { + return true + } + re, err := regexp.Compile(fmt.Sprintf("^%s$", rx)) + if err != nil { + return false + } + return re.MatchString(a) +} diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/main.go b/tools/github-changelog-generator/src/GithubChangelogGenerator/main.go new file mode 100644 index 00000000000..fa414158601 --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/main.go @@ -0,0 +1,7 @@ +package main + +import "GithubChangelogGenerator/cmd" + +func main() { + cmd.Execute() +} diff --git a/tools/github-changelog-generator/src/GithubChangelogGenerator/pkg/repo.go b/tools/github-changelog-generator/src/GithubChangelogGenerator/pkg/repo.go new file mode 100644 index 00000000000..f0d8a3c37c3 --- /dev/null +++ b/tools/github-changelog-generator/src/GithubChangelogGenerator/pkg/repo.go @@ -0,0 +1,329 @@ +package pkg + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/google/go-github/github" + "golang.org/x/oauth2" +) + +type GitHubRepo struct { + repoOwner string + repoName string + token string + repo *github.Repository + milestone *github.Milestone +} + +func NewRepo(repo string, token ...string) (*GitHubRepo, error) { + gr := &GitHubRepo{} + sx := strings.Split(repo, "/") + if len(sx) != 2 { + return nil, fmt.Errorf("repo must be in organization/repository format") + } + gr.repoOwner = sx[0] + gr.repoName = sx[1] + + client := github.NewClient(nil) + if token != nil && token[0] != "" { + gr.token = token[0] + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token[0]}, + ) + tc := oauth2.NewClient(context.Background(), ts) + client = github.NewClient(tc) + } + + var err error + gr.repo, _, err = client.Repositories.Get(context.Background(), sx[0], sx[1]) + if err != nil { + return nil, err + } + + return gr, nil +} + +func FilterMilestone(issues []*github.Issue, milestone string) []*github.Issue { + var fi []*github.Issue + if milestone != "" { + for _, issue := range issues { + if issue.Milestone.GetTitle() == milestone { + fi = append(fi, issue) + } + } + } + return fi +} + +func FilterSince(issues []*github.Issue, since time.Time) []*github.Issue { + var fi []*github.Issue + if !since.IsZero() { + for _, issue := range issues { + if issue.GetClosedAt().After(since) { + fi = append(fi, issue) + } + } + } + return fi +} + +func FilterUntil(issues []*github.Issue, until time.Time) []*github.Issue { + var fi []*github.Issue + if !until.IsZero() { + for _, issue := range issues { + if issue.GetClosedAt().Before(until) { + fi = append(fi, issue) + } + } + } + return fi +} + +func (gr *GitHubRepo) AllIssues(state ...string) ([]*github.Issue, error) { + client := github.NewClient(nil) + + if gr.token != "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: gr.token}, + ) + tc := oauth2.NewClient(context.Background(), ts) + client = github.NewClient(tc) + } + + var allIssues []*github.Issue + opt := &github.IssueListByRepoOptions{State: "closed", ListOptions: github.ListOptions{PerPage: 100}} + if len(state) == 1 { + if state[0] == "open" || state[0] == "closed" || state[0] == "all" { + opt.State = state[0] + } + } + for { + issues, resp, err := client.Issues.ListByRepo(context.Background(), gr.repoOwner, gr.repoName, opt) + if err != nil { + return nil, err + } + allIssues = append(allIssues, issues...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allIssues, nil +} + +func (gr *GitHubRepo) IssuesByMilestone(milestone string, state ...string) ([]*github.Issue, error) { + if milestone == "" { + return nil, nil + } + client := github.NewClient(nil) + + if gr.token != "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: gr.token}, + ) + tc := oauth2.NewClient(context.Background(), ts) + client = github.NewClient(tc) + } + + var allIssues []*github.Issue + mil, err := gr.Milestone(milestone) + if err != nil { + return nil, err + } + opt := &github.IssueListByRepoOptions{Milestone: strconv.Itoa(mil.GetNumber()), State: "closed", ListOptions: github.ListOptions{PerPage: 100}} + if len(state) == 1 { + if state[0] == "open" || state[0] == "closed" || state[0] == "all" { + opt.State = state[0] + } + } + for { + issues, resp, err := client.Issues.ListByRepo(context.Background(), gr.repoOwner, gr.repoName, opt) + if err != nil { + return nil, err + } + allIssues = append(allIssues, issues...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allIssues, nil +} + +func (gr *GitHubRepo) IssuesSince(time time.Time, state ...string) ([]*github.Issue, error) { + if time.IsZero() { + return nil, nil + } + client := github.NewClient(nil) + + if gr.token != "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: gr.token}, + ) + tc := oauth2.NewClient(context.Background(), ts) + client = github.NewClient(tc) + } + + var allIssues []*github.Issue + opt := &github.IssueListByRepoOptions{Since: time, State: "closed", ListOptions: github.ListOptions{PerPage: 100}} + if len(state) == 1 { + if state[0] == "open" || state[0] == "closed" || state[0] == "all" { + opt.State = state[0] + } + } + for { + issues, resp, err := client.Issues.ListByRepo(context.Background(), gr.repoOwner, gr.repoName, opt) + if err != nil { + return nil, err + } + allIssues = append(allIssues, issues...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allIssues, nil +} + +func (gr *GitHubRepo) Tags() ([]*github.RepositoryTag, error) { + client := github.NewClient(nil) + if gr.token != "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: gr.token}, + ) + tc := oauth2.NewClient(context.Background(), ts) + client = github.NewClient(tc) + } + var allTags []*github.RepositoryTag + opt := &github.ListOptions{PerPage: 100} + for { + tags, resp, err := client.Repositories.ListTags( + context.Background(), + gr.repoOwner, gr.repoName, + opt, + ) + if err != nil { + return nil, err + } + allTags = append(allTags, tags...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allTags, nil +} + +func (gr *GitHubRepo) Milestones() ([]*github.Milestone, error) { + client := github.NewClient(nil) + if gr.token != "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: gr.token}, + ) + tc := oauth2.NewClient(context.Background(), ts) + client = github.NewClient(tc) + } + var allMilestones []*github.Milestone + opt := &github.MilestoneListOptions{State: "all", ListOptions: github.ListOptions{PerPage: 100}} + for { + milestones, resp, err := client.Issues.ListMilestones( + context.Background(), + gr.repoOwner, gr.repoName, + opt, + ) + if err != nil { + return nil, err + } + allMilestones = append(allMilestones, milestones...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return allMilestones, nil +} + +func (gr *GitHubRepo) Milestone(title string) (*github.Milestone, error) { + if title == "" { + return nil, nil + } + if gr.milestone.GetTitle() == title { + return gr.milestone, nil + } + milestones, err := gr.Milestones() + if err != nil { + return nil, err + } + for _, milestone := range milestones { + if milestone.GetTitle() == title { + gr.milestone = milestone + return milestone, nil + } + } + return nil, fmt.Errorf("you didn't pass a valid milestone title") +} + +func (gr *GitHubRepo) TagCommit(name string) (*github.Commit, error) { + if name == "" { + return nil, nil + } + client := github.NewClient(nil) + if gr.token != "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: gr.token}, + ) + tc := oauth2.NewClient(context.Background(), ts) + client = github.NewClient(tc) + } + + refs, _, err := client.Git.GetRefs(context.Background(), gr.repoOwner, gr.repoName, "tags") + if err != nil { + return nil, err + } + + sha := "" + var tags []string + + refName := fmt.Sprintf("refs/tags/%s", name) + for _, ref := range refs { + if ref.GetRef() == refName { + sha = ref.Object.GetSHA() + } + tag := strings.Split(ref.GetRef(), "/") + tags = append(tags, tag[len(tag)-1]) + } + + if sha == "" { + return nil, fmt.Errorf("you didn't pass a valid tag name. the available tags are: %s", tags) + } + + commit, _, err := client.Git.GetCommit(context.Background(), gr.repoOwner, gr.repoName, sha) + if err != nil { + return nil, err + } + + return commit, err +} + +func (gr *GitHubRepo) Repository() *github.Repository { + return gr.repo +} + +func checkrate() error { + client := github.NewClient(nil) + rl, _, err := client.RateLimits(context.Background()) + if err != nil { + return err + } + if rl.Core.Remaining == 0 { + fmt.Println("GitHub API rate limit exceeded!") + fmt.Printf("GitHub API rate limit resets on %s\n", rl.Core.Reset.Format("2-Jan-2006 15:04:05")) + os.Exit(1) + } + return nil +}