2015-02-11 07:25:37 +03:00
|
|
|
|
// Copyright 2015 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
// Package gerrit contains code to interact with Gerrit servers.
|
2017-01-22 17:03:14 +03:00
|
|
|
|
//
|
|
|
|
|
// The API is not subject to the Go 1 compatibility promise and may change at
|
|
|
|
|
// any time.
|
2015-02-11 07:25:37 +03:00
|
|
|
|
package gerrit
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
2015-02-11 08:18:39 +03:00
|
|
|
|
"bytes"
|
2017-04-18 06:12:13 +03:00
|
|
|
|
"context"
|
2022-08-13 00:25:05 +03:00
|
|
|
|
"encoding/base64"
|
2015-02-11 07:25:37 +03:00
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
2015-02-11 08:18:39 +03:00
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2015-02-11 07:25:37 +03:00
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
2017-03-27 21:42:10 +03:00
|
|
|
|
"sort"
|
2015-02-11 07:25:37 +03:00
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2015-04-08 16:45:32 +03:00
|
|
|
|
"time"
|
2015-02-11 07:25:37 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Client is a Gerrit client.
|
|
|
|
|
type Client struct {
|
2019-07-04 02:25:12 +03:00
|
|
|
|
url string // URL prefix, e.g. "https://go-review.googlesource.com" (without trailing slash)
|
2015-02-11 07:25:37 +03:00
|
|
|
|
auth Auth
|
|
|
|
|
|
|
|
|
|
// HTTPClient optionally specifies an HTTP client to use
|
|
|
|
|
// instead of http.DefaultClient.
|
|
|
|
|
HTTPClient *http.Client
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewClient returns a new Gerrit client with the given URL prefix
|
|
|
|
|
// and authentication mode.
|
2019-07-04 02:25:12 +03:00
|
|
|
|
// The url should be just the scheme and hostname. For example, "https://go-review.googlesource.com".
|
2015-02-11 07:25:37 +03:00
|
|
|
|
// If auth is nil, a default is used, or requests are made unauthenticated.
|
|
|
|
|
func NewClient(url string, auth Auth) *Client {
|
|
|
|
|
if auth == nil {
|
|
|
|
|
// TODO(bradfitz): use GitCookies auth, once that exists
|
|
|
|
|
auth = NoAuth
|
|
|
|
|
}
|
|
|
|
|
return &Client{
|
|
|
|
|
url: strings.TrimSuffix(url, "/"),
|
|
|
|
|
auth: auth,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) httpClient() *http.Client {
|
|
|
|
|
if c.HTTPClient != nil {
|
|
|
|
|
return c.HTTPClient
|
|
|
|
|
}
|
|
|
|
|
return http.DefaultClient
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:19:20 +03:00
|
|
|
|
// ErrResourceNotExist is returned when the requested resource doesn't exist.
|
|
|
|
|
// It is only for use with errors.Is.
|
|
|
|
|
var ErrResourceNotExist = errors.New("gerrit: requested resource does not exist")
|
|
|
|
|
|
2022-06-13 18:45:32 +03:00
|
|
|
|
// ErrNotModified is returned when a modification didn't result in any change.
|
|
|
|
|
// It is only for use with errors.Is. Not all APIs return this error; check the documentation.
|
|
|
|
|
var ErrNotModified = errors.New("gerrit: requested modification resulted in no change")
|
|
|
|
|
|
2016-02-09 01:14:13 +03:00
|
|
|
|
// HTTPError is the error type returned when a Gerrit API call does not return
|
|
|
|
|
// the expected status.
|
|
|
|
|
type HTTPError struct {
|
2023-04-07 06:51:39 +03:00
|
|
|
|
Res *http.Response // non-nil
|
|
|
|
|
Body []byte // 4KB prefix
|
|
|
|
|
BodyErr error // any error reading Body
|
2016-02-09 01:14:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *HTTPError) Error() string {
|
2022-05-25 22:04:41 +03:00
|
|
|
|
return fmt.Sprintf("HTTP status %s on request to %s; %s", e.Res.Status, e.Res.Request.URL, e.Body)
|
2016-02-09 01:14:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:19:20 +03:00
|
|
|
|
func (e *HTTPError) Is(target error) bool {
|
2022-06-13 18:45:32 +03:00
|
|
|
|
switch target {
|
|
|
|
|
case ErrResourceNotExist:
|
|
|
|
|
return e.Res.StatusCode == http.StatusNotFound
|
|
|
|
|
case ErrNotModified:
|
|
|
|
|
// As of writing, this error text is the only way to distinguish different Conflict errors. See
|
|
|
|
|
// https://cs.opensource.google/gerrit/gerrit/gerrit/+/master:java/com/google/gerrit/server/restapi/change/ChangeEdits.java;l=346;drc=d338da307a518f7f28b94310c1c083c997ca3c6a
|
|
|
|
|
// https://cs.opensource.google/gerrit/gerrit/gerrit/+/master:java/com/google/gerrit/server/edit/ChangeEditModifier.java;l=453;drc=3bc970bb3e689d1d340382c3f5e5285d44f91dbf
|
|
|
|
|
return e.Res.StatusCode == http.StatusConflict && bytes.Contains(e.Body, []byte("no changes were made"))
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
2022-06-10 22:19:20 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 21:10:54 +03:00
|
|
|
|
// doArg is an optional argument for the Client.do method.
|
2016-02-09 01:14:13 +03:00
|
|
|
|
type doArg interface {
|
|
|
|
|
isDoArg()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type wantResStatus int
|
|
|
|
|
|
|
|
|
|
func (wantResStatus) isDoArg() {}
|
|
|
|
|
|
2021-09-17 21:10:54 +03:00
|
|
|
|
// reqBodyJSON sets the request body to a JSON encoding of v,
|
|
|
|
|
// and the request's Content-Type header to "application/json".
|
|
|
|
|
type reqBodyJSON struct{ v interface{} }
|
2016-02-09 01:14:13 +03:00
|
|
|
|
|
2021-09-17 21:10:54 +03:00
|
|
|
|
func (reqBodyJSON) isDoArg() {}
|
|
|
|
|
|
|
|
|
|
// reqBodyRaw sets the request body to r,
|
|
|
|
|
// and the request's Content-Type header to "application/octet-stream".
|
|
|
|
|
type reqBodyRaw struct{ r io.Reader }
|
|
|
|
|
|
|
|
|
|
func (reqBodyRaw) isDoArg() {}
|
2016-02-09 01:14:13 +03:00
|
|
|
|
|
|
|
|
|
type urlValues url.Values
|
|
|
|
|
|
|
|
|
|
func (urlValues) isDoArg() {}
|
|
|
|
|
|
2022-08-13 00:25:05 +03:00
|
|
|
|
// respBodyRaw returns the body of the response. If set, dst is ignored.
|
|
|
|
|
type respBodyRaw struct{ rc *io.ReadCloser }
|
|
|
|
|
|
|
|
|
|
func (respBodyRaw) isDoArg() {}
|
|
|
|
|
|
2017-01-22 17:03:14 +03:00
|
|
|
|
func (c *Client) do(ctx context.Context, dst interface{}, method, path string, opts ...doArg) error {
|
2016-02-09 01:14:13 +03:00
|
|
|
|
var arg url.Values
|
2022-08-13 00:25:05 +03:00
|
|
|
|
var requestBody io.Reader
|
2021-09-17 21:10:54 +03:00
|
|
|
|
var contentType string
|
2016-02-09 01:14:13 +03:00
|
|
|
|
var wantStatus = http.StatusOK
|
2022-08-13 00:25:05 +03:00
|
|
|
|
var responseBody *io.ReadCloser
|
2016-02-09 01:14:13 +03:00
|
|
|
|
for _, opt := range opts {
|
|
|
|
|
switch opt := opt.(type) {
|
|
|
|
|
case wantResStatus:
|
|
|
|
|
wantStatus = int(opt)
|
2021-09-17 21:10:54 +03:00
|
|
|
|
case reqBodyJSON:
|
|
|
|
|
b, err := json.MarshalIndent(opt.v, "", " ")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2022-08-13 00:25:05 +03:00
|
|
|
|
requestBody = bytes.NewReader(b)
|
2021-09-17 21:10:54 +03:00
|
|
|
|
contentType = "application/json"
|
|
|
|
|
case reqBodyRaw:
|
2022-08-13 00:25:05 +03:00
|
|
|
|
requestBody = opt.r
|
2021-09-17 21:10:54 +03:00
|
|
|
|
contentType = "application/octet-stream"
|
2016-02-09 01:14:13 +03:00
|
|
|
|
case urlValues:
|
|
|
|
|
arg = url.Values(opt)
|
2022-08-13 00:25:05 +03:00
|
|
|
|
case respBodyRaw:
|
|
|
|
|
responseBody = opt.rc
|
2016-02-09 01:14:13 +03:00
|
|
|
|
default:
|
|
|
|
|
panic(fmt.Sprintf("internal error; unsupported type %T", opt))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-12 09:57:39 +03:00
|
|
|
|
// slashA is either "/a" (for authenticated requests) or "" for unauthenticated.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication
|
|
|
|
|
slashA := "/a"
|
|
|
|
|
if _, ok := c.auth.(noAuth); ok {
|
|
|
|
|
slashA = ""
|
|
|
|
|
}
|
2015-05-27 23:23:30 +03:00
|
|
|
|
u := c.url + slashA + path
|
|
|
|
|
if arg != nil {
|
|
|
|
|
u += "?" + arg.Encode()
|
|
|
|
|
}
|
2022-08-13 00:25:05 +03:00
|
|
|
|
req, err := http.NewRequestWithContext(ctx, method, u, requestBody)
|
2015-02-11 07:25:37 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2015-02-11 08:18:39 +03:00
|
|
|
|
if contentType != "" {
|
|
|
|
|
req.Header.Set("Content-Type", contentType)
|
|
|
|
|
}
|
2022-07-12 20:41:14 +03:00
|
|
|
|
if err := c.auth.setAuth(c, req); err != nil {
|
|
|
|
|
return fmt.Errorf("setting Gerrit auth: %v", err)
|
|
|
|
|
}
|
2021-09-17 21:10:54 +03:00
|
|
|
|
res, err := c.httpClient().Do(req)
|
2015-02-11 07:25:37 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2022-08-13 00:25:05 +03:00
|
|
|
|
defer func() {
|
|
|
|
|
if responseBody != nil && *responseBody != nil {
|
|
|
|
|
// We've handed off the body to the user.
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
res.Body.Close()
|
|
|
|
|
}()
|
2015-02-11 07:25:37 +03:00
|
|
|
|
|
2016-02-09 01:14:13 +03:00
|
|
|
|
if res.StatusCode != wantStatus {
|
2022-08-13 00:25:05 +03:00
|
|
|
|
body, err := io.ReadAll(io.LimitReader(res.Body, 4<<10))
|
2016-02-09 01:14:13 +03:00
|
|
|
|
return &HTTPError{res, body, err}
|
2015-02-11 08:18:39 +03:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-13 00:25:05 +03:00
|
|
|
|
if responseBody != nil {
|
|
|
|
|
*responseBody = res.Body
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 21:10:54 +03:00
|
|
|
|
if dst == nil {
|
|
|
|
|
// Drain the response body, return an error if it's anything but empty.
|
2022-08-13 00:25:05 +03:00
|
|
|
|
body, err := io.ReadAll(io.LimitReader(res.Body, 4<<10))
|
2021-09-17 21:10:54 +03:00
|
|
|
|
if err != nil || len(body) != 0 {
|
|
|
|
|
return &HTTPError{res, body, err}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2015-02-11 07:25:37 +03:00
|
|
|
|
// The JSON response begins with an XSRF-defeating header
|
|
|
|
|
// like ")]}\n". Read that and skip it.
|
|
|
|
|
br := bufio.NewReader(res.Body)
|
|
|
|
|
if _, err := br.ReadSlice('\n'); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return json.NewDecoder(br).Decode(dst)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 01:37:06 +03:00
|
|
|
|
// Possible values for the ChangeInfo Status field.
|
|
|
|
|
const (
|
|
|
|
|
ChangeStatusNew = "NEW"
|
|
|
|
|
ChangeStatusAbandoned = "ABANDONED"
|
|
|
|
|
ChangeStatusMerged = "MERGED"
|
|
|
|
|
)
|
|
|
|
|
|
2015-02-11 07:25:37 +03:00
|
|
|
|
// ChangeInfo is a Gerrit data structure.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
|
|
|
|
|
type ChangeInfo struct {
|
|
|
|
|
// ID is the ID of the change in the format
|
|
|
|
|
// "'<project>~<branch>~<Change-Id>'", where 'project',
|
|
|
|
|
// 'branch' and 'Change-Id' are URL encoded. For 'branch' the
|
|
|
|
|
// refs/heads/ prefix is omitted.
|
2015-04-08 16:45:32 +03:00
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
ChangeNumber int `json:"_number"`
|
2018-10-25 03:20:51 +03:00
|
|
|
|
ChangeID string `json:"change_id"`
|
2015-02-11 07:25:37 +03:00
|
|
|
|
|
|
|
|
|
Project string `json:"project"`
|
|
|
|
|
|
|
|
|
|
// Branch is the name of the target branch.
|
|
|
|
|
// The refs/heads/ prefix is omitted.
|
|
|
|
|
Branch string `json:"branch"`
|
|
|
|
|
|
2018-10-25 03:20:51 +03:00
|
|
|
|
Topic string `json:"topic"`
|
|
|
|
|
Assignee *AccountInfo `json:"assignee"`
|
|
|
|
|
Hashtags []string `json:"hashtags"`
|
2015-02-11 07:25:37 +03:00
|
|
|
|
|
2018-10-25 03:20:51 +03:00
|
|
|
|
// Subject is the subject of the change
|
|
|
|
|
// (the header line of the commit message).
|
2015-02-11 07:25:37 +03:00
|
|
|
|
Subject string `json:"subject"`
|
|
|
|
|
|
|
|
|
|
// Status is the status of the change (NEW, SUBMITTED, MERGED,
|
|
|
|
|
// ABANDONED, DRAFT).
|
|
|
|
|
Status string `json:"status"`
|
|
|
|
|
|
2018-10-25 03:20:51 +03:00
|
|
|
|
Created TimeStamp `json:"created"`
|
|
|
|
|
Updated TimeStamp `json:"updated"`
|
|
|
|
|
Submitted TimeStamp `json:"submitted"`
|
|
|
|
|
Submitter *AccountInfo `json:"submitter"`
|
|
|
|
|
SubmitType string `json:"submit_type"`
|
|
|
|
|
|
|
|
|
|
// Mergeable indicates whether the change can be merged.
|
|
|
|
|
// It is not set for already-merged changes,
|
|
|
|
|
// nor if the change is untested, nor if the
|
|
|
|
|
// SKIP_MERGEABLE option has been set.
|
|
|
|
|
Mergeable bool `json:"mergeable"`
|
|
|
|
|
|
|
|
|
|
// Submittable indicates whether the change can be submitted.
|
|
|
|
|
// It is only set if requested, using the "SUBMITTABLE" option.
|
|
|
|
|
Submittable bool `json:"submittable"`
|
|
|
|
|
|
|
|
|
|
// Insertions and Deletions count inserted and deleted lines.
|
|
|
|
|
Insertions int `json:"insertions"`
|
|
|
|
|
Deletions int `json:"deletions"`
|
2015-04-08 16:45:32 +03:00
|
|
|
|
|
2015-02-11 07:25:37 +03:00
|
|
|
|
// CurrentRevision is the commit ID of the current patch set
|
|
|
|
|
// of this change. This is only set if the current revision
|
2015-04-08 16:45:32 +03:00
|
|
|
|
// is requested or if all revisions are requested (fields
|
|
|
|
|
// "CURRENT_REVISION" or "ALL_REVISIONS").
|
2015-02-11 07:25:37 +03:00
|
|
|
|
CurrentRevision string `json:"current_revision"`
|
|
|
|
|
|
2015-04-08 16:45:32 +03:00
|
|
|
|
// Revisions maps a commit ID of the patch set to a
|
|
|
|
|
// RevisionInfo entity.
|
|
|
|
|
//
|
|
|
|
|
// Only set if the current revision is requested (in which
|
|
|
|
|
// case it will only contain a key for the current revision)
|
|
|
|
|
// or if all revisions are requested.
|
|
|
|
|
Revisions map[string]RevisionInfo `json:"revisions"`
|
|
|
|
|
|
|
|
|
|
// Owner is the author of the change.
|
|
|
|
|
// The details are only filled in if field "DETAILED_ACCOUNTS" is requested.
|
|
|
|
|
Owner *AccountInfo `json:"owner"`
|
|
|
|
|
|
|
|
|
|
// Messages are included if field "MESSAGES" is requested.
|
|
|
|
|
Messages []ChangeMessageInfo `json:"messages"`
|
|
|
|
|
|
2018-10-25 03:20:51 +03:00
|
|
|
|
// Labels maps label names to LabelInfo entries.
|
2015-04-08 16:45:32 +03:00
|
|
|
|
Labels map[string]LabelInfo `json:"labels"`
|
|
|
|
|
|
2018-10-25 03:20:51 +03:00
|
|
|
|
// ReviewerUpdates are included if field "REVIEWER_UPDATES" is requested.
|
|
|
|
|
ReviewerUpdates []ReviewerUpdateInfo `json:"reviewer_updates"`
|
|
|
|
|
|
|
|
|
|
// Reviewers maps reviewer state ("REVIEWER", "CC", "REMOVED")
|
|
|
|
|
// to a list of accounts.
|
|
|
|
|
// REVIEWER lists users with at least one non-zero vote on the change.
|
|
|
|
|
// CC lists users added to the change who has not voted.
|
|
|
|
|
// REMOVED lists users who were previously reviewers on the change
|
|
|
|
|
// but who have been removed.
|
|
|
|
|
// Reviewers is only included if "DETAILED_LABELS" is requested.
|
|
|
|
|
Reviewers map[string][]*AccountInfo `json:"reviewers"`
|
|
|
|
|
|
|
|
|
|
// WorkInProgress indicates that the change is marked as a work in progress.
|
|
|
|
|
// (This means it is not yet ready for review, but it is still publicly visible.)
|
|
|
|
|
WorkInProgress bool `json:"work_in_progress"`
|
|
|
|
|
|
|
|
|
|
// HasReviewStarted indicates whether the change has ever been marked
|
|
|
|
|
// ready for review in the past (not as a work in progress).
|
|
|
|
|
HasReviewStarted bool `json:"has_review_started"`
|
|
|
|
|
|
|
|
|
|
// RevertOf lists the numeric Change-Id of the change that this change reverts.
|
|
|
|
|
RevertOf int `json:"revert_of"`
|
2015-02-11 07:25:37 +03:00
|
|
|
|
|
|
|
|
|
// MoreChanges is set on the last change from QueryChanges if
|
|
|
|
|
// the result set is truncated by an 'n' parameter.
|
|
|
|
|
MoreChanges bool `json:"_more_changes"`
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-25 03:20:51 +03:00
|
|
|
|
// ReviewerUpdateInfo is a Gerrit data structure.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#review-update-info
|
|
|
|
|
type ReviewerUpdateInfo struct {
|
|
|
|
|
Updated TimeStamp `json:"updated"`
|
|
|
|
|
UpdatedBy *AccountInfo `json:"updated_by"`
|
|
|
|
|
Reviewer *AccountInfo `json:"reviewer"`
|
|
|
|
|
State string // "REVIEWER", "CC", or "REMOVED"
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 21:10:03 +03:00
|
|
|
|
// AccountInfo is a Gerrit data structure. It's used both for getting the details
|
|
|
|
|
// for a single account, as well as for querying multiple accounts.
|
2022-04-21 17:51:50 +03:00
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#account-info.
|
2015-04-08 16:45:32 +03:00
|
|
|
|
type AccountInfo struct {
|
2022-04-21 17:51:50 +03:00
|
|
|
|
NumericID int64 `json:"_account_id"`
|
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
|
Email string `json:"email,omitempty"`
|
|
|
|
|
Username string `json:"username,omitempty"`
|
|
|
|
|
Tags []string `json:"tags,omitempty"`
|
2017-09-06 21:10:03 +03:00
|
|
|
|
|
|
|
|
|
// MoreAccounts is set on the last account from QueryAccounts if
|
|
|
|
|
// the result set is truncated by an 'n' parameter (or has more).
|
|
|
|
|
MoreAccounts bool `json:"_more_accounts"`
|
2015-04-08 16:45:32 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ai *AccountInfo) Equal(v *AccountInfo) bool {
|
|
|
|
|
if ai == nil || v == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return ai.NumericID == v.NumericID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ChangeMessageInfo struct {
|
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
Author *AccountInfo `json:"author"`
|
|
|
|
|
Time TimeStamp `json:"date"`
|
|
|
|
|
Message string `json:"message"`
|
2018-11-21 19:24:18 +03:00
|
|
|
|
Tag string `json:"tag,omitempty"`
|
2015-04-08 16:45:32 +03:00
|
|
|
|
RevisionNumber int `json:"_revision_number"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The LabelInfo entity contains information about a label on a
|
|
|
|
|
// change, always corresponding to the current patch set.
|
|
|
|
|
//
|
|
|
|
|
// There are two options that control the contents of LabelInfo:
|
|
|
|
|
// LABELS and DETAILED_LABELS.
|
|
|
|
|
//
|
|
|
|
|
// For a quick summary of the state of labels, use LABELS.
|
|
|
|
|
//
|
|
|
|
|
// For detailed information about labels, including exact numeric
|
|
|
|
|
// votes for all users and the allowed range of votes for the current
|
|
|
|
|
// user, use DETAILED_LABELS.
|
|
|
|
|
type LabelInfo struct {
|
|
|
|
|
// Optional means the label may be set, but it’s neither
|
|
|
|
|
// necessary for submission nor does it block submission if
|
|
|
|
|
// set.
|
|
|
|
|
Optional bool `json:"optional"`
|
|
|
|
|
|
|
|
|
|
// Fields set by LABELS field option:
|
2021-08-10 23:22:02 +03:00
|
|
|
|
Approved *AccountInfo `json:"approved"`
|
2015-04-08 16:45:32 +03:00
|
|
|
|
|
2021-08-10 23:22:02 +03:00
|
|
|
|
// Fields set by DETAILED_LABELS option:
|
2015-04-08 16:45:32 +03:00
|
|
|
|
All []ApprovalInfo `json:"all"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ApprovalInfo struct {
|
|
|
|
|
AccountInfo
|
|
|
|
|
Value int `json:"value"`
|
|
|
|
|
Date TimeStamp `json:"date"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The RevisionInfo entity contains information about a patch set. Not
|
|
|
|
|
// all fields are returned by default. Additional fields can be
|
|
|
|
|
// obtained by adding o parameters as described at:
|
|
|
|
|
// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
|
|
|
|
|
type RevisionInfo struct {
|
2019-01-05 19:37:53 +03:00
|
|
|
|
Draft bool `json:"draft"`
|
|
|
|
|
PatchSetNumber int `json:"_number"`
|
|
|
|
|
Created TimeStamp `json:"created"`
|
|
|
|
|
Uploader *AccountInfo `json:"uploader"`
|
|
|
|
|
Ref string `json:"ref"`
|
|
|
|
|
Fetch map[string]*FetchInfo `json:"fetch"`
|
|
|
|
|
Commit *CommitInfo `json:"commit"`
|
|
|
|
|
Files map[string]*FileInfo `json:"files"`
|
2019-06-11 01:35:51 +03:00
|
|
|
|
CommitWithFooters string `json:"commit_with_footers"`
|
2019-01-25 02:35:06 +03:00
|
|
|
|
Kind string `json:"kind"`
|
2015-04-08 16:45:32 +03:00
|
|
|
|
// TODO: more
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type CommitInfo struct {
|
|
|
|
|
Author GitPersonInfo `json:"author"`
|
|
|
|
|
Committer GitPersonInfo `json:"committer"`
|
|
|
|
|
CommitID string `json:"commit"`
|
|
|
|
|
Subject string `json:"subject"`
|
|
|
|
|
Message string `json:"message"`
|
2015-10-22 04:34:38 +03:00
|
|
|
|
Parents []CommitInfo `json:"parents"`
|
2015-04-08 16:45:32 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type GitPersonInfo struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Email string `json:"Email"`
|
|
|
|
|
Date TimeStamp `json:"date"`
|
|
|
|
|
TZOffset int `json:"tz"`
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-23 23:46:33 +03:00
|
|
|
|
func (gpi *GitPersonInfo) Equal(v *GitPersonInfo) bool {
|
|
|
|
|
if gpi == nil {
|
|
|
|
|
if gpi != v {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return gpi.Name == v.Name && gpi.Email == v.Email && gpi.Date.Equal(v.Date) &&
|
|
|
|
|
gpi.TZOffset == v.TZOffset
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-29 23:58:53 +03:00
|
|
|
|
// Possible values for the FileInfo Status field.
|
|
|
|
|
const (
|
|
|
|
|
FileInfoAdded = "A"
|
|
|
|
|
FileInfoDeleted = "D"
|
|
|
|
|
FileInfoRenamed = "R"
|
|
|
|
|
FileInfoCopied = "C"
|
|
|
|
|
FileInfoRewritten = "W"
|
|
|
|
|
)
|
|
|
|
|
|
2015-04-25 22:18:00 +03:00
|
|
|
|
type FileInfo struct {
|
|
|
|
|
Status string `json:"status"`
|
|
|
|
|
Binary bool `json:"binary"`
|
|
|
|
|
OldPath string `json:"old_path"`
|
|
|
|
|
LinesInserted int `json:"lines_inserted"`
|
|
|
|
|
LinesDeleted int `json:"lines_deleted"`
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-24 21:55:07 +03:00
|
|
|
|
type FetchInfo struct {
|
|
|
|
|
URL string `json:"url"`
|
|
|
|
|
Ref string `json:"ref"`
|
|
|
|
|
Commands map[string]string `json:"commands"`
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-11 07:25:37 +03:00
|
|
|
|
// QueryChangesOpt are options for QueryChanges.
|
|
|
|
|
type QueryChangesOpt struct {
|
|
|
|
|
// N is the number of results to return.
|
|
|
|
|
// If 0, the 'n' parameter is not sent to Gerrit.
|
|
|
|
|
N int
|
|
|
|
|
|
2018-07-09 11:38:09 +03:00
|
|
|
|
// Start is the number of results to skip (useful in pagination).
|
|
|
|
|
// To figure out if there are more results, the last ChangeInfo struct
|
|
|
|
|
// in the last call to QueryChanges will have the field MoreAccounts=true.
|
|
|
|
|
// If 0, the 'S' parameter is not sent to Gerrit.
|
|
|
|
|
Start int
|
|
|
|
|
|
2015-02-11 07:25:37 +03:00
|
|
|
|
// Fields are optional fields to also return.
|
|
|
|
|
// Example strings include "ALL_REVISIONS", "LABELS", "MESSAGES".
|
|
|
|
|
// For a complete list, see:
|
|
|
|
|
// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
|
|
|
|
|
Fields []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func condInt(n int) []string {
|
|
|
|
|
if n != 0 {
|
|
|
|
|
return []string{strconv.Itoa(n)}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-11 08:18:39 +03:00
|
|
|
|
// QueryChanges queries changes. The q parameter is a Gerrit search query.
|
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
|
|
|
|
|
// For the query syntax, see https://gerrit-review.googlesource.com/Documentation/user-search.html#_search_operators
|
2017-01-22 17:03:14 +03:00
|
|
|
|
func (c *Client) QueryChanges(ctx context.Context, q string, opts ...QueryChangesOpt) ([]*ChangeInfo, error) {
|
2015-02-11 07:25:37 +03:00
|
|
|
|
var opt QueryChangesOpt
|
|
|
|
|
switch len(opts) {
|
|
|
|
|
case 0:
|
|
|
|
|
case 1:
|
|
|
|
|
opt = opts[0]
|
|
|
|
|
default:
|
|
|
|
|
return nil, errors.New("only 1 option struct supported")
|
|
|
|
|
}
|
|
|
|
|
var changes []*ChangeInfo
|
2017-01-22 17:03:14 +03:00
|
|
|
|
err := c.do(ctx, &changes, "GET", "/changes/", urlValues{
|
2015-02-11 07:25:37 +03:00
|
|
|
|
"q": {q},
|
|
|
|
|
"n": condInt(opt.N),
|
|
|
|
|
"o": opt.Fields,
|
2018-07-09 11:38:09 +03:00
|
|
|
|
"S": condInt(opt.Start),
|
2016-02-09 01:14:13 +03:00
|
|
|
|
})
|
2015-02-11 07:25:37 +03:00
|
|
|
|
return changes, err
|
|
|
|
|
}
|
2015-02-11 08:18:39 +03:00
|
|
|
|
|
2017-07-14 20:17:43 +03:00
|
|
|
|
// GetChange returns information about a single change.
|
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change
|
2017-07-14 20:55:47 +03:00
|
|
|
|
func (c *Client) GetChange(ctx context.Context, changeID string, opts ...QueryChangesOpt) (*ChangeInfo, error) {
|
2017-07-14 20:17:43 +03:00
|
|
|
|
var opt QueryChangesOpt
|
|
|
|
|
switch len(opts) {
|
|
|
|
|
case 0:
|
|
|
|
|
case 1:
|
|
|
|
|
opt = opts[0]
|
|
|
|
|
default:
|
|
|
|
|
return nil, errors.New("only 1 option struct supported")
|
|
|
|
|
}
|
2021-09-17 21:10:54 +03:00
|
|
|
|
var change ChangeInfo
|
|
|
|
|
err := c.do(ctx, &change, "GET", "/changes/"+changeID, urlValues{
|
2017-07-14 20:17:43 +03:00
|
|
|
|
"n": condInt(opt.N),
|
|
|
|
|
"o": opt.Fields,
|
|
|
|
|
})
|
2021-09-17 21:10:54 +03:00
|
|
|
|
return &change, err
|
2017-07-14 20:17:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
2015-05-27 23:23:30 +03:00
|
|
|
|
// GetChangeDetail retrieves a change with labels, detailed labels, detailed
|
|
|
|
|
// accounts, and messages.
|
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change-detail
|
2017-01-22 17:03:14 +03:00
|
|
|
|
func (c *Client) GetChangeDetail(ctx context.Context, changeID string, opts ...QueryChangesOpt) (*ChangeInfo, error) {
|
2017-01-07 16:58:43 +03:00
|
|
|
|
var opt QueryChangesOpt
|
|
|
|
|
switch len(opts) {
|
|
|
|
|
case 0:
|
|
|
|
|
case 1:
|
|
|
|
|
opt = opts[0]
|
|
|
|
|
default:
|
|
|
|
|
return nil, errors.New("only 1 option struct supported")
|
|
|
|
|
}
|
2015-05-27 23:23:30 +03:00
|
|
|
|
var change ChangeInfo
|
2017-01-22 17:03:14 +03:00
|
|
|
|
err := c.do(ctx, &change, "GET", "/changes/"+changeID+"/detail", urlValues{
|
2017-01-07 16:58:43 +03:00
|
|
|
|
"o": opt.Fields,
|
|
|
|
|
})
|
2015-05-27 23:23:30 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &change, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-08 03:10:49 +03:00
|
|
|
|
// ListChangeComments retrieves a map of published comments for the given change ID.
|
|
|
|
|
// The map key is the file path (such as "maintner/git_test.go" or "/PATCHSET_LEVEL").
|
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-change-comments.
|
|
|
|
|
func (c *Client) ListChangeComments(ctx context.Context, changeID string) (map[string][]CommentInfo, error) {
|
|
|
|
|
var m map[string][]CommentInfo
|
|
|
|
|
if err := c.do(ctx, &m, "GET", "/changes/"+changeID+"/comments"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return m, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CommentInfo contains information about an inline comment.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#comment-info.
|
|
|
|
|
type CommentInfo struct {
|
2021-05-07 18:24:48 +03:00
|
|
|
|
PatchSet int `json:"patch_set,omitempty"`
|
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
Path string `json:"path,omitempty"`
|
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
|
Updated TimeStamp `json:"updated"`
|
|
|
|
|
Author *AccountInfo `json:"author,omitempty"`
|
|
|
|
|
InReplyTo string `json:"in_reply_to,omitempty"`
|
|
|
|
|
Unresolved *bool `json:"unresolved,omitempty"`
|
|
|
|
|
Tag string `json:"tag,omitempty"`
|
2020-07-08 03:10:49 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 23:53:24 +03:00
|
|
|
|
// ListFiles retrieves a map of filenames to FileInfo's for the given change ID and revision.
|
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-files
|
|
|
|
|
func (c *Client) ListFiles(ctx context.Context, changeID, revision string) (map[string]*FileInfo, error) {
|
|
|
|
|
var m map[string]*FileInfo
|
|
|
|
|
if err := c.do(ctx, &m, "GET", "/changes/"+changeID+"/revisions/"+revision+"/files"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return m, nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-28 13:26:53 +03:00
|
|
|
|
// ReviewInput contains information for adding a review to a revision.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#review-input
|
2015-02-11 08:18:39 +03:00
|
|
|
|
type ReviewInput struct {
|
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
|
Labels map[string]int `json:"labels,omitempty"`
|
2021-04-14 13:31:05 +03:00
|
|
|
|
Tag string `json:"tag,omitempty"`
|
2017-01-28 13:26:53 +03:00
|
|
|
|
|
|
|
|
|
// Comments contains optional per-line comments to post.
|
|
|
|
|
// The map key is a file path (such as "src/foo/bar.go").
|
|
|
|
|
Comments map[string][]CommentInput `json:"comments,omitempty"`
|
2018-05-27 05:50:10 +03:00
|
|
|
|
|
|
|
|
|
// Reviewers optionally specifies new reviewers to add to the change.
|
|
|
|
|
Reviewers []ReviewerInput `json:"reviewers,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReviewerInput contains information for adding a reviewer to a change.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#reviewer-input
|
|
|
|
|
type ReviewerInput struct {
|
2018-06-26 23:53:24 +03:00
|
|
|
|
// Reviewer is the ID of the account to be added as reviewer.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#account-id
|
|
|
|
|
Reviewer string `json:"reviewer"`
|
2018-05-27 05:50:10 +03:00
|
|
|
|
State string `json:"state,omitempty"` // REVIEWER or CC (default: REVIEWER)
|
2017-01-28 13:26:53 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CommentInput contains information for creating an inline comment.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#comment-input
|
|
|
|
|
type CommentInput struct {
|
2021-04-14 19:06:48 +03:00
|
|
|
|
Line int `json:"line,omitempty"`
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
InReplyTo string `json:"in_reply_to,omitempty"`
|
2021-04-19 20:54:07 +03:00
|
|
|
|
Unresolved *bool `json:"unresolved,omitempty"`
|
2017-01-28 13:26:53 +03:00
|
|
|
|
|
|
|
|
|
// TODO(haya14busa): more, as needed.
|
2015-02-11 08:18:39 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type reviewInfo struct {
|
|
|
|
|
Labels map[string]int `json:"labels,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetReview leaves a message on a change and/or modifies labels.
|
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-review
|
|
|
|
|
// The changeID is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-id
|
|
|
|
|
// The revision is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#revision-id
|
2017-01-22 17:03:14 +03:00
|
|
|
|
func (c *Client) SetReview(ctx context.Context, changeID, revision string, review ReviewInput) error {
|
2015-02-11 08:18:39 +03:00
|
|
|
|
var res reviewInfo
|
2017-01-22 17:03:14 +03:00
|
|
|
|
return c.do(ctx, &res, "POST", fmt.Sprintf("/changes/%s/revisions/%s/review", changeID, revision),
|
2021-09-17 21:10:54 +03:00
|
|
|
|
reqBodyJSON{&review})
|
2015-02-11 08:18:39 +03:00
|
|
|
|
}
|
2015-04-08 16:45:32 +03:00
|
|
|
|
|
2018-06-26 23:53:24 +03:00
|
|
|
|
// ReviewerInfo contains information about reviewers of a change.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#reviewer-info
|
|
|
|
|
type ReviewerInfo struct {
|
|
|
|
|
AccountInfo
|
|
|
|
|
Approvals map[string]string `json:"approvals"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListReviewers returns all reviewers on a change.
|
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-reviewers
|
|
|
|
|
// The changeID is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-id
|
|
|
|
|
func (c *Client) ListReviewers(ctx context.Context, changeID string) ([]ReviewerInfo, error) {
|
|
|
|
|
var res []ReviewerInfo
|
|
|
|
|
if err := c.do(ctx, &res, "GET", fmt.Sprintf("/changes/%s/reviewers", changeID)); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-13 03:12:32 +03:00
|
|
|
|
// HashtagsInput is the request body used when modifying a CL's hashtags.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-documentation.storage.googleapis.com/Documentation/2.15.1/rest-api-changes.html#hashtags-input
|
|
|
|
|
type HashtagsInput struct {
|
|
|
|
|
Add []string `json:"add"`
|
|
|
|
|
Remove []string `json:"remove"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetHashtags modifies the hashtags for a CL, supporting both adding
|
|
|
|
|
// and removing hashtags in one request. On success it returns the new
|
|
|
|
|
// set of hashtags.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-documentation.storage.googleapis.com/Documentation/2.15.1/rest-api-changes.html#set-hashtags
|
|
|
|
|
func (c *Client) SetHashtags(ctx context.Context, changeID string, hashtags HashtagsInput) ([]string, error) {
|
|
|
|
|
var res []string
|
2021-09-17 21:10:54 +03:00
|
|
|
|
err := c.do(ctx, &res, "POST", fmt.Sprintf("/changes/%s/hashtags", changeID), reqBodyJSON{&hashtags})
|
2018-04-13 03:12:32 +03:00
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AddHashtags is a wrapper around SetHashtags that only supports adding tags.
|
|
|
|
|
func (c *Client) AddHashtags(ctx context.Context, changeID string, tags ...string) ([]string, error) {
|
|
|
|
|
return c.SetHashtags(ctx, changeID, HashtagsInput{Add: tags})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RemoveHashtags is a wrapper around SetHashtags that only supports removing tags.
|
|
|
|
|
func (c *Client) RemoveHashtags(ctx context.Context, changeID string, tags ...string) ([]string, error) {
|
|
|
|
|
return c.SetHashtags(ctx, changeID, HashtagsInput{Remove: tags})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetHashtags returns a CL's current hashtags.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-documentation.storage.googleapis.com/Documentation/2.15.1/rest-api-changes.html#get-hashtags
|
|
|
|
|
func (c *Client) GetHashtags(ctx context.Context, changeID string) ([]string, error) {
|
|
|
|
|
var res []string
|
|
|
|
|
err := c.do(ctx, &res, "GET", fmt.Sprintf("/changes/%s/hashtags", changeID))
|
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-07 04:06:24 +03:00
|
|
|
|
// AbandonChange abandons the given change.
|
2017-09-28 22:55:40 +03:00
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#abandon-change
|
|
|
|
|
// The changeID is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-id
|
|
|
|
|
// The input for the call is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#abandon-input
|
|
|
|
|
func (c *Client) AbandonChange(ctx context.Context, changeID string, message ...string) error {
|
|
|
|
|
var msg string
|
|
|
|
|
if len(message) > 1 {
|
|
|
|
|
panic("invalid use of multiple message inputs")
|
|
|
|
|
}
|
|
|
|
|
if len(message) == 1 {
|
|
|
|
|
msg = message[0]
|
|
|
|
|
}
|
|
|
|
|
b := struct {
|
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
|
}{msg}
|
2016-02-07 04:06:24 +03:00
|
|
|
|
var change ChangeInfo
|
2021-09-17 21:10:54 +03:00
|
|
|
|
return c.do(ctx, &change, "POST", "/changes/"+changeID+"/abandon", reqBodyJSON{&b})
|
2016-02-09 01:14:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ProjectInput contains the options for creating a new project.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#project-input
|
|
|
|
|
type ProjectInput struct {
|
|
|
|
|
Parent string `json:"parent,omitempty"`
|
|
|
|
|
Description string `json:"description,omitempty"`
|
|
|
|
|
SubmitType string `json:"submit_type,omitempty"`
|
|
|
|
|
|
|
|
|
|
CreateNewChangeForAllNotInTarget string `json:"create_new_change_for_all_not_in_target,omitempty"`
|
|
|
|
|
|
|
|
|
|
// TODO(bradfitz): more, as needed.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ProjectInfo is information about a Gerrit project.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#project-info
|
|
|
|
|
type ProjectInfo struct {
|
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Parent string `json:"parent"`
|
2017-02-19 09:07:09 +03:00
|
|
|
|
CloneURL string `json:"clone_url"`
|
2016-02-09 01:14:13 +03:00
|
|
|
|
Description string `json:"description"`
|
|
|
|
|
State string `json:"state"`
|
|
|
|
|
Branches map[string]string `json:"branches"`
|
2022-12-17 03:40:39 +03:00
|
|
|
|
WebLinks []WebLinkInfo `json:"web_links,omitempty"`
|
2016-02-09 01:14:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-27 21:42:10 +03:00
|
|
|
|
// ListProjects returns the server's active projects.
|
|
|
|
|
//
|
2022-08-13 00:25:05 +03:00
|
|
|
|
// The returned slice is sorted by project ID and excludes the "All-Projects" and "All-Users" projects.
|
2017-03-27 21:42:10 +03:00
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#list-projects
|
|
|
|
|
func (c *Client) ListProjects(ctx context.Context) ([]ProjectInfo, error) {
|
|
|
|
|
var res map[string]ProjectInfo
|
2021-09-17 21:10:54 +03:00
|
|
|
|
err := c.do(ctx, &res, "GET", "/projects/")
|
2017-03-27 21:42:10 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var ret []ProjectInfo
|
|
|
|
|
for name, pi := range res {
|
2022-08-13 00:25:05 +03:00
|
|
|
|
if name == "All-Projects" || name == "All-Users" {
|
2017-03-27 21:42:10 +03:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if pi.State != "ACTIVE" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2022-08-13 00:25:05 +03:00
|
|
|
|
// https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#project-info:
|
|
|
|
|
// "name not set if returned in a map where the project name is used as map key"
|
|
|
|
|
pi.Name = name
|
2017-03-27 21:42:10 +03:00
|
|
|
|
ret = append(ret, pi)
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(ret, func(i, j int) bool { return ret[i].ID < ret[j].ID })
|
|
|
|
|
return ret, nil
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 01:14:13 +03:00
|
|
|
|
// CreateProject creates a new project.
|
2017-01-22 17:03:14 +03:00
|
|
|
|
func (c *Client) CreateProject(ctx context.Context, name string, p ...ProjectInput) (ProjectInfo, error) {
|
2016-02-09 01:14:13 +03:00
|
|
|
|
var pi ProjectInput
|
|
|
|
|
if len(p) > 1 {
|
|
|
|
|
panic("invalid use of multiple project inputs")
|
|
|
|
|
}
|
|
|
|
|
if len(p) == 1 {
|
|
|
|
|
pi = p[0]
|
|
|
|
|
}
|
|
|
|
|
var res ProjectInfo
|
2021-09-17 21:10:54 +03:00
|
|
|
|
err := c.do(ctx, &res, "PUT", fmt.Sprintf("/projects/%s", name), reqBodyJSON{&pi}, wantResStatus(http.StatusCreated))
|
2016-02-09 01:14:13 +03:00
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 21:10:54 +03:00
|
|
|
|
// CreateChange creates a new change.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#create-change.
|
|
|
|
|
func (c *Client) CreateChange(ctx context.Context, ci ChangeInput) (ChangeInfo, error) {
|
|
|
|
|
var res ChangeInfo
|
|
|
|
|
err := c.do(ctx, &res, "POST", "/changes/", reqBodyJSON{&ci}, wantResStatus(http.StatusCreated))
|
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ChangeInput contains the options for creating a new change.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-input.
|
|
|
|
|
type ChangeInput struct {
|
|
|
|
|
Project string `json:"project"`
|
|
|
|
|
Branch string `json:"branch"`
|
|
|
|
|
Subject string `json:"subject"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ChangeFileContentInChangeEdit puts content of a file to a change edit.
|
2022-06-13 18:45:32 +03:00
|
|
|
|
// If no change is made, an error that matches ErrNotModified is returned.
|
2021-09-17 21:10:54 +03:00
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#put-edit-file.
|
|
|
|
|
func (c *Client) ChangeFileContentInChangeEdit(ctx context.Context, changeID string, path string, content string) error {
|
2022-06-13 18:45:32 +03:00
|
|
|
|
return c.do(ctx, nil, "PUT", "/changes/"+changeID+"/edit/"+url.QueryEscape(path),
|
2021-09-17 21:10:54 +03:00
|
|
|
|
reqBodyRaw{strings.NewReader(content)}, wantResStatus(http.StatusNoContent))
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-25 22:04:41 +03:00
|
|
|
|
// DeleteFileInChangeEdit deletes a file from a change edit.
|
2022-06-13 18:45:32 +03:00
|
|
|
|
// If no change is made, an error that matches ErrNotModified is returned.
|
2022-05-25 22:04:41 +03:00
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-edit-file.
|
|
|
|
|
func (c *Client) DeleteFileInChangeEdit(ctx context.Context, changeID string, path string) error {
|
|
|
|
|
return c.do(ctx, nil, "DELETE", "/changes/"+changeID+"/edit/"+url.QueryEscape(path), wantResStatus(http.StatusNoContent))
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 21:10:54 +03:00
|
|
|
|
// PublishChangeEdit promotes the change edit to a regular patch set.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#publish-edit.
|
|
|
|
|
func (c *Client) PublishChangeEdit(ctx context.Context, changeID string) error {
|
|
|
|
|
return c.do(ctx, nil, "POST", "/changes/"+changeID+"/edit:publish", wantResStatus(http.StatusNoContent))
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 01:14:13 +03:00
|
|
|
|
// GetProjectInfo returns info about a project.
|
2017-01-22 17:03:14 +03:00
|
|
|
|
func (c *Client) GetProjectInfo(ctx context.Context, name string) (ProjectInfo, error) {
|
2016-02-09 01:14:13 +03:00
|
|
|
|
var res ProjectInfo
|
2017-01-22 17:03:14 +03:00
|
|
|
|
err := c.do(ctx, &res, "GET", fmt.Sprintf("/projects/%s", name))
|
2016-02-09 01:14:13 +03:00
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BranchInfo is information about a branch.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#branch-info
|
|
|
|
|
type BranchInfo struct {
|
|
|
|
|
Ref string `json:"ref"`
|
|
|
|
|
Revision string `json:"revision"`
|
|
|
|
|
CanDelete bool `json:"can_delete"`
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-23 23:46:33 +03:00
|
|
|
|
// GetProjectBranches returns the branches for the project name. The branches are stored in a map
|
|
|
|
|
// keyed by reference.
|
2017-01-22 17:03:14 +03:00
|
|
|
|
func (c *Client) GetProjectBranches(ctx context.Context, name string) (map[string]BranchInfo, error) {
|
2016-02-09 01:14:13 +03:00
|
|
|
|
var res []BranchInfo
|
2017-01-22 17:03:14 +03:00
|
|
|
|
err := c.do(ctx, &res, "GET", fmt.Sprintf("/projects/%s/branches/", name))
|
2016-02-09 01:14:13 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
m := map[string]BranchInfo{}
|
|
|
|
|
for _, bi := range res {
|
|
|
|
|
m[bi.Ref] = bi
|
|
|
|
|
}
|
|
|
|
|
return m, nil
|
2016-02-07 04:06:24 +03:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:19:20 +03:00
|
|
|
|
// GetBranch gets a particular branch in project.
|
2022-06-09 00:41:50 +03:00
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-branch.
|
|
|
|
|
func (c *Client) GetBranch(ctx context.Context, project, branch string) (BranchInfo, error) {
|
|
|
|
|
var res BranchInfo
|
|
|
|
|
err := c.do(ctx, &res, "GET", fmt.Sprintf("/projects/%s/branches/%s", project, branch))
|
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-13 00:25:05 +03:00
|
|
|
|
// GetFileContent gets a file's contents at a particular commit.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-content-from-commit.
|
|
|
|
|
func (c *Client) GetFileContent(ctx context.Context, project, commit, path string) (io.ReadCloser, error) {
|
|
|
|
|
var body io.ReadCloser
|
|
|
|
|
err := c.do(ctx, nil, "GET", fmt.Sprintf("/projects/%s/commits/%s/files/%s/content", project, commit, url.QueryEscape(path)), respBodyRaw{&body})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return readCloser{
|
|
|
|
|
Reader: base64.NewDecoder(base64.StdEncoding, body),
|
|
|
|
|
Closer: body,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type readCloser struct {
|
|
|
|
|
io.Reader
|
|
|
|
|
io.Closer
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-23 23:46:33 +03:00
|
|
|
|
// WebLinkInfo is information about a web link.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#web-link-info
|
|
|
|
|
type WebLinkInfo struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
URL string `json:"url"`
|
|
|
|
|
ImageURL string `json:"image_url"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (wli *WebLinkInfo) Equal(v *WebLinkInfo) bool {
|
|
|
|
|
if wli == nil || v == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return wli.Name == v.Name && wli.URL == v.URL && wli.ImageURL == v.ImageURL
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TagInfo is information about a tag.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#tag-info
|
|
|
|
|
type TagInfo struct {
|
|
|
|
|
Ref string `json:"ref"`
|
|
|
|
|
Revision string `json:"revision"`
|
|
|
|
|
Object string `json:"object,omitempty"`
|
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
|
Tagger *GitPersonInfo `json:"tagger,omitempty"`
|
|
|
|
|
Created TimeStamp `json:"created,omitempty"`
|
|
|
|
|
CanDelete bool `json:"can_delete"`
|
|
|
|
|
WebLinks []WebLinkInfo `json:"web_links,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ti *TagInfo) Equal(v *TagInfo) bool {
|
|
|
|
|
if ti == nil || v == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if ti.Ref != v.Ref || ti.Revision != v.Revision || ti.Object != v.Object ||
|
|
|
|
|
ti.Message != v.Message || !ti.Created.Equal(v.Created) || ti.CanDelete != v.CanDelete {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if !ti.Tagger.Equal(v.Tagger) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if len(ti.WebLinks) != len(v.WebLinks) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for i := range ti.WebLinks {
|
|
|
|
|
if !ti.WebLinks[i].Equal(&v.WebLinks[i]) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetProjectTags returns the tags for the project name. The tags are stored in a map keyed by
|
|
|
|
|
// reference.
|
|
|
|
|
func (c *Client) GetProjectTags(ctx context.Context, name string) (map[string]TagInfo, error) {
|
|
|
|
|
var res []TagInfo
|
|
|
|
|
err := c.do(ctx, &res, "GET", fmt.Sprintf("/projects/%s/tags/", name))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
m := map[string]TagInfo{}
|
|
|
|
|
for _, ti := range res {
|
|
|
|
|
m[ti.Ref] = ti
|
|
|
|
|
}
|
|
|
|
|
return m, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-10 22:19:20 +03:00
|
|
|
|
// GetTag returns a particular tag on project.
|
2022-06-08 23:31:51 +03:00
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-tag.
|
|
|
|
|
func (c *Client) GetTag(ctx context.Context, project, tag string) (TagInfo, error) {
|
|
|
|
|
var res TagInfo
|
|
|
|
|
err := c.do(ctx, &res, "GET", fmt.Sprintf("/projects/%s/tags/%s", project, tag))
|
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-25 22:04:41 +03:00
|
|
|
|
// TagInput contains information for creating a tag.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#tag-input
|
|
|
|
|
type TagInput struct {
|
|
|
|
|
// Ref is optional, and when present must be equal to the URL parameter. Removed.
|
|
|
|
|
Revision string `json:"revision,omitempty"`
|
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreateTag creates a tag on project.
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#create-tag.
|
|
|
|
|
func (c *Client) CreateTag(ctx context.Context, project, tag string, input TagInput) (TagInfo, error) {
|
|
|
|
|
var res TagInfo
|
|
|
|
|
err := c.do(ctx, &res, "PUT", fmt.Sprintf("/projects/%s/tags/%s", project, url.PathEscape(tag)), reqBodyJSON{&input}, wantResStatus(http.StatusCreated))
|
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-25 22:14:23 +03:00
|
|
|
|
// GetAccountInfo gets the specified account's information from Gerrit.
|
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-account
|
|
|
|
|
// The accountID is https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#account-id
|
|
|
|
|
//
|
|
|
|
|
// Note that getting "self" is a good way to validate host access, since it only requires peeker
|
|
|
|
|
// access to the host, not to any particular repository.
|
2017-01-22 17:03:14 +03:00
|
|
|
|
func (c *Client) GetAccountInfo(ctx context.Context, accountID string) (AccountInfo, error) {
|
2015-08-25 22:14:23 +03:00
|
|
|
|
var res AccountInfo
|
2017-01-22 17:03:14 +03:00
|
|
|
|
err := c.do(ctx, &res, "GET", fmt.Sprintf("/accounts/%s", accountID))
|
2015-08-25 22:14:23 +03:00
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 21:10:03 +03:00
|
|
|
|
// QueryAccountsOpt are options for QueryAccounts.
|
|
|
|
|
type QueryAccountsOpt struct {
|
|
|
|
|
// N is the number of results to return.
|
|
|
|
|
// If 0, the 'n' parameter is not sent to Gerrit.
|
|
|
|
|
N int
|
|
|
|
|
|
|
|
|
|
// Start is the number of results to skip (useful in pagination).
|
|
|
|
|
// To figure out if there are more results, the last AccountInfo struct
|
|
|
|
|
// in the last call to QueryAccounts will have the field MoreAccounts=true.
|
|
|
|
|
// If 0, the 'S' parameter is not sent to Gerrit.
|
|
|
|
|
Start int
|
|
|
|
|
|
|
|
|
|
// Fields are optional fields to also return.
|
|
|
|
|
// Example strings include "DETAILS", "ALL_EMAILS".
|
|
|
|
|
// By default, only the account IDs are returned.
|
|
|
|
|
// For a complete list, see:
|
|
|
|
|
// https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#query-account
|
|
|
|
|
Fields []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// QueryAccounts queries accounts. The q parameter is a Gerrit search query.
|
|
|
|
|
// For the API call and query syntax, see https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#query-account
|
|
|
|
|
func (c *Client) QueryAccounts(ctx context.Context, q string, opts ...QueryAccountsOpt) ([]*AccountInfo, error) {
|
|
|
|
|
var opt QueryAccountsOpt
|
|
|
|
|
switch len(opts) {
|
|
|
|
|
case 0:
|
|
|
|
|
case 1:
|
|
|
|
|
opt = opts[0]
|
|
|
|
|
default:
|
|
|
|
|
return nil, errors.New("only 1 option struct supported")
|
|
|
|
|
}
|
|
|
|
|
var changes []*AccountInfo
|
|
|
|
|
err := c.do(ctx, &changes, "GET", "/accounts/", urlValues{
|
|
|
|
|
"q": {q},
|
|
|
|
|
"n": condInt(opt.N),
|
|
|
|
|
"o": opt.Fields,
|
|
|
|
|
"S": condInt(opt.Start),
|
|
|
|
|
})
|
|
|
|
|
return changes, err
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-19 09:07:09 +03:00
|
|
|
|
// GetProjects returns a map of all projects on the Gerrit server.
|
|
|
|
|
func (c *Client) GetProjects(ctx context.Context, branch string) (map[string]*ProjectInfo, error) {
|
|
|
|
|
mp := make(map[string]*ProjectInfo)
|
2019-05-20 19:33:37 +03:00
|
|
|
|
err := c.do(ctx, &mp, "GET", fmt.Sprintf("/projects/?b=%s&format=JSON", branch))
|
2017-02-19 09:07:09 +03:00
|
|
|
|
return mp, err
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-08 16:45:32 +03:00
|
|
|
|
type TimeStamp time.Time
|
|
|
|
|
|
2018-10-23 23:46:33 +03:00
|
|
|
|
func (ts TimeStamp) Equal(v TimeStamp) bool {
|
|
|
|
|
return ts.Time().Equal(v.Time())
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-08 16:45:32 +03:00
|
|
|
|
// Gerrit's timestamp layout is like time.RFC3339Nano, but with a space instead of the "T",
|
|
|
|
|
// and without a timezone (it's always in UTC).
|
|
|
|
|
const timeStampLayout = "2006-01-02 15:04:05.999999999"
|
|
|
|
|
|
2017-08-01 20:23:33 +03:00
|
|
|
|
func (ts TimeStamp) MarshalJSON() ([]byte, error) {
|
|
|
|
|
return []byte(fmt.Sprintf(`"%s"`, ts.Time().UTC().Format(timeStampLayout))), nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-08 16:45:32 +03:00
|
|
|
|
func (ts *TimeStamp) UnmarshalJSON(p []byte) error {
|
|
|
|
|
if len(p) < 2 {
|
2021-09-17 21:10:54 +03:00
|
|
|
|
return errors.New("timestamp too short")
|
2015-04-08 16:45:32 +03:00
|
|
|
|
}
|
|
|
|
|
if p[0] != '"' || p[len(p)-1] != '"' {
|
|
|
|
|
return errors.New("not double-quoted")
|
|
|
|
|
}
|
|
|
|
|
s := strings.Trim(string(p), "\"")
|
|
|
|
|
t, err := time.Parse(timeStampLayout, s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
*ts = TimeStamp(t)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ts TimeStamp) Time() time.Time { return time.Time(ts) }
|
2017-04-19 21:32:22 +03:00
|
|
|
|
|
|
|
|
|
// GroupInfo contains information about a group.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#group-info.
|
|
|
|
|
type GroupInfo struct {
|
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
URL string `json:"url"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
GroupID int64 `json:"group_id"`
|
|
|
|
|
Options GroupOptionsInfo `json:"options"`
|
|
|
|
|
Owner string `json:"owner"`
|
|
|
|
|
OwnerID string `json:"owner_id"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type GroupOptionsInfo struct {
|
|
|
|
|
VisibleToAll bool `json:"visible_to_all"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) GetGroups(ctx context.Context) (map[string]*GroupInfo, error) {
|
|
|
|
|
res := make(map[string]*GroupInfo)
|
|
|
|
|
err := c.do(ctx, &res, "GET", "/groups/")
|
|
|
|
|
for k, gi := range res {
|
|
|
|
|
if gi != nil && gi.Name == "" {
|
|
|
|
|
gi.Name = k
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) GetGroupMembers(ctx context.Context, groupID string) ([]AccountInfo, error) {
|
|
|
|
|
var ais []AccountInfo
|
|
|
|
|
err := c.do(ctx, &ais, "GET", "/groups/"+groupID+"/members")
|
|
|
|
|
return ais, err
|
|
|
|
|
}
|
2021-08-10 23:22:02 +03:00
|
|
|
|
|
|
|
|
|
// SubmitChange submits the given change.
|
|
|
|
|
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#submit-change
|
|
|
|
|
// The changeID is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-id
|
|
|
|
|
func (c *Client) SubmitChange(ctx context.Context, changeID string) (ChangeInfo, error) {
|
|
|
|
|
var change ChangeInfo
|
|
|
|
|
err := c.do(ctx, &change, "POST", "/changes/"+changeID+"/submit")
|
|
|
|
|
return change, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MergeableInfo contains information about the mergeability of a change.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#mergeable-info.
|
|
|
|
|
type MergeableInfo struct {
|
|
|
|
|
SubmitType string `json:"submit_type"`
|
|
|
|
|
Strategy string `json:"strategy"`
|
|
|
|
|
Mergeable bool `json:"mergeable"`
|
|
|
|
|
CommitMerged bool `json:"commit_merged"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetMergeable retrieves mergeability information for a change at a specific revision.
|
|
|
|
|
func (c *Client) GetMergeable(ctx context.Context, changeID, revision string) (MergeableInfo, error) {
|
|
|
|
|
var mergeable MergeableInfo
|
|
|
|
|
err := c.do(ctx, &mergeable, "GET", "/changes/"+changeID+"/revisions/"+revision+"/mergeable")
|
|
|
|
|
return mergeable, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ActionInfo contains information about actions a client can make to
|
2022-03-07 15:46:50 +03:00
|
|
|
|
// manipulate a resource.
|
2021-08-10 23:22:02 +03:00
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#action-info.
|
|
|
|
|
type ActionInfo struct {
|
|
|
|
|
Method string `json:"method"`
|
|
|
|
|
Label string `json:"label"`
|
|
|
|
|
Title string `json:"title"`
|
|
|
|
|
Enabled bool `json:"enabled"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetRevisionActions retrieves revision actions.
|
|
|
|
|
func (c *Client) GetRevisionActions(ctx context.Context, changeID, revision string) (map[string]*ActionInfo, error) {
|
|
|
|
|
var actions map[string]*ActionInfo
|
|
|
|
|
err := c.do(ctx, &actions, "GET", "/changes/"+changeID+"/revisions/"+revision+"/actions")
|
|
|
|
|
return actions, err
|
|
|
|
|
}
|
2022-04-09 02:30:52 +03:00
|
|
|
|
|
|
|
|
|
// RelatedChangeAndCommitInfo contains information about a particular
|
|
|
|
|
// change at a particular commit.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#related-change-and-commit-info.
|
|
|
|
|
type RelatedChangeAndCommitInfo struct {
|
2022-05-13 22:41:42 +03:00
|
|
|
|
Project string `json:"project"`
|
|
|
|
|
ChangeID string `json:"change_id"`
|
|
|
|
|
ChangeNumber int32 `json:"_change_number"`
|
|
|
|
|
Commit CommitInfo `json:"commit"`
|
|
|
|
|
Status string `json:"status"`
|
2022-04-09 02:30:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RelatedChangesInfo contains information about a set of related changes.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#related-changes-info.
|
|
|
|
|
type RelatedChangesInfo struct {
|
|
|
|
|
Changes []RelatedChangeAndCommitInfo `json:"changes"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetRelatedChanges retrieves information about a set of related changes.
|
|
|
|
|
func (c *Client) GetRelatedChanges(ctx context.Context, changeID, revision string) (*RelatedChangesInfo, error) {
|
|
|
|
|
var changes *RelatedChangesInfo
|
|
|
|
|
err := c.do(ctx, &changes, "GET", "/changes/"+changeID+"/revisions/"+revision+"/related")
|
|
|
|
|
return changes, err
|
|
|
|
|
}
|
2022-10-04 23:57:23 +03:00
|
|
|
|
|
|
|
|
|
// GetCommitsInRefs gets refs in which the specified commits were merged into.
|
|
|
|
|
//
|
|
|
|
|
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#commits-included-in.
|
|
|
|
|
func (c *Client) GetCommitsInRefs(ctx context.Context, project string, commits, refs []string) (map[string][]string, error) {
|
|
|
|
|
result := map[string][]string{}
|
|
|
|
|
vals := url.Values{}
|
|
|
|
|
vals["commit"] = commits
|
|
|
|
|
vals["ref"] = refs
|
|
|
|
|
err := c.do(ctx, &result, "GET", "/projects/"+project+"/commits:in", urlValues(vals))
|
|
|
|
|
return result, err
|
|
|
|
|
}
|