* update github.com/xanzy/go-gitlab v0.31.0 => v0.37.0 * vendor * adapt changes Co-authored-by: techknowlogick <techknowlogick@gitea.io>tags/v1.13.0-rc1
@@ -50,7 +50,7 @@ require ( | |||
github.com/google/go-github/v32 v32.1.0 | |||
github.com/google/uuid v1.1.1 | |||
github.com/gorilla/context v1.1.1 | |||
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect | |||
github.com/hashicorp/go-retryablehttp v0.6.7 // indirect | |||
github.com/hashicorp/go-version v0.0.0-00010101000000-000000000000 | |||
github.com/huandu/xstrings v1.3.0 | |||
github.com/issue9/assert v1.3.2 // indirect | |||
@@ -94,7 +94,7 @@ require ( | |||
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 | |||
github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 | |||
github.com/urfave/cli v1.20.0 | |||
github.com/xanzy/go-gitlab v0.31.0 | |||
github.com/xanzy/go-gitlab v0.37.0 | |||
github.com/yohcop/openid-go v1.0.0 | |||
github.com/yuin/goldmark v1.2.1 | |||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 | |||
@@ -104,8 +104,8 @@ require ( | |||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d | |||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae | |||
golang.org/x/text v0.3.3 | |||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect | |||
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d | |||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect | |||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d | |||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect | |||
gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect | |||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df |
@@ -477,8 +477,8 @@ github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjh | |||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= | |||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= | |||
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= | |||
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= | |||
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= | |||
github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo= | |||
github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= | |||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= | |||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= | |||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= | |||
@@ -888,8 +888,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC | |||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= | |||
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= | |||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= | |||
github.com/xanzy/go-gitlab v0.31.0 h1:+nHztQuCXGSMluKe5Q9IRaPdz6tO8O0gMkQ0vqGpiBk= | |||
github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= | |||
github.com/xanzy/go-gitlab v0.37.0 h1:Z/CQkjj5VwbWVYVL7S70kS/TFj5H/pJumV7xbJ0YUQ8= | |||
github.com/xanzy/go-gitlab v0.37.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= | |||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= | |||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= | |||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= | |||
@@ -1072,15 +1072,14 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORK | |||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= | |||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | |||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= | |||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | |||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | |||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | |||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= | |||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | |||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= | |||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | |||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |||
@@ -1111,11 +1110,10 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn | |||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | |||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | |||
golang.org/x/tools v0.0.0-20200225230052-807dcd883420/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | |||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI= | |||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= | |||
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | |||
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d h1:XZxUC4/ZNKTjrT4/Oc9gCgIYnzPW3/CefdPjsndrVWM= | |||
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | |||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k= | |||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | |||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
@@ -1160,7 +1158,6 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ | |||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | |||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= | |||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= | |||
google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= | |||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | |||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | |||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | |||
@@ -1187,9 +1184,7 @@ gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||
gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg= | |||
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= | |||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||
gopkg.in/ini.v1 v1.60.1 h1:P5y5shSkb0CFe44qEeMBgn8JLow09MP17jlJHanke5g= | |||
gopkg.in/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||
gopkg.in/ini.v1 v1.60.2 h1:7i8mqModL63zqi8nQn8Q3+0zvSCZy1AxhBgthKfi4WU= | |||
gopkg.in/ini.v1 v1.60.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= |
@@ -234,10 +234,10 @@ func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) { | |||
var perPage = 100 | |||
var labels = make([]*base.Label, 0, perPage) | |||
for i := 1; ; i++ { | |||
ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ | |||
ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{ | |||
Page: i, | |||
PerPage: perPage, | |||
}, nil, gitlab.WithContext(g.ctx)) | |||
}}, nil, gitlab.WithContext(g.ctx)) | |||
if err != nil { | |||
return nil, err | |||
} |
@@ -1,12 +0,0 @@ | |||
sudo: false | |||
language: go | |||
go: | |||
- 1.12.4 | |||
branches: | |||
only: | |||
- master | |||
script: make updatedeps test |
@@ -35,11 +35,12 @@ import ( | |||
"net/url" | |||
"os" | |||
"regexp" | |||
"strconv" | |||
"strings" | |||
"sync" | |||
"time" | |||
"github.com/hashicorp/go-cleanhttp" | |||
cleanhttp "github.com/hashicorp/go-cleanhttp" | |||
) | |||
var ( | |||
@@ -276,12 +277,16 @@ type Logger interface { | |||
Printf(string, ...interface{}) | |||
} | |||
// LeveledLogger interface implements the basic methods that a logger library needs | |||
// LeveledLogger is an interface that can be implemented by any logger or a | |||
// logger wrapper to provide leveled logging. The methods accept a message | |||
// string and a variadic number of key-value pairs. For log.Printf style | |||
// formatting where message string contains a format specifier, use Logger | |||
// interface. | |||
type LeveledLogger interface { | |||
Error(string, ...interface{}) | |||
Info(string, ...interface{}) | |||
Debug(string, ...interface{}) | |||
Warn(string, ...interface{}) | |||
Error(msg string, keysAndValues ...interface{}) | |||
Info(msg string, keysAndValues ...interface{}) | |||
Debug(msg string, keysAndValues ...interface{}) | |||
Warn(msg string, keysAndValues ...interface{}) | |||
} | |||
// hookLogger adapts an LeveledLogger to Logger for use by the existing hook functions | |||
@@ -357,6 +362,7 @@ type Client struct { | |||
ErrorHandler ErrorHandler | |||
loggerInit sync.Once | |||
clientInit sync.Once | |||
} | |||
// NewClient creates a new Client with default settings. | |||
@@ -420,6 +426,13 @@ func DefaultRetryPolicy(ctx context.Context, resp *http.Response, err error) (bo | |||
return true, nil | |||
} | |||
// 429 Too Many Requests is recoverable. Sometimes the server puts | |||
// a Retry-After response header to indicate when the server is | |||
// available to start processing request from client. | |||
if resp.StatusCode == http.StatusTooManyRequests { | |||
return true, nil | |||
} | |||
// Check the response code. We retry on 500-range responses to allow | |||
// the server time to recover, as 500's are typically not permanent | |||
// errors and may relate to outages on the server side. This will catch | |||
@@ -431,10 +444,66 @@ func DefaultRetryPolicy(ctx context.Context, resp *http.Response, err error) (bo | |||
return false, nil | |||
} | |||
// ErrorPropagatedRetryPolicy is the same as DefaultRetryPolicy, except it | |||
// propagates errors back instead of returning nil. This allows you to inspect | |||
// why it decided to retry or not. | |||
func ErrorPropagatedRetryPolicy(ctx context.Context, resp *http.Response, err error) (bool, error) { | |||
// do not retry on context.Canceled or context.DeadlineExceeded | |||
if ctx.Err() != nil { | |||
return false, ctx.Err() | |||
} | |||
if err != nil { | |||
if v, ok := err.(*url.Error); ok { | |||
// Don't retry if the error was due to too many redirects. | |||
if redirectsErrorRe.MatchString(v.Error()) { | |||
return false, v | |||
} | |||
// Don't retry if the error was due to an invalid protocol scheme. | |||
if schemeErrorRe.MatchString(v.Error()) { | |||
return false, v | |||
} | |||
// Don't retry if the error was due to TLS cert verification failure. | |||
if _, ok := v.Err.(x509.UnknownAuthorityError); ok { | |||
return false, v | |||
} | |||
} | |||
// The error is likely recoverable so retry. | |||
return true, nil | |||
} | |||
// Check the response code. We retry on 500-range responses to allow | |||
// the server time to recover, as 500's are typically not permanent | |||
// errors and may relate to outages on the server side. This will catch | |||
// invalid response codes as well, like 0 and 999. | |||
if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != 501) { | |||
return true, fmt.Errorf("unexpected HTTP status %s", resp.Status) | |||
} | |||
return false, nil | |||
} | |||
// DefaultBackoff provides a default callback for Client.Backoff which | |||
// will perform exponential backoff based on the attempt number and limited | |||
// by the provided minimum and maximum durations. | |||
// | |||
// It also tries to parse Retry-After response header when a http.StatusTooManyRequests | |||
// (HTTP Code 429) is found in the resp parameter. Hence it will return the number of | |||
// seconds the server states it may be ready to process more requests from this client. | |||
func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration { | |||
if resp != nil { | |||
if resp.StatusCode == http.StatusTooManyRequests { | |||
if s, ok := resp.Header["Retry-After"]; ok { | |||
if sleep, err := strconv.ParseInt(s[0], 10, 64); err == nil { | |||
return time.Second * time.Duration(sleep) | |||
} | |||
} | |||
} | |||
} | |||
mult := math.Pow(2, float64(attemptNum)) * float64(min) | |||
sleep := time.Duration(mult) | |||
if float64(sleep) != mult || sleep > max { | |||
@@ -490,25 +559,31 @@ func PassthroughErrorHandler(resp *http.Response, err error, _ int) (*http.Respo | |||
// Do wraps calling an HTTP method with retries. | |||
func (c *Client) Do(req *Request) (*http.Response, error) { | |||
if c.HTTPClient == nil { | |||
c.HTTPClient = cleanhttp.DefaultPooledClient() | |||
} | |||
c.clientInit.Do(func() { | |||
if c.HTTPClient == nil { | |||
c.HTTPClient = cleanhttp.DefaultPooledClient() | |||
} | |||
}) | |||
logger := c.logger() | |||
if logger != nil { | |||
switch v := logger.(type) { | |||
case Logger: | |||
v.Printf("[DEBUG] %s %s", req.Method, req.URL) | |||
case LeveledLogger: | |||
v.Debug("performing request", "method", req.Method, "url", req.URL) | |||
case Logger: | |||
v.Printf("[DEBUG] %s %s", req.Method, req.URL) | |||
} | |||
} | |||
var resp *http.Response | |||
var err error | |||
var attempt int | |||
var shouldRetry bool | |||
var doErr, checkErr error | |||
for i := 0; ; i++ { | |||
attempt++ | |||
var code int // HTTP response code | |||
// Always rewind the request body when non-nil. | |||
@@ -527,30 +602,30 @@ func (c *Client) Do(req *Request) (*http.Response, error) { | |||
if c.RequestLogHook != nil { | |||
switch v := logger.(type) { | |||
case Logger: | |||
c.RequestLogHook(v, req.Request, i) | |||
case LeveledLogger: | |||
c.RequestLogHook(hookLogger{v}, req.Request, i) | |||
case Logger: | |||
c.RequestLogHook(v, req.Request, i) | |||
default: | |||
c.RequestLogHook(nil, req.Request, i) | |||
} | |||
} | |||
// Attempt the request | |||
resp, err = c.HTTPClient.Do(req.Request) | |||
resp, doErr = c.HTTPClient.Do(req.Request) | |||
if resp != nil { | |||
code = resp.StatusCode | |||
} | |||
// Check if we should continue with retries. | |||
checkOK, checkErr := c.CheckRetry(req.Context(), resp, err) | |||
shouldRetry, checkErr = c.CheckRetry(req.Context(), resp, doErr) | |||
if err != nil { | |||
if doErr != nil { | |||
switch v := logger.(type) { | |||
case Logger: | |||
v.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err) | |||
case LeveledLogger: | |||
v.Error("request failed", "error", err, "method", req.Method, "url", req.URL) | |||
v.Error("request failed", "error", doErr, "method", req.Method, "url", req.URL) | |||
case Logger: | |||
v.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, doErr) | |||
} | |||
} else { | |||
// Call this here to maintain the behavior of logging all requests, | |||
@@ -558,23 +633,18 @@ func (c *Client) Do(req *Request) (*http.Response, error) { | |||
if c.ResponseLogHook != nil { | |||
// Call the response logger function if provided. | |||
switch v := logger.(type) { | |||
case Logger: | |||
c.ResponseLogHook(v, resp) | |||
case LeveledLogger: | |||
c.ResponseLogHook(hookLogger{v}, resp) | |||
case Logger: | |||
c.ResponseLogHook(v, resp) | |||
default: | |||
c.ResponseLogHook(nil, resp) | |||
} | |||
} | |||
} | |||
// Now decide if we should continue. | |||
if !checkOK { | |||
if checkErr != nil { | |||
err = checkErr | |||
} | |||
c.HTTPClient.CloseIdleConnections() | |||
return resp, err | |||
if !shouldRetry { | |||
break | |||
} | |||
// We do this before drainBody because there's no need for the I/O if | |||
@@ -585,7 +655,7 @@ func (c *Client) Do(req *Request) (*http.Response, error) { | |||
} | |||
// We're going to retry, consume any response to reuse the connection. | |||
if err == nil && resp != nil { | |||
if doErr == nil { | |||
c.drainBody(resp.Body) | |||
} | |||
@@ -596,10 +666,10 @@ func (c *Client) Do(req *Request) (*http.Response, error) { | |||
} | |||
if logger != nil { | |||
switch v := logger.(type) { | |||
case Logger: | |||
v.Printf("[DEBUG] %s: retrying in %s (%d left)", desc, wait, remain) | |||
case LeveledLogger: | |||
v.Debug("retrying request", "request", desc, "timeout", wait, "remaining", remain) | |||
case Logger: | |||
v.Printf("[DEBUG] %s: retrying in %s (%d left)", desc, wait, remain) | |||
} | |||
} | |||
select { | |||
@@ -608,21 +678,44 @@ func (c *Client) Do(req *Request) (*http.Response, error) { | |||
return nil, req.Context().Err() | |||
case <-time.After(wait): | |||
} | |||
// Make shallow copy of http Request so that we can modify its body | |||
// without racing against the closeBody call in persistConn.writeLoop. | |||
httpreq := *req.Request | |||
req.Request = &httpreq | |||
} | |||
// this is the closest we have to success criteria | |||
if doErr == nil && checkErr == nil && !shouldRetry { | |||
return resp, nil | |||
} | |||
defer c.HTTPClient.CloseIdleConnections() | |||
err := doErr | |||
if checkErr != nil { | |||
err = checkErr | |||
} | |||
if c.ErrorHandler != nil { | |||
c.HTTPClient.CloseIdleConnections() | |||
return c.ErrorHandler(resp, err, c.RetryMax+1) | |||
return c.ErrorHandler(resp, err, attempt) | |||
} | |||
// By default, we close the response body and return an error without | |||
// returning the response | |||
if resp != nil { | |||
resp.Body.Close() | |||
c.drainBody(resp.Body) | |||
} | |||
// this means CheckRetry thought the request was a failure, but didn't | |||
// communicate why | |||
if err == nil { | |||
return nil, fmt.Errorf("%s %s giving up after %d attempt(s)", | |||
req.Method, req.URL, attempt) | |||
} | |||
c.HTTPClient.CloseIdleConnections() | |||
return nil, fmt.Errorf("%s %s giving up after %d attempts", | |||
req.Method, req.URL, c.RetryMax+1) | |||
return nil, fmt.Errorf("%s %s giving up after %d attempt(s): %w", | |||
req.Method, req.URL, attempt, err) | |||
} | |||
// Try to read the response body so we can reuse this connection. | |||
@@ -632,10 +725,10 @@ func (c *Client) drainBody(body io.ReadCloser) { | |||
if err != nil { | |||
if c.logger() != nil { | |||
switch v := c.logger().(type) { | |||
case Logger: | |||
v.Printf("[ERR] error reading response body: %v", err) | |||
case LeveledLogger: | |||
v.Error("error reading response body", "error", err) | |||
case Logger: | |||
v.Printf("[ERR] error reading response body: %v", err) | |||
} | |||
} | |||
} |
@@ -1,7 +1,9 @@ | |||
package retryablehttp | |||
import ( | |||
"errors" | |||
"net/http" | |||
"net/url" | |||
"sync" | |||
) | |||
@@ -39,5 +41,12 @@ func (rt *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | |||
} | |||
// Execute the request. | |||
return rt.Client.Do(retryableReq) | |||
resp, err := rt.Client.Do(retryableReq) | |||
// If we got an error returned by standard library's `Do` method, unwrap it | |||
// otherwise we will wind up erroneously re-nesting the error. | |||
if _, ok := err.(*url.Error); ok { | |||
return resp, errors.Unwrap(err) | |||
} | |||
return resp, err | |||
} |
@@ -44,6 +44,7 @@ to add new and/or missing endpoints. Currently the following services are suppor | |||
- [x] Group Milestones | |||
- [x] Group-Level Variables | |||
- [x] Groups | |||
- [x] Instance Clusters | |||
- [x] Issue Boards | |||
- [x] Issues | |||
- [x] Jobs | |||
@@ -161,7 +162,7 @@ func main() { | |||
s := &gitlab.CreateProjectSnippetOptions{ | |||
Title: gitlab.String("Dummy Snippet"), | |||
FileName: gitlab.String("snippet.go"), | |||
Code: gitlab.String("package main...."), | |||
Content: gitlab.String("package main...."), | |||
Visibility: gitlab.Visibility(gitlab.PublicVisibility), | |||
} | |||
_, _, err = git.ProjectSnippets.CreateSnippet(project.ID, s) |
@@ -38,8 +38,10 @@ type Branch struct { | |||
Protected bool `json:"protected"` | |||
Merged bool `json:"merged"` | |||
Default bool `json:"default"` | |||
CanPush bool `json:"can_push"` | |||
DevelopersCanPush bool `json:"developers_can_push"` | |||
DevelopersCanMerge bool `json:"developers_can_merge"` | |||
WebURL string `json:"web_url"` | |||
} | |||
func (b Branch) String() string { |
@@ -24,6 +24,16 @@ func WithCustomBackoff(backoff retryablehttp.Backoff) ClientOptionFunc { | |||
} | |||
} | |||
// WithCustomLimiter injects a custom rate limiter to the client. | |||
func WithCustomLimiter(limiter RateLimiter) ClientOptionFunc { | |||
return func(c *Client) error { | |||
c.configureLimiterOnce.Do(func() { | |||
c.limiter = limiter | |||
}) | |||
return nil | |||
} | |||
} | |||
// WithCustomRetry can be used to configure a custom retry policy. | |||
func WithCustomRetry(checkRetry retryablehttp.CheckRetry) ClientOptionFunc { | |||
return func(c *Client) error { |
@@ -50,6 +50,7 @@ type Commit struct { | |||
Status *BuildStateValue `json:"status"` | |||
LastPipeline *PipelineInfo `json:"last_pipeline"` | |||
ProjectID int `json:"project_id"` | |||
WebURL string `json:"web_url"` | |||
} | |||
// CommitStats represents the number of added and deleted files in a commit. | |||
@@ -118,11 +119,13 @@ const ( | |||
// CommitAction represents a single file action within a commit. | |||
type CommitAction struct { | |||
Action FileAction `url:"action" json:"action"` | |||
FilePath string `url:"file_path" json:"file_path"` | |||
PreviousPath string `url:"previous_path,omitempty" json:"previous_path,omitempty"` | |||
Content string `url:"content,omitempty" json:"content,omitempty"` | |||
Encoding string `url:"encoding,omitempty" json:"encoding,omitempty"` | |||
Action FileAction `url:"action" json:"action"` | |||
FilePath string `url:"file_path" json:"file_path"` | |||
PreviousPath string `url:"previous_path,omitempty" json:"previous_path,omitempty"` | |||
Content string `url:"content,omitempty" json:"content,omitempty"` | |||
Encoding string `url:"encoding,omitempty" json:"encoding,omitempty"` | |||
LastCommitID string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"` | |||
ExecuteFilemode bool `url:"execute_filemode,omitempty" json:"execute_filemode,omitempty"` | |||
} | |||
// CommitRef represents the reference of branches/tags in a commit. |
@@ -35,6 +35,7 @@ type Deployment struct { | |||
Ref string `json:"ref"` | |||
SHA string `json:"sha"` | |||
CreatedAt *time.Time `json:"created_at"` | |||
UpdatedAt *time.Time `json:"updated_at"` | |||
User *ProjectUser `json:"user"` | |||
Environment *Environment `json:"environment"` | |||
Deployable struct { |
@@ -0,0 +1,133 @@ | |||
package gitlab | |||
import "fmt" | |||
// EpicIssuesService handles communication with the epic issue related methods | |||
// of the GitLab API. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ee/api/epic_issues.html | |||
type EpicIssuesService struct { | |||
client *Client | |||
} | |||
// EpicIssueAssignment contains both the epic and issue objects returned from | |||
// Gitlab with the assignment ID. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ee/api/epic_issues.html | |||
type EpicIssueAssignment struct { | |||
ID int `json:"id"` | |||
Epic *Epic `json:"epic"` | |||
Issue *Issue `json:"issue"` | |||
} | |||
// ListEpicIssues get a list of epic issues. | |||
// | |||
// Gitlab API docs: | |||
// https://docs.gitlab.com/ee/api/epic_issues.html#list-issues-for-an-epic | |||
func (s *EpicIssuesService) ListEpicIssues(gid interface{}, epic int, opt *ListOptions, options ...RequestOptionFunc) ([]*Issue, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/epics/%d/issues", pathEscape(group), epic) | |||
req, err := s.client.NewRequest("GET", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var is []*Issue | |||
resp, err := s.client.Do(req, &is) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return is, resp, err | |||
} | |||
// AssignEpicIssue assigns an existing issue to an epic. | |||
// | |||
// Gitlab API Docs: | |||
// https://docs.gitlab.com/ee/api/epic_issues.html#assign-an-issue-to-the-epic | |||
func (s *EpicIssuesService) AssignEpicIssue(gid interface{}, epic, issue int, options ...RequestOptionFunc) (*EpicIssueAssignment, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/epics/%d/issues/%d", pathEscape(group), epic, issue) | |||
req, err := s.client.NewRequest("POST", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
a := new(EpicIssueAssignment) | |||
resp, err := s.client.Do(req, a) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return a, resp, err | |||
} | |||
// RemoveEpicIssue removes an issue from an epic. | |||
// | |||
// Gitlab API Docs: | |||
// https://docs.gitlab.com/ee/api/epic_issues.html#remove-an-issue-from-the-epic | |||
func (s *EpicIssuesService) RemoveEpicIssue(gid interface{}, epic, epicIssue int, options ...RequestOptionFunc) (*EpicIssueAssignment, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/epics/%d/issues/%d", pathEscape(group), epic, epicIssue) | |||
req, err := s.client.NewRequest("DELETE", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
a := new(EpicIssueAssignment) | |||
resp, err := s.client.Do(req, a) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return a, resp, err | |||
} | |||
// UpdateEpicIsssueAssignmentOptions describes the UpdateEpicIssueAssignment() | |||
// options. | |||
// | |||
// Gitlab API Docs: | |||
// https://docs.gitlab.com/ee/api/epic_issues.html#update-epic---issue-association | |||
type UpdateEpicIsssueAssignmentOptions struct { | |||
*ListOptions | |||
MoveBeforeID *int `url:"move_before_id,omitempty" json:"move_before_id,omitempty"` | |||
MoveAfterID *int `url:"move_after_id,omitempty" json:"move_after_id,omitempty"` | |||
} | |||
// UpdateEpicIssueAssignment moves an issue before or after another issue in an | |||
// epic issue list. | |||
// | |||
// Gitlab API Docs: | |||
// https://docs.gitlab.com/ee/api/epic_issues.html#update-epic---issue-association | |||
func (s *EpicIssuesService) UpdateEpicIssueAssignment(gid interface{}, epic, epicIssue int, opt *UpdateEpicIsssueAssignmentOptions, options ...RequestOptionFunc) ([]*Issue, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/epics/%d/issues/%d", pathEscape(group), epic, epicIssue) | |||
req, err := s.client.NewRequest("PUT", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var is []*Issue | |||
resp, err := s.client.Do(req, &is) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return is, resp, err | |||
} |
@@ -30,16 +30,12 @@ type Epic struct { | |||
ID int `json:"id"` | |||
IID int `json:"iid"` | |||
GroupID int `json:"group_id"` | |||
Author *EpicAuthor `json:"author"` | |||
ParentID int `json:"parent_id"` | |||
Title string `json:"title"` | |||
Description string `json:"description"` | |||
State string `json:"state"` | |||
Upvotes int `json:"upvotes"` | |||
Downvotes int `json:"downvotes"` | |||
Labels []string `json:"labels"` | |||
Title string `json:"title"` | |||
UpdatedAt *time.Time `json:"updated_at"` | |||
CreatedAt *time.Time `json:"created_at"` | |||
UserNotesCount int `json:"user_notes_count"` | |||
WebURL string `json:"web_url"` | |||
Author *EpicAuthor `json:"author"` | |||
StartDate *ISOTime `json:"start_date"` | |||
StartDateIsFixed bool `json:"start_date_is_fixed"` | |||
StartDateFixed *ISOTime `json:"start_date_fixed"` | |||
@@ -48,6 +44,13 @@ type Epic struct { | |||
DueDateIsFixed bool `json:"due_date_is_fixed"` | |||
DueDateFixed *ISOTime `json:"due_date_fixed"` | |||
DueDateFromMilestones *ISOTime `json:"due_date_from_milestones"` | |||
CreatedAt *time.Time `json:"created_at"` | |||
UpdatedAt *time.Time `json:"updated_at"` | |||
Labels []string `json:"labels"` | |||
Upvotes int `json:"upvotes"` | |||
Downvotes int `json:"downvotes"` | |||
UserNotesCount int `json:"user_notes_count"` | |||
URL string `json:"url"` | |||
} | |||
func (e Epic) String() string { | |||
@@ -59,12 +62,20 @@ func (e Epic) String() string { | |||
// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#list-epics-for-a-group | |||
type ListGroupEpicsOptions struct { | |||
ListOptions | |||
State *string `url:"state,omitempty" json:"state,omitempty"` | |||
Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
WithLabelDetails *bool `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
State *string `url:"state,omitempty" json:"state,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
IncludeAncestorGroups *bool `url:"include_ancestor_groups,omitempty" json:"include_ancestor_groups,omitempty"` | |||
IncludeDescendantGroups *bool `url:"include_descendant_groups,omitempty" json:"include_descendant_groups,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
} | |||
// ListGroupEpics gets a list of group epics. This function accepts pagination | |||
@@ -116,6 +127,30 @@ func (s *EpicsService) GetEpic(gid interface{}, epic int, options ...RequestOpti | |||
return e, resp, err | |||
} | |||
// GetEpicLinks gets all child epics of an epic. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ee/api/epic_links.html | |||
func (s *EpicsService) GetEpicLinks(gid interface{}, epic int, options ...RequestOptionFunc) ([]*Epic, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/epics/%d/epics", pathEscape(group), epic) | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var e []*Epic | |||
resp, err := s.client.Do(req, &e) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return e, resp, err | |||
} | |||
// CreateEpicOptions represents the available CreateEpic() options. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#new-epic |
@@ -85,6 +85,7 @@ type TagEvent struct { | |||
UserID int `json:"user_id"` | |||
UserName string `json:"user_name"` | |||
UserAvatar string `json:"user_avatar"` | |||
UserEmail string `json:"user_email"` | |||
ProjectID int `json:"project_id"` | |||
Message string `json:"message"` | |||
Project struct { | |||
@@ -443,8 +444,8 @@ type IssueCommentEvent struct { | |||
TimeEstimate int `json:"time_estimate"` | |||
Confidential bool `json:"confidential"` | |||
TotalTimeSpent int `json:"total_time_spent"` | |||
HumanTotalTimeSpent int `json:"human_total_time_spent"` | |||
HumanTimeEstimate int `json:"human_time_estimate"` | |||
HumanTotalTimeSpent string `json:"human_total_time_spent"` | |||
HumanTimeEstimate string `json:"human_time_estimate"` | |||
AssigneeIDs []int `json:"assignee_ids"` | |||
AssigneeID int `json:"assignee_id"` | |||
} `json:"issue"` | |||
@@ -593,6 +594,10 @@ type MergeEvent struct { | |||
Previous int `json:"previous"` | |||
Current int `json:"current"` | |||
} `json:"source_project_id"` | |||
StateID struct { | |||
Previous int `json:"previous"` | |||
Current int `json:"current"` | |||
} `json:"state_id"` | |||
TargetBranch struct { | |||
Previous string `json:"previous"` | |||
Current string `json:"current"` |
@@ -20,7 +20,6 @@ package gitlab | |||
import ( | |||
"context" | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"io" | |||
"io/ioutil" | |||
@@ -63,268 +62,6 @@ const ( | |||
privateToken | |||
) | |||
// AccessLevelValue represents a permission level within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html | |||
type AccessLevelValue int | |||
// List of available access levels | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html | |||
const ( | |||
NoPermissions AccessLevelValue = 0 | |||
GuestPermissions AccessLevelValue = 10 | |||
ReporterPermissions AccessLevelValue = 20 | |||
DeveloperPermissions AccessLevelValue = 30 | |||
MaintainerPermissions AccessLevelValue = 40 | |||
OwnerPermissions AccessLevelValue = 50 | |||
// These are deprecated and should be removed in a future version | |||
MasterPermissions AccessLevelValue = 40 | |||
OwnerPermission AccessLevelValue = 50 | |||
) | |||
// BuildStateValue represents a GitLab build state. | |||
type BuildStateValue string | |||
// These constants represent all valid build states. | |||
const ( | |||
Pending BuildStateValue = "pending" | |||
Created BuildStateValue = "created" | |||
Running BuildStateValue = "running" | |||
Success BuildStateValue = "success" | |||
Failed BuildStateValue = "failed" | |||
Canceled BuildStateValue = "canceled" | |||
Skipped BuildStateValue = "skipped" | |||
Manual BuildStateValue = "manual" | |||
) | |||
// DeploymentStatusValue represents a Gitlab deployment status. | |||
type DeploymentStatusValue string | |||
// These constants represent all valid deployment statuses. | |||
const ( | |||
DeploymentStatusCreated DeploymentStatusValue = "created" | |||
DeploymentStatusRunning DeploymentStatusValue = "running" | |||
DeploymentStatusSuccess DeploymentStatusValue = "success" | |||
DeploymentStatusFailed DeploymentStatusValue = "failed" | |||
DeploymentStatusCanceled DeploymentStatusValue = "canceled" | |||
) | |||
// ISOTime represents an ISO 8601 formatted date | |||
type ISOTime time.Time | |||
// ISO 8601 date format | |||
const iso8601 = "2006-01-02" | |||
// MarshalJSON implements the json.Marshaler interface | |||
func (t ISOTime) MarshalJSON() ([]byte, error) { | |||
if y := time.Time(t).Year(); y < 0 || y >= 10000 { | |||
// ISO 8901 uses 4 digits for the years | |||
return nil, errors.New("json: ISOTime year outside of range [0,9999]") | |||
} | |||
b := make([]byte, 0, len(iso8601)+2) | |||
b = append(b, '"') | |||
b = time.Time(t).AppendFormat(b, iso8601) | |||
b = append(b, '"') | |||
return b, nil | |||
} | |||
// UnmarshalJSON implements the json.Unmarshaler interface | |||
func (t *ISOTime) UnmarshalJSON(data []byte) error { | |||
// Ignore null, like in the main JSON package | |||
if string(data) == "null" { | |||
return nil | |||
} | |||
isotime, err := time.Parse(`"`+iso8601+`"`, string(data)) | |||
*t = ISOTime(isotime) | |||
return err | |||
} | |||
// EncodeValues implements the query.Encoder interface | |||
func (t *ISOTime) EncodeValues(key string, v *url.Values) error { | |||
if t == nil || (time.Time(*t)).IsZero() { | |||
return nil | |||
} | |||
v.Add(key, t.String()) | |||
return nil | |||
} | |||
// String implements the Stringer interface | |||
func (t ISOTime) String() string { | |||
return time.Time(t).Format(iso8601) | |||
} | |||
// NotificationLevelValue represents a notification level. | |||
type NotificationLevelValue int | |||
// String implements the fmt.Stringer interface. | |||
func (l NotificationLevelValue) String() string { | |||
return notificationLevelNames[l] | |||
} | |||
// MarshalJSON implements the json.Marshaler interface. | |||
func (l NotificationLevelValue) MarshalJSON() ([]byte, error) { | |||
return json.Marshal(l.String()) | |||
} | |||
// UnmarshalJSON implements the json.Unmarshaler interface. | |||
func (l *NotificationLevelValue) UnmarshalJSON(data []byte) error { | |||
var raw interface{} | |||
if err := json.Unmarshal(data, &raw); err != nil { | |||
return err | |||
} | |||
switch raw := raw.(type) { | |||
case float64: | |||
*l = NotificationLevelValue(raw) | |||
case string: | |||
*l = notificationLevelTypes[raw] | |||
case nil: | |||
// No action needed. | |||
default: | |||
return fmt.Errorf("json: cannot unmarshal %T into Go value of type %T", raw, *l) | |||
} | |||
return nil | |||
} | |||
// List of valid notification levels. | |||
const ( | |||
DisabledNotificationLevel NotificationLevelValue = iota | |||
ParticipatingNotificationLevel | |||
WatchNotificationLevel | |||
GlobalNotificationLevel | |||
MentionNotificationLevel | |||
CustomNotificationLevel | |||
) | |||
var notificationLevelNames = [...]string{ | |||
"disabled", | |||
"participating", | |||
"watch", | |||
"global", | |||
"mention", | |||
"custom", | |||
} | |||
var notificationLevelTypes = map[string]NotificationLevelValue{ | |||
"disabled": DisabledNotificationLevel, | |||
"participating": ParticipatingNotificationLevel, | |||
"watch": WatchNotificationLevel, | |||
"global": GlobalNotificationLevel, | |||
"mention": MentionNotificationLevel, | |||
"custom": CustomNotificationLevel, | |||
} | |||
// VisibilityValue represents a visibility level within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
type VisibilityValue string | |||
// List of available visibility levels. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
const ( | |||
PrivateVisibility VisibilityValue = "private" | |||
InternalVisibility VisibilityValue = "internal" | |||
PublicVisibility VisibilityValue = "public" | |||
) | |||
// ProjectCreationLevelValue represents a project creation level within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
type ProjectCreationLevelValue string | |||
// List of available project creation levels. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
const ( | |||
NoOneProjectCreation ProjectCreationLevelValue = "noone" | |||
MaintainerProjectCreation ProjectCreationLevelValue = "maintainer" | |||
DeveloperProjectCreation ProjectCreationLevelValue = "developer" | |||
) | |||
// SubGroupCreationLevelValue represents a sub group creation level within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
type SubGroupCreationLevelValue string | |||
// List of available sub group creation levels. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
const ( | |||
OwnerSubGroupCreationLevelValue SubGroupCreationLevelValue = "owner" | |||
MaintainerSubGroupCreationLevelValue SubGroupCreationLevelValue = "maintainer" | |||
) | |||
// VariableTypeValue represents a variable type within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
type VariableTypeValue string | |||
// List of available variable types. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
const ( | |||
EnvVariableType VariableTypeValue = "env_var" | |||
FileVariableType VariableTypeValue = "file" | |||
) | |||
// MergeMethodValue represents a project merge type within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method | |||
type MergeMethodValue string | |||
// List of available merge type | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method | |||
const ( | |||
NoFastForwardMerge MergeMethodValue = "merge" | |||
FastForwardMerge MergeMethodValue = "ff" | |||
RebaseMerge MergeMethodValue = "rebase_merge" | |||
) | |||
// EventTypeValue represents actions type for contribution events | |||
type EventTypeValue string | |||
// List of available action type | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#action-types | |||
const ( | |||
CreatedEventType EventTypeValue = "created" | |||
UpdatedEventType EventTypeValue = "updated" | |||
ClosedEventType EventTypeValue = "closed" | |||
ReopenedEventType EventTypeValue = "reopened" | |||
PushedEventType EventTypeValue = "pushed" | |||
CommentedEventType EventTypeValue = "commented" | |||
MergedEventType EventTypeValue = "merged" | |||
JoinedEventType EventTypeValue = "joined" | |||
LeftEventType EventTypeValue = "left" | |||
DestroyedEventType EventTypeValue = "destroyed" | |||
ExpiredEventType EventTypeValue = "expired" | |||
) | |||
// EventTargetTypeValue represents actions type value for contribution events | |||
type EventTargetTypeValue string | |||
// List of available action type | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#target-types | |||
const ( | |||
IssueEventTargetType EventTargetTypeValue = "issue" | |||
MilestoneEventTargetType EventTargetTypeValue = "milestone" | |||
MergeRequestEventTargetType EventTargetTypeValue = "merge_request" | |||
NoteEventTargetType EventTargetTypeValue = "note" | |||
ProjectEventTargetType EventTargetTypeValue = "project" | |||
SnippetEventTargetType EventTargetTypeValue = "snippet" | |||
UserEventTargetType EventTargetTypeValue = "user" | |||
) | |||
// A Client manages communication with the GitLab API. | |||
type Client struct { | |||
// HTTP client used to communicate with the API. | |||
@@ -338,12 +75,12 @@ type Client struct { | |||
// disableRetries is used to disable the default retry logic. | |||
disableRetries bool | |||
// configLimiter is used to make sure the limiter is configured exactly | |||
// configureLimiterOnce is used to make sure the limiter is configured exactly | |||
// once and block all other calls until the initial (one) call is done. | |||
configureLimiterOnce sync.Once | |||
// Limiter is used to limit API calls and prevent 429 responses. | |||
limiter *rate.Limiter | |||
limiter RateLimiter | |||
// Token type used to make authenticated API calls. | |||
authType authType | |||
@@ -354,6 +91,9 @@ type Client struct { | |||
// Token used to make authenticated API calls. | |||
token string | |||
// Protects the token field from concurrent read/write accesses. | |||
tokenLock sync.RWMutex | |||
// User agent used when communicating with the GitLab API. | |||
UserAgent string | |||
@@ -373,6 +113,7 @@ type Client struct { | |||
Deployments *DeploymentsService | |||
Discussions *DiscussionsService | |||
Environments *EnvironmentsService | |||
EpicIssues *EpicIssuesService | |||
Epics *EpicsService | |||
Events *EventsService | |||
Features *FeaturesService | |||
@@ -385,8 +126,11 @@ type Client struct { | |||
GroupMilestones *GroupMilestonesService | |||
GroupVariables *GroupVariablesService | |||
Groups *GroupsService | |||
InstanceCluster *InstanceClustersService | |||
InstanceVariables *InstanceVariablesService | |||
IssueLinks *IssueLinksService | |||
Issues *IssuesService | |||
IssuesStatistics *IssuesStatisticsService | |||
Jobs *JobsService | |||
Keys *KeysService | |||
Labels *LabelsService | |||
@@ -406,6 +150,7 @@ type Client struct { | |||
ProjectCluster *ProjectClustersService | |||
ProjectImportExport *ProjectImportExportService | |||
ProjectMembers *ProjectMembersService | |||
ProjectMirrors *ProjectMirrorService | |||
ProjectSnippets *ProjectSnippetsService | |||
ProjectVariables *ProjectVariablesService | |||
Projects *ProjectsService | |||
@@ -441,6 +186,11 @@ type ListOptions struct { | |||
PerPage int `url:"per_page,omitempty" json:"per_page,omitempty"` | |||
} | |||
// RateLimiter describes the interface that all (custom) rate limiters must implement. | |||
type RateLimiter interface { | |||
Wait(context.Context) error | |||
} | |||
// NewClient returns a new GitLab API client. To use API methods which require | |||
// authentication, provide a valid private or personal token. | |||
func NewClient(token string, options ...ClientOptionFunc) (*Client, error) { | |||
@@ -465,11 +215,6 @@ func NewBasicAuthClient(username, password string, options ...ClientOptionFunc) | |||
client.username = username | |||
client.password = password | |||
err = client.requestOAuthToken(context.Background()) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return client, nil | |||
} | |||
@@ -485,22 +230,6 @@ func NewOAuthClient(token string, options ...ClientOptionFunc) (*Client, error) | |||
return client, nil | |||
} | |||
func (c *Client) requestOAuthToken(ctx context.Context) error { | |||
config := &oauth2.Config{ | |||
Endpoint: oauth2.Endpoint{ | |||
AuthURL: fmt.Sprintf("%s://%s/oauth/authorize", c.BaseURL().Scheme, c.BaseURL().Host), | |||
TokenURL: fmt.Sprintf("%s://%s/oauth/token", c.BaseURL().Scheme, c.BaseURL().Host), | |||
}, | |||
} | |||
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.client) | |||
t, err := config.PasswordCredentialsToken(ctx, c.username, c.password) | |||
if err != nil { | |||
return err | |||
} | |||
c.token = t.AccessToken | |||
return nil | |||
} | |||
func newClient(options ...ClientOptionFunc) (*Client, error) { | |||
c := &Client{UserAgent: userAgent} | |||
@@ -547,6 +276,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) { | |||
c.Deployments = &DeploymentsService{client: c} | |||
c.Discussions = &DiscussionsService{client: c} | |||
c.Environments = &EnvironmentsService{client: c} | |||
c.EpicIssues = &EpicIssuesService{client: c} | |||
c.Epics = &EpicsService{client: c} | |||
c.Events = &EventsService{client: c} | |||
c.Features = &FeaturesService{client: c} | |||
@@ -559,8 +289,10 @@ func newClient(options ...ClientOptionFunc) (*Client, error) { | |||
c.GroupMilestones = &GroupMilestonesService{client: c} | |||
c.GroupVariables = &GroupVariablesService{client: c} | |||
c.Groups = &GroupsService{client: c} | |||
c.InstanceCluster = &InstanceClustersService{client: c} | |||
c.IssueLinks = &IssueLinksService{client: c} | |||
c.Issues = &IssuesService{client: c, timeStats: timeStats} | |||
c.IssuesStatistics = &IssuesStatisticsService{client: c} | |||
c.Jobs = &JobsService{client: c} | |||
c.Keys = &KeysService{client: c} | |||
c.Labels = &LabelsService{client: c} | |||
@@ -580,6 +312,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) { | |||
c.ProjectCluster = &ProjectClustersService{client: c} | |||
c.ProjectImportExport = &ProjectImportExportService{client: c} | |||
c.ProjectMembers = &ProjectMembersService{client: c} | |||
c.ProjectMirrors = &ProjectMirrorService{client: c} | |||
c.ProjectSnippets = &ProjectSnippetsService{client: c} | |||
c.ProjectVariables = &ProjectVariablesService{client: c} | |||
c.Projects = &ProjectsService{client: c} | |||
@@ -755,13 +488,6 @@ func (c *Client) NewRequest(method, path string, opt interface{}, options []Requ | |||
reqHeaders := make(http.Header) | |||
reqHeaders.Set("Accept", "application/json") | |||
switch c.authType { | |||
case basicAuth, oAuthToken: | |||
reqHeaders.Set("Authorization", "Bearer "+c.token) | |||
case privateToken: | |||
reqHeaders.Set("PRIVATE-TOKEN", c.token) | |||
} | |||
if c.UserAgent != "" { | |||
reqHeaders.Set("User-Agent", c.UserAgent) | |||
} | |||
@@ -875,23 +601,47 @@ func (c *Client) Do(req *retryablehttp.Request, v interface{}) (*Response, error | |||
c.configureLimiterOnce.Do(func() { c.configureLimiter() }) | |||
// Wait will block until the limiter can obtain a new token. | |||
if err := c.limiter.Wait(req.Context()); err != nil { | |||
err := c.limiter.Wait(req.Context()) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// Set the correct authentication header. If using basic auth, then check | |||
// if we already have a token and if not first authenticate and get one. | |||
var basicAuthToken string | |||
switch c.authType { | |||
case basicAuth: | |||
c.tokenLock.RLock() | |||
basicAuthToken = c.token | |||
c.tokenLock.RUnlock() | |||
if basicAuthToken == "" { | |||
// If we don't have a token yet, we first need to request one. | |||
basicAuthToken, err = c.requestOAuthToken(req.Context(), basicAuthToken) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
req.Header.Set("Authorization", "Bearer "+basicAuthToken) | |||
case oAuthToken: | |||
req.Header.Set("Authorization", "Bearer "+c.token) | |||
case privateToken: | |||
req.Header.Set("PRIVATE-TOKEN", c.token) | |||
} | |||
resp, err := c.client.Do(req) | |||
if err != nil { | |||
return nil, err | |||
} | |||
defer resp.Body.Close() | |||
if resp.StatusCode == http.StatusUnauthorized && c.authType == basicAuth { | |||
err = c.requestOAuthToken(req.Context()) | |||
if err != nil { | |||
resp.Body.Close() | |||
// The token most likely expired, so we need to request a new one and try again. | |||
if _, err := c.requestOAuthToken(req.Context(), basicAuthToken); err != nil { | |||
return nil, err | |||
} | |||
return c.Do(req, v) | |||
} | |||
defer resp.Body.Close() | |||
response := newResponse(resp) | |||
@@ -913,6 +663,32 @@ func (c *Client) Do(req *retryablehttp.Request, v interface{}) (*Response, error | |||
return response, err | |||
} | |||
func (c *Client) requestOAuthToken(ctx context.Context, token string) (string, error) { | |||
c.tokenLock.Lock() | |||
defer c.tokenLock.Unlock() | |||
// Return early if the token was updated while waiting for the lock. | |||
if c.token != token { | |||
return c.token, nil | |||
} | |||
config := &oauth2.Config{ | |||
Endpoint: oauth2.Endpoint{ | |||
AuthURL: strings.TrimSuffix(c.baseURL.String(), apiVersionPath) + "oauth/authorize", | |||
TokenURL: strings.TrimSuffix(c.baseURL.String(), apiVersionPath) + "oauth/token", | |||
}, | |||
} | |||
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.client.HTTPClient) | |||
t, err := config.PasswordCredentialsToken(ctx, c.username, c.password) | |||
if err != nil { | |||
return "", err | |||
} | |||
c.token = t.AccessToken | |||
return c.token, nil | |||
} | |||
// Helper function to accept and format both the project ID or name as project | |||
// identifier for all API calls. | |||
func parseID(id interface{}) (string, error) { | |||
@@ -1012,129 +788,3 @@ func parseError(raw interface{}) string { | |||
return fmt.Sprintf("failed to parse unexpected error type: %T", raw) | |||
} | |||
} | |||
// Bool is a helper routine that allocates a new bool value | |||
// to store v and returns a pointer to it. | |||
func Bool(v bool) *bool { | |||
p := new(bool) | |||
*p = v | |||
return p | |||
} | |||
// Int is a helper routine that allocates a new int32 value | |||
// to store v and returns a pointer to it, but unlike Int32 | |||
// its argument value is an int. | |||
func Int(v int) *int { | |||
p := new(int) | |||
*p = v | |||
return p | |||
} | |||
// String is a helper routine that allocates a new string value | |||
// to store v and returns a pointer to it. | |||
func String(v string) *string { | |||
p := new(string) | |||
*p = v | |||
return p | |||
} | |||
// Time is a helper routine that allocates a new time.Time value | |||
// to store v and returns a pointer to it. | |||
func Time(v time.Time) *time.Time { | |||
p := new(time.Time) | |||
*p = v | |||
return p | |||
} | |||
// AccessLevel is a helper routine that allocates a new AccessLevelValue | |||
// to store v and returns a pointer to it. | |||
func AccessLevel(v AccessLevelValue) *AccessLevelValue { | |||
p := new(AccessLevelValue) | |||
*p = v | |||
return p | |||
} | |||
// BuildState is a helper routine that allocates a new BuildStateValue | |||
// to store v and returns a pointer to it. | |||
func BuildState(v BuildStateValue) *BuildStateValue { | |||
p := new(BuildStateValue) | |||
*p = v | |||
return p | |||
} | |||
// DeploymentStatus is a helper routine that allocates a new | |||
// DeploymentStatusValue to store v and returns a pointer to it. | |||
func DeploymentStatus(v DeploymentStatusValue) *DeploymentStatusValue { | |||
p := new(DeploymentStatusValue) | |||
*p = v | |||
return p | |||
} | |||
// NotificationLevel is a helper routine that allocates a new NotificationLevelValue | |||
// to store v and returns a pointer to it. | |||
func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue { | |||
p := new(NotificationLevelValue) | |||
*p = v | |||
return p | |||
} | |||
// VariableType is a helper routine that allocates a new VariableTypeValue | |||
// to store v and returns a pointer to it. | |||
func VariableType(v VariableTypeValue) *VariableTypeValue { | |||
p := new(VariableTypeValue) | |||
*p = v | |||
return p | |||
} | |||
// Visibility is a helper routine that allocates a new VisibilityValue | |||
// to store v and returns a pointer to it. | |||
func Visibility(v VisibilityValue) *VisibilityValue { | |||
p := new(VisibilityValue) | |||
*p = v | |||
return p | |||
} | |||
// ProjectCreationLevel is a helper routine that allocates a new ProjectCreationLevelValue | |||
// to store v and returns a pointer to it. | |||
func ProjectCreationLevel(v ProjectCreationLevelValue) *ProjectCreationLevelValue { | |||
p := new(ProjectCreationLevelValue) | |||
*p = v | |||
return p | |||
} | |||
// SubGroupCreationLevel is a helper routine that allocates a new SubGroupCreationLevelValue | |||
// to store v and returns a pointer to it. | |||
func SubGroupCreationLevel(v SubGroupCreationLevelValue) *SubGroupCreationLevelValue { | |||
p := new(SubGroupCreationLevelValue) | |||
*p = v | |||
return p | |||
} | |||
// MergeMethod is a helper routine that allocates a new MergeMethod | |||
// to sotre v and returns a pointer to it. | |||
func MergeMethod(v MergeMethodValue) *MergeMethodValue { | |||
p := new(MergeMethodValue) | |||
*p = v | |||
return p | |||
} | |||
// BoolValue is a boolean value with advanced json unmarshaling features. | |||
type BoolValue bool | |||
// UnmarshalJSON allows 1 and 0 to be considered as boolean values | |||
// Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/50122 | |||
func (t *BoolValue) UnmarshalJSON(b []byte) error { | |||
switch string(b) { | |||
case `"1"`: | |||
*t = true | |||
return nil | |||
case `"0"`: | |||
*t = false | |||
return nil | |||
default: | |||
var v bool | |||
err := json.Unmarshal(b, &v) | |||
*t = BoolValue(v) | |||
return err | |||
} | |||
} |
@@ -93,13 +93,13 @@ func (s *GroupClustersService) GetCluster(pid interface{}, cluster int, options | |||
return nil, nil, err | |||
} | |||
pc := new(GroupCluster) | |||
resp, err := s.client.Do(req, &pc) | |||
gc := new(GroupCluster) | |||
resp, err := s.client.Do(req, &gc) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return pc, resp, err | |||
return gc, resp, err | |||
} | |||
// AddGroupClusterOptions represents the available AddCluster() options. | |||
@@ -141,13 +141,13 @@ func (s *GroupClustersService) AddCluster(pid interface{}, opt *AddGroupClusterO | |||
return nil, nil, err | |||
} | |||
pc := new(GroupCluster) | |||
resp, err := s.client.Do(req, pc) | |||
gc := new(GroupCluster) | |||
resp, err := s.client.Do(req, gc) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return pc, resp, err | |||
return gc, resp, err | |||
} | |||
// EditGroupClusterOptions represents the available EditCluster() options. | |||
@@ -185,13 +185,13 @@ func (s *GroupClustersService) EditCluster(pid interface{}, cluster int, opt *Ed | |||
return nil, nil, err | |||
} | |||
pc := new(GroupCluster) | |||
resp, err := s.client.Do(req, pc) | |||
gc := new(GroupCluster) | |||
resp, err := s.client.Do(req, gc) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return pc, resp, err | |||
return gc, resp, err | |||
} | |||
// DeleteCluster deletes an existing group cluster. |
@@ -51,6 +51,35 @@ func (s *GroupLabelsService) ListGroupLabels(gid interface{}, opt *ListGroupLabe | |||
return l, resp, err | |||
} | |||
// GetGroupLabel get a single label for a given group. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/group_labels.html#get-a-single-group-label | |||
func (s *GroupLabelsService) GetGroupLabel(gid interface{}, labelID interface{}, options ...RequestOptionFunc) (*GroupLabel, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
label, err := parseID(labelID) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/labels/%s", pathEscape(group), label) | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var l *GroupLabel | |||
resp, err := s.client.Do(req, &l) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return l, resp, err | |||
} | |||
// CreateGroupLabelOptions represents the available CreateGroupLabel() options. | |||
// | |||
// GitLab API docs: |
@@ -176,6 +176,50 @@ func (s *GroupMembersService) AddGroupMember(gid interface{}, opt *AddGroupMembe | |||
return gm, resp, err | |||
} | |||
// ShareWithGroup shares a group with the group. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/groups.html#share-groups-with-groups | |||
func (s *GroupMembersService) ShareWithGroup(gid interface{}, opt *ShareWithGroupOptions, options ...RequestOptionFunc) (*Group, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/share", pathEscape(group)) | |||
req, err := s.client.NewRequest("POST", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
g := new(Group) | |||
resp, err := s.client.Do(req, g) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return g, resp, err | |||
} | |||
// DeleteShareWithGroup allows to unshare a group from a group. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/groups.html#delete-link-sharing-group-with-another-group | |||
func (s *GroupMembersService) DeleteShareWithGroup(gid interface{}, groupID int, options ...RequestOptionFunc) (*Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/share/%d", pathEscape(group), groupID) | |||
req, err := s.client.NewRequest("DELETE", u, nil, options) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return s.client.Do(req, nil) | |||
} | |||
// EditGroupMemberOptions represents the available EditGroupMember() | |||
// options. | |||
// |
@@ -247,3 +247,42 @@ func (s *GroupMilestonesService) GetGroupMilestoneMergeRequests(gid interface{}, | |||
return mr, resp, err | |||
} | |||
type BurndownChartEvent struct { | |||
CreatedAt *time.Time `json:"created_at"` | |||
Weight *int `json:"weight"` | |||
Action *string `json:"action"` | |||
} | |||
// GetGroupMilestoneBurndownChartEventsOptions represents the available | |||
// GetGroupMilestoneBurndownChartEventsOptions() options. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/group_milestones.html#get-all-burndown-chart-events-for-a-single-milestone-starter | |||
type GetGroupMilestoneBurndownChartEventsOptions ListOptions | |||
// GetGroupMilestoneBurndownChartEvents gets all merge requests assigned to a | |||
// single group milestone. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/group_milestones.html#get-all-burndown-chart-events-for-a-single-milestone-starter | |||
func (s *GroupMilestonesService) GetGroupMilestoneBurndownChartEvents(gid interface{}, milestone int, opt *GetGroupMilestoneBurndownChartEventsOptions, options ...RequestOptionFunc) ([]*BurndownChartEvent, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/milestones/%d/burndown_events", pathEscape(group), milestone) | |||
req, err := s.client.NewRequest("GET", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var be []*BurndownChartEvent | |||
resp, err := s.client.Do(req, &be) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return be, resp, err | |||
} |
@@ -18,6 +18,7 @@ package gitlab | |||
import ( | |||
"fmt" | |||
"time" | |||
) | |||
// GroupsService handles communication with the group related methods of | |||
@@ -32,38 +33,46 @@ type GroupsService struct { | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html | |||
type Group struct { | |||
ID int `json:"id"` | |||
Name string `json:"name"` | |||
Path string `json:"path"` | |||
Description string `json:"description"` | |||
MembershipLock bool `json:"membership_lock"` | |||
Visibility VisibilityValue `json:"visibility"` | |||
LFSEnabled bool `json:"lfs_enabled"` | |||
AvatarURL string `json:"avatar_url"` | |||
WebURL string `json:"web_url"` | |||
RequestAccessEnabled bool `json:"request_access_enabled"` | |||
FullName string `json:"full_name"` | |||
FullPath string `json:"full_path"` | |||
ParentID int `json:"parent_id"` | |||
Projects []*Project `json:"projects"` | |||
Statistics *StorageStatistics `json:"statistics"` | |||
CustomAttributes []*CustomAttribute `json:"custom_attributes"` | |||
ShareWithGroupLock bool `json:"share_with_group_lock"` | |||
RequireTwoFactorAuth bool `json:"require_two_factor_authentication"` | |||
TwoFactorGracePeriod int `json:"two_factor_grace_period"` | |||
ProjectCreationLevel ProjectCreationLevelValue `json:"project_creation_level"` | |||
AutoDevopsEnabled bool `json:"auto_devops_enabled"` | |||
SubGroupCreationLevel SubGroupCreationLevelValue `json:"subgroup_creation_level"` | |||
EmailsDisabled bool `json:"emails_disabled"` | |||
MentionsDisabled bool `json:"mentions_disabled"` | |||
RunnersToken string `json:"runners_token"` | |||
SharedProjects []*Project `json:"shared_projects"` | |||
LDAPCN string `json:"ldap_cn"` | |||
LDAPAccess AccessLevelValue `json:"ldap_access"` | |||
LDAPGroupLinks []*LDAPGroupLink `json:"ldap_group_links"` | |||
SharedRunnersMinutesLimit int `json:"shared_runners_minutes_limit"` | |||
ExtraSharedRunnersMinutesLimit int `json:"extra_shared_runners_minutes_limit"` | |||
MarkedForDeletionOn *ISOTime `json:"marked_for_deletion_on"` | |||
ID int `json:"id"` | |||
Name string `json:"name"` | |||
Path string `json:"path"` | |||
Description string `json:"description"` | |||
MembershipLock bool `json:"membership_lock"` | |||
Visibility VisibilityValue `json:"visibility"` | |||
LFSEnabled bool `json:"lfs_enabled"` | |||
AvatarURL string `json:"avatar_url"` | |||
WebURL string `json:"web_url"` | |||
RequestAccessEnabled bool `json:"request_access_enabled"` | |||
FullName string `json:"full_name"` | |||
FullPath string `json:"full_path"` | |||
ParentID int `json:"parent_id"` | |||
Projects []*Project `json:"projects"` | |||
Statistics *StorageStatistics `json:"statistics"` | |||
CustomAttributes []*CustomAttribute `json:"custom_attributes"` | |||
ShareWithGroupLock bool `json:"share_with_group_lock"` | |||
RequireTwoFactorAuth bool `json:"require_two_factor_authentication"` | |||
TwoFactorGracePeriod int `json:"two_factor_grace_period"` | |||
ProjectCreationLevel ProjectCreationLevelValue `json:"project_creation_level"` | |||
AutoDevopsEnabled bool `json:"auto_devops_enabled"` | |||
SubGroupCreationLevel SubGroupCreationLevelValue `json:"subgroup_creation_level"` | |||
EmailsDisabled bool `json:"emails_disabled"` | |||
MentionsDisabled bool `json:"mentions_disabled"` | |||
RunnersToken string `json:"runners_token"` | |||
SharedProjects []*Project `json:"shared_projects"` | |||
SharedWithGroups []struct { | |||
GroupID int `json:"group_id"` | |||
GroupName string `json:"group_name"` | |||
GroupFullPath string `json:"group_full_path"` | |||
GroupAccessLevel int `json:"group_access_level"` | |||
ExpiresAt *ISOTime `json:"expires_at"` | |||
} `json:"shared_with_groups"` | |||
LDAPCN string `json:"ldap_cn"` | |||
LDAPAccess AccessLevelValue `json:"ldap_access"` | |||
LDAPGroupLinks []*LDAPGroupLink `json:"ldap_group_links"` | |||
SharedRunnersMinutesLimit int `json:"shared_runners_minutes_limit"` | |||
ExtraSharedRunnersMinutesLimit int `json:"extra_shared_runners_minutes_limit"` | |||
MarkedForDeletionOn *ISOTime `json:"marked_for_deletion_on"` | |||
CreatedAt *time.Time `json:"created_at"` | |||
} | |||
type LDAPGroupLink struct { | |||
@@ -85,6 +94,7 @@ type ListGroupsOptions struct { | |||
SkipGroups []int `url:"skip_groups,omitempty" json:"skip_groups,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Statistics *bool `url:"statistics,omitempty" json:"statistics,omitempty"` | |||
TopLevelOnly *bool `url:"top_level_only,omitempty" json:"top_level_only,omitempty"` | |||
WithCustomAttributes *bool `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"` | |||
} | |||
@@ -388,7 +398,7 @@ func (s *GroupsService) ListGroupLDAPLinks(gid interface{}, options ...RequestOp | |||
type AddGroupLDAPLinkOptions struct { | |||
CN *string `url:"cn,omitempty" json:"cn,omitempty"` | |||
GroupAccess *int `url:"group_access,omitempty" json:"group_access,omitempty"` | |||
Provider *string `url:"provider,omitempty" json:"provider,ommitempty"` | |||
Provider *string `url:"provider,omitempty" json:"provider,omitempty"` | |||
} | |||
// AddGroupLDAPLink creates a new group LDAP link. Available only for users who |
@@ -0,0 +1,151 @@ | |||
// | |||
// Copyright 2020, Serena Fang | |||
// | |||
// 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 gitlab | |||
import ( | |||
"fmt" | |||
"time" | |||
) | |||
// InstanceClustersService handles communication with the | |||
// instance clusters related methods of the GitLab API. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_clusters.html | |||
type InstanceClustersService struct { | |||
client *Client | |||
} | |||
// InstanceCluster represents a GitLab Instance Cluster. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ee/api/instance_clusters.html | |||
type InstanceCluster struct { | |||
ID int `json:"id"` | |||
Name string `json:"name"` | |||
Domain string `json:"domain"` | |||
CreatedAt *time.Time `json:"created_at"` | |||
ProviderType string `json:"provider_type"` | |||
PlatformType string `json:"platform_type"` | |||
EnvironmentScope string `json:"environment_scope"` | |||
ClusterType string `json:"cluster_type"` | |||
User *User `json:"user"` | |||
PlatformKubernetes *PlatformKubernetes `json:"platform_kubernetes"` | |||
ManagementProject *ManagementProject `json:"management_project"` | |||
} | |||
func (v InstanceCluster) String() string { | |||
return Stringify(v) | |||
} | |||
// ListClusters gets a list of all instance clusters. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_clusters.html#list-instance-clusters | |||
func (s *InstanceClustersService) ListClusters(options ...RequestOptionFunc) ([]*InstanceCluster, *Response, error) { | |||
u := "admin/clusters" | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var ics []*InstanceCluster | |||
resp, err := s.client.Do(req, &ics) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return ics, resp, err | |||
} | |||
// GetCluster gets an instance cluster. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_clusters.html#get-a-single-instance-cluster | |||
func (s *InstanceClustersService) GetCluster(cluster int, options ...RequestOptionFunc) (*InstanceCluster, *Response, error) { | |||
u := fmt.Sprintf("admin/clusters/%d", cluster) | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
ic := new(InstanceCluster) | |||
resp, err := s.client.Do(req, &ic) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return ic, resp, err | |||
} | |||
// AddCluster adds an existing cluster to the instance. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_clusters.html#add-existing-instance-cluster | |||
func (s *InstanceClustersService) AddCluster(opt *AddClusterOptions, options ...RequestOptionFunc) (*InstanceCluster, *Response, error) { | |||
u := "admin/clusters/add" | |||
req, err := s.client.NewRequest("POST", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
ic := new(InstanceCluster) | |||
resp, err := s.client.Do(req, ic) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return ic, resp, err | |||
} | |||
// EditCluster updates an existing instance cluster. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_clusters.html#edit-instance-cluster | |||
func (s *InstanceClustersService) EditCluster(cluster int, opt *EditClusterOptions, options ...RequestOptionFunc) (*InstanceCluster, *Response, error) { | |||
u := fmt.Sprintf("admin/clusters/%d", cluster) | |||
req, err := s.client.NewRequest("PUT", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
ic := new(InstanceCluster) | |||
resp, err := s.client.Do(req, ic) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return ic, resp, err | |||
} | |||
// DeleteCluster deletes an existing instance cluster. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_clusters.html#delete-instance-cluster | |||
func (s *InstanceClustersService) DeleteCluster(cluster int, options ...RequestOptionFunc) (*Response, error) { | |||
u := fmt.Sprintf("admin/clusters/%d", cluster) | |||
req, err := s.client.NewRequest("DELETE", u, nil, options) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return s.client.Do(req, nil) | |||
} |
@@ -0,0 +1,179 @@ | |||
// | |||
// Copyright 2018, Patrick Webster | |||
// | |||
// 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 gitlab | |||
import ( | |||
"fmt" | |||
"net/url" | |||
) | |||
// InstanceVariablesService handles communication with the | |||
// instance level CI variables related methods of the GitLab API. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html | |||
type InstanceVariablesService struct { | |||
client *Client | |||
} | |||
// InstanceVariable represents a GitLab instance level CI Variable. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html | |||
type InstanceVariable struct { | |||
Key string `json:"key"` | |||
Value string `json:"value"` | |||
VariableType VariableTypeValue `json:"variable_type"` | |||
Protected bool `json:"protected"` | |||
Masked bool `json:"masked"` | |||
} | |||
func (v InstanceVariable) String() string { | |||
return Stringify(v) | |||
} | |||
// ListInstanceVariablesOptions represents the available options for listing variables | |||
// for an instance. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html#list-all-instance-variables | |||
type ListInstanceVariablesOptions ListOptions | |||
// ListVariables gets a list of all variables for an instance. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html#list-all-instance-variables | |||
func (s *InstanceVariablesService) ListVariables(opt *ListInstanceVariablesOptions, options ...RequestOptionFunc) ([]*InstanceVariable, *Response, error) { | |||
u := "admin/ci/variables" | |||
req, err := s.client.NewRequest("GET", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var vs []*InstanceVariable | |||
resp, err := s.client.Do(req, &vs) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return vs, resp, err | |||
} | |||
// GetVariable gets a variable. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html#show-instance-variable-details | |||
func (s *InstanceVariablesService) GetVariable(key string, options ...RequestOptionFunc) (*InstanceVariable, *Response, error) { | |||
u := fmt.Sprintf("admin/ci/variables/%s", url.PathEscape(key)) | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
v := new(InstanceVariable) | |||
resp, err := s.client.Do(req, v) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return v, resp, err | |||
} | |||
// CreateInstanceVariableOptions represents the available CreateVariable() | |||
// options. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html#create-instance-variable | |||
type CreateInstanceVariableOptions struct { | |||
Key *string `url:"key,omitempty" json:"key,omitempty"` | |||
Value *string `url:"value,omitempty" json:"value,omitempty"` | |||
VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"` | |||
Protected *bool `url:"protected,omitempty" json:"protected,omitempty"` | |||
Masked *bool `url:"masked,omitempty" json:"masked,omitempty"` | |||
} | |||
// CreateVariable creates a new instance level CI variable. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html#create-instance-variable | |||
func (s *InstanceVariablesService) CreateVariable(opt *CreateInstanceVariableOptions, options ...RequestOptionFunc) (*InstanceVariable, *Response, error) { | |||
u := "admin/ci/variables" | |||
req, err := s.client.NewRequest("POST", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
v := new(InstanceVariable) | |||
resp, err := s.client.Do(req, v) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return v, resp, err | |||
} | |||
// UpdateInstanceVariableOptions represents the available UpdateVariable() | |||
// options. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html#update-instance-variable | |||
type UpdateInstanceVariableOptions struct { | |||
Value *string `url:"value,omitempty" json:"value,omitempty"` | |||
VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"` | |||
Protected *bool `url:"protected,omitempty" json:"protected,omitempty"` | |||
Masked *bool `url:"masked,omitempty" json:"masked,omitempty"` | |||
} | |||
// UpdateVariable updates the position of an existing | |||
// instance level CI variable. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html#update-instance-variable | |||
func (s *InstanceVariablesService) UpdateVariable(key string, opt *UpdateInstanceVariableOptions, options ...RequestOptionFunc) (*InstanceVariable, *Response, error) { | |||
u := fmt.Sprintf("admin/ci/variables/%s", url.PathEscape(key)) | |||
req, err := s.client.NewRequest("PUT", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
v := new(InstanceVariable) | |||
resp, err := s.client.Do(req, v) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return v, resp, err | |||
} | |||
// RemoveVariable removes an instance level CI variable. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/instance_level_ci_variables.html#remove-instance-variable | |||
func (s *InstanceVariablesService) RemoveVariable(key string, options ...RequestOptionFunc) (*Response, error) { | |||
u := fmt.Sprintf("admin/ci/variables/%s", url.PathEscape(key)) | |||
req, err := s.client.NewRequest("DELETE", u, nil, options) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return s.client.Do(req, nil) | |||
} |
@@ -17,6 +17,7 @@ | |||
package gitlab | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"net/url" | |||
@@ -60,6 +61,16 @@ type IssueReferences struct { | |||
Full string `json:"full"` | |||
} | |||
// IssueCloser represents a closer of the issue. | |||
type IssueCloser struct { | |||
ID int `json:"id"` | |||
State string `json:"state"` | |||
WebURL string `json:"web_url"` | |||
Name string `json:"name"` | |||
AvatarURL string `json:"avatar_url"` | |||
Username string `json:"username"` | |||
} | |||
// IssueLinks represents links of the issue. | |||
type IssueLinks struct { | |||
Self string `json:"self"` | |||
@@ -83,9 +94,11 @@ type Issue struct { | |||
Assignee *IssueAssignee `json:"assignee"` | |||
UpdatedAt *time.Time `json:"updated_at"` | |||
ClosedAt *time.Time `json:"closed_at"` | |||
ClosedBy *IssueCloser `json:"closed_by"` | |||
Title string `json:"title"` | |||
CreatedAt *time.Time `json:"created_at"` | |||
Labels Labels `json:"labels"` | |||
LabelDetails []*LabelDetails `json:"label_details"` | |||
Upvotes int `json:"upvotes"` | |||
Downvotes int `json:"downvotes"` | |||
DueDate *ISOTime `json:"due_date"` | |||
@@ -100,6 +113,8 @@ type Issue struct { | |||
Links *IssueLinks `json:"_links"` | |||
IssueLinkID int `json:"issue_link_id"` | |||
MergeRequestCount int `json:"merge_requests_count"` | |||
EpicIssueID int `json:"epic_issue_id"` | |||
Epic *Epic `json:"epic"` | |||
TaskCompletionStatus struct { | |||
Count int `json:"count"` | |||
CompletedCount int `json:"completed_count"` | |||
@@ -110,42 +125,104 @@ func (i Issue) String() string { | |||
return Stringify(i) | |||
} | |||
func (i *Issue) UnmarshalJSON(data []byte) error { | |||
type alias Issue | |||
raw := make(map[string]interface{}) | |||
err := json.Unmarshal(data, &raw) | |||
if err != nil { | |||
return err | |||
} | |||
labelDetails, ok := raw["labels"].([]interface{}) | |||
if ok && len(labelDetails) > 0 { | |||
// We only want to change anything if we got label details. | |||
if _, ok := labelDetails[0].(map[string]interface{}); !ok { | |||
return json.Unmarshal(data, (*alias)(i)) | |||
} | |||
labels := make([]interface{}, len(labelDetails)) | |||
for i, details := range labelDetails { | |||
labels[i] = details.(map[string]interface{})["name"] | |||
} | |||
// Set the correct values | |||
raw["labels"] = labels | |||
raw["label_details"] = labelDetails | |||
data, err = json.Marshal(raw) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
return json.Unmarshal(data, (*alias)(i)) | |||
} | |||
// Labels is a custom type with specific marshaling characteristics. | |||
type Labels []string | |||
// MarshalJSON implements the json.Marshaler interface. | |||
func (l *Labels) MarshalJSON() ([]byte, error) { | |||
if *l == nil { | |||
return []byte(`null`), nil | |||
} | |||
return json.Marshal(strings.Join(*l, ",")) | |||
} | |||
// UnmarshalJSON implements the json.Unmarshaler interface. | |||
func (l *Labels) UnmarshalJSON(data []byte) error { | |||
type alias Labels | |||
if !bytes.HasPrefix(data, []byte("[")) { | |||
data = []byte(fmt.Sprintf("[%s]", string(data))) | |||
} | |||
return json.Unmarshal(data, (*alias)(l)) | |||
} | |||
// EncodeValues implements the query.EncodeValues interface | |||
func (l *Labels) EncodeValues(key string, v *url.Values) error { | |||
v.Set(key, strings.Join(*l, ",")) | |||
return nil | |||
} | |||
// LabelDetails represents detailed label information. | |||
type LabelDetails struct { | |||
ID int `json:"id"` | |||
Name string `json:"name"` | |||
Color string `json:"color"` | |||
Description string `json:"description"` | |||
DescriptionHTML string `json:"description_html"` | |||
TextColor string `json:"text_color"` | |||
} | |||
// ListIssuesOptions represents the available ListIssues() options. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-issues | |||
type ListIssuesOptions struct { | |||
ListOptions | |||
State *string `url:"state,omitempty" json:"state,omitempty"` | |||
Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
WithLabelDetails *bool `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"` | |||
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
Scope *string `url:"scope,omitempty" json:"scope,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` | |||
State *string `url:"state,omitempty" json:"state,omitempty"` | |||
Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
NotLabels Labels `url:"not[labels],comma,omitempty" json:"not[labels],omitempty"` | |||
WithLabelDetails *bool `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"` | |||
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
NotMilestone *string `url:"not[milestone],omitempty" json:"not[milestone],omitempty"` | |||
Scope *string `url:"scope,omitempty" json:"scope,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
NotAuthorID []int `url:"not[author_id],omitempty" json:"not[author_id],omitempty"` | |||
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` | |||
NotAssigneeID []int `url:"not[assignee_id],omitempty" json:"not[assignee_id],omitempty"` | |||
AssigneeUsername *string `url:"assignee_username,omitempty" json:"assignee_username,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
NotMyReactionEmoji []string `url:"not[my_reaction_emoji],omitempty" json:"not[my_reaction_emoji],omitempty"` | |||
IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` | |||
} | |||
// ListIssues gets all issues created by authenticated user. This function | |||
@@ -172,22 +249,30 @@ func (s *IssuesService) ListIssues(opt *ListIssuesOptions, options ...RequestOpt | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-group-issues | |||
type ListGroupIssuesOptions struct { | |||
ListOptions | |||
State *string `url:"state,omitempty" json:"state,omitempty"` | |||
Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` | |||
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
Scope *string `url:"scope,omitempty" json:"scope,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
In *string `url:"in,omitempty" json:"in,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
State *string `url:"state,omitempty" json:"state,omitempty"` | |||
Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
NotLabels Labels `url:"not[labels],comma,omitempty" json:"not[labels],omitempty"` | |||
WithLabelDetails *bool `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"` | |||
IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` | |||
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
NotMilestone *string `url:"not[milestone],omitempty" json:"not[milestone],omitempty"` | |||
Scope *string `url:"scope,omitempty" json:"scope,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
NotAuthorID []int `url:"not[author_id],omitempty" json:"not[author_id],omitempty"` | |||
AuthorUsername *string `url:"author_username,omitempty" json:"author_username,omitempty"` | |||
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` | |||
NotAssigneeID []int `url:"not[assignee_id],omitempty" json:"not[assignee_id],omitempty"` | |||
AssigneeUsername *string `url:"assignee_username,omitempty" json:"assignee_username,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
NotMyReactionEmoji []string `url:"not[my_reaction_emoji],omitempty" json:"not[my_reaction_emoji],omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
In *string `url:"in,omitempty" json:"in,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
} | |||
// ListGroupIssues gets a list of group issues. This function accepts | |||
@@ -220,24 +305,30 @@ func (s *IssuesService) ListGroupIssues(pid interface{}, opt *ListGroupIssuesOpt | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-project-issues | |||
type ListProjectIssuesOptions struct { | |||
ListOptions | |||
IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` | |||
State *string `url:"state,omitempty" json:"state,omitempty"` | |||
Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
WithLabelDetails *bool `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"` | |||
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
Scope *string `url:"scope,omitempty" json:"scope,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
In *string `url:"in,omitempty" json:"in,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` | |||
IIDs []int `url:"iids[],omitempty" json:"iids,omitempty"` | |||
State *string `url:"state,omitempty" json:"state,omitempty"` | |||
Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
NotLabels Labels `url:"not[labels],comma,omitempty" json:"not[labels],omitempty"` | |||
WithLabelDetails *bool `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"` | |||
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
NotMilestone []string `url:"not[milestone],omitempty" json:"not[milestone],omitempty"` | |||
Scope *string `url:"scope,omitempty" json:"scope,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
NotAuthorID []int `url:"not[author_id],omitempty" json:"not[author_id],omitempty"` | |||
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` | |||
NotAssigneeID []int `url:"not[assignee_id],omitempty" json:"not[assignee_id],omitempty"` | |||
AssigneeUsername *string `url:"assignee_username,omitempty" json:"assignee_username,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
NotMyReactionEmoji []string `url:"not[my_reaction_emoji],omitempty" json:"not[my_reaction_emoji],omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
In *string `url:"in,omitempty" json:"in,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` | |||
} | |||
// ListProjectIssues gets a list of project issues. This function accepts | |||
@@ -341,6 +432,8 @@ type UpdateIssueOptions struct { | |||
AssigneeIDs []int `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"` | |||
MilestoneID *int `url:"milestone_id,omitempty" json:"milestone_id,omitempty"` | |||
Labels *Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
AddLabels *Labels `url:"add_labels,comma,omitempty" json:"add_labels,omitempty"` | |||
RemoveLabels *Labels `url:"remove_labels,comma,omitempty" json:"remove_labels,omitempty"` | |||
StateEvent *string `url:"state_event,omitempty" json:"state_event,omitempty"` | |||
UpdatedAt *time.Time `url:"updated_at,omitempty" json:"updated_at,omitempty"` | |||
DueDate *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"` | |||
@@ -585,3 +678,28 @@ func (s *IssuesService) ResetSpentTime(pid interface{}, issue int, options ...Re | |||
func (s *IssuesService) GetTimeSpent(pid interface{}, issue int, options ...RequestOptionFunc) (*TimeStats, *Response, error) { | |||
return s.timeStats.getTimeSpent(pid, "issues", issue, options...) | |||
} | |||
// GetParticipants gets a list of issue participants. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/issues.html#participants-on-issues | |||
func (s *IssuesService) GetParticipants(pid interface{}, issue int, options ...RequestOptionFunc) ([]*BasicUser, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/issues/%d/participants", pathEscape(project), issue) | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var bu []*BasicUser | |||
resp, err := s.client.Do(req, &bu) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return bu, resp, err | |||
} |
@@ -0,0 +1,186 @@ | |||
// | |||
// Copyright 2017, Sander van Harmelen | |||
// | |||
// 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 gitlab | |||
import ( | |||
"fmt" | |||
"time" | |||
) | |||
// IssuesStatisticsService handles communication with the issues statistics | |||
// related methods of the GitLab API. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ee/api/issues_statistics.html | |||
type IssuesStatisticsService struct { | |||
client *Client | |||
} | |||
// IssuesStatistics represents a GitLab issues statistic. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ee/api/issues_statistics.html | |||
type IssuesStatistics struct { | |||
Statistics struct { | |||
Counts struct { | |||
All int `json:"all"` | |||
Closed int `json:"closed"` | |||
Opened int `json:"opened"` | |||
} `json:"counts"` | |||
} `json:"statistics"` | |||
} | |||
func (n IssuesStatistics) String() string { | |||
return Stringify(n) | |||
} | |||
// GetIssuesStatisticsOptions represents the available GetIssuesStatistics() options. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/issues_statistics.html#get-issues-statistics | |||
type GetIssuesStatisticsOptions struct { | |||
Labels *Labels `url:"labels,omitempty" json:"labels,omitempty"` | |||
Milestone *Milestone `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
Scope *string `url:"scope,omitempty" json:"scope,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
AuthorUsername *string `url:"author_username,omitempty" json:"author_username,omitempty"` | |||
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` | |||
AssigneeUsername []string `url:"assignee_username,omitempty" json:"assignee_username,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
IIDs []int `url:"iids,omitempty" json:"iids,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
In *string `url:"in,omitempty" json:"in,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` | |||
} | |||
// GetIssuesStatistics gets issues statistics on all issues the authenticated | |||
// user has access to. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/issues_statistics.html#get-issues-statistics | |||
func (s *IssuesStatisticsService) GetIssuesStatistics(opt *GetIssuesStatisticsOptions, options ...RequestOptionFunc) (*IssuesStatistics, *Response, error) { | |||
req, err := s.client.NewRequest("GET", "issues_statistics", opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
is := new(IssuesStatistics) | |||
resp, err := s.client.Do(req, is) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return is, resp, err | |||
} | |||
// GetGroupIssuesStatisticsOptions represents the available GetGroupIssuesStatistics() | |||
// options. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/issues_statistics.html#get-group-issues-statistics | |||
type GetGroupIssuesStatisticsOptions struct { | |||
Labels *Labels `url:"labels,omitempty" json:"labels,omitempty"` | |||
IIDs []int `url:"iids,omitempty" json:"iids,omitempty"` | |||
Milestone *Milestone `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
Scope *string `url:"scope,omitempty" json:"scope,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
AuthorUsername *string `url:"author_username,omitempty" json:"author_username,omitempty"` | |||
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` | |||
AssigneeUsername []string `url:"assignee_username,omitempty" json:"assignee_username,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` | |||
} | |||
// GetGroupIssuesStatistics gets issues count statistics for given group. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/issues_statistics.html#get-group-issues-statistics | |||
func (s *IssuesStatisticsService) GetGroupIssuesStatistics(gid interface{}, opt *GetGroupIssuesStatisticsOptions, options ...RequestOptionFunc) (*IssuesStatistics, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/issues_statistics", pathEscape(group)) | |||
req, err := s.client.NewRequest("GET", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
is := new(IssuesStatistics) | |||
resp, err := s.client.Do(req, is) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return is, resp, err | |||
} | |||
// GetProjectIssuesStatisticsOptions represents the available | |||
// GetProjectIssuesStatistics() options. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/issues_statistics.html#get-project-issues-statistics | |||
type GetProjectIssuesStatisticsOptions struct { | |||
IIDs []int `url:"iids,omitempty" json:"iids,omitempty"` | |||
Labels *Labels `url:"labels,omitempty" json:"labels,omitempty"` | |||
Milestone *Milestone `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
Scope *string `url:"scope,omitempty" json:"scope,omitempty"` | |||
AuthorID *int `url:"author_id,omitempty" json:"author_id,omitempty"` | |||
AuthorUsername *string `url:"author_username,omitempty" json:"author_username,omitempty"` | |||
AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` | |||
AssigneeUsername []string `url:"assignee_username,omitempty" json:"assignee_username,omitempty"` | |||
MyReactionEmoji *string `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` | |||
} | |||
// GetProjectIssuesStatistics gets issues count statistics for given project. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/issues_statistics.html#get-project-issues-statistics | |||
func (s *IssuesStatisticsService) GetProjectIssuesStatistics(pid interface{}, opt *GetProjectIssuesStatisticsOptions, options ...RequestOptionFunc) (*IssuesStatistics, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/issues_statistics", pathEscape(project)) | |||
req, err := s.client.NewRequest("GET", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
is := new(IssuesStatistics) | |||
resp, err := s.client.Do(req, is) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return is, resp, err | |||
} |
@@ -73,7 +73,11 @@ func (l Label) String() string { | |||
// ListLabelsOptions represents the available ListLabels() options. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#list-labels | |||
type ListLabelsOptions ListOptions | |||
type ListLabelsOptions struct { | |||
ListOptions | |||
WithCounts *bool `url:"with_counts,omitempty" json:"with_counts,omitempty"` | |||
IncludeAncestorGroups *bool `url:"include_ancestor_groups,omitempty" json:"include_ancestor_groups,omitempty"` | |||
} | |||
// ListLabels gets all labels for given project. | |||
// | |||
@@ -99,6 +103,34 @@ func (s *LabelsService) ListLabels(pid interface{}, opt *ListLabelsOptions, opti | |||
return l, resp, err | |||
} | |||
// GetLabel get a single label for a given project. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#get-a-single-project-label | |||
func (s *LabelsService) GetLabel(pid interface{}, labelID interface{}, options ...RequestOptionFunc) (*Label, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
label, err := parseID(labelID) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/labels/%s", pathEscape(project), label) | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var l *Label | |||
resp, err := s.client.Do(req, &l) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return l, resp, err | |||
} | |||
// CreateLabelOptions represents the available CreateLabel() options. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#create-a-new-label | |||
@@ -248,3 +280,26 @@ func (s *LabelsService) UnsubscribeFromLabel(pid interface{}, labelID interface{ | |||
return s.client.Do(req, nil) | |||
} | |||
// PromoteLabel Promotes a project label to a group label. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/labels.html#promote-a-project-label-to-a-group-label | |||
func (s *LabelsService) PromoteLabel(pid interface{}, labelID interface{}, options ...RequestOptionFunc) (*Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, err | |||
} | |||
label, err := parseID(labelID) | |||
if err != nil { | |||
return nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/labels/%s/promote", pathEscape(project), label) | |||
req, err := s.client.NewRequest("PUT", u, nil, options) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return s.client.Do(req, nil) | |||
} |
@@ -16,6 +16,8 @@ | |||
package gitlab | |||
import "time" | |||
// LicenseService handles communication with the license | |||
// related methods of the GitLab API. | |||
// | |||
@@ -30,17 +32,30 @@ type LicenseService struct { | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/license.html | |||
type License struct { | |||
StartsAt *ISOTime `json:"starts_at"` | |||
ExpiresAt *ISOTime `json:"expires_at"` | |||
Licensee struct { | |||
ID int `json:"id"` | |||
Plan string `json:"plan"` | |||
CreatedAt *time.Time `json:"created_at"` | |||
StartsAt *ISOTime `json:"starts_at"` | |||
ExpiresAt *ISOTime `json:"expires_at"` | |||
HistoricalMax int `json:"historical_max"` | |||
MaximumUserCount int `json:"maximum_user_count"` | |||
Expired bool `json:"expired"` | |||
Overage int `json:"overage"` | |||
UserLimit int `json:"user_limit"` | |||
ActiveUsers int `json:"active_users"` | |||
Licensee struct { | |||
Name string `json:"Name"` | |||
Company string `json:"Company"` | |||
Email string `json:"Email"` | |||
} `json:"licensee"` | |||
UserLimit int `json:"user_limit"` | |||
ActiveUsers int `json:"active_users"` | |||
AddOns struct { | |||
GitLabFileLocks int `json:"GitLabFileLocks"` | |||
// Add on codes that may occur in legacy licenses that don't have a plan yet. | |||
// https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/models/license.rb | |||
AddOns struct { | |||
GitLabAuditorUser int `json:"GitLab_Auditor_User"` | |||
GitLabDeployBoard int `json:"GitLab_DeployBoard"` | |||
GitLabFileLocks int `json:"GitLab_FileLocks"` | |||
GitLabGeo int `json:"GitLab_Geo"` | |||
GitLabServiceDesk int `json:"GitLab_ServiceDesk"` | |||
} `json:"add_ons"` | |||
} | |||
@@ -240,7 +240,7 @@ type ListProjectMergeRequestsOptions struct { | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Milestone *string `url:"milestone,omitempty" json:"milestone,omitempty"` | |||
View *string `url:"view,omitempty" json:"view,omitempty"` | |||
Labels *Labels `url:"labels,omitempty" json:"labels,omitempty"` | |||
Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
CreatedBefore *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
@@ -399,6 +399,31 @@ func (s *MergeRequestsService) GetMergeRequestChanges(pid interface{}, mergeRequ | |||
return m, resp, err | |||
} | |||
// GetMergeRequestParticipants gets a list of merge request participants. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr-participants | |||
func (s *MergeRequestsService) GetMergeRequestParticipants(pid interface{}, mergeRequest int, options ...RequestOptionFunc) ([]*BasicUser, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/merge_requests/%d/participants", pathEscape(project), mergeRequest) | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var ps []*BasicUser | |||
resp, err := s.client.Do(req, &ps) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return ps, resp, err | |||
} | |||
// ListMergeRequestPipelines gets all pipelines for the provided merge request. | |||
// | |||
// GitLab API docs: | |||
@@ -424,6 +449,31 @@ func (s *MergeRequestsService) ListMergeRequestPipelines(pid interface{}, mergeR | |||
return p, resp, err | |||
} | |||
// CreateMergeRequestPipeline creates a new pipeline for a merge request. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/merge_requests.html#create-mr-pipeline | |||
func (s *MergeRequestsService) CreateMergeRequestPipeline(pid interface{}, mergeRequest int, options ...RequestOptionFunc) (*PipelineInfo, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/merge_requests/%d/pipelines", pathEscape(project), mergeRequest) | |||
req, err := s.client.NewRequest("POST", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
p := new(PipelineInfo) | |||
resp, err := s.client.Do(req, p) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return p, resp, err | |||
} | |||
// GetIssuesClosedOnMergeOptions represents the available GetIssuesClosedOnMerge() | |||
// options. | |||
// |
@@ -41,6 +41,7 @@ type Milestone struct { | |||
StartDate *ISOTime `json:"start_date"` | |||
DueDate *ISOTime `json:"due_date"` | |||
State string `json:"state"` | |||
WebURL string `json:"web_url"` | |||
UpdatedAt *time.Time `json:"updated_at"` | |||
CreatedAt *time.Time `json:"created_at"` | |||
} |
@@ -230,6 +230,25 @@ func (s *PipelineSchedulesService) DeletePipelineSchedule(pid interface{}, sched | |||
return s.client.Do(req, nil) | |||
} | |||
// RunPipelineSchedule triggers a new scheduled pipeline to run immediately. | |||
// | |||
// Gitlab API docs: | |||
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#run-a-scheduled-pipeline-immediately | |||
func (s *PipelineSchedulesService) RunPipelineSchedule(pid interface{}, schedule int, options ...RequestOptionFunc) (*Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/play", pathEscape(project), schedule) | |||
req, err := s.client.NewRequest("POST", u, nil, options) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return s.client.Do(req, nil) | |||
} | |||
// CreatePipelineScheduleVariableOptions represents the available | |||
// CreatePipelineScheduleVariable() options. | |||
// |
@@ -109,7 +109,7 @@ type ListProjectPipelinesOptions struct { | |||
Name *string `url:"name,omitempty" json:"name,omitempty"` | |||
Username *string `url:"username,omitempty" json:"username,omitempty"` | |||
UpdatedAfter *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"` | |||
UpdatedBefore *time.Time `url:"update_before,omitempty" json:"updated_before,omitempty"` | |||
UpdatedBefore *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
} |
@@ -0,0 +1,145 @@ | |||
// | |||
// Copyright 2017, Sander van Harmelen | |||
// | |||
// 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 gitlab | |||
import ( | |||
"fmt" | |||
"time" | |||
) | |||
// ProjectMirrorService handles communication with the project mirror | |||
// related methods of the GitLab API. | |||
// | |||
// GitLAb API docs: https://docs.gitlab.com/ce/api/remote_mirrors.html | |||
type ProjectMirrorService struct { | |||
client *Client | |||
} | |||
// ProjectMirror represents a project mirror configuration. | |||
// | |||
// GitLAb API docs: https://docs.gitlab.com/ce/api/remote_mirrors.html | |||
type ProjectMirror struct { | |||
Enabled bool `json:"enabled"` | |||
ID int `json:"id"` | |||
LastError string `json:"last_error"` | |||
LastSuccessfulUpdateAt *time.Time `json:"last_successful_update_at"` | |||
LastUpdateAt *time.Time `json:"last_update_at"` | |||
LastUpdateStartedAt *time.Time `json:"last_update_started_at"` | |||
OnlyProtectedBranches bool `json:"only_protected_branches"` | |||
KeepDivergentRefs bool `json:"keep_divergent_refs"` | |||
UpdateStatus string `json:"update_status"` | |||
URL string `json:"url"` | |||
} | |||
// ListProjectMirror gets a list of mirrors configured on the project. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/remote_mirrors.html#list-a-projects-remote-mirrors | |||
func (s *ProjectMirrorService) ListProjectMirror(pid interface{}, options ...RequestOptionFunc) ([]*ProjectMirror, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/remote_mirrors", pathEscape(project)) | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var pm []*ProjectMirror | |||
resp, err := s.client.Do(req, &pm) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return pm, resp, err | |||
} | |||
// AddProjectMirrorOptions contains the properties requires to create | |||
// a new project mirror. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/remote_mirrors.html#create-a-remote-mirror | |||
type AddProjectMirrorOptions struct { | |||
URL *string `url:"url,omitempty" json:"url,omitempty"` | |||
Enabled *bool `url:"enabled,omitempty" json:"enabled,omitempty"` | |||
OnlyProtectedBranches *bool `url:"only_protected_branches,omitempty" json:"only_protected_branches,omitempty"` | |||
KeepDivergentRefs *bool `url:"keep_divergent_refs,omitempty" json:"keep_divergent_refs,omitempty"` | |||
} | |||
// AddProjectMirror creates a new mirror on the project. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/remote_mirrors.html#create-a-remote-mirror | |||
func (s *ProjectMirrorService) AddProjectMirror(pid interface{}, opt *AddProjectMirrorOptions, options ...RequestOptionFunc) (*ProjectMirror, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/remote_mirrors", pathEscape(project)) | |||
req, err := s.client.NewRequest("POST", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
pm := new(ProjectMirror) | |||
resp, err := s.client.Do(req, pm) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return pm, resp, err | |||
} | |||
// EditProjectMirrorOptions contains the properties requires to edit | |||
// an existing project mirror. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/remote_mirrors.html#update-a-remote-mirrors-attributes | |||
type EditProjectMirrorOptions struct { | |||
Enabled *bool `url:"enabled,omitempty" json:"enabled,omitempty"` | |||
OnlyProtectedBranches *bool `url:"only_protected_branches,omitempty" json:"only_protected_branches,omitempty"` | |||
KeepDivergentRefs *bool `url:"keep_divergent_refs,omitempty" json:"keep_divergent_refs,omitempty"` | |||
} | |||
// EditProjectMirror updates a project team member to a specified access level.. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/remote_mirrors.html#update-a-remote-mirrors-attributes | |||
func (s *ProjectMirrorService) EditProjectMirror(pid interface{}, mirror int, opt *EditProjectMirrorOptions, options ...RequestOptionFunc) (*ProjectMirror, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/remote_mirrors/%d", pathEscape(project), mirror) | |||
req, err := s.client.NewRequest("PUT", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
pm := new(ProjectMirror) | |||
resp, err := s.client.Do(req, pm) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return pm, resp, err | |||
} |
@@ -91,7 +91,7 @@ type CreateProjectSnippetOptions struct { | |||
Title *string `url:"title,omitempty" json:"title,omitempty"` | |||
FileName *string `url:"file_name,omitempty" json:"file_name,omitempty"` | |||
Description *string `url:"description,omitempty" json:"description,omitempty"` | |||
Code *string `url:"code,omitempty" json:"code,omitempty"` | |||
Content *string `url:"content,omitempty" json:"content,omitempty"` | |||
Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` | |||
} | |||
@@ -129,7 +129,7 @@ type UpdateProjectSnippetOptions struct { | |||
Title *string `url:"title,omitempty" json:"title,omitempty"` | |||
FileName *string `url:"file_name,omitempty" json:"file_name,omitempty"` | |||
Description *string `url:"description,omitempty" json:"description,omitempty"` | |||
Code *string `url:"code,omitempty" json:"code,omitempty"` | |||
Content *string `url:"content,omitempty" json:"content,omitempty"` | |||
Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` | |||
} | |||
@@ -38,57 +38,68 @@ type ProjectsService struct { | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html | |||
type Project struct { | |||
ID int `json:"id"` | |||
Description string `json:"description"` | |||
DefaultBranch string `json:"default_branch"` | |||
Public bool `json:"public"` | |||
Visibility VisibilityValue `json:"visibility"` | |||
SSHURLToRepo string `json:"ssh_url_to_repo"` | |||
HTTPURLToRepo string `json:"http_url_to_repo"` | |||
WebURL string `json:"web_url"` | |||
ReadmeURL string `json:"readme_url"` | |||
TagList []string `json:"tag_list"` | |||
Owner *User `json:"owner"` | |||
Name string `json:"name"` | |||
NameWithNamespace string `json:"name_with_namespace"` | |||
Path string `json:"path"` | |||
PathWithNamespace string `json:"path_with_namespace"` | |||
IssuesEnabled bool `json:"issues_enabled"` | |||
OpenIssuesCount int `json:"open_issues_count"` | |||
MergeRequestsEnabled bool `json:"merge_requests_enabled"` | |||
ApprovalsBeforeMerge int `json:"approvals_before_merge"` | |||
JobsEnabled bool `json:"jobs_enabled"` | |||
WikiEnabled bool `json:"wiki_enabled"` | |||
SnippetsEnabled bool `json:"snippets_enabled"` | |||
ResolveOutdatedDiffDiscussions bool `json:"resolve_outdated_diff_discussions"` | |||
ContainerRegistryEnabled bool `json:"container_registry_enabled"` | |||
CreatedAt *time.Time `json:"created_at,omitempty"` | |||
LastActivityAt *time.Time `json:"last_activity_at,omitempty"` | |||
CreatorID int `json:"creator_id"` | |||
Namespace *ProjectNamespace `json:"namespace"` | |||
ImportStatus string `json:"import_status"` | |||
ImportError string `json:"import_error"` | |||
Permissions *Permissions `json:"permissions"` | |||
MarkedForDeletionAt *ISOTime `json:"marked_for_deletion_at"` | |||
Archived bool `json:"archived"` | |||
AvatarURL string `json:"avatar_url"` | |||
SharedRunnersEnabled bool `json:"shared_runners_enabled"` | |||
ForksCount int `json:"forks_count"` | |||
StarCount int `json:"star_count"` | |||
RunnersToken string `json:"runners_token"` | |||
PublicBuilds bool `json:"public_builds"` | |||
OnlyAllowMergeIfPipelineSucceeds bool `json:"only_allow_merge_if_pipeline_succeeds"` | |||
OnlyAllowMergeIfAllDiscussionsAreResolved bool `json:"only_allow_merge_if_all_discussions_are_resolved"` | |||
RemoveSourceBranchAfterMerge bool `json:"remove_source_branch_after_merge"` | |||
LFSEnabled bool `json:"lfs_enabled"` | |||
RequestAccessEnabled bool `json:"request_access_enabled"` | |||
MergeMethod MergeMethodValue `json:"merge_method"` | |||
ForkedFromProject *ForkParent `json:"forked_from_project"` | |||
Mirror bool `json:"mirror"` | |||
MirrorUserID int `json:"mirror_user_id"` | |||
MirrorTriggerBuilds bool `json:"mirror_trigger_builds"` | |||
OnlyMirrorProtectedBranches bool `json:"only_mirror_protected_branches"` | |||
MirrorOverwritesDivergedBranches bool `json:"mirror_overwrites_diverged_branches"` | |||
ID int `json:"id"` | |||
Description string `json:"description"` | |||
DefaultBranch string `json:"default_branch"` | |||
Public bool `json:"public"` | |||
Visibility VisibilityValue `json:"visibility"` | |||
SSHURLToRepo string `json:"ssh_url_to_repo"` | |||
HTTPURLToRepo string `json:"http_url_to_repo"` | |||
WebURL string `json:"web_url"` | |||
ReadmeURL string `json:"readme_url"` | |||
TagList []string `json:"tag_list"` | |||
Owner *User `json:"owner"` | |||
Name string `json:"name"` | |||
NameWithNamespace string `json:"name_with_namespace"` | |||
Path string `json:"path"` | |||
PathWithNamespace string `json:"path_with_namespace"` | |||
IssuesEnabled bool `json:"issues_enabled"` | |||
OpenIssuesCount int `json:"open_issues_count"` | |||
MergeRequestsEnabled bool `json:"merge_requests_enabled"` | |||
ApprovalsBeforeMerge int `json:"approvals_before_merge"` | |||
JobsEnabled bool `json:"jobs_enabled"` | |||
WikiEnabled bool `json:"wiki_enabled"` | |||
SnippetsEnabled bool `json:"snippets_enabled"` | |||
ResolveOutdatedDiffDiscussions bool `json:"resolve_outdated_diff_discussions"` | |||
ContainerRegistryEnabled bool `json:"container_registry_enabled"` | |||
CreatedAt *time.Time `json:"created_at,omitempty"` | |||
LastActivityAt *time.Time `json:"last_activity_at,omitempty"` | |||
CreatorID int `json:"creator_id"` | |||
Namespace *ProjectNamespace `json:"namespace"` | |||
ImportStatus string `json:"import_status"` | |||
ImportError string `json:"import_error"` | |||
Permissions *Permissions `json:"permissions"` | |||
MarkedForDeletionAt *ISOTime `json:"marked_for_deletion_at"` | |||
Archived bool `json:"archived"` | |||
AvatarURL string `json:"avatar_url"` | |||
SharedRunnersEnabled bool `json:"shared_runners_enabled"` | |||
ForksCount int `json:"forks_count"` | |||
StarCount int `json:"star_count"` | |||
RunnersToken string `json:"runners_token"` | |||
PublicBuilds bool `json:"public_builds"` | |||
OnlyAllowMergeIfPipelineSucceeds bool `json:"only_allow_merge_if_pipeline_succeeds"` | |||
OnlyAllowMergeIfAllDiscussionsAreResolved bool `json:"only_allow_merge_if_all_discussions_are_resolved"` | |||
RemoveSourceBranchAfterMerge bool `json:"remove_source_branch_after_merge"` | |||
LFSEnabled bool `json:"lfs_enabled"` | |||
RequestAccessEnabled bool `json:"request_access_enabled"` | |||
MergeMethod MergeMethodValue `json:"merge_method"` | |||
ForkedFromProject *ForkParent `json:"forked_from_project"` | |||
Mirror bool `json:"mirror"` | |||
MirrorUserID int `json:"mirror_user_id"` | |||
MirrorTriggerBuilds bool `json:"mirror_trigger_builds"` | |||
OnlyMirrorProtectedBranches bool `json:"only_mirror_protected_branches"` | |||
MirrorOverwritesDivergedBranches bool `json:"mirror_overwrites_diverged_branches"` | |||
ServiceDeskEnabled bool `json:"service_desk_enabled"` | |||
ServiceDeskAddress string `json:"service_desk_address"` | |||
IssuesAccessLevel AccessControlValue `json:"issues_access_level"` | |||
RepositoryAccessLevel AccessControlValue `json:"repository_access_level"` | |||
MergeRequestsAccessLevel AccessControlValue `json:"merge_requests_access_level"` | |||
ForkingAccessLevel AccessControlValue `json:"forking_access_level"` | |||
WikiAccessLevel AccessControlValue `json:"wiki_access_level"` | |||
BuildsAccessLevel AccessControlValue `json:"builds_access_level"` | |||
SnippetsAccessLevel AccessControlValue `json:"snippets_access_level"` | |||
PagesAccessLevel AccessControlValue `json:"pages_access_level"` | |||
AutocloseReferencedIssues bool `json:"autoclose_referenced_issues"` | |||
SharedWithGroups []struct { | |||
GroupID int `json:"group_id"` | |||
GroupName string `json:"group_name"` | |||
@@ -213,20 +224,27 @@ func (s ProjectApprovalRule) String() string { | |||
type ListProjectsOptions struct { | |||
ListOptions | |||
Archived *bool `url:"archived,omitempty" json:"archived,omitempty"` | |||
Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
SearchNamespaces *bool `url:"search_namespaces,omitempty" json:"search_namespaces,omitempty"` | |||
Simple *bool `url:"simple,omitempty" json:"simple,omitempty"` | |||
Owned *bool `url:"owned,omitempty" json:"owned,omitempty"` | |||
Membership *bool `url:"membership,omitempty" json:"membership,omitempty"` | |||
Starred *bool `url:"starred,omitempty" json:"starred,omitempty"` | |||
Statistics *bool `url:"statistics,omitempty" json:"statistics,omitempty"` | |||
Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` | |||
WithCustomAttributes *bool `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"` | |||
WithIssuesEnabled *bool `url:"with_issues_enabled,omitempty" json:"with_issues_enabled,omitempty"` | |||
WithMergeRequestsEnabled *bool `url:"with_merge_requests_enabled,omitempty" json:"with_merge_requests_enabled,omitempty"` | |||
MinAccessLevel *AccessLevelValue `url:"min_access_level,omitempty" json:"min_access_level,omitempty"` | |||
WithCustomAttributes *bool `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"` | |||
WithProgrammingLanguage *string `url:"with_programming_language,omitempty" json:"with_programming_language,omitempty"` | |||
WikiChecksumFailed *bool `url:"wiki_checksum_failed,omitempty" json:"wiki_checksum_failed,omitempty"` | |||
RepositoryChecksumFailed *bool `url:"repository_checksum_failed,omitempty" json:"repository_checksum_failed,omitempty"` | |||
MinAccessLevel *AccessLevelValue `url:"min_access_level,omitempty" json:"min_access_level,omitempty"` | |||
IDAfter *int `url:"id_after,omitempty" json:"id_after,omitempty"` | |||
IDBefore *int `url:"id_before,omitempty" json:"id_before,omitempty"` | |||
LastActivityAfter *time.Time `url:"last_activity_after,omitempty" json:"last_activity_after,omitempty"` | |||
LastActivityBefore *time.Time `url:"last_activity_before,omitempty" json:"last_activity_before,omitempty"` | |||
} | |||
// ListProjects gets a list of projects accessible by the authenticated user. | |||
@@ -444,39 +462,60 @@ func (s *ProjectsService) GetProjectEvents(pid interface{}, opt *GetProjectEvent | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ee/api/projects.html#create-project | |||
type CreateProjectOptions struct { | |||
Name *string `url:"name,omitempty" json:"name,omitempty"` | |||
Path *string `url:"path,omitempty" json:"path,omitempty"` | |||
DefaultBranch *string `url:"default_branch,omitempty" json:"default_branch,omitempty"` | |||
NamespaceID *int `url:"namespace_id,omitempty" json:"namespace_id,omitempty"` | |||
Description *string `url:"description,omitempty" json:"description,omitempty"` | |||
IssuesEnabled *bool `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"` | |||
MergeRequestsEnabled *bool `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"` | |||
JobsEnabled *bool `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"` | |||
WikiEnabled *bool `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"` | |||
SnippetsEnabled *bool `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"` | |||
ResolveOutdatedDiffDiscussions *bool `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"` | |||
ContainerRegistryEnabled *bool `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"` | |||
SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"` | |||
Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` | |||
ImportURL *string `url:"import_url,omitempty" json:"import_url,omitempty"` | |||
PublicBuilds *bool `url:"public_builds,omitempty" json:"public_builds,omitempty"` | |||
OnlyAllowMergeIfPipelineSucceeds *bool `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"` | |||
OnlyAllowMergeIfAllDiscussionsAreResolved *bool `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"` | |||
MergeMethod *MergeMethodValue `url:"merge_method,omitempty" json:"merge_method,omitempty"` | |||
RemoveSourceBranchAfterMerge *bool `url:"remove_source_branch_after_merge,omitempty" json:"remove_source_branch_after_merge,omitempty"` | |||
LFSEnabled *bool `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"` | |||
RequestAccessEnabled *bool `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"` | |||
TagList *[]string `url:"tag_list,omitempty" json:"tag_list,omitempty"` | |||
PrintingMergeRequestLinkEnabled *bool `url:"printing_merge_request_link_enabled,omitempty" json:"printing_merge_request_link_enabled,omitempty"` | |||
BuildCoverageRegex *string `url:"build_coverage_regex,omitempty" json:"build_coverage_regex,omitempty"` | |||
CIConfigPath *string `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"` | |||
ApprovalsBeforeMerge *int `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"` | |||
Mirror *bool `url:"mirror,omitempty" json:"mirror,omitempty"` | |||
MirrorTriggerBuilds *bool `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"` | |||
InitializeWithReadme *bool `url:"initialize_with_readme,omitempty" json:"initialize_with_readme,omitempty"` | |||
TemplateName *string `url:"template_name,omitempty" json:"template_name,omitempty"` | |||
UseCustomTemplate *bool `url:"use_custom_template,omitempty" json:"use_custom_template,omitempty"` | |||
GroupWithProjectTemplatesID *int `url:"group_with_project_templates_id,omitempty" json:"group_with_project_templates_id,omitempty"` | |||
Name *string `url:"name,omitempty" json:"name,omitempty"` | |||
Path *string `url:"path,omitempty" json:"path,omitempty"` | |||
NamespaceID *int `url:"namespace_id,omitempty" json:"namespace_id,omitempty"` | |||
DefaultBranch *string `url:"default_branch,omitempty" json:"default_branch,omitempty"` | |||
Description *string `url:"description,omitempty" json:"description,omitempty"` | |||
IssuesAccessLevel *AccessControlValue `url:"issues_access_level,omitempty" json:"issues_access_level,omitempty"` | |||
RepositoryAccessLevel *AccessControlValue `url:"repository_access_level,omitempty" json:"repository_access_level,omitempty"` | |||
MergeRequestsAccessLevel *AccessControlValue `url:"merge_requests_access_level,omitempty" json:"merge_requests_access_level,omitempty"` | |||
ForkingAccessLevel *AccessControlValue `url:"forking_access_level,omitempty" json:"forking_access_level,omitempty"` | |||
BuildsAccessLevel *AccessControlValue `url:"builds_access_level,omitempty" json:"builds_access_level,omitempty"` | |||
WikiAccessLevel *AccessControlValue `url:"wiki_access_level,omitempty" json:"wiki_access_level,omitempty"` | |||
SnippetsAccessLevel *AccessControlValue `url:"snippets_access_level,omitempty" json:"snippets_access_level,omitempty"` | |||
PagesAccessLevel *AccessControlValue `url:"pages_access_level,omitempty" json:"pages_access_level,omitempty"` | |||
EmailsDisabled *bool `url:"emails_disabled,omitempty" json:"emails_disabled,omitempty"` | |||
ResolveOutdatedDiffDiscussions *bool `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"` | |||
ContainerRegistryEnabled *bool `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"` | |||
SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"` | |||
Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` | |||
ImportURL *string `url:"import_url,omitempty" json:"import_url,omitempty"` | |||
PublicBuilds *bool `url:"public_builds,omitempty" json:"public_builds,omitempty"` | |||
OnlyAllowMergeIfPipelineSucceeds *bool `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"` | |||
OnlyAllowMergeIfAllDiscussionsAreResolved *bool `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"` | |||
MergeMethod *MergeMethodValue `url:"merge_method,omitempty" json:"merge_method,omitempty"` | |||
RemoveSourceBranchAfterMerge *bool `url:"remove_source_branch_after_merge,omitempty" json:"remove_source_branch_after_merge,omitempty"` | |||
LFSEnabled *bool `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"` | |||
RequestAccessEnabled *bool `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"` | |||
TagList *[]string `url:"tag_list,omitempty" json:"tag_list,omitempty"` | |||
PrintingMergeRequestLinkEnabled *bool `url:"printing_merge_request_link_enabled,omitempty" json:"printing_merge_request_link_enabled,omitempty"` | |||
BuildGitStrategy *string `url:"build_git_strategy,omitempty" json:"build_git_strategy,omitempty"` | |||
BuildTimeout *int `url:"build_timeout,omitempty" json:"build_timeout,omitempty"` | |||
AutoCancelPendingPipelines *string `url:"auto_cancel_pending_pipelines,omitempty" json:"auto_cancel_pending_pipelines,omitempty"` | |||
BuildCoverageRegex *string `url:"build_coverage_regex,omitempty" json:"build_coverage_regex,omitempty"` | |||
CIConfigPath *string `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"` | |||
AutoDevopsEnabled *bool `url:"auto_devops_enabled,omitempty" json:"auto_devops_enabled,omitempty"` | |||
AutoDevopsDeployStrategy *string `url:"auto_devops_deploy_strategy,omitempty" json:"auto_devops_deploy_strategy,omitempty"` | |||
ApprovalsBeforeMerge *int `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"` | |||
ExternalAuthorizationClassificationLabel *string `url:"external_authorization_classification_label,omitempty" json:"external_authorization_classification_label,omitempty"` | |||
Mirror *bool `url:"mirror,omitempty" json:"mirror,omitempty"` | |||
MirrorTriggerBuilds *bool `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"` | |||
InitializeWithReadme *bool `url:"initialize_with_readme,omitempty" json:"initialize_with_readme,omitempty"` | |||
TemplateName *string `url:"template_name,omitempty" json:"template_name,omitempty"` | |||
TemplateProjectID *int `url:"template_project_id,omitempty" json:"template_project_id,omitempty"` | |||
UseCustomTemplate *bool `url:"use_custom_template,omitempty" json:"use_custom_template,omitempty"` | |||
GroupWithProjectTemplatesID *int `url:"group_with_project_templates_id,omitempty" json:"group_with_project_templates_id,omitempty"` | |||
PackagesEnabled *bool `url:"packages_enabled,omitempty" json:"packages_enabled,omitempty"` | |||
ServiceDeskEnabled *bool `url:"service_desk_enabled,omitempty" json:"service_desk_enabled,omitempty"` | |||
AutocloseReferencedIssues *bool `url:"autoclose_referenced_issues,omitempty" json:"autoclose_referenced_issues,omitempty"` | |||
// Deprecated members | |||
IssuesEnabled *bool `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"` | |||
MergeRequestsEnabled *bool `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"` | |||
JobsEnabled *bool `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"` | |||
WikiEnabled *bool `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"` | |||
SnippetsEnabled *bool `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"` | |||
} | |||
// CreateProject creates a new project owned by the authenticated user. | |||
@@ -530,39 +569,57 @@ func (s *ProjectsService) CreateProjectForUser(user int, opt *CreateProjectForUs | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project | |||
type EditProjectOptions struct { | |||
Name *string `url:"name,omitempty" json:"name,omitempty"` | |||
Path *string `url:"path,omitempty" json:"path,omitempty"` | |||
DefaultBranch *string `url:"default_branch,omitempty" json:"default_branch,omitempty"` | |||
Description *string `url:"description,omitempty" json:"description,omitempty"` | |||
IssuesEnabled *bool `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"` | |||
MergeRequestsEnabled *bool `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"` | |||
JobsEnabled *bool `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"` | |||
WikiEnabled *bool `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"` | |||
SnippetsEnabled *bool `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"` | |||
ResolveOutdatedDiffDiscussions *bool `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"` | |||
ContainerRegistryEnabled *bool `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"` | |||
SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"` | |||
Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` | |||
ImportURL *string `url:"import_url,omitempty" json:"import_url,omitempty"` | |||
PublicBuilds *bool `url:"public_builds,omitempty" json:"public_builds,omitempty"` | |||
OnlyAllowMergeIfPipelineSucceeds *bool `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"` | |||
OnlyAllowMergeIfAllDiscussionsAreResolved *bool `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"` | |||
MergeMethod *MergeMethodValue `url:"merge_method,omitempty" json:"merge_method,omitempty"` | |||
RemoveSourceBranchAfterMerge *bool `url:"remove_source_branch_after_merge,omitempty" json:"remove_source_branch_after_merge,omitempty"` | |||
LFSEnabled *bool `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"` | |||
RequestAccessEnabled *bool `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"` | |||
TagList *[]string `url:"tag_list,omitempty" json:"tag_list,omitempty"` | |||
BuildCoverageRegex *string `url:"build_coverage_regex,omitempty" json:"build_coverage_regex,omitempty"` | |||
CIConfigPath *string `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"` | |||
CIDefaultGitDepth *int `url:"ci_default_git_depth,omitempty" json:"ci_default_git_depth,omitempty"` | |||
ApprovalsBeforeMerge *int `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"` | |||
ExternalAuthorizationClassificationLabel *string `url:"external_authorization_classification_label,omitempty" json:"external_authorization_classification_label,omitempty"` | |||
Mirror *bool `url:"mirror,omitempty" json:"mirror,omitempty"` | |||
MirrorTriggerBuilds *bool `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"` | |||
MirrorUserID *int `url:"mirror_user_id,omitempty" json:"mirror_user_id,omitempty"` | |||
OnlyMirrorProtectedBranches *bool `url:"only_mirror_protected_branches,omitempty" json:"only_mirror_protected_branches,omitempty"` | |||
MirrorOverwritesDivergedBranches *bool `url:"mirror_overwrites_diverged_branches,omitempty" json:"mirror_overwrites_diverged_branches,omitempty"` | |||
PackagesEnabled *bool `url:"packages_enabled,omitempty" json:"packages_enabled,omitempty"` | |||
Name *string `url:"name,omitempty" json:"name,omitempty"` | |||
Path *string `url:"path,omitempty" json:"path,omitempty"` | |||
DefaultBranch *string `url:"default_branch,omitempty" json:"default_branch,omitempty"` | |||
Description *string `url:"description,omitempty" json:"description,omitempty"` | |||
IssuesAccessLevel *AccessControlValue `url:"issues_access_level,omitempty" json:"issues_access_level,omitempty"` | |||
RepositoryAccessLevel *AccessControlValue `url:"repository_access_level,omitempty" json:"repository_access_level,omitempty"` | |||
MergeRequestsAccessLevel *AccessControlValue `url:"merge_requests_access_level,omitempty" json:"merge_requests_access_level,omitempty"` | |||
ForkingAccessLevel *AccessControlValue `url:"forking_access_level,omitempty" json:"forking_access_level,omitempty"` | |||
BuildsAccessLevel *AccessControlValue `url:"builds_access_level,omitempty" json:"builds_access_level,omitempty"` | |||
WikiAccessLevel *AccessControlValue `url:"wiki_access_level,omitempty" json:"wiki_access_level,omitempty"` | |||
SnippetsAccessLevel *AccessControlValue `url:"snippets_access_level,omitempty" json:"snippets_access_level,omitempty"` | |||
PagesAccessLevel *AccessControlValue `url:"pages_access_level,omitempty" json:"pages_access_level,omitempty"` | |||
EmailsDisabled *bool `url:"emails_disabled,omitempty" json:"emails_disabled,omitempty"` | |||
ResolveOutdatedDiffDiscussions *bool `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"` | |||
ContainerRegistryEnabled *bool `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"` | |||
SharedRunnersEnabled *bool `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"` | |||
Visibility *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"` | |||
ImportURL *string `url:"import_url,omitempty" json:"import_url,omitempty"` | |||
PublicBuilds *bool `url:"public_builds,omitempty" json:"public_builds,omitempty"` | |||
OnlyAllowMergeIfPipelineSucceeds *bool `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"` | |||
OnlyAllowMergeIfAllDiscussionsAreResolved *bool `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"` | |||
MergeMethod *MergeMethodValue `url:"merge_method,omitempty" json:"merge_method,omitempty"` | |||
RemoveSourceBranchAfterMerge *bool `url:"remove_source_branch_after_merge,omitempty" json:"remove_source_branch_after_merge,omitempty"` | |||
LFSEnabled *bool `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"` | |||
RequestAccessEnabled *bool `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"` | |||
TagList *[]string `url:"tag_list,omitempty" json:"tag_list,omitempty"` | |||
BuildGitStrategy *string `url:"build_git_strategy,omitempty" json:"build_git_strategy,omitempty"` | |||
BuildTimeout *int `url:"build_timeout,omitempty" json:"build_timeout,omitempty"` | |||
AutoCancelPendingPipelines *string `url:"auto_cancel_pending_pipelines,omitempty" json:"auto_cancel_pending_pipelines,omitempty"` | |||
BuildCoverageRegex *string `url:"build_coverage_regex,omitempty" json:"build_coverage_regex,omitempty"` | |||
CIConfigPath *string `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"` | |||
CIDefaultGitDepth *int `url:"ci_default_git_depth,omitempty" json:"ci_default_git_depth,omitempty"` | |||
AutoDevopsEnabled *bool `url:"auto_devops_enabled,omitempty" json:"auto_devops_enabled,omitempty"` | |||
AutoDevopsDeployStrategy *string `url:"auto_devops_deploy_strategy,omitempty" json:"auto_devops_deploy_strategy,omitempty"` | |||
ApprovalsBeforeMerge *int `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"` | |||
ExternalAuthorizationClassificationLabel *string `url:"external_authorization_classification_label,omitempty" json:"external_authorization_classification_label,omitempty"` | |||
Mirror *bool `url:"mirror,omitempty" json:"mirror,omitempty"` | |||
MirrorUserID *int `url:"mirror_user_id,omitempty" json:"mirror_user_id,omitempty"` | |||
MirrorTriggerBuilds *bool `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"` | |||
OnlyMirrorProtectedBranches *bool `url:"only_mirror_protected_branches,omitempty" json:"only_mirror_protected_branches,omitempty"` | |||
MirrorOverwritesDivergedBranches *bool `url:"mirror_overwrites_diverged_branches,omitempty" json:"mirror_overwrites_diverged_branches,omitempty"` | |||
PackagesEnabled *bool `url:"packages_enabled,omitempty" json:"packages_enabled,omitempty"` | |||
ServiceDeskEnabled *bool `url:"service_desk_enabled,omitempty" json:"service_desk_enabled,omitempty"` | |||
AutocloseReferencedIssues *bool `url:"autoclose_referenced_issues,omitempty" json:"autoclose_referenced_issues,omitempty"` | |||
// Deprecated members | |||
IssuesEnabled *bool `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"` | |||
MergeRequestsEnabled *bool `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"` | |||
JobsEnabled *bool `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"` | |||
WikiEnabled *bool `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"` | |||
SnippetsEnabled *bool `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"` | |||
} | |||
// EditProject updates an existing project. | |||
@@ -816,6 +873,7 @@ type ProjectHook struct { | |||
ConfidentialNoteEvents bool `json:"confidential_note_events"` | |||
ProjectID int `json:"project_id"` | |||
PushEvents bool `json:"push_events"` | |||
PushEventsBranchFilter string `json:"push_events_branch_filter"` | |||
IssuesEvents bool `json:"issues_events"` | |||
ConfidentialIssuesEvents bool `json:"confidential_issues_events"` | |||
MergeRequestsEvents bool `json:"merge_requests_events"` | |||
@@ -891,6 +949,7 @@ type AddProjectHookOptions struct { | |||
URL *string `url:"url,omitempty" json:"url,omitempty"` | |||
ConfidentialNoteEvents *bool `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"` | |||
PushEvents *bool `url:"push_events,omitempty" json:"push_events,omitempty"` | |||
PushEventsBranchFilter *string `url:"push_events_branch_filter,omitempty" json:"push_events_branch_filter,omitempty"` | |||
IssuesEvents *bool `url:"issues_events,omitempty" json:"issues_events,omitempty"` | |||
ConfidentialIssuesEvents *bool `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"` | |||
MergeRequestsEvents *bool `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"` | |||
@@ -936,6 +995,7 @@ type EditProjectHookOptions struct { | |||
URL *string `url:"url,omitempty" json:"url,omitempty"` | |||
ConfidentialNoteEvents *bool `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"` | |||
PushEvents *bool `url:"push_events,omitempty" json:"push_events,omitempty"` | |||
PushEventsBranchFilter *string `url:"push_events_branch_filter,omitempty" json:"push_events_branch_filter,omitempty"` | |||
IssuesEvents *bool `url:"issues_events,omitempty" json:"issues_events,omitempty"` | |||
ConfidentialIssuesEvents *bool `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"` | |||
MergeRequestsEvents *bool `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"` |
@@ -37,6 +37,8 @@ type ProtectedBranchesService struct { | |||
// https://docs.gitlab.com/ce/api/protected_branches.html#protected-branches-api | |||
type BranchAccessDescription struct { | |||
AccessLevel AccessLevelValue `json:"access_level"` | |||
UserID int `json:"user_id"` | |||
GroupID int `json:"group_id"` | |||
AccessLevelDescription string `json:"access_level_description"` | |||
} | |||
@@ -49,6 +51,7 @@ type ProtectedBranch struct { | |||
Name string `json:"name"` | |||
PushAccessLevels []*BranchAccessDescription `json:"push_access_levels"` | |||
MergeAccessLevels []*BranchAccessDescription `json:"merge_access_levels"` | |||
UnprotectAccessLevels []*BranchAccessDescription `json:"unprotect_access_levels"` | |||
CodeOwnerApprovalRequired bool `json:"code_owner_approval_required"` | |||
} | |||
@@ -115,10 +118,24 @@ func (s *ProtectedBranchesService) GetProtectedBranch(pid interface{}, branch st | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/protected_branches.html#protect-repository-branches | |||
type ProtectRepositoryBranchesOptions struct { | |||
Name *string `url:"name,omitempty" json:"name,omitempty"` | |||
PushAccessLevel *AccessLevelValue `url:"push_access_level,omitempty" json:"push_access_level,omitempty"` | |||
MergeAccessLevel *AccessLevelValue `url:"merge_access_level,omitempty" json:"merge_access_level,omitempty"` | |||
CodeOwnerApprovalRequired *bool `url:"code_owner_approval_required,omitempty" json:"code_owner_approval_required,omitempty"` | |||
Name *string `url:"name,omitempty" json:"name,omitempty"` | |||
PushAccessLevel *AccessLevelValue `url:"push_access_level,omitempty" json:"push_access_level,omitempty"` | |||
MergeAccessLevel *AccessLevelValue `url:"merge_access_level,omitempty" json:"merge_access_level,omitempty"` | |||
UnprotectAccessLevel *AccessLevelValue `url:"unprotect_access_level,omitempty" json:"unprotect_access_level,omitempty"` | |||
AllowedToPush []*ProtectBranchPermissionOptions `url:"allowed_to_push,omitempty" json:"allowed_to_push,omitempty"` | |||
AllowedToMerge []*ProtectBranchPermissionOptions `url:"allowed_to_merge,omitempty" json:"allowed_to_merge,omitempty"` | |||
AllowedToUnprotect []*ProtectBranchPermissionOptions `url:"allowed_to_unprotect,omitempty" json:"allowed_to_unprotect,omitempty"` | |||
CodeOwnerApprovalRequired *bool `url:"code_owner_approval_required,omitempty" json:"code_owner_approval_required,omitempty"` | |||
} | |||
// ProtectBranchPermissionOptions represents a branch permission option. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/protected_branches.html#protect-repository-branches | |||
type ProtectBranchPermissionOptions struct { | |||
UserID *int `url:"user_id,omitempty" json:"user_id,omitempty"` | |||
GroupID *int `url:"group_id,omitempty" json:"group_id,omitempty"` | |||
AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"` | |||
} | |||
// ProtectRepositoryBranches protects a single repository branch or several |
@@ -21,6 +21,7 @@ import ( | |||
"fmt" | |||
"net/url" | |||
"strconv" | |||
"time" | |||
) | |||
// RepositoryFilesService handles communication with the repository files | |||
@@ -35,15 +36,16 @@ type RepositoryFilesService struct { | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html | |||
type File struct { | |||
FileName string `json:"file_name"` | |||
FilePath string `json:"file_path"` | |||
Size int `json:"size"` | |||
Encoding string `json:"encoding"` | |||
Content string `json:"content"` | |||
Ref string `json:"ref"` | |||
BlobID string `json:"blob_id"` | |||
CommitID string `json:"commit_id"` | |||
SHA256 string `json:"content_sha256"` | |||
FileName string `json:"file_name"` | |||
FilePath string `json:"file_path"` | |||
Size int `json:"size"` | |||
Encoding string `json:"encoding"` | |||
Content string `json:"content"` | |||
Ref string `json:"ref"` | |||
BlobID string `json:"blob_id"` | |||
CommitID string `json:"commit_id"` | |||
SHA256 string `json:"content_sha256"` | |||
LastCommitID string `json:"last_commit_id"` | |||
} | |||
func (r File) String() string { | |||
@@ -123,13 +125,14 @@ func (s *RepositoryFilesService) GetFileMetaData(pid interface{}, fileName strin | |||
} | |||
f := &File{ | |||
BlobID: resp.Header.Get("X-Gitlab-Blob-Id"), | |||
CommitID: resp.Header.Get("X-Gitlab-Last-Commit-Id"), | |||
Encoding: resp.Header.Get("X-Gitlab-Encoding"), | |||
FileName: resp.Header.Get("X-Gitlab-File-Name"), | |||
FilePath: resp.Header.Get("X-Gitlab-File-Path"), | |||
Ref: resp.Header.Get("X-Gitlab-Ref"), | |||
SHA256: resp.Header.Get("X-Gitlab-Content-Sha256"), | |||
BlobID: resp.Header.Get("X-Gitlab-Blob-Id"), | |||
CommitID: resp.Header.Get("X-Gitlab-Last-Commit-Id"), | |||
Encoding: resp.Header.Get("X-Gitlab-Encoding"), | |||
FileName: resp.Header.Get("X-Gitlab-File-Name"), | |||
FilePath: resp.Header.Get("X-Gitlab-File-Path"), | |||
Ref: resp.Header.Get("X-Gitlab-Ref"), | |||
SHA256: resp.Header.Get("X-Gitlab-Content-Sha256"), | |||
LastCommitID: resp.Header.Get("X-Gitlab-Last-Commit-Id"), | |||
} | |||
if sizeString := resp.Header.Get("X-Gitlab-Size"); sizeString != "" { | |||
@@ -142,6 +145,66 @@ func (s *RepositoryFilesService) GetFileMetaData(pid interface{}, fileName strin | |||
return f, resp, err | |||
} | |||
// FileBlameRange represents one item of blame information. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html | |||
type FileBlameRange struct { | |||
Commit struct { | |||
ID string `json:"id"` | |||
ParentIDs []string `json:"parent_ids"` | |||
Message string `json:"message"` | |||
AuthoredDate *time.Time `json:"authored_date"` | |||
AuthorName string `json:"author_name"` | |||
AuthorEmail string `json:"author_email"` | |||
CommittedDate *time.Time `json:"committed_date"` | |||
CommitterName string `json:"committer_name"` | |||
CommitterEmail string `json:"committer_email"` | |||
} `json:"commit"` | |||
Lines []string `json:"lines"` | |||
} | |||
func (b FileBlameRange) String() string { | |||
return Stringify(b) | |||
} | |||
// GetFileBlameOptions represents the available GetFileBlame() options. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository | |||
type GetFileBlameOptions struct { | |||
Ref *string `url:"ref,omitempty" json:"ref,omitempty"` | |||
} | |||
// GetFileBlame allows you to receive blame information. Each blame range | |||
// contains lines and corresponding commit info. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository | |||
func (s *RepositoryFilesService) GetFileBlame(pid interface{}, file string, opt *GetFileBlameOptions, options ...RequestOptionFunc) ([]*FileBlameRange, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf( | |||
"projects/%s/repository/files/%s/blame", | |||
pathEscape(project), | |||
url.PathEscape(file), | |||
) | |||
req, err := s.client.NewRequest("GET", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var br []*FileBlameRange | |||
resp, err := s.client.Do(req, &br) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return br, resp, err | |||
} | |||
// GetRawFileOptions represents the available GetRawFile() options. | |||
// | |||
// GitLab API docs: | |||
@@ -282,9 +345,11 @@ func (s *RepositoryFilesService) UpdateFile(pid interface{}, fileName string, op | |||
// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository | |||
type DeleteFileOptions struct { | |||
Branch *string `url:"branch,omitempty" json:"branch,omitempty"` | |||
StartBranch *string `url:"start_branch,omitempty" json:"start_branch,omitempty"` | |||
AuthorEmail *string `url:"author_email,omitempty" json:"author_email,omitempty"` | |||
AuthorName *string `url:"author_name,omitempty" json:"author_name,omitempty"` | |||
CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"` | |||
LastCommitID *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"` | |||
} | |||
// DeleteFile deletes an existing file in a repository |
@@ -336,6 +336,44 @@ func (s *RunnersService) DisableProjectRunner(pid interface{}, runner int, optio | |||
return s.client.Do(req, nil) | |||
} | |||
// ListGroupsRunnersOptions represents the available ListGroupsRunners() options. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/runners.html#list-groups-runners | |||
type ListGroupsRunnersOptions struct { | |||
ListOptions | |||
Type *string `url:"type,omitempty" json:"type,omitempty"` | |||
Status *string `url:"status,omitempty" json:"status,omitempty"` | |||
TagList []string `url:"tag_list,comma,omitempty" json:"tag_list,omitempty"` | |||
} | |||
// ListGroupsRunners lists all runners (specific and shared) available in the | |||
// group as well it’s ancestor groups. Shared runners are listed if at least one | |||
// shared runner is defined. | |||
// | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ee/api/runners.html#list-groups-runners | |||
func (s *RunnersService) ListGroupsRunners(gid interface{}, opt *ListGroupsRunnersOptions, options ...RequestOptionFunc) ([]*Runner, *Response, error) { | |||
group, err := parseID(gid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("groups/%s/runners", pathEscape(group)) | |||
req, err := s.client.NewRequest("GET", u, opt, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var rs []*Runner | |||
resp, err := s.client.Do(req, &rs) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return rs, resp, err | |||
} | |||
// RegisterNewRunnerOptions represents the available RegisterNewRunner() | |||
// options. | |||
// |
@@ -54,6 +54,30 @@ type Service struct { | |||
WikiPageEvents bool `json:"wiki_page_events"` | |||
} | |||
// ListServices gets a list of all active services. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/services.html#list-all-active-services | |||
func (s *ServicesService) ListServices(pid interface{}, options ...RequestOptionFunc) ([]*Service, *Response, error) { | |||
project, err := parseID(pid) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
u := fmt.Sprintf("projects/%s/services", pathEscape(project)) | |||
req, err := s.client.NewRequest("GET", u, nil, options) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var svcs []*Service | |||
resp, err := s.client.Do(req, &svcs) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return svcs, resp, err | |||
} | |||
// DroneCIService represents Drone CI service settings. | |||
// | |||
// GitLab API docs: | |||
@@ -251,7 +275,7 @@ type GithubService struct { | |||
// https://docs.gitlab.com/ce/api/services.html#github-premium | |||
type GithubServiceProperties struct { | |||
RepositoryURL string `json:"repository_url,omitempty"` | |||
StaticContext string `json:"static_context,omitempty"` | |||
StaticContext bool `json:"static_context,omitempty"` | |||
} | |||
// GetGithubService gets Github service settings for a project. | |||
@@ -665,7 +689,17 @@ type MicrosoftTeamsService struct { | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/services.html#microsoft-teams | |||
type MicrosoftTeamsServiceProperties struct { | |||
WebHook string `json:"webhook"` | |||
WebHook string `json:"webhook"` | |||
NotifyOnlyBrokenPipelines BoolValue `json:"notify_only_broken_pipelines"` | |||
BranchesToBeNotified string `json:"branches_to_be_notified"` | |||
IssuesEvents BoolValue `json:"issues_events"` | |||
ConfidentialIssuesEvents BoolValue `json:"confidential_issues_events"` | |||
MergeRequestsEvents BoolValue `json:"merge_requests_events"` | |||
TagPushEvents BoolValue `json:"tag_push_events"` | |||
NoteEvents BoolValue `json:"note_events"` | |||
ConfidentialNoteEvents BoolValue `json:"confidential_note_events"` | |||
PipelineEvents BoolValue `json:"pipeline_events"` | |||
WikiPageEvents BoolValue `json:"wiki_page_events"` | |||
} | |||
// GetMicrosoftTeamsService gets MicrosoftTeams service settings for a project. | |||
@@ -699,7 +733,18 @@ func (s *ServicesService) GetMicrosoftTeamsService(pid interface{}, options ...R | |||
// GitLab API docs: | |||
// https://docs.gitlab.com/ce/api/services.html#create-edit-microsoft-teams-service | |||
type SetMicrosoftTeamsServiceOptions struct { | |||
WebHook *string `url:"webhook,omitempty" json:"webhook,omitempty"` | |||
WebHook *string `url:"webhook,omitempty" json:"webhook,omitempty"` | |||
NotifyOnlyBrokenPipelines *bool `url:"notify_only_broken_pipelines" json:"notify_only_broken_pipelines"` | |||
BranchesToBeNotified *string `url:"branches_to_be_notified,omitempty" json:"branches_to_be_notified,omitempty"` | |||
PushEvents *bool `url:"push_events,omitempty" json:"push_events,omitempty"` | |||
IssuesEvents *bool `url:"issues_events,omitempty" json:"issues_events,omitempty"` | |||
ConfidentialIssuesEvents *bool `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"` | |||
MergeRequestsEvents *bool `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"` | |||
TagPushEvents *bool `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"` | |||
NoteEvents *bool `url:"note_events,omitempty" json:"note_events,omitempty"` | |||
ConfidentialNoteEvents *bool `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"` | |||
PipelineEvents *bool `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"` | |||
WikiPageEvents *bool `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"` | |||
} | |||
// SetMicrosoftTeamsService sets Microsoft Teams service for a project | |||
@@ -756,6 +801,7 @@ type PipelinesEmailProperties struct { | |||
Recipients string `json:"recipients,omitempty"` | |||
NotifyOnlyBrokenPipelines BoolValue `json:"notify_only_broken_pipelines,omitempty"` | |||
NotifyOnlyDefaultBranch BoolValue `json:"notify_only_default_branch,omitempty"` | |||
BranchesToBeNotified string `json:"branches_to_be_notified,omitempty"` | |||
} | |||
// GetPipelinesEmailService gets Pipelines Email service settings for a project. |
@@ -58,6 +58,7 @@ func (t Tag) String() string { | |||
type ListTagsOptions struct { | |||
ListOptions | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Search *string `url:"search,omitempty" json:"search,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
} | |||
@@ -0,0 +1,430 @@ | |||
package gitlab | |||
import ( | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"net/url" | |||
"time" | |||
) | |||
// AccessControlValue represents an access control value within GitLab, | |||
// used for managing access to certain project features. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html | |||
type AccessControlValue string | |||
// List of available access control values. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html | |||
const ( | |||
DisabledAccessControl AccessControlValue = "disabled" | |||
EnabledAccessControl AccessControlValue = "enabled" | |||
PrivateAccessControl AccessControlValue = "private" | |||
PublicAccessControl AccessControlValue = "public" | |||
) | |||
// AccessControl is a helper routine that allocates a new AccessControlValue | |||
// to store v and returns a pointer to it. | |||
func AccessControl(v AccessControlValue) *AccessControlValue { | |||
p := new(AccessControlValue) | |||
*p = v | |||
return p | |||
} | |||
// AccessLevelValue represents a permission level within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html | |||
type AccessLevelValue int | |||
// List of available access levels | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html | |||
const ( | |||
NoPermissions AccessLevelValue = 0 | |||
GuestPermissions AccessLevelValue = 10 | |||
ReporterPermissions AccessLevelValue = 20 | |||
DeveloperPermissions AccessLevelValue = 30 | |||
MaintainerPermissions AccessLevelValue = 40 | |||
OwnerPermissions AccessLevelValue = 50 | |||
// These are deprecated and should be removed in a future version | |||
MasterPermissions AccessLevelValue = 40 | |||
OwnerPermission AccessLevelValue = 50 | |||
) | |||
// AccessLevel is a helper routine that allocates a new AccessLevelValue | |||
// to store v and returns a pointer to it. | |||
func AccessLevel(v AccessLevelValue) *AccessLevelValue { | |||
p := new(AccessLevelValue) | |||
*p = v | |||
return p | |||
} | |||
// BuildStateValue represents a GitLab build state. | |||
type BuildStateValue string | |||
// These constants represent all valid build states. | |||
const ( | |||
Pending BuildStateValue = "pending" | |||
Created BuildStateValue = "created" | |||
Running BuildStateValue = "running" | |||
Success BuildStateValue = "success" | |||
Failed BuildStateValue = "failed" | |||
Canceled BuildStateValue = "canceled" | |||
Skipped BuildStateValue = "skipped" | |||
Manual BuildStateValue = "manual" | |||
) | |||
// BuildState is a helper routine that allocates a new BuildStateValue | |||
// to store v and returns a pointer to it. | |||
func BuildState(v BuildStateValue) *BuildStateValue { | |||
p := new(BuildStateValue) | |||
*p = v | |||
return p | |||
} | |||
// DeploymentStatusValue represents a Gitlab deployment status. | |||
type DeploymentStatusValue string | |||
// These constants represent all valid deployment statuses. | |||
const ( | |||
DeploymentStatusCreated DeploymentStatusValue = "created" | |||
DeploymentStatusRunning DeploymentStatusValue = "running" | |||
DeploymentStatusSuccess DeploymentStatusValue = "success" | |||
DeploymentStatusFailed DeploymentStatusValue = "failed" | |||
DeploymentStatusCanceled DeploymentStatusValue = "canceled" | |||
) | |||
// DeploymentStatus is a helper routine that allocates a new | |||
// DeploymentStatusValue to store v and returns a pointer to it. | |||
func DeploymentStatus(v DeploymentStatusValue) *DeploymentStatusValue { | |||
p := new(DeploymentStatusValue) | |||
*p = v | |||
return p | |||
} | |||
// ISOTime represents an ISO 8601 formatted date | |||
type ISOTime time.Time | |||
// ISO 8601 date format | |||
const iso8601 = "2006-01-02" | |||
// MarshalJSON implements the json.Marshaler interface | |||
func (t ISOTime) MarshalJSON() ([]byte, error) { | |||
if y := time.Time(t).Year(); y < 0 || y >= 10000 { | |||
// ISO 8901 uses 4 digits for the years | |||
return nil, errors.New("json: ISOTime year outside of range [0,9999]") | |||
} | |||
b := make([]byte, 0, len(iso8601)+2) | |||
b = append(b, '"') | |||
b = time.Time(t).AppendFormat(b, iso8601) | |||
b = append(b, '"') | |||
return b, nil | |||
} | |||
// UnmarshalJSON implements the json.Unmarshaler interface | |||
func (t *ISOTime) UnmarshalJSON(data []byte) error { | |||
// Ignore null, like in the main JSON package | |||
if string(data) == "null" { | |||
return nil | |||
} | |||
isotime, err := time.Parse(`"`+iso8601+`"`, string(data)) | |||
*t = ISOTime(isotime) | |||
return err | |||
} | |||
// EncodeValues implements the query.Encoder interface | |||
func (t *ISOTime) EncodeValues(key string, v *url.Values) error { | |||
if t == nil || (time.Time(*t)).IsZero() { | |||
return nil | |||
} | |||
v.Add(key, t.String()) | |||
return nil | |||
} | |||
// String implements the Stringer interface | |||
func (t ISOTime) String() string { | |||
return time.Time(t).Format(iso8601) | |||
} | |||
// NotificationLevelValue represents a notification level. | |||
type NotificationLevelValue int | |||
// String implements the fmt.Stringer interface. | |||
func (l NotificationLevelValue) String() string { | |||
return notificationLevelNames[l] | |||
} | |||
// MarshalJSON implements the json.Marshaler interface. | |||
func (l NotificationLevelValue) MarshalJSON() ([]byte, error) { | |||
return json.Marshal(l.String()) | |||
} | |||
// UnmarshalJSON implements the json.Unmarshaler interface. | |||
func (l *NotificationLevelValue) UnmarshalJSON(data []byte) error { | |||
var raw interface{} | |||
if err := json.Unmarshal(data, &raw); err != nil { | |||
return err | |||
} | |||
switch raw := raw.(type) { | |||
case float64: | |||
*l = NotificationLevelValue(raw) | |||
case string: | |||
*l = notificationLevelTypes[raw] | |||
case nil: | |||
// No action needed. | |||
default: | |||
return fmt.Errorf("json: cannot unmarshal %T into Go value of type %T", raw, *l) | |||
} | |||
return nil | |||
} | |||
// List of valid notification levels. | |||
const ( | |||
DisabledNotificationLevel NotificationLevelValue = iota | |||
ParticipatingNotificationLevel | |||
WatchNotificationLevel | |||
GlobalNotificationLevel | |||
MentionNotificationLevel | |||
CustomNotificationLevel | |||
) | |||
var notificationLevelNames = [...]string{ | |||
"disabled", | |||
"participating", | |||
"watch", | |||
"global", | |||
"mention", | |||
"custom", | |||
} | |||
var notificationLevelTypes = map[string]NotificationLevelValue{ | |||
"disabled": DisabledNotificationLevel, | |||
"participating": ParticipatingNotificationLevel, | |||
"watch": WatchNotificationLevel, | |||
"global": GlobalNotificationLevel, | |||
"mention": MentionNotificationLevel, | |||
"custom": CustomNotificationLevel, | |||
} | |||
// NotificationLevel is a helper routine that allocates a new NotificationLevelValue | |||
// to store v and returns a pointer to it. | |||
func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue { | |||
p := new(NotificationLevelValue) | |||
*p = v | |||
return p | |||
} | |||
// VisibilityValue represents a visibility level within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
type VisibilityValue string | |||
// List of available visibility levels. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
const ( | |||
PrivateVisibility VisibilityValue = "private" | |||
InternalVisibility VisibilityValue = "internal" | |||
PublicVisibility VisibilityValue = "public" | |||
) | |||
// Visibility is a helper routine that allocates a new VisibilityValue | |||
// to store v and returns a pointer to it. | |||
func Visibility(v VisibilityValue) *VisibilityValue { | |||
p := new(VisibilityValue) | |||
*p = v | |||
return p | |||
} | |||
// ProjectCreationLevelValue represents a project creation level within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
type ProjectCreationLevelValue string | |||
// List of available project creation levels. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
const ( | |||
NoOneProjectCreation ProjectCreationLevelValue = "noone" | |||
MaintainerProjectCreation ProjectCreationLevelValue = "maintainer" | |||
DeveloperProjectCreation ProjectCreationLevelValue = "developer" | |||
) | |||
// ProjectCreationLevel is a helper routine that allocates a new ProjectCreationLevelValue | |||
// to store v and returns a pointer to it. | |||
func ProjectCreationLevel(v ProjectCreationLevelValue) *ProjectCreationLevelValue { | |||
p := new(ProjectCreationLevelValue) | |||
*p = v | |||
return p | |||
} | |||
// SubGroupCreationLevelValue represents a sub group creation level within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
type SubGroupCreationLevelValue string | |||
// List of available sub group creation levels. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
const ( | |||
OwnerSubGroupCreationLevelValue SubGroupCreationLevelValue = "owner" | |||
MaintainerSubGroupCreationLevelValue SubGroupCreationLevelValue = "maintainer" | |||
) | |||
// SubGroupCreationLevel is a helper routine that allocates a new SubGroupCreationLevelValue | |||
// to store v and returns a pointer to it. | |||
func SubGroupCreationLevel(v SubGroupCreationLevelValue) *SubGroupCreationLevelValue { | |||
p := new(SubGroupCreationLevelValue) | |||
*p = v | |||
return p | |||
} | |||
// VariableTypeValue represents a variable type within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
type VariableTypeValue string | |||
// List of available variable types. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/ | |||
const ( | |||
EnvVariableType VariableTypeValue = "env_var" | |||
FileVariableType VariableTypeValue = "file" | |||
) | |||
// VariableType is a helper routine that allocates a new VariableTypeValue | |||
// to store v and returns a pointer to it. | |||
func VariableType(v VariableTypeValue) *VariableTypeValue { | |||
p := new(VariableTypeValue) | |||
*p = v | |||
return p | |||
} | |||
// MergeMethodValue represents a project merge type within GitLab. | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method | |||
type MergeMethodValue string | |||
// List of available merge type | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method | |||
const ( | |||
NoFastForwardMerge MergeMethodValue = "merge" | |||
FastForwardMerge MergeMethodValue = "ff" | |||
RebaseMerge MergeMethodValue = "rebase_merge" | |||
) | |||
// MergeMethod is a helper routine that allocates a new MergeMethod | |||
// to sotre v and returns a pointer to it. | |||
func MergeMethod(v MergeMethodValue) *MergeMethodValue { | |||
p := new(MergeMethodValue) | |||
*p = v | |||
return p | |||
} | |||
// EventTypeValue represents actions type for contribution events | |||
type EventTypeValue string | |||
// List of available action type | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#action-types | |||
const ( | |||
CreatedEventType EventTypeValue = "created" | |||
UpdatedEventType EventTypeValue = "updated" | |||
ClosedEventType EventTypeValue = "closed" | |||
ReopenedEventType EventTypeValue = "reopened" | |||
PushedEventType EventTypeValue = "pushed" | |||
CommentedEventType EventTypeValue = "commented" | |||
MergedEventType EventTypeValue = "merged" | |||
JoinedEventType EventTypeValue = "joined" | |||
LeftEventType EventTypeValue = "left" | |||
DestroyedEventType EventTypeValue = "destroyed" | |||
ExpiredEventType EventTypeValue = "expired" | |||
) | |||
// EventTargetTypeValue represents actions type value for contribution events | |||
type EventTargetTypeValue string | |||
// List of available action type | |||
// | |||
// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#target-types | |||
const ( | |||
IssueEventTargetType EventTargetTypeValue = "issue" | |||
MilestoneEventTargetType EventTargetTypeValue = "milestone" | |||
MergeRequestEventTargetType EventTargetTypeValue = "merge_request" | |||
NoteEventTargetType EventTargetTypeValue = "note" | |||
ProjectEventTargetType EventTargetTypeValue = "project" | |||
SnippetEventTargetType EventTargetTypeValue = "snippet" | |||
UserEventTargetType EventTargetTypeValue = "user" | |||
) | |||
// Bool is a helper routine that allocates a new bool value | |||
// to store v and returns a pointer to it. | |||
func Bool(v bool) *bool { | |||
p := new(bool) | |||
*p = v | |||
return p | |||
} | |||
// Int is a helper routine that allocates a new int32 value | |||
// to store v and returns a pointer to it, but unlike Int32 | |||
// its argument value is an int. | |||
func Int(v int) *int { | |||
p := new(int) | |||
*p = v | |||
return p | |||
} | |||
// String is a helper routine that allocates a new string value | |||
// to store v and returns a pointer to it. | |||
func String(v string) *string { | |||
p := new(string) | |||
*p = v | |||
return p | |||
} | |||
// Time is a helper routine that allocates a new time.Time value | |||
// to store v and returns a pointer to it. | |||
func Time(v time.Time) *time.Time { | |||
p := new(time.Time) | |||
*p = v | |||
return p | |||
} | |||
// BoolValue is a boolean value with advanced json unmarshaling features. | |||
type BoolValue bool | |||
// UnmarshalJSON allows 1, 0, "true", and "false" to be considered as boolean values | |||
// Needed for: | |||
// https://gitlab.com/gitlab-org/gitlab-ce/issues/50122 | |||
// https://gitlab.com/gitlab-org/gitlab/-/issues/233941 | |||
// https://github.com/gitlabhq/terraform-provider-gitlab/issues/348 | |||
func (t *BoolValue) UnmarshalJSON(b []byte) error { | |||
switch string(b) { | |||
case `"1"`: | |||
*t = true | |||
return nil | |||
case `"0"`: | |||
*t = false | |||
return nil | |||
case `"true"`: | |||
*t = true | |||
return nil | |||
case `"false"`: | |||
*t = false | |||
return nil | |||
default: | |||
var v bool | |||
err := json.Unmarshal(b, &v) | |||
*t = BoolValue(v) | |||
return err | |||
} | |||
} |
@@ -112,6 +112,7 @@ type ListUsersOptions struct { | |||
CreatedAfter *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"` | |||
OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` | |||
Sort *string `url:"sort,omitempty" json:"sort,omitempty"` | |||
External *bool `url:"external,omitempty" json:"external,omitempty"` | |||
WithCustomAttributes *bool `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"` | |||
} | |||
@@ -829,6 +830,7 @@ type UserActivity struct { | |||
// GitLap API docs: | |||
// https://docs.gitlab.com/ce/api/users.html#get-user-activities-admin-only | |||
type GetUserActivitiesOptions struct { | |||
ListOptions | |||
From *ISOTime `url:"from,omitempty" json:"from,omitempty"` | |||
} | |||
@@ -53,10 +53,9 @@ func Every(interval time.Duration) Limit { | |||
// | |||
// The methods AllowN, ReserveN, and WaitN consume n tokens. | |||
type Limiter struct { | |||
limit Limit | |||
burst int | |||
mu sync.Mutex | |||
limit Limit | |||
burst int | |||
tokens float64 | |||
// last is the last time the limiter's tokens field was updated | |||
last time.Time | |||
@@ -76,6 +75,8 @@ func (lim *Limiter) Limit() Limit { | |||
// Burst values allow more events to happen at once. | |||
// A zero Burst allows no events, unless limit == Inf. | |||
func (lim *Limiter) Burst() int { | |||
lim.mu.Lock() | |||
defer lim.mu.Unlock() | |||
return lim.burst | |||
} | |||
@@ -229,7 +230,7 @@ func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) { | |||
lim.mu.Unlock() | |||
if n > burst && limit != Inf { | |||
return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, lim.burst) | |||
return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, burst) | |||
} | |||
// Check if ctx is already cancelled | |||
select { | |||
@@ -359,6 +360,7 @@ func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duratio | |||
// advance calculates and returns an updated state for lim resulting from the passage of time. | |||
// lim is not changed. | |||
// advance requires that lim.mu is held. | |||
func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) { | |||
last := lim.last | |||
if now.Before(last) { |
@@ -198,8 +198,12 @@ func TypeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Ty | |||
X: ast.NewIdent(pkgName), | |||
Sel: ast.NewIdent(t.Obj().Name()), | |||
} | |||
case *types.Struct: | |||
return ast.NewIdent(t.String()) | |||
case *types.Interface: | |||
return ast.NewIdent(t.String()) | |||
default: | |||
return nil // TODO: anonymous structs, but who does that | |||
return nil | |||
} | |||
} | |||
@@ -693,8 +693,8 @@ func candidateImportName(pkg *pkg) string { | |||
return "" | |||
} | |||
// GetAllCandidates gets all of the packages starting with prefix that can be | |||
// imported by filename, sorted by import path. | |||
// GetAllCandidates calls wrapped for each package whose name starts with | |||
// searchPrefix, and can be imported from filename with the package name filePkg. | |||
func GetAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { | |||
callback := &scanCallback{ | |||
rootFound: func(gopathwalk.Root) bool { | |||
@@ -728,6 +728,35 @@ func GetAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix | |||
return getCandidatePkgs(ctx, callback, filename, filePkg, env) | |||
} | |||
// GetImportPaths calls wrapped for each package whose import path starts with | |||
// searchPrefix, and can be imported from filename with the package name filePkg. | |||
func GetImportPaths(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { | |||
callback := &scanCallback{ | |||
rootFound: func(gopathwalk.Root) bool { | |||
return true | |||
}, | |||
dirFound: func(pkg *pkg) bool { | |||
if !canUse(filename, pkg.dir) { | |||
return false | |||
} | |||
return strings.HasPrefix(pkg.importPathShort, searchPrefix) | |||
}, | |||
packageNameLoaded: func(pkg *pkg) bool { | |||
wrapped(ImportFix{ | |||
StmtInfo: ImportInfo{ | |||
ImportPath: pkg.importPathShort, | |||
Name: candidateImportName(pkg), | |||
}, | |||
IdentName: pkg.packageName, | |||
FixType: AddImport, | |||
Relevance: pkg.relevance, | |||
}) | |||
return false | |||
}, | |||
} | |||
return getCandidatePkgs(ctx, callback, filename, filePkg, env) | |||
} | |||
// A PackageExport is a package and its exports. | |||
type PackageExport struct { | |||
Fix *ImportFix |
@@ -420,7 +420,7 @@ github.com/gorilla/securecookie | |||
github.com/gorilla/sessions | |||
# github.com/hashicorp/go-cleanhttp v0.5.1 | |||
github.com/hashicorp/go-cleanhttp | |||
# github.com/hashicorp/go-retryablehttp v0.6.6 | |||
# github.com/hashicorp/go-retryablehttp v0.6.7 | |||
## explicit | |||
github.com/hashicorp/go-retryablehttp | |||
# github.com/hashicorp/go-version v0.0.0-00010101000000-000000000000 => github.com/6543/go-version v1.2.3 | |||
@@ -728,7 +728,7 @@ github.com/unknwon/paginater | |||
github.com/urfave/cli | |||
# github.com/willf/bitset v1.1.10 | |||
github.com/willf/bitset | |||
# github.com/xanzy/go-gitlab v0.31.0 | |||
# github.com/xanzy/go-gitlab v0.37.0 | |||
## explicit | |||
github.com/xanzy/go-gitlab | |||
# github.com/xanzy/ssh-agent v0.2.1 | |||
@@ -846,10 +846,10 @@ golang.org/x/text/transform | |||
golang.org/x/text/unicode/bidi | |||
golang.org/x/text/unicode/norm | |||
golang.org/x/text/width | |||
# golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 | |||
# golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e | |||
## explicit | |||
golang.org/x/time/rate | |||
# golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d | |||
# golang.org/x/tools v0.0.0-20200825202427-b303f430e36d | |||
## explicit | |||
golang.org/x/tools/cover | |||
golang.org/x/tools/go/analysis |