From ab6f12175563375478e60a9dee3fb8eb575c07f6 Mon Sep 17 00:00:00 2001 From: Kristen Carlson Accardi Date: Tue, 5 Jul 2016 11:20:55 -0700 Subject: [PATCH 1/2] release: add release notes generator to ciao tree ciao-release is a small program which will generate the release notes for ciao releases. It uses the github api to obtain the list of pull requests closed on the master branch since the time of the last tag, up to the release candidate sha. The rc sha must be passed in as a string argument. It also gets the list of issue events since the last tag and reports any closed issues. Finally, it gets the list of all commits since the last tag. The lists are output into a plain text file that is called "release.txt". This file is used to create an annotated tag for the next release. Signed-off-by: Kristen Carlson Accardi --- _release/ciao-release/main.go | 213 ++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 _release/ciao-release/main.go diff --git a/_release/ciao-release/main.go b/_release/ciao-release/main.go new file mode 100644 index 000000000..7a4d1758e --- /dev/null +++ b/_release/ciao-release/main.go @@ -0,0 +1,213 @@ +// +// Copyright (c) 2016 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package main + +import ( + "flag" + "fmt" + "github.com/golang/glog" + "github.com/google/go-github/github" + "golang.org/x/oauth2" + "os" + "strings" + "time" +) + +const repoOwner = "01org" +const repo = "ciao" + +var rcSHA = flag.String("head", "", "The sha of the release candidate") + +func main() { + flag.Parse() + + f, err := os.Create("release.txt") + if err != nil { + glog.Error(err) + os.Exit(1) + } + defer f.Close() + + ghToken := os.Getenv("GITHUB_TOKEN") + if ghToken == "" { + glog.Fatal("You must set GITHUB_TOKEN env var") + os.Exit(1) + } + + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: ghToken}, + ) + + tc := oauth2.NewClient(oauth2.NoContext, ts) + + client := github.NewClient(tc) + + headCommit, _, _ := client.Repositories.GetCommit(repoOwner, repo, *rcSHA) + if headCommit == nil { + glog.Fatal("The SHA was not valid") + os.Exit(1) + } + + release, _, _ := client.Repositories.GetLatestRelease(repoOwner, repo) + + var tags []*github.RepositoryTag + var tagsOpts github.ListOptions + + for { + t, resp, err := client.Repositories.ListTags(repoOwner, repo, &tagsOpts) + if err != nil { + glog.Fatal(err) + } + + tags = append(tags, t...) + + if resp.NextPage == 0 { + break + } + + tagsOpts.Page = resp.NextPage + } + + var lastRelease time.Time + + for _, t := range tags { + if *t.Name == *release.TagName { + c, _, _ := client.Repositories.GetCommit(repoOwner, repo, *t.Commit.SHA) + if c == nil { + glog.Fatal("The SHA was not valid") + os.Exit(1) + } + lastRelease = *c.Commit.Committer.Date + } + } + + rc := *headCommit.Commit.Committer.Date + + fmt.Fprintf(f, "Changes since last release\n\n") + + var prs []*github.PullRequest + + prOpts := github.PullRequestListOptions{ + State: "closed", + Base: "master", + } + + for { + pr, resp, err := client.PullRequests.List(repoOwner, repo, &prOpts) + if err != nil { + glog.Fatal(err) + } + + prs = append(prs, pr...) + + if resp.NextPage == 0 { + break + } + + prOpts.Page = resp.NextPage + } + + prmap := make(map[int]github.PullRequest) + for _, pr := range prs { + prmap[*pr.Number] = *pr + } + + var events []*github.IssueEvent + var eventOpts github.ListOptions + + for { + e, resp, err := client.Issues.ListRepositoryEvents(repoOwner, repo, &eventOpts) + if err != nil { + glog.Fatal(err) + } + + events = append(events, e...) + + if resp.NextPage == 0 { + break + } + + eventOpts.Page = resp.NextPage + } + + eventsmap := make(map[string][]*github.IssueEvent) + + for _, e := range events { + key := *e.Event + + if key == "merged" || key == "closed" { + num := *e.Issue.Number + + if e.Issue.PullRequestLinks != nil { + _, ok := prmap[num] + if !ok { + continue + } + } + + if lastRelease.IsZero() { + eventsmap[key] = append(eventsmap[key], e) + } else { + if lastRelease.Before(*e.CreatedAt) { + if e.CreatedAt.Before(rc) { + eventsmap[key] = append(eventsmap[key], e) + } + } + } + } + } + + for key, list := range eventsmap { + fmt.Fprintf(f, "---%s---\n", key) + for _, e := range list { + i := *e.Issue + fmt.Fprintf(f, "\tIssue/PR #%d: %s\n", *i.Number, *i.Title) + fmt.Fprintf(f, "\tURL: %s\n\n", *i.HTMLURL) + } + fmt.Fprintf(f, "\n") + } + + copts := github.CommitsListOptions{ + Since: lastRelease, + Until: rc, + } + + var commits []*github.RepositoryCommit + for { + c, resp, err := client.Repositories.ListCommits(repoOwner, repo, &copts) + if err != nil { + glog.Fatal(err) + } + + commits = append(commits, c...) + if resp.NextPage == 0 { + break + } + if resp != nil { + copts.ListOptions.Page = resp.NextPage + } + } + + if len(commits) > 0 { + fmt.Fprintln(f, "---Full Change Log---") + } + + for _, c := range commits { + lines := strings.Split(*c.Commit.Message, "\n") + fmt.Fprintf(f, "\t%s\n", lines[0]) + } +} From d7ce0165b3729bad839f2246c219244361ebda8e Mon Sep 17 00:00:00 2001 From: Kristen Carlson Accardi Date: Wed, 6 Jul 2016 10:19:47 -0700 Subject: [PATCH 2/2] release: ciao-release: refactor refactor the ciao-release code to be more readable and to handle os.Exit() with deferrable error handling. Signed-off-by: Kristen Carlson Accardi --- _release/ciao-release/main.go | 135 +++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 36 deletions(-) diff --git a/_release/ciao-release/main.go b/_release/ciao-release/main.go index 7a4d1758e..48dfeaf85 100644 --- a/_release/ciao-release/main.go +++ b/_release/ciao-release/main.go @@ -17,6 +17,7 @@ package main import ( + "errors" "flag" "fmt" "github.com/golang/glog" @@ -32,20 +33,10 @@ const repo = "ciao" var rcSHA = flag.String("head", "", "The sha of the release candidate") -func main() { - flag.Parse() - - f, err := os.Create("release.txt") - if err != nil { - glog.Error(err) - os.Exit(1) - } - defer f.Close() - +func getAuthenticatedClient() (*github.Client, error) { ghToken := os.Getenv("GITHUB_TOKEN") if ghToken == "" { - glog.Fatal("You must set GITHUB_TOKEN env var") - os.Exit(1) + return nil, errors.New("You must set GITHUB_TOKEN env var") } ts := oauth2.StaticTokenSource( @@ -56,11 +47,23 @@ func main() { client := github.NewClient(tc) + return client, nil +} + +func getReleaseCandidateTime(client *github.Client) (time.Time, error) { + rc := time.Time{} + headCommit, _, _ := client.Repositories.GetCommit(repoOwner, repo, *rcSHA) if headCommit == nil { - glog.Fatal("The SHA was not valid") - os.Exit(1) + return rc, errors.New("The SHA was not valid") } + rc = *headCommit.Commit.Committer.Date + + return rc, nil +} + +func getLastReleaseTime(client *github.Client) (time.Time, error) { + var lastRelease time.Time release, _, _ := client.Repositories.GetLatestRelease(repoOwner, repo) @@ -82,23 +85,20 @@ func main() { tagsOpts.Page = resp.NextPage } - var lastRelease time.Time - for _, t := range tags { if *t.Name == *release.TagName { c, _, _ := client.Repositories.GetCommit(repoOwner, repo, *t.Commit.SHA) if c == nil { - glog.Fatal("The SHA was not valid") - os.Exit(1) + return lastRelease, errors.New("The SHA was not valid") } lastRelease = *c.Commit.Committer.Date } } - rc := *headCommit.Commit.Committer.Date - - fmt.Fprintf(f, "Changes since last release\n\n") + return lastRelease, nil +} +func getAllPullRequests(client *github.Client) (map[int]github.PullRequest, error) { var prs []*github.PullRequest prOpts := github.PullRequestListOptions{ @@ -109,7 +109,7 @@ func main() { for { pr, resp, err := client.PullRequests.List(repoOwner, repo, &prOpts) if err != nil { - glog.Fatal(err) + return nil, err } prs = append(prs, pr...) @@ -126,13 +126,22 @@ func main() { prmap[*pr.Number] = *pr } + return prmap, nil +} + +func getIssueEvents(client *github.Client, lastRelease time.Time, rc time.Time) (map[string][]*github.IssueEvent, error) { + prmap, err := getAllPullRequests(client) + if err != nil { + return nil, err + } + var events []*github.IssueEvent var eventOpts github.ListOptions for { e, resp, err := client.Issues.ListRepositoryEvents(repoOwner, repo, &eventOpts) if err != nil { - glog.Fatal(err) + return nil, err } events = append(events, e...) @@ -171,26 +180,21 @@ func main() { } } - for key, list := range eventsmap { - fmt.Fprintf(f, "---%s---\n", key) - for _, e := range list { - i := *e.Issue - fmt.Fprintf(f, "\tIssue/PR #%d: %s\n", *i.Number, *i.Title) - fmt.Fprintf(f, "\tURL: %s\n\n", *i.HTMLURL) - } - fmt.Fprintf(f, "\n") - } + return eventsmap, err +} + +func getCommits(client *github.Client, since time.Time, until time.Time) ([]*github.RepositoryCommit, error) { + var commits []*github.RepositoryCommit copts := github.CommitsListOptions{ - Since: lastRelease, - Until: rc, + Since: since, + Until: until, } - var commits []*github.RepositoryCommit for { c, resp, err := client.Repositories.ListCommits(repoOwner, repo, &copts) if err != nil { - glog.Fatal(err) + return nil, err } commits = append(commits, c...) @@ -202,6 +206,53 @@ func main() { } } + return commits, nil +} + +func generateReleaseNotes() error { + client, err := getAuthenticatedClient() + if err != nil { + return err + } + + rc, err := getReleaseCandidateTime(client) + if err != nil { + return err + } + + lastRelease, err := getLastReleaseTime(client) + if err != nil { + return err + } + + eventsmap, err := getIssueEvents(client, lastRelease, rc) + if err != nil { + return err + } + + commits, err := getCommits(client, lastRelease, rc) + if err != nil { + return err + } + + f, err := os.Create("release.txt") + if err != nil { + return err + } + defer f.Close() + + fmt.Fprintf(f, "Changes since last release\n\n") + + for key, list := range eventsmap { + fmt.Fprintf(f, "---%s---\n", key) + for _, e := range list { + i := *e.Issue + fmt.Fprintf(f, "\tIssue/PR #%d: %s\n", *i.Number, *i.Title) + fmt.Fprintf(f, "\tURL: %s\n\n", *i.HTMLURL) + } + fmt.Fprintf(f, "\n") + } + if len(commits) > 0 { fmt.Fprintln(f, "---Full Change Log---") } @@ -210,4 +261,16 @@ func main() { lines := strings.Split(*c.Commit.Message, "\n") fmt.Fprintf(f, "\t%s\n", lines[0]) } + + return nil +} + +func main() { + flag.Parse() + + err := generateReleaseNotes() + if err != nil { + glog.Fatal(err) + os.Exit(1) + } }