tools: Add snippets tool
Gathers information about changes merged and reviewed for team weekly reports.
Change-Id: I53e3acc45679b4822c506d16980393fbaf337b3b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/59022
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/tools/gerrit-stats b/tools/gerrit-stats
new file mode 100755
index 0000000..3fcfb32
--- /dev/null
+++ b/tools/gerrit-stats
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+# Copyright 2021 The Tint Authors
+#
+# 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.
+
+set -e # Fail on any error.
+
+if [ ! -x "$(which go)" ] ; then
+ echo "error: go needs to be on \$PATH to use $0"
+ exit 1
+fi
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
+ROOT_DIR="$( cd "${SCRIPT_DIR}/.." >/dev/null 2>&1 && pwd )"
+BINARY="${SCRIPT_DIR}/bin/gerrit-stats"
+
+# Rebuild the binary.
+# Note, go caches build artifacts, so this is quick for repeat calls
+pushd "${SCRIPT_DIR}/src/cmd/gerrit-stats" > /dev/null
+ go build -o "${BINARY}" main.go
+popd > /dev/null
+
+"${BINARY}" "$@"
diff --git a/tools/snippets b/tools/snippets
new file mode 100755
index 0000000..d58232d
--- /dev/null
+++ b/tools/snippets
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+# Copyright 2021 The Tint Authors
+#
+# 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.
+
+set -e # Fail on any error.
+
+if [ ! -x "$(which go)" ] ; then
+ echo "error: go needs to be on \$PATH to use $0"
+ exit 1
+fi
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
+ROOT_DIR="$( cd "${SCRIPT_DIR}/.." >/dev/null 2>&1 && pwd )"
+BINARY="${SCRIPT_DIR}/bin/snippets"
+
+# Rebuild the binary.
+# Note, go caches build artifacts, so this is quick for repeat calls
+pushd "${SCRIPT_DIR}/src/cmd/snippets" > /dev/null
+ go build -o "${BINARY}" main.go
+popd > /dev/null
+
+"${BINARY}" "$@"
diff --git a/tools/src/cmd/gerrit-stats/main.go b/tools/src/cmd/gerrit-stats/main.go
index dd27fc3..33e30fd 100644
--- a/tools/src/cmd/gerrit-stats/main.go
+++ b/tools/src/cmd/gerrit-stats/main.go
@@ -18,20 +18,15 @@
import (
"flag"
"fmt"
- "io/ioutil"
"net/url"
"os"
"regexp"
- "strings"
"time"
- "github.com/andygrunwald/go-gerrit"
+ "dawn.googlesource.com/tint/tools/src/gerrit"
)
-const (
- yyyymmdd = "2006-01-02"
- gerritURL = "https://dawn-review.googlesource.com/"
-)
+const yyyymmdd = "2006-01-02"
var (
// See https://dawn-review.googlesource.com/new-password for obtaining
@@ -54,26 +49,6 @@
}
}
-func queryChanges(client *gerrit.Client, queryParts ...string) ([]gerrit.ChangeInfo, string, error) {
- query := strings.Join(queryParts, "+")
- out := []gerrit.ChangeInfo{}
- for {
- changes, _, err := client.Changes.QueryChanges(&gerrit.QueryChangeOptions{
- QueryOptions: gerrit.QueryOptions{Query: []string{query}},
- Skip: len(out),
- })
- if err != nil {
- return nil, "", err
- }
-
- out = append(out, *changes...)
- if len(*changes) == 0 || !(*changes)[len(*changes)-1].MoreChanges {
- break
- }
- }
- return out, query, nil
-}
-
func run() error {
var after, before time.Time
var err error
@@ -98,43 +73,22 @@
after = before.Add(-time.Hour * time.Duration(24**daysFlag))
}
- client, err := gerrit.NewClient(gerritURL, nil)
+ g, err := gerrit.New(gerrit.Config{Username: *gerritUser, Password: *gerritPass})
if err != nil {
- return fmt.Errorf("Couldn't create gerrit client: %w", err)
+ return err
}
- if *gerritUser == "" {
- cookiesFile := os.Getenv("HOME") + "/.gitcookies"
- if cookies, err := ioutil.ReadFile(cookiesFile); err == nil {
- re := regexp.MustCompile(`dawn-review.googlesource.com\s+(?:FALSE|TRUE)[\s/]+(?:FALSE|TRUE)\s+[0-9]+\s+.\s+(.*)=(.*)`)
- match := re.FindStringSubmatch(string(cookies))
- if len(match) == 3 {
- *gerritUser, *gerritPass = match[1], match[2]
- }
- }
- }
-
- if *gerritUser != "" {
- client.Authentication.SetBasicAuth(*gerritUser, *gerritPass)
- }
-
- submitted, submittedQuery, err := queryChanges(client,
+ submitted, submittedQuery, err := g.QueryChanges(
"status:merged",
"owner:"+user,
"after:"+date(after),
"before:"+date(before),
"repo:"+*repoFlag)
if err != nil {
- if *gerritUser == "" {
- return fmt.Errorf(`Query failed, possibly because of authentication.
-See https://dawn-review.googlesource.com/new-password for obtaining a username
-and password which can be provided with --gerrit-user and --gerrit-pass.
-%w`, err)
- }
return fmt.Errorf("Query failed: %w", err)
}
- reviewed, reviewQuery, err := queryChanges(client,
+ reviewed, reviewQuery, err := g.QueryChanges(
"commentby:"+user,
"-owner:"+user,
"after:"+date(after),
@@ -183,8 +137,8 @@
}
fmt.Printf("\n")
- fmt.Printf("Submitted query: %vq/%v\n", gerritURL, url.QueryEscape(submittedQuery))
- fmt.Printf("Review query: %vq/%v\n", gerritURL, url.QueryEscape(reviewQuery))
+ fmt.Printf("Submitted query: %vq/%v\n", gerrit.URL, url.QueryEscape(submittedQuery))
+ fmt.Printf("Review query: %vq/%v\n", gerrit.URL, url.QueryEscape(reviewQuery))
return nil
}
diff --git a/tools/src/cmd/snippets/main.go b/tools/src/cmd/snippets/main.go
new file mode 100644
index 0000000..a8b94fc
--- /dev/null
+++ b/tools/src/cmd/snippets/main.go
@@ -0,0 +1,112 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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.
+
+// snippets gathers information about changes merged for weekly reports (snippets).
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "dawn.googlesource.com/tint/tools/src/gerrit"
+)
+
+const yyyymmdd = "2006-01-02"
+
+var (
+ // See https://dawn-review.googlesource.com/new-password for obtaining
+ // username and password for gerrit.
+ gerritUser = flag.String("gerrit-user", "", "gerrit authentication username")
+ gerritPass = flag.String("gerrit-pass", "", "gerrit authentication password")
+ userFlag = flag.String("user", "", "user name / email")
+ afterFlag = flag.String("after", "", "start date")
+ beforeFlag = flag.String("before", "", "end date")
+ daysFlag = flag.Int("days", 7, "interval in days (used if --after is not specified)")
+)
+
+func main() {
+ flag.Parse()
+ if err := run(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
+
+func run() error {
+ var after, before time.Time
+ var err error
+ user := *userFlag
+ if user == "" {
+ return fmt.Errorf("Missing required 'user' flag")
+ }
+ if *beforeFlag != "" {
+ before, err = time.Parse(yyyymmdd, *beforeFlag)
+ if err != nil {
+ return fmt.Errorf("Couldn't parse before date: %w", err)
+ }
+ } else {
+ before = time.Now()
+ }
+ if *afterFlag != "" {
+ after, err = time.Parse(yyyymmdd, *afterFlag)
+ if err != nil {
+ return fmt.Errorf("Couldn't parse after date: %w", err)
+ }
+ } else {
+ after = before.Add(-time.Hour * time.Duration(24**daysFlag))
+ }
+
+ g, err := gerrit.New(gerrit.Config{Username: *gerritUser, Password: *gerritPass})
+ if err != nil {
+ return err
+ }
+
+ submitted, _, err := g.QueryChanges(
+ "status:merged",
+ "owner:"+user,
+ "after:"+date(after),
+ "before:"+date(before))
+ if err != nil {
+ return fmt.Errorf("Query failed: %w", err)
+ }
+
+ changesByProject := map[string][]string{}
+ for _, change := range submitted {
+ str := fmt.Sprintf(`* [%s](%sc/%s/+/%d)`, change.Subject, gerrit.URL, change.Project, change.Number)
+ changesByProject[change.Project] = append(changesByProject[change.Project], str)
+ }
+
+ for _, project := range []string{"tint", "dawn"} {
+ if changes := changesByProject[project]; len(changes) > 0 {
+ fmt.Println("##", strings.Title(project))
+ for _, change := range changes {
+ fmt.Println(change)
+ }
+ fmt.Println()
+ }
+ }
+
+ return nil
+}
+
+func today() time.Time {
+ return time.Now()
+}
+
+func date(t time.Time) string {
+ return t.Format(yyyymmdd)
+}
diff --git a/tools/src/gerrit/gerrit.go b/tools/src/gerrit/gerrit.go
new file mode 100644
index 0000000..0485bbd
--- /dev/null
+++ b/tools/src/gerrit/gerrit.go
@@ -0,0 +1,90 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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.
+
+// gerrit provides helpers for obtaining information from Tint's gerrit instance
+package gerrit
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "regexp"
+ "strings"
+
+ "github.com/andygrunwald/go-gerrit"
+)
+
+const URL = "https://dawn-review.googlesource.com/"
+
+// G is the interface to gerrit
+type G struct {
+ client *gerrit.Client
+ authenticated bool
+}
+
+type Config struct {
+ Username string
+ Password string
+}
+
+func New(cfg Config) (*G, error) {
+ client, err := gerrit.NewClient(URL, nil)
+ if err != nil {
+ return nil, fmt.Errorf("Couldn't create gerrit client: %w", err)
+ }
+
+ user, pass := cfg.Username, cfg.Password
+ if user == "" {
+ cookiesFile := os.Getenv("HOME") + "/.gitcookies"
+ if cookies, err := ioutil.ReadFile(cookiesFile); err == nil {
+ re := regexp.MustCompile(`dawn-review.googlesource.com\s+(?:FALSE|TRUE)[\s/]+(?:FALSE|TRUE)\s+[0-9]+\s+.\s+(.*)=(.*)`)
+ match := re.FindStringSubmatch(string(cookies))
+ if len(match) == 3 {
+ user, pass = match[1], match[2]
+ }
+ }
+ }
+
+ if user != "" {
+ client.Authentication.SetBasicAuth(user, pass)
+ }
+
+ return &G{client, user != ""}, nil
+}
+
+func (g *G) QueryChanges(queryParts ...string) (changes []gerrit.ChangeInfo, query string, err error) {
+ changes = []gerrit.ChangeInfo{}
+ query = strings.Join(queryParts, "+")
+ for {
+ batch, _, err := g.client.Changes.QueryChanges(&gerrit.QueryChangeOptions{
+ QueryOptions: gerrit.QueryOptions{Query: []string{query}},
+ Skip: len(changes),
+ })
+ if err != nil {
+ if !g.authenticated {
+ err = fmt.Errorf(`Query failed, possibly because of authentication.
+ See https://dawn-review.googlesource.com/new-password for obtaining a username
+ and password which can be provided with --gerrit-user and --gerrit-pass.
+ %w`, err)
+ }
+ return nil, "", err
+ }
+
+ changes = append(changes, *batch...)
+ if len(*batch) == 0 || !(*batch)[len(*batch)-1].MoreChanges {
+ break
+ }
+ }
+ return changes, query, nil
+}