aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2026-01-29 09:21:15 +0100
committerDmitry Vyukov <dvyukov@google.com>2026-01-30 11:03:40 +0000
commit5f1dd856122a81d1cf6bcad71da663f42484f321 (patch)
treec35188a6b0590bf0d0c1d229fa83cc8c96205069 /pkg
parentb31760143a55012873c68e5ff5b51cd9c376729f (diff)
pkg/gerrit: add package
Package gerrit provides gerrit client for: https://linux.googlesource.com/Documentation/#gerrit-code-reviews-for-the-linux-kernel
Diffstat (limited to 'pkg')
-rw-r--r--pkg/gerrit/gerrit.go79
-rw-r--r--pkg/gerrit/repos.go61
-rw-r--r--pkg/gerrit/repos_test.go33
3 files changed, 173 insertions, 0 deletions
diff --git a/pkg/gerrit/gerrit.go b/pkg/gerrit/gerrit.go
new file mode 100644
index 000000000..f9e6d2c5f
--- /dev/null
+++ b/pkg/gerrit/gerrit.go
@@ -0,0 +1,79 @@
+// Copyright 2026 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+// Package gerrit provides gerrit client for:
+// https://linux.googlesource.com/Documentation/#gerrit-code-reviews-for-the-linux-kernel
+// For documentation on the API see:
+// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html
+package gerrit
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+)
+
+const host = "https://linux-review.googlesource.com"
+
+func CreateChange(ctx context.Context, repo, branch, baseCommit, description, diff string) (
+ changeID int, link string, err error) {
+ project, err := projectForRepo(repo)
+ if err != nil {
+ return 0, "", err
+ }
+ req := map[string]any{
+ "project": project,
+ "branch": branch,
+ "subject": description,
+ "base_commit": baseCommit,
+ "patch": map[string]any{
+ "patch": diff,
+ },
+ }
+ resp := new(struct {
+ Number int `json:"_number"`
+ })
+ err = request(ctx, "changes/", req, resp)
+ link = fmt.Sprintf("%v/c/%v/+/%v", host, project, resp.Number)
+ return resp.Number, link, err
+}
+
+func request(ctx context.Context, api string, req map[string]any, resp any) error {
+ ts, err := google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/gerritcodereview")
+ if err != nil {
+ return fmt.Errorf("gerrit: failed to get token source: %w", err)
+ }
+ reqData, err := json.Marshal(req)
+ if err != nil {
+ return fmt.Errorf("gerrit: failed to marshal CreateChange: %w", err)
+ }
+ endpoint := fmt.Sprintf("%v/a/%v", host, api)
+ httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(reqData))
+ if err != nil {
+ return fmt.Errorf("gerrit: failed to create request: %w", err)
+ }
+ httpReq.Header.Set("Content-Type", "application/json; charset=UTF-8")
+ httpResp, err := oauth2.NewClient(ctx, ts).Do(httpReq)
+ if err != nil {
+ return fmt.Errorf("gerrit: failed to call %v: %w", api, err)
+ }
+ defer httpResp.Body.Close()
+ body, err := io.ReadAll(httpResp.Body)
+ if err != nil || httpResp.StatusCode < 200 || httpResp.StatusCode > 299 {
+ return fmt.Errorf("gerrit: failed to call %v: %v %v err:%w: %s",
+ api, httpResp.StatusCode, http.StatusText(httpResp.StatusCode), err, body)
+ }
+ // Responses may start with ")]}'" for XSSI protection; trim it.
+ const xssiPrefix = ")]}'\n"
+ body = bytes.TrimPrefix(body, []byte(xssiPrefix))
+ if err := json.Unmarshal(body, resp); err != nil {
+ return fmt.Errorf("gerrit: failed to unmarshal %v response: %w\n%s", api, err, body)
+ }
+ return nil
+}
diff --git a/pkg/gerrit/repos.go b/pkg/gerrit/repos.go
new file mode 100644
index 000000000..b2c793111
--- /dev/null
+++ b/pkg/gerrit/repos.go
@@ -0,0 +1,61 @@
+// Copyright 2026 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package gerrit
+
+import (
+ "fmt"
+)
+
+func projectForRepo(repo string) (string, error) {
+ project := projects[repo]
+ if project == "" {
+ return "", fmt.Errorf("gerrit: repo %q is not supported", repo)
+ }
+ return project, nil
+}
+
+var projects = func() map[string]string {
+ // There are few others non-kernel.org-based, but we don't support them here now.
+ kernelOrgRepos := []string{
+ "bluetooth/bluetooth-next",
+ "bpf/bpf",
+ "bpf/bpf-next",
+ "brauner/linux",
+ "davem/net",
+ "davem/net-next",
+ "dhowells/keyutils",
+ "dhowells/linux-fs",
+ "dtor/input",
+ "gregkh/char-misc",
+ "gregkh/driver-core",
+ "gregkh/staging",
+ "gregkh/tty",
+ "gregkh/usb",
+ "herbert/crypto-2.6",
+ "hid/hid",
+ "jmorris/linux-security",
+ "klassert/ipsec",
+ "klassert/ipsec-next",
+ "mellanox/linux",
+ "paulmck/linux-rcu",
+ "tip/tip",
+ "tiwai/sound",
+ "tj/cgroup",
+ "tj/wq",
+ "torvalds/linux",
+ "viro/vfs",
+ "will/linux",
+ "zohar/linux-integrity",
+ }
+ res := map[string]string{}
+ for _, repo := range kernelOrgRepos {
+ project := "linux/kernel/git/" + repo
+ const kernelOrgPrefix = "git.kernel.org/pub/scm/linux/kernel/git"
+ const googleSrcPrefix = "kernel.googlesource.com/pub/scm/linux/kernel/git"
+ res[fmt.Sprintf("git://%v/%v.git", kernelOrgPrefix, repo)] = project
+ res[fmt.Sprintf("https://%v/%v.git", kernelOrgPrefix, repo)] = project
+ res[fmt.Sprintf("https://%v/%v.git", googleSrcPrefix, repo)] = project
+ }
+ return res
+}()
diff --git a/pkg/gerrit/repos_test.go b/pkg/gerrit/repos_test.go
new file mode 100644
index 000000000..019396eae
--- /dev/null
+++ b/pkg/gerrit/repos_test.go
@@ -0,0 +1,33 @@
+// Copyright 2026 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package gerrit
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestProjectForRepo(t *testing.T) {
+ {
+ got, err := projectForRepo("git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git")
+ require.NoError(t, err)
+ require.Equal(t, "linux/kernel/git/torvalds/linux", got)
+ }
+ {
+ got, err := projectForRepo("https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git")
+ require.NoError(t, err)
+ require.Equal(t, "linux/kernel/git/bpf/bpf-next", got)
+ }
+ {
+ got, err := projectForRepo("https://kernel.googlesource.com/pub/scm/linux/kernel/git/davem/net.git")
+ require.NoError(t, err)
+ require.Equal(t, "linux/kernel/git/davem/net", got)
+ }
+ {
+ // Valid repo, but we don't mirror it.
+ _, err := projectForRepo("git://git.kernel.org/pub/scm/linux/kernel/git/ast/bpf.git")
+ require.Error(t, err)
+ }
+}