[tools] Add 'add-gerrit-hashtags' tool
Parses the CL descriptions and adds missing hashtags to Gerrit changes.
Also add ./tools/push-to-gerrit which runs this after pushing the local branch's changes to 'main'.
Use this for the VSCode 'push' task.
Change-Id: I4c3f5982f6fdc7c1c6ebe770fc7811b1b38795d1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/133061
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Ben Clayton <bclayton@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index c0c1554..acd0666 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -125,17 +125,12 @@
{
"label": "push",
"type": "shell",
- "command": "git",
- "args": [
- "push",
- "origin",
- "HEAD:refs/for/main"
- ],
+ "command": "./tools/push-to-gerrit",
"options": {
"cwd": "${workspaceRoot}"
},
"problemMatcher": [],
- }
+ },
],
"inputs": [
{
diff --git a/go.mod b/go.mod
index 7eb200f..a5d09ed 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@
go 1.18
require (
- github.com/andygrunwald/go-gerrit v0.0.0-20220427111355-d3e91fbf2db5
+ github.com/andygrunwald/go-gerrit v0.0.0-20230508072829-423d372345aa
github.com/ben-clayton/webidlparser v0.0.0-20210923100217-8ba896ded094
github.com/fatih/color v1.13.0
github.com/google/go-cmp v0.5.9
diff --git a/go.sum b/go.sum
index a648592..785efdb 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,8 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/andygrunwald/go-gerrit v0.0.0-20220427111355-d3e91fbf2db5 h1:HBlTlvyq4siv4ZK41DebGIX11/9gFBqUF8G64AePjyQ=
github.com/andygrunwald/go-gerrit v0.0.0-20220427111355-d3e91fbf2db5/go.mod h1:aqcjwEnmLLSalFNYR0p2ttnEXOVVRctIzsUMHbEcruU=
+github.com/andygrunwald/go-gerrit v0.0.0-20230508072829-423d372345aa h1:bGSzPoUh/2eduqGEk54TCoB4v81MVi6Hr3+fYQwFrBM=
+github.com/andygrunwald/go-gerrit v0.0.0-20230508072829-423d372345aa/go.mod h1:SeP12EkHZxEVjuJ2HZET304NBtHGG2X6w2Gzd0QXAZw=
github.com/ben-clayton/webidlparser v0.0.0-20210923100217-8ba896ded094 h1:CTVJdI6oUCRNucMEmoh3c2U88DesoPtefsxKhoZ1WuQ=
github.com/ben-clayton/webidlparser v0.0.0-20210923100217-8ba896ded094/go.mod h1:bV550SPlMos7UhMprxlm14XTBTpKHSUZ8Q4Id5qQuyw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
diff --git a/tools/push-to-gerrit b/tools/push-to-gerrit
new file mode 100755
index 0000000..ed11cc8
--- /dev/null
+++ b/tools/push-to-gerrit
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+# Copyright 2023 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.
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
+
+git push origin HEAD:refs/for/main
+
+${SCRIPT_DIR}/run add-gerrit-hashtags
diff --git a/tools/src/cmd/add-gerrit-hashtags/main.go b/tools/src/cmd/add-gerrit-hashtags/main.go
new file mode 100644
index 0000000..e0ae0b2
--- /dev/null
+++ b/tools/src/cmd/add-gerrit-hashtags/main.go
@@ -0,0 +1,168 @@
+// Copyright 2023 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.
+
+// add-gerrit-hashtags adds any missing hashtags parsed from the CL description to the Gerrit change.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "os/exec"
+ "regexp"
+ "strings"
+ "time"
+
+ "dawn.googlesource.com/dawn/tools/src/container"
+ "dawn.googlesource.com/dawn/tools/src/dawn"
+ "dawn.googlesource.com/dawn/tools/src/gerrit"
+ "dawn.googlesource.com/dawn/tools/src/git"
+)
+
+const (
+ toolName = "add-gerrit-hashtags"
+ 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")
+ repoFlag = flag.String("repo", "dawn", "the project (tint or dawn)")
+ userFlag = flag.String("user", defaultUser(), "user name / email")
+ afterFlag = flag.String("after", "", "start date")
+ beforeFlag = flag.String("before", "", "end date")
+ daysFlag = flag.Int("days", 30, "interval in days (used if --after is not specified)")
+ verboseFlag = flag.Bool("v", false, "verbose mode - lists all the changes")
+ dryrunFlag = flag.Bool("dry", false, "dry mode. Don't apply any changes")
+)
+
+func defaultUser() string {
+ if gitExe, err := exec.LookPath("git"); err == nil {
+ if g, err := git.New(gitExe); err == nil {
+ if cwd, err := os.Getwd(); err == nil {
+ if r, err := g.Open(cwd); err == nil {
+ if cfg, err := r.Config(nil); err == nil {
+ return cfg["user.email"]
+ }
+ }
+ }
+ }
+ }
+ return ""
+}
+
+func main() {
+ flag.Usage = func() {
+ out := flag.CommandLine.Output()
+ fmt.Fprintf(out, "%v adds any missing hashtags parsed from the CL description to the Gerrit change.\n", toolName)
+ fmt.Fprintf(out, "\n")
+ flag.PrintDefaults()
+ }
+ 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().Add(24 * time.Hour)
+ }
+ 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(dawn.GerritURL, gerrit.Credentials{
+ Username: *gerritUser, Password: *gerritPass,
+ })
+ if err != nil {
+ return err
+ }
+
+ submitted, _, err := g.QueryChanges(
+ "owner:"+user,
+ "after:"+date(after),
+ "before:"+date(before),
+ "repo:"+*repoFlag)
+ if err != nil {
+ return fmt.Errorf("Query failed: %w", err)
+ }
+
+ numUpdated := 0
+ for _, cl := range submitted {
+ expected := parseHashtags(cl.Subject)
+ got := container.NewSet(cl.Hashtags...)
+ if !got.ContainsAll(expected) {
+ toAdd := expected.Clone()
+ toAdd.RemoveAll(got)
+ fmt.Printf("%v: %v missing hashtags: %v\n", cl.Number, cl.Subject, strings.Join(toAdd.List(), ", "))
+ if !*dryrunFlag {
+ if err := g.AddHashtags(cl.ChangeID, toAdd); err != nil {
+ return err
+ }
+ numUpdated++
+ }
+ }
+ }
+
+ if numUpdated > 0 {
+ fmt.Println()
+ fmt.Println(numUpdated, "changes updated with new hashtags")
+ } else {
+ fmt.Println("no changes updated")
+ }
+
+ return nil
+}
+
+var reBracketHashtag = regexp.MustCompile(`\[(\w+)\]`)
+var reColonHashtag = regexp.MustCompile(`^(\w+):`)
+
+func parseHashtags(subject string) container.Set[string] {
+ out := container.NewSet[string]()
+ for _, match := range reBracketHashtag.FindAllStringSubmatch(subject, -1) {
+ out.Add(match[1])
+ }
+ if match := reColonHashtag.FindStringSubmatch(subject); len(match) > 1 {
+ out.Add(match[1])
+ }
+ return out
+}
+
+func today() time.Time {
+ return time.Now()
+}
+
+func date(t time.Time) string {
+ return t.Format(yyyymmdd)
+}
diff --git a/tools/src/cmd/benchdiff/main.go b/tools/src/cmd/benchdiff/main.go
index ad9b9bd..66503aa 100644
--- a/tools/src/cmd/benchdiff/main.go
+++ b/tools/src/cmd/benchdiff/main.go
@@ -35,11 +35,11 @@
func main() {
flag.ErrHelp = errors.New("benchdiff is a tool to compare two benchmark results")
- flag.Parse()
flag.Usage = func() {
fmt.Fprintln(os.Stderr, "benchdiff <benchmark-a> <benchmark-b>")
flag.PrintDefaults()
}
+ flag.Parse()
args := flag.Args()
if len(args) < 2 {
diff --git a/tools/src/cmd/cts/common/results.go b/tools/src/cmd/cts/common/results.go
index a1647f3..00d829a 100644
--- a/tools/src/cmd/cts/common/results.go
+++ b/tools/src/cmd/cts/common/results.go
@@ -116,7 +116,7 @@
if err != nil {
return nil, err
}
- *ps, err = gerrit.LatestPatchest(strconv.Itoa(ps.Change))
+ *ps, err = gerrit.LatestPatchset(strconv.Itoa(ps.Change))
if err != nil {
err := fmt.Errorf("failed to find latest patchset of change %v: %w",
ps.Change, err)
@@ -288,7 +288,7 @@
// LatestPatchset returns the most recent patchset for the given change.
func LatestPatchset(g *gerrit.Gerrit, change int) (gerrit.Patchset, error) {
- ps, err := g.LatestPatchest(strconv.Itoa(change))
+ ps, err := g.LatestPatchset(strconv.Itoa(change))
if err != nil {
err := fmt.Errorf("failed to find latest patchset of change %v: %w",
ps.Change, err)
diff --git a/tools/src/gerrit/gerrit.go b/tools/src/gerrit/gerrit.go
index 8633afb..228f824 100644
--- a/tools/src/gerrit/gerrit.go
+++ b/tools/src/gerrit/gerrit.go
@@ -25,6 +25,7 @@
"strconv"
"strings"
+ "dawn.googlesource.com/dawn/tools/src/container"
"github.com/andygrunwald/go-gerrit"
)
@@ -55,8 +56,8 @@
// ChangeInfo is an alias to gerrit.ChangeInfo
type ChangeInfo = gerrit.ChangeInfo
-// LatestPatchest returns the latest Patchset from the ChangeInfo
-func LatestPatchest(change *ChangeInfo) Patchset {
+// LatestPatchset returns the latest Patchset from the ChangeInfo
+func LatestPatchset(change *ChangeInfo) Patchset {
u, _ := url.Parse(change.URL)
ps := Patchset{
Host: u.Host,
@@ -198,11 +199,11 @@
return Patchset{}, g.maybeWrapError(err)
}
- return g.LatestPatchest(changeID)
+ return g.LatestPatchset(changeID)
}
-// LatestPatchest returns the latest patchset for the change.
-func (g *Gerrit) LatestPatchest(changeID string) (Patchset, error) {
+// LatestPatchset returns the latest patchset for the change.
+func (g *Gerrit) LatestPatchset(changeID string) (Patchset, error) {
change, _, err := g.client.Changes.GetChange(changeID, &gerrit.ChangeOptions{
AdditionalFields: []string{"CURRENT_REVISION"},
})
@@ -218,6 +219,17 @@
return ps, nil
}
+// AddHashtags adds the given hashtags to the change
+func (g *Gerrit) AddHashtags(changeID string, tags container.Set[string]) error {
+ _, resp, err := g.client.Changes.SetHashtags(changeID, &gerrit.HashtagsInput{
+ Add: tags.List(),
+ })
+ if err != nil && resp.StatusCode != 409 { // 409: already ready
+ return g.maybeWrapError(err)
+ }
+ return nil
+}
+
// CommentSide is an enumerator for specifying which side code-comments should
// be shown.
type CommentSide int