]> source.dussan.org Git - gitea.git/commitdiff
[Vendor] update gitea-sdk v0.14.0 (#15103)
author6543 <6543@obermui.de>
Mon, 22 Mar 2021 17:03:18 +0000 (18:03 +0100)
committerGitHub <noreply@github.com>
Mon, 22 Mar 2021 17:03:18 +0000 (18:03 +0100)
* upgraded code.gitea.io/sdk/gitea v0.13.2 => v0.14.0

* rm workaround

55 files changed:
go.mod
go.sum
modules/migrations/gitea_downloader.go
vendor/code.gitea.io/sdk/gitea/admin_cron.go
vendor/code.gitea.io/sdk/gitea/admin_org.go
vendor/code.gitea.io/sdk/gitea/admin_repo.go
vendor/code.gitea.io/sdk/gitea/admin_user.go
vendor/code.gitea.io/sdk/gitea/attachment.go
vendor/code.gitea.io/sdk/gitea/client.go
vendor/code.gitea.io/sdk/gitea/fork.go
vendor/code.gitea.io/sdk/gitea/git_blob.go
vendor/code.gitea.io/sdk/gitea/git_hook.go
vendor/code.gitea.io/sdk/gitea/go.mod
vendor/code.gitea.io/sdk/gitea/go.sum
vendor/code.gitea.io/sdk/gitea/helper.go [new file with mode: 0644]
vendor/code.gitea.io/sdk/gitea/hook.go
vendor/code.gitea.io/sdk/gitea/issue.go
vendor/code.gitea.io/sdk/gitea/issue_comment.go
vendor/code.gitea.io/sdk/gitea/issue_label.go
vendor/code.gitea.io/sdk/gitea/issue_milestone.go
vendor/code.gitea.io/sdk/gitea/issue_reaction.go
vendor/code.gitea.io/sdk/gitea/issue_stopwatch.go
vendor/code.gitea.io/sdk/gitea/issue_subscription.go
vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go
vendor/code.gitea.io/sdk/gitea/notifications.go
vendor/code.gitea.io/sdk/gitea/org.go
vendor/code.gitea.io/sdk/gitea/org_member.go
vendor/code.gitea.io/sdk/gitea/org_team.go
vendor/code.gitea.io/sdk/gitea/pull.go
vendor/code.gitea.io/sdk/gitea/pull_review.go
vendor/code.gitea.io/sdk/gitea/release.go
vendor/code.gitea.io/sdk/gitea/repo.go
vendor/code.gitea.io/sdk/gitea/repo_branch.go
vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go
vendor/code.gitea.io/sdk/gitea/repo_collaborator.go
vendor/code.gitea.io/sdk/gitea/repo_commit.go
vendor/code.gitea.io/sdk/gitea/repo_file.go
vendor/code.gitea.io/sdk/gitea/repo_key.go
vendor/code.gitea.io/sdk/gitea/repo_migrate.go
vendor/code.gitea.io/sdk/gitea/repo_refs.go
vendor/code.gitea.io/sdk/gitea/repo_stars.go [new file with mode: 0644]
vendor/code.gitea.io/sdk/gitea/repo_tag.go
vendor/code.gitea.io/sdk/gitea/repo_topics.go
vendor/code.gitea.io/sdk/gitea/repo_transfer.go
vendor/code.gitea.io/sdk/gitea/repo_tree.go
vendor/code.gitea.io/sdk/gitea/repo_watch.go
vendor/code.gitea.io/sdk/gitea/settings.go
vendor/code.gitea.io/sdk/gitea/status.go
vendor/code.gitea.io/sdk/gitea/user.go
vendor/code.gitea.io/sdk/gitea/user_app.go
vendor/code.gitea.io/sdk/gitea/user_follow.go
vendor/code.gitea.io/sdk/gitea/user_gpgkey.go
vendor/code.gitea.io/sdk/gitea/user_key.go
vendor/code.gitea.io/sdk/gitea/version.go
vendor/modules.txt

diff --git a/go.mod b/go.mod
index 33586e67feec59f54629c995cd98a1920dcc89aa..6311d551c066d8914a6dc360adfb3bc48fdeb7e3 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.14
 require (
        cloud.google.com/go v0.78.0 // indirect
        code.gitea.io/gitea-vet v0.2.1
-       code.gitea.io/sdk/gitea v0.13.2
+       code.gitea.io/sdk/gitea v0.14.0
        gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7
        gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e
        gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e
diff --git a/go.sum b/go.sum
index cf7148fbbeadc83b9881a70509541360d1dcc599..1170db6631c941e5ed0167634bca648fd3fbcfab 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
 code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
-code.gitea.io/sdk/gitea v0.13.2 h1:wAnT/J7Z62q3fJXbgnecoaOBh8CM1Qq0/DakWxiv4yA=
-code.gitea.io/sdk/gitea v0.13.2/go.mod h1:lee2y8LeV3kQb2iK+hHlMqoadL4bp27QOkOV/hawLKg=
+code.gitea.io/sdk/gitea v0.14.0 h1:m4J352I3p9+bmJUfS+g0odeQzBY/5OXP91Gv6D4fnJ0=
+code.gitea.io/sdk/gitea v0.14.0/go.mod h1:89WiyOX1KEcvjP66sRHdu0RafojGo60bT9UqW17VbWs=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7 h1:xCVJPY823C8RWpgMabTw2kOglDrg0iS3GcQU6wdwHkU=
 gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7/go.mod h1:AyfTrwtfYN54R/HmVvMYPnSTenH5bVoyh8x6tBluxEA=
index 87ec9c102a1e7dabf7612b3a6b9b53fcb3e59f61..40820ae3759ced5c1b6a2b52a1a8273a08526711 100644 (file)
@@ -525,9 +525,6 @@ func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullReques
                                headRepoName = pr.Head.Repository.Name
                                headCloneURL = pr.Head.Repository.CloneURL
                        }
-                       if err := fixPullHeadSha(g.client, pr); err != nil {
-                               return nil, false, fmt.Errorf("error while resolving head git ref: %s for pull #%d. Error: %v", pr.Head.Ref, pr.Index, err)
-                       }
                        headSHA = pr.Head.Sha
                        headRef = pr.Head.Ref
                }
@@ -679,22 +676,3 @@ func (g *GiteaDownloader) GetReviews(index int64) ([]*base.Review, error) {
        }
        return allReviews, nil
 }
-
-// fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675
-// When no head sha is available, this is because the branch got deleted in the base repo.
-// pr.Head.Ref points in this case not to the head repo branch name, but the base repo ref,
-// which stays available to resolve the commit sha.
-func fixPullHeadSha(client *gitea_sdk.Client, pr *gitea_sdk.PullRequest) error {
-       owner := pr.Base.Repository.Owner.UserName
-       repo := pr.Base.Repository.Name
-       if pr.Head != nil && pr.Head.Sha == "" {
-               refs, _, err := client.GetRepoRefs(owner, repo, pr.Head.Ref)
-               if err != nil {
-                       return err
-               } else if len(refs) == 0 {
-                       return fmt.Errorf("unable to resolve PR ref '%s'", pr.Head.Ref)
-               }
-               pr.Head.Sha = refs[0].Object.SHA
-       }
-       return nil
-}
index 99006b696bf28133595369e691f3d6f2f8012944..84316da2b141a493cd738b9d7f64480309d240a1 100644 (file)
@@ -39,6 +39,9 @@ func (c *Client) RunCronTasks(task string) (*Response, error) {
        if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
                return nil, err
        }
+       if err := escapeValidatePathSegments(&task); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil)
        return resp, err
 }
index e572680fc0a7fbc87d23a55a9502406e9c2ad631..26bf81fb4d9087978875c8f557237fd80a67d007 100644 (file)
@@ -26,6 +26,9 @@ func (c *Client) AdminListOrgs(opt AdminListOrgsOptions) ([]*Organization, *Resp
 
 // AdminCreateOrg create an organization
 func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
index cbd97881793a6568649ed64c7a8f7e73ad289314..8666690cde763762e23a0afe13fef4cb53024792 100644 (file)
@@ -12,6 +12,9 @@ import (
 
 // AdminCreateRepo create a repo
 func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
index 5f4917896e49118299a832f57b2f9da6a2c2cf03..161dc6058dd2de27e1a95f56e699bf91da8ca47d 100644 (file)
@@ -63,25 +63,28 @@ func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error)
 
 // EditUserOption edit user options
 type EditUserOption struct {
-       SourceID                int64  `json:"source_id"`
-       LoginName               string `json:"login_name"`
-       FullName                string `json:"full_name"`
-       Email                   string `json:"email"`
-       Password                string `json:"password"`
-       MustChangePassword      *bool  `json:"must_change_password"`
-       Website                 string `json:"website"`
-       Location                string `json:"location"`
-       Active                  *bool  `json:"active"`
-       Admin                   *bool  `json:"admin"`
-       AllowGitHook            *bool  `json:"allow_git_hook"`
-       AllowImportLocal        *bool  `json:"allow_import_local"`
-       MaxRepoCreation         *int   `json:"max_repo_creation"`
-       ProhibitLogin           *bool  `json:"prohibit_login"`
-       AllowCreateOrganization *bool  `json:"allow_create_organization"`
+       SourceID                int64   `json:"source_id"`
+       LoginName               string  `json:"login_name"`
+       Email                   *string `json:"email"`
+       FullName                *string `json:"full_name"`
+       Password                string  `json:"password"`
+       MustChangePassword      *bool   `json:"must_change_password"`
+       Website                 *string `json:"website"`
+       Location                *string `json:"location"`
+       Active                  *bool   `json:"active"`
+       Admin                   *bool   `json:"admin"`
+       AllowGitHook            *bool   `json:"allow_git_hook"`
+       AllowImportLocal        *bool   `json:"allow_import_local"`
+       MaxRepoCreation         *int    `json:"max_repo_creation"`
+       ProhibitLogin           *bool   `json:"prohibit_login"`
+       AllowCreateOrganization *bool   `json:"allow_create_organization"`
 }
 
 // AdminEditUser modify user informations
 func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, err
@@ -92,12 +95,18 @@ func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, erro
 
 // AdminDeleteUser delete one user according name
 func (c *Client) AdminDeleteUser(user string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s", user), nil, nil)
        return resp, err
 }
 
 // AdminCreateUserPublicKey adds a public key for the user
 func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*PublicKey, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
@@ -109,6 +118,9 @@ func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*Pu
 
 // AdminDeleteUserPublicKey deletes a user's public key
 func (c *Client) AdminDeleteUserPublicKey(user string, keyID int) (*Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s/keys/%d", user, keyID), nil, nil)
        return resp, err
 }
index 6f67ae3de5a14b2183d0fd46f496b49af33d53b4..24c57baf4b044bc384eb2ac2d91a89457b4b1b29 100644 (file)
@@ -31,6 +31,9 @@ type ListReleaseAttachmentsOptions struct {
 
 // ListReleaseAttachments list release's attachments
 func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt ListReleaseAttachmentsOptions) ([]*Attachment, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        attachments := make([]*Attachment, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET",
@@ -41,6 +44,9 @@ func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt Li
 
 // GetReleaseAttachment returns the requested attachment
 func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64) (*Attachment, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        a := new(Attachment)
        resp, err := c.getParsedResponse("GET",
                fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id),
@@ -50,6 +56,9 @@ func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64
 
 // CreateReleaseAttachment creates an attachment for the given release
 func (c *Client) CreateReleaseAttachment(user, repo string, release int64, file io.Reader, filename string) (*Attachment, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        // Write file to body
        body := new(bytes.Buffer)
        writer := multipart.NewWriter(body)
@@ -80,6 +89,9 @@ type EditAttachmentOptions struct {
 
 // EditReleaseAttachment updates the given attachment with the given options
 func (c *Client) EditReleaseAttachment(user, repo string, release int64, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&form)
        if err != nil {
                return nil, nil, err
@@ -91,6 +103,9 @@ func (c *Client) EditReleaseAttachment(user, repo string, release int64, attachm
 
 // DeleteReleaseAttachment deletes the given attachment including the uploaded file
 func (c *Client) DeleteReleaseAttachment(user, repo string, release int64, id int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), nil, nil)
        return resp, err
 }
index e08bc9edd8467e2204c56c9935d0643473a9a26e..9f857f85aebda4ae1b8ab6f44e55f8590391808c 100644 (file)
@@ -13,6 +13,7 @@ import (
        "io"
        "io/ioutil"
        "net/http"
+       "net/url"
        "strings"
        "sync"
 
@@ -23,22 +24,23 @@ var jsonHeader = http.Header{"content-type": []string{"application/json"}}
 
 // Version return the library version
 func Version() string {
-       return "0.13.0"
+       return "0.14.0"
 }
 
-// Client represents a Gitea API client.
+// Client represents a thread-safe Gitea API client.
 type Client struct {
-       url           string
-       accessToken   string
-       username      string
-       password      string
-       otp           string
-       sudo          string
-       debug         bool
-       client        *http.Client
-       ctx           context.Context
-       serverVersion *version.Version
-       versionLock   sync.RWMutex
+       url            string
+       accessToken    string
+       username       string
+       password       string
+       otp            string
+       sudo           string
+       debug          bool
+       client         *http.Client
+       ctx            context.Context
+       mutex          sync.RWMutex
+       serverVersion  *version.Version
+       getVersionOnce sync.Once
 }
 
 // Response represents the gitea response
@@ -47,6 +49,7 @@ type Response struct {
 }
 
 // NewClient initializes and returns a API client.
+// Usage of all gitea.Client methods is concurrency-safe.
 func NewClient(url string, options ...func(*Client)) (*Client, error) {
        client := &Client{
                url:    strings.TrimSuffix(url, "/"),
@@ -56,7 +59,7 @@ func NewClient(url string, options ...func(*Client)) (*Client, error) {
        for _, opt := range options {
                opt(client)
        }
-       if err := client.checkServerVersionGreaterThanOrEqual(version1_10_0); err != nil {
+       if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
                return nil, err
        }
        return client, nil
@@ -72,14 +75,23 @@ func NewClientWithHTTP(url string, httpClient *http.Client) *Client {
 // SetHTTPClient is an option for NewClient to set custom http client
 func SetHTTPClient(httpClient *http.Client) func(client *Client) {
        return func(client *Client) {
-               client.client = httpClient
+               client.SetHTTPClient(httpClient)
        }
 }
 
+// SetHTTPClient replaces default http.Client with user given one.
+func (c *Client) SetHTTPClient(client *http.Client) {
+       c.mutex.Lock()
+       c.client = client
+       c.mutex.Unlock()
+}
+
 // SetToken is an option for NewClient to set token
 func SetToken(token string) func(client *Client) {
        return func(client *Client) {
+               client.mutex.Lock()
                client.accessToken = token
+               client.mutex.Unlock()
        }
 }
 
@@ -92,7 +104,9 @@ func SetBasicAuth(username, password string) func(client *Client) {
 
 // SetBasicAuth sets username and password
 func (c *Client) SetBasicAuth(username, password string) {
+       c.mutex.Lock()
        c.username, c.password = username, password
+       c.mutex.Unlock()
 }
 
 // SetOTP is an option for NewClient to set OTP for 2FA
@@ -104,7 +118,9 @@ func SetOTP(otp string) func(client *Client) {
 
 // SetOTP sets OTP for 2FA
 func (c *Client) SetOTP(otp string) {
+       c.mutex.Lock()
        c.otp = otp
+       c.mutex.Unlock()
 }
 
 // SetContext is an option for NewClient to set context
@@ -116,12 +132,9 @@ func SetContext(ctx context.Context) func(client *Client) {
 
 // SetContext set context witch is used for http requests
 func (c *Client) SetContext(ctx context.Context) {
+       c.mutex.Lock()
        c.ctx = ctx
-}
-
-// SetHTTPClient replaces default http.Client with user given one.
-func (c *Client) SetHTTPClient(client *http.Client) {
-       c.client = client
+       c.mutex.Unlock()
 }
 
 // SetSudo is an option for NewClient to set sudo header
@@ -133,43 +146,57 @@ func SetSudo(sudo string) func(client *Client) {
 
 // SetSudo sets username to impersonate.
 func (c *Client) SetSudo(sudo string) {
+       c.mutex.Lock()
        c.sudo = sudo
+       c.mutex.Unlock()
 }
 
 // SetDebugMode is an option for NewClient to enable debug mode
 func SetDebugMode() func(client *Client) {
        return func(client *Client) {
+               client.mutex.Lock()
                client.debug = true
+               client.mutex.Unlock()
        }
 }
 
 func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) {
-       if c.debug {
+       c.mutex.RLock()
+       debug := c.debug
+       if debug {
                fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body)
        }
        req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body)
+
+       client := c.client // client ref can change from this point on so safe it
+       c.mutex.RUnlock()
+
        if err != nil {
                return nil, nil, err
        }
-       resp, err := c.client.Do(req)
+
+       resp, err := client.Do(req)
        if err != nil {
                return nil, nil, err
        }
 
        defer resp.Body.Close()
        data, err := ioutil.ReadAll(resp.Body)
-       if c.debug {
+       if debug {
                fmt.Printf("Response: %v\n\n", resp)
        }
        return data, &Response{resp}, nil
 }
 
 func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
-       if c.debug {
+       c.mutex.RLock()
+       debug := c.debug
+       if debug {
                fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, body)
        }
        req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body)
        if err != nil {
+               c.mutex.RUnlock()
                return nil, err
        }
        if len(c.accessToken) != 0 {
@@ -184,51 +211,83 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
        if len(c.sudo) != 0 {
                req.Header.Set("Sudo", c.sudo)
        }
+
+       client := c.client // client ref can change from this point on so safe it
+       c.mutex.RUnlock()
+
        for k, v := range header {
                req.Header[k] = v
        }
 
-       resp, err := c.client.Do(req)
+       resp, err := client.Do(req)
        if err != nil {
                return nil, err
        }
-       if c.debug {
+       if debug {
                fmt.Printf("Response: %v\n\n", resp)
        }
        return &Response{resp}, nil
 }
 
-func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) {
-       resp, err := c.doRequest(method, path, header, body)
-       if err != nil {
-               return nil, nil, err
+// Converts a response for a HTTP status code indicating an error condition
+// (non-2XX) to a well-known error value and response body. For non-problematic
+// (2XX) status codes nil will be returned. Note that on a non-2XX response, the
+// response body stream will have been read and, hence, is closed on return.
+func statusCodeToErr(resp *Response) (body []byte, err error) {
+       // no error
+       if resp.StatusCode/100 == 2 {
+               return nil, nil
        }
-       defer resp.Body.Close()
 
+       //
+       // error: body will be read for details
+       //
+       defer resp.Body.Close()
        data, err := ioutil.ReadAll(resp.Body)
        if err != nil {
-               return nil, resp, err
+               return nil, fmt.Errorf("body read on HTTP error %d: %v", resp.StatusCode, err)
        }
 
        switch resp.StatusCode {
        case 403:
-               return data, resp, errors.New("403 Forbidden")
+               return data, errors.New("403 Forbidden")
        case 404:
-               return data, resp, errors.New("404 Not Found")
+               return data, errors.New("404 Not Found")
        case 409:
-               return data, resp, errors.New("409 Conflict")
+               return data, errors.New("409 Conflict")
        case 422:
-               return data, resp, fmt.Errorf("422 Unprocessable Entity: %s", string(data))
+               return data, fmt.Errorf("422 Unprocessable Entity: %s", string(data))
        }
 
-       if resp.StatusCode/100 != 2 {
-               errMap := make(map[string]interface{})
-               if err = json.Unmarshal(data, &errMap); err != nil {
-                       // when the JSON can't be parsed, data was probably empty or a plain string,
-                       // so we try to return a helpful error anyway
-                       return data, resp, fmt.Errorf("Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body", resp.StatusCode, path, method, header, string(data))
-               }
-               return data, resp, errors.New(errMap["message"].(string))
+       path := resp.Request.URL.Path
+       method := resp.Request.Method
+       header := resp.Request.Header
+       errMap := make(map[string]interface{})
+       if err = json.Unmarshal(data, &errMap); err != nil {
+               // when the JSON can't be parsed, data was probably empty or a
+               // plain string, so we try to return a helpful error anyway
+               return data, fmt.Errorf("Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body", resp.StatusCode, path, method, header, string(data))
+       }
+       return data, errors.New(errMap["message"].(string))
+}
+
+func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) {
+       resp, err := c.doRequest(method, path, header, body)
+       if err != nil {
+               return nil, nil, err
+       }
+       defer resp.Body.Close()
+
+       // check for errors
+       data, err := statusCodeToErr(resp)
+       if err != nil {
+               return data, resp, err
+       }
+
+       // success (2XX), read body
+       data, err = ioutil.ReadAll(resp.Body)
+       if err != nil {
+               return nil, resp, err
        }
 
        return data, resp, nil
@@ -251,3 +310,24 @@ func (c *Client) getStatusCode(method, path string, header http.Header, body io.
 
        return resp.StatusCode, resp, nil
 }
+
+// pathEscapeSegments escapes segments of a path while not escaping forward slash
+func pathEscapeSegments(path string) string {
+       slice := strings.Split(path, "/")
+       for index := range slice {
+               slice[index] = url.PathEscape(slice[index])
+       }
+       escapedPath := strings.Join(slice, "/")
+       return escapedPath
+}
+
+// escapeValidatePathSegments is a help function to validate and encode url path segments
+func escapeValidatePathSegments(seg ...*string) error {
+       for i := range seg {
+               if seg[i] == nil || len(*seg[i]) == 0 {
+                       return fmt.Errorf("path segment [%d] is empty", i)
+               }
+               *seg[i] = url.PathEscape(*seg[i])
+       }
+       return nil
+}
index a5197125da6794ecd30251a7e8df75c4f3aef3e9..c8e5323bc0ec29e5017323b982cd00b6ee58f3ed 100644 (file)
@@ -17,6 +17,9 @@ type ListForksOptions struct {
 
 // ListForks list a repository's forks
 func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        forks := make([]*Repository, opt.PageSize)
        resp, err := c.getParsedResponse("GET",
@@ -33,6 +36,9 @@ type CreateForkOption struct {
 
 // CreateFork create a fork of a repository
 func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(form)
        if err != nil {
                return nil, nil, err
index 262fd92dfd2be4bc2c4307734d7ed46d6c53aae2..7668672dc07e9584495a86b475dba5ad76078f8e 100644 (file)
@@ -19,6 +19,9 @@ type GitBlobResponse struct {
 
 // GetBlob get the blob of a repository file
 func (c *Client) GetBlob(user, repo, sha string) (*GitBlobResponse, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil {
+               return nil, nil, err
+       }
        blob := new(GitBlobResponse)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/blobs/%s", user, repo, sha), nil, nil, blob)
        return blob, resp, err
index 520ce538ed9d087fd9ddd9c519a42c84dbcc6c23..d8fbf71bd964052dddae752ab2a5448b3dc0c62e 100644 (file)
@@ -24,6 +24,9 @@ type ListRepoGitHooksOptions struct {
 
 // ListRepoGitHooks list all the Git hooks of one repository
 func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions) ([]*GitHook, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        hooks := make([]*GitHook, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
@@ -32,6 +35,9 @@ func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions
 
 // GetRepoGitHook get a Git hook of a repository
 func (c *Client) GetRepoGitHook(user, repo, id string) (*GitHook, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
+               return nil, nil, err
+       }
        h := new(GitHook)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil, h)
        return h, resp, err
@@ -44,6 +50,9 @@ type EditGitHookOption struct {
 
 // EditRepoGitHook modify one Git hook of a repository
 func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
+               return nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, err
@@ -54,6 +63,9 @@ func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (
 
 // DeleteRepoGitHook delete one Git hook from a repository
 func (c *Client) DeleteRepoGitHook(user, repo, id string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil)
        return resp, err
 }
index fe2a46ae0660782295d71a09a556a6f84e1a10a6..ac2a262ab349cb8b809d8fbd7fe8f4a30af901d5 100644 (file)
@@ -1,6 +1,6 @@
 module code.gitea.io/sdk/gitea
 
-go 1.12
+go 1.13
 
 require (
        github.com/hashicorp/go-version v1.2.1
index e210c1c42850bb809d682875386c71cf7eacf541..82997d0cceaaed6bb202efd7087d4c05533d1c02 100644 (file)
@@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
diff --git a/vendor/code.gitea.io/sdk/gitea/helper.go b/vendor/code.gitea.io/sdk/gitea/helper.go
new file mode 100644 (file)
index 0000000..ff8038b
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package gitea
+
+// OptionalBool convert a bool to a bool reference
+func OptionalBool(v bool) *bool {
+       return &v
+}
+
+// OptionalString convert a string to a string reference
+func OptionalString(v string) *string {
+       return &v
+}
+
+// OptionalInt64 convert a int64 to a int64 reference
+func OptionalInt64(v int64) *int64 {
+       return &v
+}
index 3a6be65ef7661bfac5d0341bcbfbb6313860fa75..af4154e2abb8691cb432ef197259534457b6db3c 100644 (file)
@@ -31,6 +31,9 @@ type ListHooksOptions struct {
 
 // ListOrgHooks list all the hooks of one organization
 func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        hooks := make([]*Hook, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks?%s", org, opt.getURLQuery().Encode()), nil, nil, &hooks)
@@ -39,6 +42,9 @@ func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Respo
 
 // ListRepoHooks list all the hooks of one repository
 func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        hooks := make([]*Hook, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
@@ -47,6 +53,9 @@ func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook
 
 // GetOrgHook get a hook of an organization
 func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, nil, err
+       }
        h := new(Hook)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h)
        return h, resp, err
@@ -54,6 +63,9 @@ func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) {
 
 // GetRepoHook get a hook of a repository
 func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        h := new(Hook)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h)
        return h, resp, err
@@ -78,6 +90,9 @@ func (opt CreateHookOption) Validate() error {
 
 // CreateOrgHook create one hook for an organization, with options
 func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -92,6 +107,9 @@ func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Respon
 
 // CreateRepoHook create one hook for a repository, with options
 func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
@@ -111,6 +129,9 @@ type EditHookOption struct {
 
 // EditOrgHook modify one hook of an organization, with hook id and options
 func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, err
@@ -121,6 +142,9 @@ func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Respons
 
 // EditRepoHook modify one hook of a repository, with hook id and options
 func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, err
@@ -131,12 +155,18 @@ func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (
 
 // DeleteOrgHook delete one hook from an organization, with hook id
 func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil)
        return resp, err
 }
 
 // DeleteRepoHook delete one hook from a repository, with hook id
 func (c *Client) DeleteRepoHook(user, repo string, id int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil)
        return resp, err
 }
index 7eef44711905f7646ac6acda5faa159862d5bc5f..688924634ba04e87d01e5638c954224dd0bd09ad 100644 (file)
@@ -32,16 +32,20 @@ type RepositoryMeta struct {
 type Issue struct {
        ID               int64      `json:"id"`
        URL              string     `json:"url"`
+       HTMLURL          string     `json:"html_url"`
        Index            int64      `json:"number"`
        Poster           *User      `json:"user"`
        OriginalAuthor   string     `json:"original_author"`
        OriginalAuthorID int64      `json:"original_author_id"`
        Title            string     `json:"title"`
        Body             string     `json:"body"`
+       Ref              string     `json:"ref"`
        Labels           []*Label   `json:"labels"`
        Milestone        *Milestone `json:"milestone"`
-       Assignee         *User      `json:"assignee"`
-       Assignees        []*User    `json:"assignees"`
+       // deprecated
+       // TODO: rm on sdk 0.15.0
+       Assignee  *User   `json:"assignee"`
+       Assignees []*User `json:"assignees"`
        // Whether the issue is open or closed
        State       StateType        `json:"state"`
        IsLocked    bool             `json:"is_locked"`
@@ -128,11 +132,17 @@ func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) {
                        }
                }
        }
+       for i := range issues {
+               c.issueBackwardsCompatibility(issues[i])
+       }
        return issues, resp, err
 }
 
 // ListRepoIssues returns all issues for a given repository
 func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        issues := make([]*Issue, 0, opt.PageSize)
 
@@ -146,16 +156,23 @@ func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Iss
                        }
                }
        }
+       for i := range issues {
+               c.issueBackwardsCompatibility(issues[i])
+       }
        return issues, resp, err
 }
 
 // GetIssue returns a single issue for a given repository
 func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        issue := new(Issue)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue)
        if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil && issue.Repository != nil {
                issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0]
        }
+       c.issueBackwardsCompatibility(issue)
        return issue, resp, err
 }
 
@@ -163,7 +180,9 @@ func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, e
 type CreateIssueOption struct {
        Title string `json:"title"`
        Body  string `json:"body"`
-       // username of assignee
+       Ref   string `json:"ref"`
+       // deprecated
+       // TODO: rm on sdk 0.15.0
        Assignee  string     `json:"assignee"`
        Assignees []string   `json:"assignees"`
        Deadline  *time.Time `json:"due_date"`
@@ -184,6 +203,9 @@ func (opt CreateIssueOption) Validate() error {
 
 // CreateIssue create a new issue for a given repository
 func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -194,18 +216,23 @@ func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue,
        issue := new(Issue)
        resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo),
                jsonHeader, bytes.NewReader(body), issue)
+       c.issueBackwardsCompatibility(issue)
        return issue, resp, err
 }
 
 // EditIssueOption options for editing an issue
 type EditIssueOption struct {
-       Title     string     `json:"title"`
-       Body      *string    `json:"body"`
-       Assignee  *string    `json:"assignee"`
-       Assignees []string   `json:"assignees"`
-       Milestone *int64     `json:"milestone"`
-       State     *StateType `json:"state"`
-       Deadline  *time.Time `json:"due_date"`
+       Title string  `json:"title"`
+       Body  *string `json:"body"`
+       Ref   *string `json:"ref"`
+       // deprecated
+       // TODO: rm on sdk 0.15.0
+       Assignee       *string    `json:"assignee"`
+       Assignees      []string   `json:"assignees"`
+       Milestone      *int64     `json:"milestone"`
+       State          *StateType `json:"state"`
+       Deadline       *time.Time `json:"due_date"`
+       RemoveDeadline *bool      `json:"unset_due_date"`
 }
 
 // Validate the EditIssueOption struct
@@ -218,6 +245,9 @@ func (opt EditIssueOption) Validate() error {
 
 // EditIssue modify an existing issue for a given repository
 func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) (*Issue, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -229,5 +259,14 @@ func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption)
        resp, err := c.getParsedResponse("PATCH",
                fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index),
                jsonHeader, bytes.NewReader(body), issue)
+       c.issueBackwardsCompatibility(issue)
        return issue, resp, err
 }
+
+func (c *Client) issueBackwardsCompatibility(issue *Issue) {
+       if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil {
+               c.mutex.RLock()
+               issue.HTMLURL = fmt.Sprintf("%s/%s/issues/%d", c.url, issue.Repository.FullName, issue.Index)
+               c.mutex.RUnlock()
+       }
+}
index 4eff850d13f98515561c111b1e2e7dcfc40ea11d..8131a6edc279c96e9c72143533d257681f96f061 100644 (file)
@@ -47,6 +47,9 @@ func (opt *ListIssueCommentOptions) QueryEncode() string {
 
 // ListIssueComments list comments on an issue.
 func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index))
        link.RawQuery = opt.QueryEncode()
@@ -57,6 +60,9 @@ func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssu
 
 // ListRepoIssueComments list comments for a given repo.
 func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo))
        link.RawQuery = opt.QueryEncode()
@@ -67,6 +73,9 @@ func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentO
 
 // GetIssueComment get a comment for a given repo by id.
 func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        comment := new(Comment)
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return comment, nil, err
@@ -90,6 +99,9 @@ func (opt CreateIssueCommentOption) Validate() error {
 
 // CreateIssueComment create comment on an issue.
 func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -117,6 +129,9 @@ func (opt EditIssueCommentOption) Validate() error {
 
 // EditIssueComment edits an issue comment.
 func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditIssueCommentOption) (*Comment, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -131,6 +146,9 @@ func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditI
 
 // DeleteIssueComment deletes an issue comment.
 func (c *Client) DeleteIssueComment(owner, repo string, commentID int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), nil, nil)
        return resp, err
 }
index b664dacd1f67c73c38a65294c4fd4ab881e2ed63..f343ee5ef6a529bfdc0ec70ae5728d81c5a7782b 100644 (file)
@@ -29,6 +29,9 @@ type ListLabelsOptions struct {
 
 // ListRepoLabels list labels of one repository
 func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*Label, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        labels := make([]*Label, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels?%s", owner, repo, opt.getURLQuery().Encode()), nil, nil, &labels)
@@ -37,6 +40,9 @@ func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*L
 
 // GetRepoLabel get one label of repository by repo it
 func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        label := new(Label)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label)
        return label, resp, err
@@ -67,6 +73,9 @@ func (opt CreateLabelOption) Validate() error {
 
 // CreateLabel create one label of repository
 func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -114,6 +123,9 @@ func (opt EditLabelOption) Validate() error {
 
 // EditLabel modify one label with options
 func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -128,12 +140,18 @@ func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*
 
 // DeleteLabel delete one label of repository by id
 func (c *Client) DeleteLabel(owner, repo string, id int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil)
        return resp, err
 }
 
 // GetIssueLabels get labels of one issue via issue id
 func (c *Client) GetIssueLabels(owner, repo string, index int64, opts ListLabelsOptions) ([]*Label, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        labels := make([]*Label, 0, 5)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/labels?%s", owner, repo, index, opts.getURLQuery().Encode()), nil, nil, &labels)
        return labels, resp, err
@@ -147,6 +165,9 @@ type IssueLabelsOption struct {
 
 // AddIssueLabels add one or more labels to one issue
 func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
@@ -158,6 +179,9 @@ func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabels
 
 // ReplaceIssueLabels replace old labels of issue with new labels
 func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
@@ -170,12 +194,18 @@ func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLa
 // DeleteIssueLabel delete one label of one issue by issue id and label id
 // TODO: maybe we need delete by label name and issue id
 func (c *Client) DeleteIssueLabel(owner, repo string, index, label int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, label), nil, nil)
        return resp, err
 }
 
 // ClearIssueLabels delete all the labels of one issue.
 func (c *Client) ClearIssueLabels(owner, repo string, index int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil)
        return resp, err
 }
index 236c2367b57aa438e648fcf6338293fd1a327af9..a865a45839ebd0ab1622c2ae90020884baca8f73 100644 (file)
@@ -49,6 +49,9 @@ func (opt *ListMilestoneOption) QueryEncode() string {
 
 // ListRepoMilestones list all the milestones of one repository
 func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption) ([]*Milestone, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        milestones := make([]*Milestone, 0, opt.PageSize)
 
@@ -60,6 +63,9 @@ func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption)
 
 // GetMilestone get one milestone by repo name and milestone id
 func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        milestone := new(Milestone)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone)
        return milestone, resp, err
@@ -72,6 +78,9 @@ func (c *Client) GetMilestoneByName(owner, repo string, name string) (*Milestone
                m, resp, err := c.resolveMilestoneByName(owner, repo, name)
                return m, resp, err
        }
+       if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
+               return nil, nil, err
+       }
        milestone := new(Milestone)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone)
        return milestone, resp, err
@@ -95,6 +104,9 @@ func (opt CreateMilestoneOption) Validate() error {
 
 // CreateMilestone create one milestone with options
 func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -135,6 +147,9 @@ func (opt EditMilestoneOption) Validate() error {
 
 // EditMilestone modify milestone with options
 func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -157,6 +172,9 @@ func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMi
                }
                return c.EditMilestone(owner, repo, m.ID, opt)
        }
+       if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -171,6 +189,9 @@ func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMi
 
 // DeleteMilestone delete one milestone by id
 func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil)
        return resp, err
 }
@@ -185,6 +206,9 @@ func (c *Client) DeleteMilestoneByName(owner, repo string, name string) (*Respon
                }
                return c.DeleteMilestone(owner, repo, m.ID)
        }
+       if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil)
        return resp, err
 }
index 4ec203c594222e6b12e3e2982f831212d17f1d28..b45c0666466af2ce596803aebcdc7c1caabdfa91 100644 (file)
@@ -20,7 +20,7 @@ type Reaction struct {
 
 // GetIssueReactions get a list reactions of an issue
 func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, nil, err
        }
        reactions := make([]*Reaction, 0, 10)
@@ -30,7 +30,7 @@ func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction
 
 // GetIssueCommentReactions get a list of reactions from a comment of an issue
 func (c *Client) GetIssueCommentReactions(owner, repo string, commentID int64) ([]*Reaction, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, nil, err
        }
        reactions := make([]*Reaction, 0, 10)
@@ -45,7 +45,7 @@ type editReactionOption struct {
 
 // PostIssueReaction add a reaction to an issue
 func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction string) (*Reaction, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, nil, err
        }
        reactionResponse := new(Reaction)
@@ -61,7 +61,7 @@ func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction str
 
 // DeleteIssueReaction remove a reaction from an issue
 func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction string) (*Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, err
        }
        body, err := json.Marshal(&editReactionOption{Reaction: reaction})
@@ -74,7 +74,7 @@ func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction s
 
 // PostIssueCommentReaction add a reaction to a comment of an issue
 func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Reaction, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, nil, err
        }
        reactionResponse := new(Reaction)
@@ -90,7 +90,7 @@ func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, r
 
 // DeleteIssueCommentReaction remove a reaction from a comment of an issue
 func (c *Client) DeleteIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, err
        }
        body, err := json.Marshal(&editReactionOption{Reaction: reaction})
index fac2c07efdcb089b9e920016a8d2395a72df72b6..ebb0b8ae4042a168ae02fa007fd26e762f2b961b 100644 (file)
@@ -11,8 +11,13 @@ import (
 
 // StopWatch represents a running stopwatch of an issue / pr
 type StopWatch struct {
-       Created    time.Time `json:"created"`
-       IssueIndex int64     `json:"issue_index"`
+       Created       time.Time `json:"created"`
+       Seconds       int64     `json:"seconds"`
+       Duration      string    `json:"duration"`
+       IssueIndex    int64     `json:"issue_index"`
+       IssueTitle    string    `json:"issue_title"`
+       RepoOwnerName string    `json:"repo_owner_name"`
+       RepoName      string    `json:"repo_name"`
 }
 
 // GetMyStopwatches list all stopwatches
@@ -24,6 +29,9 @@ func (c *Client) GetMyStopwatches() ([]*StopWatch, *Response, error) {
 
 // DeleteIssueStopwatch delete / cancel a specific stopwatch
 func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/delete", owner, repo, index), nil, nil)
        return resp, err
 }
@@ -31,6 +39,9 @@ func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Respons
 // StartIssueStopWatch starts a stopwatch for an existing issue for a given
 // repository
 func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index), nil, nil)
        return resp, err
 }
@@ -38,6 +49,9 @@ func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response
 // StopIssueStopWatch stops an existing stopwatch for an issue in a given
 // repository
 func (c *Client) StopIssueStopWatch(owner, repo string, index int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index), nil, nil)
        return resp, err
 }
index 0af4228c6012a8158228454c16f87c7cce0b3182..86853c718683cb1e22fcbdf2c49fb99785ece2e5 100644 (file)
@@ -11,7 +11,7 @@ import (
 
 // GetIssueSubscribers get list of users who subscribed on an issue
 func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, nil, err
        }
        subscribers := make([]*User, 0, 10)
@@ -21,7 +21,7 @@ func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User,
 
 // AddIssueSubscription Subscribe user to issue
 func (c *Client) AddIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil {
                return nil, err
        }
        status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
@@ -39,7 +39,7 @@ func (c *Client) AddIssueSubscription(owner, repo string, index int64, user stri
 
 // DeleteIssueSubscription unsubscribe user from issue
 func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil {
                return nil, err
        }
        status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
@@ -57,6 +57,9 @@ func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user s
 
 // CheckIssueSubscription check if current user is subscribed to an issue
 func (c *Client) CheckIssueSubscription(owner, repo string, index int64) (*WatchInfo, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
index 9a9922e665095f241cf60a0a972cb8ffb90ec84a..c558516237db0ec901e47baefb15e97955a3399c 100644 (file)
@@ -8,6 +8,7 @@ import (
        "bytes"
        "encoding/json"
        "fmt"
+       "net/url"
        "time"
 )
 
@@ -25,33 +26,50 @@ type TrackedTime struct {
        Issue   *Issue `json:"issue"`
 }
 
-// GetUserTrackedTimes list tracked times of a user
-func (c *Client) GetUserTrackedTimes(owner, repo, user string) ([]*TrackedTime, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
-               return nil, nil, err
+// ListTrackedTimesOptions options for listing repository's tracked times
+type ListTrackedTimesOptions struct {
+       ListOptions
+       Since  time.Time
+       Before time.Time
+       // User filter is only used by ListRepoTrackedTimes !!!
+       User string
+}
+
+// QueryEncode turns options into querystring argument
+func (opt *ListTrackedTimesOptions) QueryEncode() string {
+       query := opt.getURLQuery()
+
+       if !opt.Since.IsZero() {
+               query.Add("since", opt.Since.Format(time.RFC3339))
        }
-       times := make([]*TrackedTime, 0, 10)
-       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times/%s", owner, repo, user), nil, nil, &times)
-       return times, resp, err
+       if !opt.Before.IsZero() {
+               query.Add("before", opt.Before.Format(time.RFC3339))
+       }
+
+       if len(opt.User) != 0 {
+               query.Add("user", opt.User)
+       }
+
+       return query.Encode()
 }
 
-// GetRepoTrackedTimes list tracked times of a repository
-func (c *Client) GetRepoTrackedTimes(owner, repo string) ([]*TrackedTime, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+// ListRepoTrackedTimes list tracked times of a repository
+func (c *Client) ListRepoTrackedTimes(owner, repo string, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, nil, err
        }
-       times := make([]*TrackedTime, 0, 10)
-       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times", owner, repo), nil, nil, &times)
+       link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/times", owner, repo))
+       opt.setDefaults()
+       link.RawQuery = opt.QueryEncode()
+       times := make([]*TrackedTime, 0, opt.PageSize)
+       resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &times)
        return times, resp, err
 }
 
 // GetMyTrackedTimes list tracked times of the current user
 func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
-               return nil, nil, err
-       }
        times := make([]*TrackedTime, 0, 10)
-       resp, err := c.getParsedResponse("GET", "/user/times", nil, nil, &times)
+       resp, err := c.getParsedResponse("GET", "/user/times", jsonHeader, nil, &times)
        return times, resp, err
 }
 
@@ -75,7 +93,7 @@ func (opt AddTimeOption) Validate() error {
 
 // AddTime adds time to issue with the given index
 func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, nil, err
        }
        if err := opt.Validate(); err != nil {
@@ -92,36 +110,33 @@ func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*T
        return t, resp, err
 }
 
-// ListTrackedTimesOptions options for listing repository's tracked times
-type ListTrackedTimesOptions struct {
-       ListOptions
-}
-
-// ListTrackedTimes list tracked times of a single issue for a given repository
-func (c *Client) ListTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+// ListIssueTrackedTimes list tracked times of a single issue for a given repository
+func (c *Client) ListIssueTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, nil, err
        }
+       link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index))
        opt.setDefaults()
+       link.RawQuery = opt.QueryEncode()
        times := make([]*TrackedTime, 0, opt.PageSize)
-       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/times?%s", owner, repo, index, opt.getURLQuery().Encode()), nil, nil, &times)
+       resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &times)
        return times, resp, err
 }
 
 // ResetIssueTime reset tracked time of a single issue for a given repository
 func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, err
        }
-       _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), nil, nil)
+       _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), jsonHeader, nil)
        return resp, err
 }
 
 // DeleteTime delete a specific tracked time by id of a single issue for a given repository
 func (c *Client) DeleteTime(owner, repo string, index, timeID int64) (*Response, error) {
-       if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
                return nil, err
        }
-       _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), nil, nil)
+       _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), jsonHeader, nil)
        return resp, err
 }
index 30b6b6d01c62daa63758f1a623253702008cf627..de53e2ce99df7f1741e32441fbb2ba3496b83030 100644 (file)
@@ -176,14 +176,17 @@ func (c *Client) ReadNotifications(opt MarkNotificationOptions) (*Response, erro
 }
 
 // ListRepoNotifications list users's notification threads on a specific repo
-func (c *Client) ListRepoNotifications(owner, reponame string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
+func (c *Client) ListRepoNotifications(owner, repo string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
        if err := opt.Validate(c); err != nil {
                return nil, nil, err
        }
-       link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, reponame))
+       link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo))
        link.RawQuery = opt.QueryEncode()
        threads := make([]*NotificationThread, 0, 10)
        resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads)
@@ -191,14 +194,17 @@ func (c *Client) ListRepoNotifications(owner, reponame string, opt ListNotificat
 }
 
 // ReadRepoNotifications mark notification threads as read on a specific repo
-func (c *Client) ReadRepoNotifications(owner, reponame string, opt MarkNotificationOptions) (*Response, error) {
+func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, err
        }
        if err := opt.Validate(c); err != nil {
                return nil, err
        }
-       link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, reponame))
+       link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo))
        link.RawQuery = opt.QueryEncode()
        _, resp, err := c.getResponse("PUT", link.String(), nil, nil)
        return resp, err
index ed6c678e64b012780ad2c332d4a818f5ad051c31..b7c439bc4fc0de9e0fb83460e356aa0f7404a400 100644 (file)
@@ -52,6 +52,9 @@ func (c *Client) ListMyOrgs(opt ListOrgsOptions) ([]*Organization, *Response, er
 
 // ListUserOrgs list all of some user's organizations
 func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        orgs := make([]*Organization, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs?%s", user, opt.getURLQuery().Encode()), nil, nil, &orgs)
@@ -60,6 +63,9 @@ func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization
 
 // GetOrg get one organization by name
 func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) {
+       if err := escapeValidatePathSegments(&orgname); err != nil {
+               return nil, nil, err
+       }
        org := new(Organization)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org)
        return org, resp, err
@@ -124,6 +130,9 @@ func (opt EditOrgOption) Validate() error {
 
 // EditOrg modify one organization via options
 func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) {
+       if err := escapeValidatePathSegments(&orgname); err != nil {
+               return nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, err
        }
@@ -137,6 +146,9 @@ func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) {
 
 // DeleteOrg deletes an organization
 func (c *Client) DeleteOrg(orgname string) (*Response, error) {
+       if err := escapeValidatePathSegments(&orgname); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, nil)
        return resp, err
 }
index 0f5b54296720454cd5aa9089c4fe22ffb7751344..1eed90f6b65df431e717d1bf2560a966c1169f9a 100644 (file)
@@ -12,7 +12,10 @@ import (
 
 // DeleteOrgMembership remove a member from an organization
 func (c *Client) DeleteOrgMembership(org, user string) (*Response, error) {
-       _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
+       if err := escapeValidatePathSegments(&org, &user); err != nil {
+               return nil, err
+       }
+       _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil)
        return resp, err
 }
 
@@ -23,10 +26,13 @@ type ListOrgMembershipOption struct {
 
 // ListOrgMembership list an organization's members
 func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        users := make([]*User, 0, opt.PageSize)
 
-       link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", url.PathEscape(org)))
+       link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", org))
        link.RawQuery = opt.getURLQuery().Encode()
        resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
        return users, resp, err
@@ -34,10 +40,13 @@ func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*
 
 // ListPublicOrgMembership list an organization's members
 func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        users := make([]*User, 0, opt.PageSize)
 
-       link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", url.PathEscape(org)))
+       link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", org))
        link.RawQuery = opt.getURLQuery().Encode()
        resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
        return users, resp, err
@@ -45,7 +54,10 @@ func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption
 
 // CheckOrgMembership Check if a user is a member of an organization
 func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) {
-       status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
+       if err := escapeValidatePathSegments(&org, &user); err != nil {
+               return false, nil, err
+       }
+       status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil)
        if err != nil {
                return false, resp, err
        }
@@ -61,7 +73,10 @@ func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) {
 
 // CheckPublicOrgMembership Check if a user is a member of an organization
 func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, error) {
-       status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
+       if err := escapeValidatePathSegments(&org, &user); err != nil {
+               return false, nil, err
+       }
+       status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
        if err != nil {
                return false, resp, err
        }
@@ -77,15 +92,18 @@ func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, er
 
 // SetPublicOrgMembership publicize/conceal a user's membership
 func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) {
+       if err := escapeValidatePathSegments(&org, &user); err != nil {
+               return nil, err
+       }
        var (
                status int
                err    error
                resp   *Response
        )
        if visible {
-               status, resp, err = c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
+               status, resp, err = c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
        } else {
-               status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
+               status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
        }
        if err != nil {
                return resp, err
index 148d2c2c3e6ec5fd29aadb2676e94f6f886ae4b2..0373c6ed2d00b26b2366c7bfa9e05e1b8a13c08f 100644 (file)
@@ -30,6 +30,9 @@ type ListTeamsOptions struct {
 
 // ListOrgTeams lists all teams of an organization
 func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        teams := make([]*Team, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams?%s", org, opt.getURLQuery().Encode()), nil, nil, &teams)
@@ -83,6 +86,9 @@ func (opt CreateTeamOption) Validate() error {
 
 // CreateTeam creates a team for an organization
 func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -159,6 +165,9 @@ func (c *Client) ListTeamMembers(id int64, opt ListTeamMembersOptions) ([]*User,
 
 // GetTeamMember gets a member of a team
 func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        m := new(User)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m)
        return m, resp, err
@@ -166,12 +175,18 @@ func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error)
 
 // AddTeamMember adds a member to a team
 func (c *Client) AddTeamMember(id int64, user string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
        return resp, err
 }
 
 // RemoveTeamMember removes a member from a team
 func (c *Client) RemoveTeamMember(id int64, user string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
        return resp, err
 }
@@ -191,12 +206,18 @@ func (c *Client) ListTeamRepositories(id int64, opt ListTeamRepositoriesOptions)
 
 // AddTeamRepository adds a repository to a team
 func (c *Client) AddTeamRepository(id int64, org, repo string) (*Response, error) {
+       if err := escapeValidatePathSegments(&org, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
        return resp, err
 }
 
 // RemoveTeamRepository removes a repository from a team
 func (c *Client) RemoveTeamRepository(id int64, org, repo string) (*Response, error) {
+       if err := escapeValidatePathSegments(&org, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
        return resp, err
 }
index c41ab3b01830a7e4830a47df0a9a0c500d9be3b7..7c946e8918735473e77db68d4c0777e115ce9819 100644 (file)
@@ -99,19 +99,37 @@ func (opt *ListPullRequestsOptions) QueryEncode() string {
 
 // ListRepoPullRequests list PRs of one repository
 func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        prs := make([]*PullRequest, 0, opt.PageSize)
 
        link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", owner, repo))
        link.RawQuery = opt.QueryEncode()
        resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &prs)
+       if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
+               for i := range prs {
+                       if err := fixPullHeadSha(c, prs[i]); err != nil {
+                               return prs, resp, err
+                       }
+               }
+       }
        return prs, resp, err
 }
 
 // GetPullRequest get information of one PR
 func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        pr := new(PullRequest)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr)
+       if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
+               if err := fixPullHeadSha(c, pr); err != nil {
+                       return pr, resp, err
+               }
+       }
        return pr, resp, err
 }
 
@@ -130,6 +148,9 @@ type CreatePullRequestOption struct {
 
 // CreatePullRequest create pull request with options
 func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
@@ -169,6 +190,9 @@ func (opt EditPullRequestOption) Validate(c *Client) error {
 
 // EditPullRequest modify pull request with PR id and options
 func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRequestOption) (*PullRequest, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(c); err != nil {
                return nil, nil, err
        }
@@ -202,6 +226,9 @@ func (opt MergePullRequestOption) Validate(c *Client) error {
 
 // MergePullRequest merge a PR to repository by PR id
 func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePullRequestOption) (bool, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return false, nil, err
+       }
        if err := opt.Validate(c); err != nil {
                return false, nil, err
        }
@@ -218,6 +245,9 @@ func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePull
 
 // IsPullRequestMerged test if one PR is merged to one repository
 func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return false, nil, err
+       }
        status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil)
 
        if err != nil {
@@ -229,6 +259,9 @@ func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Re
 
 // getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR
 func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64) ([]byte, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo, &kind); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
                r, _, err2 := c.GetRepo(owner, repo)
                if err2 != nil {
@@ -251,3 +284,23 @@ func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *
 func (c *Client) GetPullRequestDiff(owner, repo string, index int64) ([]byte, *Response, error) {
        return c.getPullRequestDiffOrPatch(owner, repo, "diff", index)
 }
+
+// fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675
+// When no head sha is available, this is because the branch got deleted in the base repo.
+// pr.Head.Ref points in this case not to the head repo branch name, but the base repo ref,
+// which stays available to resolve the commit sha. This is fixed for gitea >= 1.14.0
+func fixPullHeadSha(client *Client, pr *PullRequest) error {
+       if pr.Base != nil && pr.Base.Repository != nil && pr.Base.Repository.Owner != nil &&
+               pr.Head != nil && pr.Head.Ref != "" && pr.Head.Sha == "" {
+               owner := pr.Base.Repository.Owner.UserName
+               repo := pr.Base.Repository.Name
+               refs, _, err := client.GetRepoRefs(owner, repo, pr.Head.Ref)
+               if err != nil {
+                       return err
+               } else if len(refs) == 0 {
+                       return fmt.Errorf("unable to resolve PR ref '%s'", pr.Head.Ref)
+               }
+               pr.Head.Sha = refs[0].Object.SHA
+       }
+       return nil
+}
index fc0c22c2d71f37789fdf1cc54d6738ce02d31618..fa7921b023beb1da9e9bedae6b15568a10486150 100644 (file)
@@ -33,15 +33,19 @@ const (
 
 // PullReview represents a pull request review
 type PullReview struct {
-       ID                int64           `json:"id"`
-       Reviewer          *User           `json:"user"`
-       State             ReviewStateType `json:"state"`
-       Body              string          `json:"body"`
-       CommitID          string          `json:"commit_id"`
-       Stale             bool            `json:"stale"`
-       Official          bool            `json:"official"`
-       CodeCommentsCount int             `json:"comments_count"`
-       Submitted         time.Time       `json:"submitted_at"`
+       ID           int64           `json:"id"`
+       Reviewer     *User           `json:"user"`
+       ReviewerTeam *Team           `json:"team"`
+       State        ReviewStateType `json:"state"`
+       Body         string          `json:"body"`
+       CommitID     string          `json:"commit_id"`
+       // Stale indicates if the pull has changed since the review
+       Stale bool `json:"stale"`
+       // Official indicates if the review counts towards the required approval limit, if PR base is a protected branch
+       Official          bool      `json:"official"`
+       Dismissed         bool      `json:"dismissed"`
+       CodeCommentsCount int       `json:"comments_count"`
+       Submitted         time.Time `json:"submitted_at"`
 
        HTMLURL     string `json:"html_url"`
        HTMLPullURL string `json:"pull_request_url"`
@@ -93,6 +97,17 @@ type SubmitPullReviewOptions struct {
        Body  string          `json:"body"`
 }
 
+// DismissPullReviewOptions are options to dismiss a pull review
+type DismissPullReviewOptions struct {
+       Message string `json:"message"`
+}
+
+// PullReviewRequestOptions are options to add or remove pull review requests
+type PullReviewRequestOptions struct {
+       Reviewers     []string `json:"reviewers"`
+       TeamReviewers []string `json:"team_reviewers"`
+}
+
 // ListPullReviewsOptions options for listing PullReviews
 type ListPullReviewsOptions struct {
        ListOptions
@@ -132,6 +147,9 @@ func (opt CreatePullReviewComment) Validate() error {
 
 // ListPullReviews lists all reviews of a pull request
 func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullReviewsOptions) ([]*PullReview, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
@@ -147,6 +165,9 @@ func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullRe
 
 // GetPullReview gets a specific review of a pull request
 func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
@@ -158,6 +179,9 @@ func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview
 
 // ListPullReviewComments lists all comments of a pull request review
 func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]*PullReviewComment, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
@@ -170,6 +194,9 @@ func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]
 
 // DeletePullReview delete a specific review from a pull request
 func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, err
        }
@@ -180,6 +207,9 @@ func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Respons
 
 // CreatePullReview create a review to an pull request
 func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePullReviewOptions) (*PullReview, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
@@ -200,6 +230,9 @@ func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePul
 
 // SubmitPullReview submit a pending review to an pull request
 func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt SubmitPullReviewOptions) (*PullReview, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
@@ -217,3 +250,75 @@ func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt Submi
                jsonHeader, bytes.NewReader(body), r)
        return r, resp, err
 }
+
+// CreateReviewRequests create review requests to an pull request
+func (c *Client) CreateReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
+       if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
+               return nil, err
+       }
+       body, err := json.Marshal(&opt)
+       if err != nil {
+               return nil, err
+       }
+
+       _, resp, err := c.getResponse("POST",
+               fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index),
+               jsonHeader, bytes.NewReader(body))
+       return resp, err
+}
+
+// DeleteReviewRequests delete review requests to an pull request
+func (c *Client) DeleteReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
+       if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
+               return nil, err
+       }
+       body, err := json.Marshal(&opt)
+       if err != nil {
+               return nil, err
+       }
+
+       _, resp, err := c.getResponse("DELETE",
+               fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index),
+               jsonHeader, bytes.NewReader(body))
+       return resp, err
+}
+
+// DismissPullReview dismiss a review for a pull request
+func (c *Client) DismissPullReview(owner, repo string, index, id int64, opt DismissPullReviewOptions) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
+       if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
+               return nil, err
+       }
+       body, err := json.Marshal(&opt)
+       if err != nil {
+               return nil, err
+       }
+
+       _, resp, err := c.getResponse("POST",
+               fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/dismissals", owner, repo, index, id),
+               jsonHeader, bytes.NewReader(body))
+       return resp, err
+}
+
+// UnDismissPullReview cancel to dismiss a review for a pull request
+func (c *Client) UnDismissPullReview(owner, repo string, index, id int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
+       if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
+               return nil, err
+       }
+
+       _, resp, err := c.getResponse("POST",
+               fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, id),
+               jsonHeader, nil)
+       return resp, err
+}
index d8d28c5bd7d5f7723f46a147bf3eb34bdf5fc233..7d36e7106e47ee911b7c61c0de1f25b2c25ab0af 100644 (file)
@@ -21,6 +21,7 @@ type Release struct {
        Title        string        `json:"name"`
        Note         string        `json:"body"`
        URL          string        `json:"url"`
+       HTMLURL      string        `json:"html_url"`
        TarURL       string        `json:"tarball_url"`
        ZipURL       string        `json:"zipball_url"`
        IsDraft      bool          `json:"draft"`
@@ -37,32 +38,41 @@ type ListReleasesOptions struct {
 }
 
 // ListReleases list releases of a repository
-func (c *Client) ListReleases(user, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) {
+func (c *Client) ListReleases(owner, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        releases := make([]*Release, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET",
-               fmt.Sprintf("/repos/%s/%s/releases?%s", user, repo, opt.getURLQuery().Encode()),
+               fmt.Sprintf("/repos/%s/%s/releases?%s", owner, repo, opt.getURLQuery().Encode()),
                nil, nil, &releases)
        return releases, resp, err
 }
 
 // GetRelease get a release of a repository by id
-func (c *Client) GetRelease(user, repo string, id int64) (*Release, *Response, error) {
+func (c *Client) GetRelease(owner, repo string, id int64) (*Release, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        r := new(Release)
        resp, err := c.getParsedResponse("GET",
-               fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
+               fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id),
                jsonHeader, nil, &r)
        return r, resp, err
 }
 
 // GetReleaseByTag get a release of a repository by tag
-func (c *Client) GetReleaseByTag(user, repo string, tag string) (*Release, *Response, error) {
+func (c *Client) GetReleaseByTag(owner, repo string, tag string) (*Release, *Response, error) {
        if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
-               return c.fallbackGetReleaseByTag(user, repo, tag)
+               return c.fallbackGetReleaseByTag(owner, repo, tag)
+       }
+       if err := escapeValidatePathSegments(&owner, &repo, &tag); err != nil {
+               return nil, nil, err
        }
        r := new(Release)
        resp, err := c.getParsedResponse("GET",
-               fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag),
+               fmt.Sprintf("/repos/%s/%s/releases/tags/%s", owner, repo, tag),
                nil, nil, &r)
        return r, resp, err
 }
@@ -86,7 +96,10 @@ func (opt CreateReleaseOption) Validate() error {
 }
 
 // CreateRelease create a release
-func (c *Client) CreateRelease(user, repo string, opt CreateReleaseOption) (*Release, *Response, error) {
+func (c *Client) CreateRelease(owner, repo string, opt CreateReleaseOption) (*Release, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, nil, err
        }
@@ -96,7 +109,7 @@ func (c *Client) CreateRelease(user, repo string, opt CreateReleaseOption) (*Rel
        }
        r := new(Release)
        resp, err := c.getParsedResponse("POST",
-               fmt.Sprintf("/repos/%s/%s/releases", user, repo),
+               fmt.Sprintf("/repos/%s/%s/releases", owner, repo),
                jsonHeader, bytes.NewReader(body), r)
        return r, resp, err
 }
@@ -112,30 +125,50 @@ type EditReleaseOption struct {
 }
 
 // EditRelease edit a release
-func (c *Client) EditRelease(user, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) {
+func (c *Client) EditRelease(owner, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(form)
        if err != nil {
                return nil, nil, err
        }
        r := new(Release)
        resp, err := c.getParsedResponse("PATCH",
-               fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
+               fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id),
                jsonHeader, bytes.NewReader(body), r)
        return r, resp, err
 }
 
-// DeleteRelease delete a release from a repository
+// DeleteRelease delete a release from a repository, keeping its tag
 func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE",
                fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
                nil, nil)
        return resp, err
 }
 
+// DeleteReleaseByTag deletes a release frm a repository by tag
+func (c *Client) DeleteReleaseByTag(user, repo string, tag string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
+               return nil, err
+       }
+       if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
+               return nil, err
+       }
+       _, resp, err := c.getResponse("DELETE",
+               fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag),
+               nil, nil)
+       return resp, err
+}
+
 // fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 )
-func (c *Client) fallbackGetReleaseByTag(user, repo string, tag string) (*Release, *Response, error) {
+func (c *Client) fallbackGetReleaseByTag(owner, repo string, tag string) (*Release, *Response, error) {
        for i := 1; ; i++ {
-               rl, resp, err := c.ListReleases(user, repo, ListReleasesOptions{ListOptions{Page: i}})
+               rl, resp, err := c.ListReleases(owner, repo, ListReleasesOptions{ListOptions{Page: i}})
                if err != nil {
                        return nil, resp, err
                }
index a09f5a651b14dd24d97a89e22c35d51104b1006d..67bd9563085dbaf95a8a659fcadaf1d3024434e4 100644 (file)
@@ -9,6 +9,7 @@ import (
        "bytes"
        "encoding/json"
        "fmt"
+       "io"
        "net/url"
        "strings"
        "time"
@@ -21,42 +22,77 @@ type Permission struct {
        Pull  bool `json:"pull"`
 }
 
+// InternalTracker represents settings for internal tracker
+type InternalTracker struct {
+       // Enable time tracking (Built-in issue tracker)
+       EnableTimeTracker bool `json:"enable_time_tracker"`
+       // Let only contributors track time (Built-in issue tracker)
+       AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"`
+       // Enable dependencies for issues and pull requests (Built-in issue tracker)
+       EnableIssueDependencies bool `json:"enable_issue_dependencies"`
+}
+
+// ExternalTracker represents settings for external tracker
+type ExternalTracker struct {
+       // URL of external issue tracker.
+       ExternalTrackerURL string `json:"external_tracker_url"`
+       // External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.
+       ExternalTrackerFormat string `json:"external_tracker_format"`
+       // External Issue Tracker Number Format, either `numeric` or `alphanumeric`
+       ExternalTrackerStyle string `json:"external_tracker_style"`
+}
+
+// ExternalWiki represents setting for external wiki
+type ExternalWiki struct {
+       // URL of external wiki.
+       ExternalWikiURL string `json:"external_wiki_url"`
+}
+
 // Repository represents a repository
 type Repository struct {
-       ID                        int64       `json:"id"`
-       Owner                     *User       `json:"owner"`
-       Name                      string      `json:"name"`
-       FullName                  string      `json:"full_name"`
-       Description               string      `json:"description"`
-       Empty                     bool        `json:"empty"`
-       Private                   bool        `json:"private"`
-       Fork                      bool        `json:"fork"`
-       Parent                    *Repository `json:"parent"`
-       Mirror                    bool        `json:"mirror"`
-       Size                      int         `json:"size"`
-       HTMLURL                   string      `json:"html_url"`
-       SSHURL                    string      `json:"ssh_url"`
-       CloneURL                  string      `json:"clone_url"`
-       OriginalURL               string      `json:"original_url"`
-       Website                   string      `json:"website"`
-       Stars                     int         `json:"stars_count"`
-       Forks                     int         `json:"forks_count"`
-       Watchers                  int         `json:"watchers_count"`
-       OpenIssues                int         `json:"open_issues_count"`
-       DefaultBranch             string      `json:"default_branch"`
-       Archived                  bool        `json:"archived"`
-       Created                   time.Time   `json:"created_at"`
-       Updated                   time.Time   `json:"updated_at"`
-       Permissions               *Permission `json:"permissions,omitempty"`
-       HasIssues                 bool        `json:"has_issues"`
-       HasWiki                   bool        `json:"has_wiki"`
-       HasPullRequests           bool        `json:"has_pull_requests"`
-       IgnoreWhitespaceConflicts bool        `json:"ignore_whitespace_conflicts"`
-       AllowMerge                bool        `json:"allow_merge_commits"`
-       AllowRebase               bool        `json:"allow_rebase"`
-       AllowRebaseMerge          bool        `json:"allow_rebase_explicit"`
-       AllowSquash               bool        `json:"allow_squash_merge"`
-       AvatarURL                 string      `json:"avatar_url"`
+       ID                        int64            `json:"id"`
+       Owner                     *User            `json:"owner"`
+       Name                      string           `json:"name"`
+       FullName                  string           `json:"full_name"`
+       Description               string           `json:"description"`
+       Empty                     bool             `json:"empty"`
+       Private                   bool             `json:"private"`
+       Fork                      bool             `json:"fork"`
+       Template                  bool             `json:"template"`
+       Parent                    *Repository      `json:"parent"`
+       Mirror                    bool             `json:"mirror"`
+       Size                      int              `json:"size"`
+       HTMLURL                   string           `json:"html_url"`
+       SSHURL                    string           `json:"ssh_url"`
+       CloneURL                  string           `json:"clone_url"`
+       OriginalURL               string           `json:"original_url"`
+       Website                   string           `json:"website"`
+       Stars                     int              `json:"stars_count"`
+       Forks                     int              `json:"forks_count"`
+       Watchers                  int              `json:"watchers_count"`
+       OpenIssues                int              `json:"open_issues_count"`
+       OpenPulls                 int              `json:"open_pr_counter"`
+       Releases                  int              `json:"release_counter"`
+       DefaultBranch             string           `json:"default_branch"`
+       Archived                  bool             `json:"archived"`
+       Created                   time.Time        `json:"created_at"`
+       Updated                   time.Time        `json:"updated_at"`
+       Permissions               *Permission      `json:"permissions,omitempty"`
+       HasIssues                 bool             `json:"has_issues"`
+       InternalTracker           *InternalTracker `json:"internal_tracker,omitempty"`
+       ExternalTracker           *ExternalTracker `json:"external_tracker,omitempty"`
+       HasWiki                   bool             `json:"has_wiki"`
+       ExternalWiki              *ExternalWiki    `json:"external_wiki,omitempty"`
+       HasPullRequests           bool             `json:"has_pull_requests"`
+       HasProjects               bool             `json:"has_projects"`
+       IgnoreWhitespaceConflicts bool             `json:"ignore_whitespace_conflicts"`
+       AllowMerge                bool             `json:"allow_merge_commits"`
+       AllowRebase               bool             `json:"allow_rebase"`
+       AllowRebaseMerge          bool             `json:"allow_rebase_explicit"`
+       AllowSquash               bool             `json:"allow_squash_merge"`
+       AvatarURL                 string           `json:"avatar_url"`
+       Internal                  bool             `json:"internal"`
+       MirrorInterval            string           `json:"mirror_interval"`
 }
 
 // RepoType represent repo type
@@ -102,6 +138,9 @@ func (c *Client) ListMyRepos(opt ListReposOptions) ([]*Repository, *Response, er
 
 // ListUserRepos list all repositories of one user by user's name
 func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        repos := make([]*Repository, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos?%s", user, opt.getURLQuery().Encode()), nil, nil, &repos)
@@ -115,6 +154,9 @@ type ListOrgReposOptions struct {
 
 // ListOrgRepos list all repositories of one organization by organization's name
 func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        repos := make([]*Repository, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos?%s", org, opt.getURLQuery().Encode()), nil, nil, &repos)
@@ -315,6 +357,9 @@ func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error
 
 // CreateOrgRepo creates an organization repository for authenticated user.
 func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&org); err != nil {
+               return nil, nil, err
+       }
        if err := opt.Validate(c); err != nil {
                return nil, nil, err
        }
@@ -329,6 +374,9 @@ func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *
 
 // GetRepo returns information of a repository of given owner.
 func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
+               return nil, nil, err
+       }
        repo := new(Repository)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo)
        return repo, resp, err
@@ -346,14 +394,24 @@ type EditRepoOption struct {
        // Note: you will get a 422 error if the organization restricts changing repository visibility to organization
        // owners and a non-owner tries to change the value of private.
        Private *bool `json:"private,omitempty"`
+       // either `true` to make this repository a template or `false` to make it a normal repository
+       Template *bool `json:"template,omitempty"`
        // either `true` to enable issues for this repository or `false` to disable them.
        HasIssues *bool `json:"has_issues,omitempty"`
+       // set this structure to configure internal issue tracker (requires has_issues)
+       InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
+       // set this structure to use external issue tracker (requires has_issues)
+       ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
        // either `true` to enable the wiki for this repository or `false` to disable it.
        HasWiki *bool `json:"has_wiki,omitempty"`
+       // set this structure to use external wiki instead of internal (requires has_wiki)
+       ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
        // sets the default branch for this repository.
        DefaultBranch *string `json:"default_branch,omitempty"`
        // either `true` to allow pull requests, or `false` to prevent pull request.
        HasPullRequests *bool `json:"has_pull_requests,omitempty"`
+       // either `true` to enable project unit, or `false` to disable them.
+       HasProjects *bool `json:"has_projects,omitempty"`
        // either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`.
        IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"`
        // either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`.
@@ -366,10 +424,15 @@ type EditRepoOption struct {
        AllowSquash *bool `json:"allow_squash_merge,omitempty"`
        // set to `true` to archive this repository.
        Archived *bool `json:"archived,omitempty"`
+       // set to a string like `8h30m0s` to set the mirror interval time
+       MirrorInterval *string `json:"mirror_interval,omitempty"`
 }
 
 // EditRepo edit the properties of a repository
 func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
@@ -381,18 +444,27 @@ func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Reposito
 
 // DeleteRepo deletes a repository of user or organization.
 func (c *Client) DeleteRepo(owner, repo string) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil)
        return resp, err
 }
 
 // MirrorSync adds a mirrored repository to the mirror sync queue.
 func (c *Client) MirrorSync(owner, repo string) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil)
        return resp, err
 }
 
 // GetRepoLanguages return language stats of a repo
 func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        langMap := make(map[string]int64)
 
        data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil)
@@ -418,5 +490,30 @@ const (
 // GetArchive get an archive of a repository by git reference
 // e.g.: ref -> master, 70b7c74b33, v1.2.1, ...
 func (c *Client) GetArchive(owner, repo, ref string, ext ArchiveType) ([]byte, *Response, error) {
-       return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, url.PathEscape(ref), ext), nil, nil)
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
+       ref = pathEscapeSegments(ref)
+       return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
+}
+
+// GetArchiveReader gets a `git archive` for a particular tree-ish git reference
+// such as a branch name (`master`), a commit hash (`70b7c74b33`), a tag
+// (`v1.2.1`). The archive is returned as a byte stream in a ReadCloser. It is
+// the responsibility of the client to close the reader.
+func (c *Client) GetArchiveReader(owner, repo, ref string, ext ArchiveType) (io.ReadCloser, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
+       ref = pathEscapeSegments(ref)
+       resp, err := c.doRequest("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
+       if err != nil {
+               return nil, resp, err
+       }
+
+       if _, err := statusCodeToErr(resp); err != nil {
+               return nil, resp, err
+       }
+
+       return resp.Body, resp, nil
 }
index 6b0eec27e98052d2830dc5ddcc29ec12f4de0569..0b7e873c4fa153d72ec3593ac07618363adf4e92 100644 (file)
@@ -20,9 +20,6 @@ type PayloadUser struct {
        UserName string `json:"username"`
 }
 
-// FIXME: consider using same format as API when commits API are added.
-//        applies to PayloadCommit and PayloadCommitVerification
-
 // PayloadCommit represents a commit
 type PayloadCommit struct {
        // sha1 hash of the commit
@@ -66,6 +63,9 @@ type ListRepoBranchesOptions struct {
 
 // ListRepoBranches list all the branches of one repository
 func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions) ([]*Branch, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        branches := make([]*Branch, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &branches)
@@ -74,6 +74,9 @@ func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions
 
 // GetRepoBranch get one branch's information of one repository
 func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil {
+               return nil, nil, err
+       }
        b := new(Branch)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b)
        if err != nil {
@@ -84,6 +87,9 @@ func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, e
 
 // DeleteRepoBranch delete a branch in a repository
 func (c *Client) DeleteRepoBranch(user, repo, branch string) (bool, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil {
+               return false, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return false, nil, err
        }
@@ -118,6 +124,9 @@ func (opt CreateBranchOption) Validate() error {
 
 // CreateBranch creates a branch for a user's repository
 func (c *Client) CreateBranch(owner, repo string, opt CreateBranchOption) (*Branch, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
                return nil, nil, err
        }
index 1e520807a122824122bc7b4429bc0f58dacb0331..22bd7b962954e5876e14f4d6887528fd3f009482 100644 (file)
@@ -14,75 +14,78 @@ import (
 
 // BranchProtection represents a branch protection for a repository
 type BranchProtection struct {
-       BranchName                  string    `json:"branch_name"`
-       EnablePush                  bool      `json:"enable_push"`
-       EnablePushWhitelist         bool      `json:"enable_push_whitelist"`
-       PushWhitelistUsernames      []string  `json:"push_whitelist_usernames"`
-       PushWhitelistTeams          []string  `json:"push_whitelist_teams"`
-       PushWhitelistDeployKeys     bool      `json:"push_whitelist_deploy_keys"`
-       EnableMergeWhitelist        bool      `json:"enable_merge_whitelist"`
-       MergeWhitelistUsernames     []string  `json:"merge_whitelist_usernames"`
-       MergeWhitelistTeams         []string  `json:"merge_whitelist_teams"`
-       EnableStatusCheck           bool      `json:"enable_status_check"`
-       StatusCheckContexts         []string  `json:"status_check_contexts"`
-       RequiredApprovals           int64     `json:"required_approvals"`
-       EnableApprovalsWhitelist    bool      `json:"enable_approvals_whitelist"`
-       ApprovalsWhitelistUsernames []string  `json:"approvals_whitelist_username"`
-       ApprovalsWhitelistTeams     []string  `json:"approvals_whitelist_teams"`
-       BlockOnRejectedReviews      bool      `json:"block_on_rejected_reviews"`
-       BlockOnOutdatedBranch       bool      `json:"block_on_outdated_branch"`
-       DismissStaleApprovals       bool      `json:"dismiss_stale_approvals"`
-       RequireSignedCommits        bool      `json:"require_signed_commits"`
-       ProtectedFilePatterns       string    `json:"protected_file_patterns"`
-       Created                     time.Time `json:"created_at"`
-       Updated                     time.Time `json:"updated_at"`
+       BranchName                    string    `json:"branch_name"`
+       EnablePush                    bool      `json:"enable_push"`
+       EnablePushWhitelist           bool      `json:"enable_push_whitelist"`
+       PushWhitelistUsernames        []string  `json:"push_whitelist_usernames"`
+       PushWhitelistTeams            []string  `json:"push_whitelist_teams"`
+       PushWhitelistDeployKeys       bool      `json:"push_whitelist_deploy_keys"`
+       EnableMergeWhitelist          bool      `json:"enable_merge_whitelist"`
+       MergeWhitelistUsernames       []string  `json:"merge_whitelist_usernames"`
+       MergeWhitelistTeams           []string  `json:"merge_whitelist_teams"`
+       EnableStatusCheck             bool      `json:"enable_status_check"`
+       StatusCheckContexts           []string  `json:"status_check_contexts"`
+       RequiredApprovals             int64     `json:"required_approvals"`
+       EnableApprovalsWhitelist      bool      `json:"enable_approvals_whitelist"`
+       ApprovalsWhitelistUsernames   []string  `json:"approvals_whitelist_username"`
+       ApprovalsWhitelistTeams       []string  `json:"approvals_whitelist_teams"`
+       BlockOnRejectedReviews        bool      `json:"block_on_rejected_reviews"`
+       BlockOnOfficialReviewRequests bool      `json:"block_on_official_review_requests"`
+       BlockOnOutdatedBranch         bool      `json:"block_on_outdated_branch"`
+       DismissStaleApprovals         bool      `json:"dismiss_stale_approvals"`
+       RequireSignedCommits          bool      `json:"require_signed_commits"`
+       ProtectedFilePatterns         string    `json:"protected_file_patterns"`
+       Created                       time.Time `json:"created_at"`
+       Updated                       time.Time `json:"updated_at"`
 }
 
 // CreateBranchProtectionOption options for creating a branch protection
 type CreateBranchProtectionOption struct {
-       BranchName                  string   `json:"branch_name"`
-       EnablePush                  bool     `json:"enable_push"`
-       EnablePushWhitelist         bool     `json:"enable_push_whitelist"`
-       PushWhitelistUsernames      []string `json:"push_whitelist_usernames"`
-       PushWhitelistTeams          []string `json:"push_whitelist_teams"`
-       PushWhitelistDeployKeys     bool     `json:"push_whitelist_deploy_keys"`
-       EnableMergeWhitelist        bool     `json:"enable_merge_whitelist"`
-       MergeWhitelistUsernames     []string `json:"merge_whitelist_usernames"`
-       MergeWhitelistTeams         []string `json:"merge_whitelist_teams"`
-       EnableStatusCheck           bool     `json:"enable_status_check"`
-       StatusCheckContexts         []string `json:"status_check_contexts"`
-       RequiredApprovals           int64    `json:"required_approvals"`
-       EnableApprovalsWhitelist    bool     `json:"enable_approvals_whitelist"`
-       ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
-       ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"`
-       BlockOnRejectedReviews      bool     `json:"block_on_rejected_reviews"`
-       BlockOnOutdatedBranch       bool     `json:"block_on_outdated_branch"`
-       DismissStaleApprovals       bool     `json:"dismiss_stale_approvals"`
-       RequireSignedCommits        bool     `json:"require_signed_commits"`
-       ProtectedFilePatterns       string   `json:"protected_file_patterns"`
+       BranchName                    string   `json:"branch_name"`
+       EnablePush                    bool     `json:"enable_push"`
+       EnablePushWhitelist           bool     `json:"enable_push_whitelist"`
+       PushWhitelistUsernames        []string `json:"push_whitelist_usernames"`
+       PushWhitelistTeams            []string `json:"push_whitelist_teams"`
+       PushWhitelistDeployKeys       bool     `json:"push_whitelist_deploy_keys"`
+       EnableMergeWhitelist          bool     `json:"enable_merge_whitelist"`
+       MergeWhitelistUsernames       []string `json:"merge_whitelist_usernames"`
+       MergeWhitelistTeams           []string `json:"merge_whitelist_teams"`
+       EnableStatusCheck             bool     `json:"enable_status_check"`
+       StatusCheckContexts           []string `json:"status_check_contexts"`
+       RequiredApprovals             int64    `json:"required_approvals"`
+       EnableApprovalsWhitelist      bool     `json:"enable_approvals_whitelist"`
+       ApprovalsWhitelistUsernames   []string `json:"approvals_whitelist_username"`
+       ApprovalsWhitelistTeams       []string `json:"approvals_whitelist_teams"`
+       BlockOnRejectedReviews        bool     `json:"block_on_rejected_reviews"`
+       BlockOnOfficialReviewRequests bool     `json:"block_on_official_review_requests"`
+       BlockOnOutdatedBranch         bool     `json:"block_on_outdated_branch"`
+       DismissStaleApprovals         bool     `json:"dismiss_stale_approvals"`
+       RequireSignedCommits          bool     `json:"require_signed_commits"`
+       ProtectedFilePatterns         string   `json:"protected_file_patterns"`
 }
 
 // EditBranchProtectionOption options for editing a branch protection
 type EditBranchProtectionOption struct {
-       EnablePush                  *bool    `json:"enable_push"`
-       EnablePushWhitelist         *bool    `json:"enable_push_whitelist"`
-       PushWhitelistUsernames      []string `json:"push_whitelist_usernames"`
-       PushWhitelistTeams          []string `json:"push_whitelist_teams"`
-       PushWhitelistDeployKeys     *bool    `json:"push_whitelist_deploy_keys"`
-       EnableMergeWhitelist        *bool    `json:"enable_merge_whitelist"`
-       MergeWhitelistUsernames     []string `json:"merge_whitelist_usernames"`
-       MergeWhitelistTeams         []string `json:"merge_whitelist_teams"`
-       EnableStatusCheck           *bool    `json:"enable_status_check"`
-       StatusCheckContexts         []string `json:"status_check_contexts"`
-       RequiredApprovals           *int64   `json:"required_approvals"`
-       EnableApprovalsWhitelist    *bool    `json:"enable_approvals_whitelist"`
-       ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
-       ApprovalsWhitelistTeams     []string `json:"approvals_whitelist_teams"`
-       BlockOnRejectedReviews      *bool    `json:"block_on_rejected_reviews"`
-       BlockOnOutdatedBranch       *bool    `json:"block_on_outdated_branch"`
-       DismissStaleApprovals       *bool    `json:"dismiss_stale_approvals"`
-       RequireSignedCommits        *bool    `json:"require_signed_commits"`
-       ProtectedFilePatterns       *string  `json:"protected_file_patterns"`
+       EnablePush                    *bool    `json:"enable_push"`
+       EnablePushWhitelist           *bool    `json:"enable_push_whitelist"`
+       PushWhitelistUsernames        []string `json:"push_whitelist_usernames"`
+       PushWhitelistTeams            []string `json:"push_whitelist_teams"`
+       PushWhitelistDeployKeys       *bool    `json:"push_whitelist_deploy_keys"`
+       EnableMergeWhitelist          *bool    `json:"enable_merge_whitelist"`
+       MergeWhitelistUsernames       []string `json:"merge_whitelist_usernames"`
+       MergeWhitelistTeams           []string `json:"merge_whitelist_teams"`
+       EnableStatusCheck             *bool    `json:"enable_status_check"`
+       StatusCheckContexts           []string `json:"status_check_contexts"`
+       RequiredApprovals             *int64   `json:"required_approvals"`
+       EnableApprovalsWhitelist      *bool    `json:"enable_approvals_whitelist"`
+       ApprovalsWhitelistUsernames   []string `json:"approvals_whitelist_username"`
+       ApprovalsWhitelistTeams       []string `json:"approvals_whitelist_teams"`
+       BlockOnRejectedReviews        *bool    `json:"block_on_rejected_reviews"`
+       BlockOnOfficialReviewRequests *bool    `json:"block_on_official_review_requests"`
+       BlockOnOutdatedBranch         *bool    `json:"block_on_outdated_branch"`
+       DismissStaleApprovals         *bool    `json:"dismiss_stale_approvals"`
+       RequireSignedCommits          *bool    `json:"require_signed_commits"`
+       ProtectedFilePatterns         *string  `json:"protected_file_patterns"`
 }
 
 // ListBranchProtectionsOptions list branch protection options
@@ -92,6 +95,9 @@ type ListBranchProtectionsOptions struct {
 
 // ListBranchProtections list branch protections for a repo
 func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
@@ -104,6 +110,9 @@ func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtect
 
 // GetBranchProtection gets a branch protection
 func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
@@ -114,6 +123,9 @@ func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtectio
 
 // CreateBranchProtection creates a branch protection for a repo
 func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
@@ -128,6 +140,9 @@ func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProt
 
 // EditBranchProtection edits a branch protection for a repo
 func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
@@ -142,6 +157,9 @@ func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchPr
 
 // DeleteBranchProtection deletes a branch protection for a repo
 func (c *Client) DeleteBranchProtection(owner, repo, name string) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
+               return nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, err
        }
index 63c4eafd5d79def3088740b385aaf91c3a3ef735..2019e22db564d4bc0b4342a9067213c5891c81ba 100644 (file)
@@ -17,6 +17,9 @@ type ListCollaboratorsOptions struct {
 
 // ListCollaborators list a repository's collaborators
 func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        collaborators := make([]*User, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET",
@@ -27,6 +30,9 @@ func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptio
 
 // IsCollaborator check if a user is a collaborator of a repository
 func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
+               return false, nil, err
+       }
        status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
        if err != nil {
                return false, resp, err
@@ -78,6 +84,9 @@ func (opt AddCollaboratorOption) Validate() error {
 
 // AddCollaborator add some user as a collaborator of a repository
 func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollaboratorOption) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
+               return nil, err
+       }
        if err := opt.Validate(); err != nil {
                return nil, err
        }
@@ -91,6 +100,9 @@ func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollabo
 
 // DeleteCollaborator remove a collaborator from a repository
 func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE",
                fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
        return resp, err
index 4b90b9cb65b4ff4876ab33ece7929693cc59b05a..9bb65b3b11e2563e3bca1b0fe4e83ccac5d82638 100644 (file)
@@ -42,11 +42,12 @@ type RepoCommit struct {
 // Commit contains information generated from a Git commit.
 type Commit struct {
        *CommitMeta
-       HTMLURL    string        `json:"html_url"`
-       RepoCommit *RepoCommit   `json:"commit"`
-       Author     *User         `json:"author"`
-       Committer  *User         `json:"committer"`
-       Parents    []*CommitMeta `json:"parents"`
+       HTMLURL    string                 `json:"html_url"`
+       RepoCommit *RepoCommit            `json:"commit"`
+       Author     *User                  `json:"author"`
+       Committer  *User                  `json:"committer"`
+       Parents    []*CommitMeta          `json:"parents"`
+       Files      []*CommitAffectedFiles `json:"files"`
 }
 
 // CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
@@ -55,8 +56,16 @@ type CommitDateOptions struct {
        Committer time.Time `json:"committer"`
 }
 
+// CommitAffectedFiles store information about files affected by the commit
+type CommitAffectedFiles struct {
+       Filename string `json:"filename"`
+}
+
 // GetSingleCommit returns a single commit
 func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &commitID); err != nil {
+               return nil, nil, err
+       }
        commit := new(Commit)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s", user, repo, commitID), nil, nil, &commit)
        return commit, resp, err
@@ -80,6 +89,9 @@ func (opt *ListCommitOptions) QueryEncode() string {
 
 // ListRepoCommits return list of commits from a repo
 func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo))
        opt.setDefaults()
        commits := make([]*Commit, 0, opt.PageSize)
index b104980a8d287000633a266cd6aff356c959d3e8..6f99ea06bfba5f70c31f0d0cfad158b18e5f91a1 100644 (file)
@@ -9,6 +9,8 @@ import (
        "bytes"
        "encoding/json"
        "fmt"
+       "net/url"
+       "strings"
 )
 
 // FileOptions options for all file APIs
@@ -23,6 +25,8 @@ type FileOptions struct {
        Author    Identity          `json:"author"`
        Committer Identity          `json:"committer"`
        Dates     CommitDateOptions `json:"dates"`
+       // Add a Signed-off-by trailer by the committer at the end of the commit log message.
+       Signoff bool `json:"signoff"`
 }
 
 // CreateFileOptions options for creating files
@@ -113,25 +117,65 @@ type FileDeleteResponse struct {
 }
 
 // GetFile downloads a file of repository, ref can be branch/tag/commit.
-// e.g.: ref -> master, tree -> macaron.go(no leading slash)
-func (c *Client) GetFile(user, repo, ref, tree string) ([]byte, *Response, error) {
-       return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", user, repo, ref, tree), nil, nil)
+// e.g.: ref -> master, filepath -> README.md (no leading slash)
+func (c *Client) GetFile(owner, repo, ref, filepath string) ([]byte, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
+       filepath = pathEscapeSegments(filepath)
+       if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
+               ref = pathEscapeSegments(ref)
+               return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", owner, repo, ref, filepath), nil, nil)
+       }
+       return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil)
 }
 
-// GetContents get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
+// GetContents get the metadata and contents of a file in a repository
 // ref is optional
 func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) {
+       data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath)
+       if err != nil {
+               return nil, resp, err
+       }
        cr := new(ContentsResponse)
-       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, ref), jsonHeader, nil, cr)
+       if json.Unmarshal(data, &cr) != nil {
+               return nil, resp, fmt.Errorf("expect file, got directory")
+       }
        return cr, resp, err
 }
 
+// ListContents gets a list of entries in a dir
+// ref is optional
+func (c *Client) ListContents(owner, repo, ref, filepath string) ([]*ContentsResponse, *Response, error) {
+       data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath)
+       if err != nil {
+               return nil, resp, err
+       }
+       crl := make([]*ContentsResponse, 0)
+       if json.Unmarshal(data, &crl) != nil {
+               return nil, resp, fmt.Errorf("expect directory, got file")
+       }
+       return crl, resp, err
+}
+
+func (c *Client) getDirOrFileContents(owner, repo, ref, filepath string) ([]byte, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
+       filepath = pathEscapeSegments(strings.TrimPrefix(filepath, "/"))
+       return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), jsonHeader, nil)
+}
+
 // CreateFile create a file in a repository
 func (c *Client) CreateFile(owner, repo, filepath string, opt CreateFileOptions) (*FileResponse, *Response, error) {
        var err error
        if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
                return nil, nil, err
        }
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
+       filepath = pathEscapeSegments(filepath)
 
        body, err := json.Marshal(&opt)
        if err != nil {
@@ -149,6 +193,11 @@ func (c *Client) UpdateFile(owner, repo, filepath string, opt UpdateFileOptions)
                return nil, nil, err
        }
 
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
+       filepath = pathEscapeSegments(filepath)
+
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
@@ -164,6 +213,10 @@ func (c *Client) DeleteFile(owner, repo, filepath string, opt DeleteFileOptions)
        if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
                return nil, err
        }
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
+       filepath = pathEscapeSegments(filepath)
 
        body, err := json.Marshal(&opt)
        if err != nil {
index cfdfe0cd42f6d1cf6b16ab4b7656883492eabc58..ee2ff4084e32c0d5f364cc111342749e4082a555 100644 (file)
@@ -46,6 +46,9 @@ func (opt *ListDeployKeysOptions) QueryEncode() string {
 
 // ListDeployKeys list all the deploy keys of one repository
 func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([]*DeployKey, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/keys", user, repo))
        opt.setDefaults()
        link.RawQuery = opt.QueryEncode()
@@ -56,6 +59,9 @@ func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([
 
 // GetDeployKey get one deploy key with key id
 func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        key := new(DeployKey)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key)
        return key, resp, err
@@ -63,6 +69,9 @@ func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Resp
 
 // CreateDeployKey options when create one deploy key
 func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opt)
        if err != nil {
                return nil, nil, err
@@ -74,6 +83,9 @@ func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*Deplo
 
 // DeleteDeployKey delete deploy key with key id
 func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil)
        return resp, err
 }
index 518c6ae649e667ea8a2710556bcca99de2d06b7b..91f19d6aea47876d6b60e18f35adbf324ada4b18 100644 (file)
@@ -22,10 +22,8 @@ const (
        GitServiceGitlab GitServiceType = "gitlab"
        // GitServiceGitea represents a gitea service
        GitServiceGitea GitServiceType = "gitea"
-
-       // Not supported jet
-       // // GitServiceGogs represents a gogs service
-       // GitServiceGogs GitServiceType = "gogs"
+       // GitServiceGogs represents a gogs service
+       GitServiceGogs GitServiceType = "gogs"
 )
 
 // MigrateRepoOption options for migrating a repository from an external service
@@ -33,21 +31,22 @@ type MigrateRepoOption struct {
        RepoName  string `json:"repo_name"`
        RepoOwner string `json:"repo_owner"`
        // deprecated use RepoOwner
-       RepoOwnerID  int64          `json:"uid"`
-       CloneAddr    string         `json:"clone_addr"`
-       Service      GitServiceType `json:"service"`
-       AuthUsername string         `json:"auth_username"`
-       AuthPassword string         `json:"auth_password"`
-       AuthToken    string         `json:"auth_token"`
-       Mirror       bool           `json:"mirror"`
-       Private      bool           `json:"private"`
-       Description  string         `json:"description"`
-       Wiki         bool           `json:"wiki"`
-       Milestones   bool           `json:"milestones"`
-       Labels       bool           `json:"labels"`
-       Issues       bool           `json:"issues"`
-       PullRequests bool           `json:"pull_requests"`
-       Releases     bool           `json:"releases"`
+       RepoOwnerID    int64          `json:"uid"`
+       CloneAddr      string         `json:"clone_addr"`
+       Service        GitServiceType `json:"service"`
+       AuthUsername   string         `json:"auth_username"`
+       AuthPassword   string         `json:"auth_password"`
+       AuthToken      string         `json:"auth_token"`
+       Mirror         bool           `json:"mirror"`
+       Private        bool           `json:"private"`
+       Description    string         `json:"description"`
+       Wiki           bool           `json:"wiki"`
+       Milestones     bool           `json:"milestones"`
+       Labels         bool           `json:"labels"`
+       Issues         bool           `json:"issues"`
+       PullRequests   bool           `json:"pull_requests"`
+       Releases       bool           `json:"releases"`
+       MirrorInterval string         `json:"mirror_interval"`
 }
 
 // Validate the MigrateRepoOption struct
@@ -67,17 +66,24 @@ func (opt *MigrateRepoOption) Validate(c *Client) error {
        switch opt.Service {
        case GitServiceGithub:
                if len(opt.AuthToken) == 0 {
-                       return fmt.Errorf("github require token authentication")
+                       return fmt.Errorf("github requires token authentication")
                }
        case GitServiceGitlab, GitServiceGitea:
                if len(opt.AuthToken) == 0 {
-                       return fmt.Errorf("%s require token authentication", opt.Service)
+                       return fmt.Errorf("%s requires token authentication", opt.Service)
                }
                // Gitlab is supported since 1.12.0 but api cant handle it until 1.13.0
                // https://github.com/go-gitea/gitea/pull/12672
                if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
                        return fmt.Errorf("migrate from service %s need gitea >= 1.13.0", opt.Service)
                }
+       case GitServiceGogs:
+               if len(opt.AuthToken) == 0 {
+                       return fmt.Errorf("gogs requires token authentication")
+               }
+               if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
+                       return fmt.Errorf("migrate from service gogs need gitea >= 1.14.0")
+               }
        }
        return nil
 }
index fa1698a4999c15ac790b33de1ffa5c173075aeb7..c954a80ef2f300a60a10b4941f2a76de203ad0a0 100644 (file)
@@ -27,7 +27,11 @@ type GitObject struct {
 
 // GetRepoRef get one ref's information of one repository
 func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        ref = strings.TrimPrefix(ref, "refs/")
+       ref = pathEscapeSegments(ref)
        r := new(Reference)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil, &r)
        if _, ok := err.(*json.UnmarshalTypeError); ok {
@@ -42,7 +46,12 @@ func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, erro
 
 // GetRepoRefs get list of ref's information of one repository
 func (c *Client) GetRepoRefs(user, repo, ref string) ([]*Reference, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        ref = strings.TrimPrefix(ref, "refs/")
+       ref = pathEscapeSegments(ref)
+
        data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil)
        if err != nil {
                return nil, resp, err
diff --git a/vendor/code.gitea.io/sdk/gitea/repo_stars.go b/vendor/code.gitea.io/sdk/gitea/repo_stars.go
new file mode 100644 (file)
index 0000000..01243c2
--- /dev/null
@@ -0,0 +1,96 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package gitea
+
+import (
+       "fmt"
+       "net/http"
+)
+
+// ListStargazersOptions options for listing a repository's stargazers
+type ListStargazersOptions struct {
+       ListOptions
+}
+
+// ListRepoStargazers list a repository's stargazers
+func (c *Client) ListRepoStargazers(user, repo string, opt ListStargazersOptions) ([]*User, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
+       opt.setDefaults()
+       stargazers := make([]*User, 0, opt.PageSize)
+       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/stargazers?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &stargazers)
+       return stargazers, resp, err
+}
+
+// GetStarredRepos returns the repos that the given user has starred
+func (c *Client) GetStarredRepos(user string) ([]*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
+       repos := make([]*Repository, 0, 10)
+       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/starred", user), jsonHeader, nil, &repos)
+       return repos, resp, err
+}
+
+// GetMyStarredRepos returns the repos that the authenticated user has starred
+func (c *Client) GetMyStarredRepos() ([]*Repository, *Response, error) {
+       repos := make([]*Repository, 0, 10)
+       resp, err := c.getParsedResponse("GET", "/user/starred", jsonHeader, nil, &repos)
+       return repos, resp, err
+}
+
+// IsRepoStarring returns whether the authenticated user has starred the repo or not
+func (c *Client) IsRepoStarring(user, repo string) (bool, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return false, nil, err
+       }
+       _, resp, err := c.getResponse("GET", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
+       if resp != nil {
+               switch resp.StatusCode {
+               case http.StatusNotFound:
+                       return false, resp, nil
+               case http.StatusNoContent:
+                       return true, resp, nil
+               default:
+                       return false, resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
+               }
+       }
+       return false, nil, err
+}
+
+// StarRepo star specified repo as the authenticated user
+func (c *Client) StarRepo(user, repo string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, err
+       }
+       _, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
+       if resp != nil {
+               switch resp.StatusCode {
+               case http.StatusNoContent:
+                       return resp, nil
+               default:
+                       return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
+               }
+       }
+       return nil, err
+}
+
+// UnStarRepo remove star to specified repo as the authenticated user
+func (c *Client) UnStarRepo(user, repo string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, err
+       }
+       _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
+       if resp != nil {
+               switch resp.StatusCode {
+               case http.StatusNoContent:
+                       return resp, nil
+               default:
+                       return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
+               }
+       }
+       return nil, err
+}
index 19eed5b9af3e9f797412ae6caf4f7d5f9d48b081..0a3c806a410bcff38e118ed4a6bcf6b95f75dad9 100644 (file)
@@ -24,8 +24,25 @@ type ListRepoTagsOptions struct {
 
 // ListRepoTags list all the branches of one repository
 func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Tag, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        tags := make([]*Tag, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &tags)
        return tags, resp, err
 }
+
+// DeleteTag deletes a tag from a repository, if no release refers to it
+func (c *Client) DeleteTag(user, repo string, tag string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
+               return nil, err
+       }
+       if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
+               return nil, err
+       }
+       _, resp, err := c.getResponse("DELETE",
+               fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag),
+               nil, nil)
+       return resp, err
+}
index fd05d0899c65ac2898d3f8142a5ebee9c9709aca..92f2228cd9327f93539d3119ca5d51f8e79b1211 100644 (file)
@@ -22,6 +22,9 @@ type topicsList struct {
 
 // ListRepoTopics list all repository's topics
 func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([]string, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
 
        list := new(topicsList)
@@ -34,9 +37,10 @@ func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([
 
 // SetRepoTopics replaces the list of repo's topics
 func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, error) {
-
+       if err := escapeValidatePathSegments(&user, &repo); err != nil {
+               return nil, err
+       }
        l := topicsList{Topics: list}
-
        body, err := json.Marshal(&l)
        if err != nil {
                return nil, err
@@ -47,12 +51,18 @@ func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, err
 
 // AddRepoTopic adds a topic to a repo's topics list
 func (c *Client) AddRepoTopic(user, repo, topic string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
        return resp, err
 }
 
 // DeleteRepoTopic deletes a topic from repo's topics list
 func (c *Client) DeleteRepoTopic(user, repo, topic string) (*Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
        return resp, err
 }
index d8d661bc441d9acf3e064d318d2038ba98080389..be06010a785c4dbc183b39b6a0d7f0a25f04530d 100644 (file)
@@ -20,6 +20,9 @@ type TransferRepoOption struct {
 
 // TransferRepo transfers the ownership of a repository
 func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
+               return nil, nil, err
+       }
        if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
                return nil, nil, err
        }
index ce36e460b171576c39b0425d851555ceb1f50e05..452394a3c8fcbcb457bc0c3f398ec4f8af53a83f 100644 (file)
@@ -31,6 +31,9 @@ type GitTreeResponse struct {
 // GetTrees downloads a file of repository, ref can be branch/tag/commit.
 // e.g.: ref -> master, tree -> macaron.go(no leading slash)
 func (c *Client) GetTrees(user, repo, ref string, recursive bool) (*GitTreeResponse, *Response, error) {
+       if err := escapeValidatePathSegments(&user, &repo, &ref); err != nil {
+               return nil, nil, err
+       }
        trees := new(GitTreeResponse)
        var path = fmt.Sprintf("/repos/%s/%s/git/trees/%s", user, repo, ref)
        if recursive {
index 7358705fe8206795fe1b72a9ab38ee2e656e8e80..f499aff2218c517081e8485ff6cc566336c44cc4 100644 (file)
@@ -22,6 +22,9 @@ type WatchInfo struct {
 
 // GetWatchedRepos list all the watched repos of user
 func (c *Client) GetWatchedRepos(user string) ([]*Repository, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        repos := make([]*Repository, 0, 10)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/subscriptions", user), nil, nil, &repos)
        return repos, resp, err
@@ -35,8 +38,11 @@ func (c *Client) GetMyWatchedRepos() ([]*Repository, *Response, error) {
 }
 
 // CheckRepoWatch check if the current user is watching a repo
-func (c *Client) CheckRepoWatch(repoUser, repoName string) (bool, *Response, error) {
-       status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), nil, nil)
+func (c *Client) CheckRepoWatch(owner, repo string) (bool, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return false, nil, err
+       }
+       status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil)
        if err != nil {
                return false, resp, err
        }
@@ -51,8 +57,11 @@ func (c *Client) CheckRepoWatch(repoUser, repoName string) (bool, *Response, err
 }
 
 // WatchRepo start to watch a repository
-func (c *Client) WatchRepo(repoUser, repoName string) (*Response, error) {
-       status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), nil, nil)
+func (c *Client) WatchRepo(owner, repo string) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
+       status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil)
        if err != nil {
                return resp, err
        }
@@ -63,8 +72,11 @@ func (c *Client) WatchRepo(repoUser, repoName string) (*Response, error) {
 }
 
 // UnWatchRepo stop to watch a repository
-func (c *Client) UnWatchRepo(repoUser, repoName string) (*Response, error) {
-       status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), nil, nil)
+func (c *Client) UnWatchRepo(owner, repo string) (*Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, err
+       }
+       status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil)
        if err != nil {
                return resp, err
        }
index 9fa0a7ae7e95e847fb0f1711a441380b41091539..134d2ad9933aa147ddfd673b09d0a490204eae54 100644 (file)
@@ -6,13 +6,15 @@ package gitea
 
 // GlobalUISettings represent the global ui settings of a gitea instance witch is exposed by API
 type GlobalUISettings struct {
+       DefaultTheme     string   `json:"default_theme"`
        AllowedReactions []string `json:"allowed_reactions"`
 }
 
 // GlobalRepoSettings represent the global repository settings of a gitea instance witch is exposed by API
 type GlobalRepoSettings struct {
-       MirrorsDisabled bool `json:"mirrors_disabled"`
-       HTTPGitDisabled bool `json:"http_git_disabled"`
+       MirrorsDisabled    bool `json:"mirrors_disabled"`
+       HTTPGitDisabled    bool `json:"http_git_disabled"`
+       MigrationsDisabled bool `json:"migrations_disabled"`
 }
 
 // GlobalAPISettings contains global api settings exposed by it
index 7c23b891ed93e7ce4da0ab8b8357b67414158399..fe5d9711f98bf3f66dff50fd3871c6b5c2643993 100644 (file)
@@ -8,6 +8,7 @@ import (
        "bytes"
        "encoding/json"
        "fmt"
+       "net/url"
        "time"
 )
 
@@ -51,12 +52,15 @@ type CreateStatusOption struct {
 
 // CreateStatus creates a new Status for a given Commit
 func (c *Client) CreateStatus(owner, repo, sha string, opts CreateStatusOption) (*Status, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo); err != nil {
+               return nil, nil, err
+       }
        body, err := json.Marshal(&opts)
        if err != nil {
                return nil, nil, err
        }
        status := new(Status)
-       resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/statuses/%s", owner, repo, sha), jsonHeader, bytes.NewReader(body), status)
+       resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/statuses/%s", owner, repo, url.QueryEscape(sha)), jsonHeader, bytes.NewReader(body), status)
        return status, resp, err
 }
 
@@ -65,11 +69,14 @@ type ListStatusesOption struct {
        ListOptions
 }
 
-// ListStatuses returns all statuses for a given Commit
-func (c *Client) ListStatuses(owner, repo, sha string, opt ListStatusesOption) ([]*Status, *Response, error) {
+// ListStatuses returns all statuses for a given Commit by ref
+func (c *Client) ListStatuses(owner, repo, ref string, opt ListStatusesOption) ([]*Status, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo, &ref); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        statuses := make([]*Status, 0, opt.PageSize)
-       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/statuses?%s", owner, repo, sha, opt.getURLQuery().Encode()), nil, nil, &statuses)
+       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/statuses?%s", owner, repo, ref, opt.getURLQuery().Encode()), jsonHeader, nil, &statuses)
        return statuses, resp, err
 }
 
@@ -85,8 +92,17 @@ type CombinedStatus struct {
 }
 
 // GetCombinedStatus returns the CombinedStatus for a given Commit
-func (c *Client) GetCombinedStatus(owner, repo, sha string) (*CombinedStatus, *Response, error) {
+func (c *Client) GetCombinedStatus(owner, repo, ref string) (*CombinedStatus, *Response, error) {
+       if err := escapeValidatePathSegments(&owner, &repo, &ref); err != nil {
+               return nil, nil, err
+       }
        status := new(CombinedStatus)
-       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/status", owner, repo, sha), nil, nil, status)
+       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/status", owner, repo, ref), jsonHeader, nil, status)
+
+       // gitea api return empty body if nothing here jet
+       if resp != nil && resp.StatusCode == 200 && err != nil {
+               return status, resp, nil
+       }
+
        return status, resp, err
 }
index e909c6861c814bbdda65c46b33a652034a7d0aaf..209523632d680eaa3b09a207b2d45f3f0cd982c3 100644 (file)
@@ -30,6 +30,9 @@ type User struct {
 
 // GetUserInfo get user info by user's name
 func (c *Client) GetUserInfo(user string) (*User, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        u := new(User)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s", user), nil, nil, u)
        return u, resp, err
index 7f7696dc4dcdf6ea0df8ff5b98440845302d9d20..2921eea7df858e0f1d521ba8adfe7ad63a8cf298 100644 (file)
@@ -9,6 +9,7 @@ import (
        "bytes"
        "encoding/json"
        "fmt"
+       "net/url"
        "reflect"
 )
 
@@ -27,12 +28,15 @@ type ListAccessTokensOptions struct {
 
 // ListAccessTokens lists all the access tokens of user
 func (c *Client) ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, *Response, error) {
-       if len(c.username) == 0 {
+       c.mutex.RLock()
+       username := c.username
+       c.mutex.RUnlock()
+       if len(username) == 0 {
                return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed")
        }
        opts.setDefaults()
        tokens := make([]*AccessToken, 0, opts.PageSize)
-       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/tokens?%s", c.username, opts.getURLQuery().Encode()), jsonHeader, nil, &tokens)
+       resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/tokens?%s", url.PathEscape(username), opts.getURLQuery().Encode()), jsonHeader, nil, &tokens)
        return tokens, resp, err
 }
 
@@ -43,7 +47,10 @@ type CreateAccessTokenOption struct {
 
 // CreateAccessToken create one access token with options
 func (c *Client) CreateAccessToken(opt CreateAccessTokenOption) (*AccessToken, *Response, error) {
-       if len(c.username) == 0 {
+       c.mutex.RLock()
+       username := c.username
+       c.mutex.RUnlock()
+       if len(username) == 0 {
                return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed")
        }
        body, err := json.Marshal(&opt)
@@ -51,13 +58,16 @@ func (c *Client) CreateAccessToken(opt CreateAccessTokenOption) (*AccessToken, *
                return nil, nil, err
        }
        t := new(AccessToken)
-       resp, err := c.getParsedResponse("POST", fmt.Sprintf("/users/%s/tokens", c.username), jsonHeader, bytes.NewReader(body), t)
+       resp, err := c.getParsedResponse("POST", fmt.Sprintf("/users/%s/tokens", url.PathEscape(username)), jsonHeader, bytes.NewReader(body), t)
        return t, resp, err
 }
 
 // DeleteAccessToken delete token, identified by ID and if not available by name
 func (c *Client) DeleteAccessToken(value interface{}) (*Response, error) {
-       if len(c.username) == 0 {
+       c.mutex.RLock()
+       username := c.username
+       c.mutex.RUnlock()
+       if len(username) == 0 {
                return nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed")
        }
 
@@ -75,6 +85,6 @@ func (c *Client) DeleteAccessToken(value interface{}) (*Response, error) {
                return nil, fmt.Errorf("only string and int64 supported")
        }
 
-       _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/users/%s/tokens/%s", c.username, token), jsonHeader, nil)
+       _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/users/%s/tokens/%s", url.PathEscape(username), url.PathEscape(token)), jsonHeader, nil)
        return resp, err
 }
index c8bafc01e5e35a24ab638de74e6ebe433ffc222e..7bd340ca26668019b15346addf792c2e3f4ab150 100644 (file)
@@ -21,6 +21,9 @@ func (c *Client) ListMyFollowers(opt ListFollowersOptions) ([]*User, *Response,
 
 // ListFollowers list all the followers of one user
 func (c *Client) ListFollowers(user string, opt ListFollowersOptions) ([]*User, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        users := make([]*User, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/followers?%s", user, opt.getURLQuery().Encode()), nil, nil, &users)
@@ -42,6 +45,9 @@ func (c *Client) ListMyFollowing(opt ListFollowingOptions) ([]*User, *Response,
 
 // ListFollowing list all the users the user followed
 func (c *Client) ListFollowing(user string, opt ListFollowingOptions) ([]*User, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        users := make([]*User, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/following?%s", user, opt.getURLQuery().Encode()), nil, nil, &users)
@@ -50,24 +56,38 @@ func (c *Client) ListFollowing(user string, opt ListFollowingOptions) ([]*User,
 
 // IsFollowing if current user followed the target
 func (c *Client) IsFollowing(target string) (bool, *Response) {
+       if err := escapeValidatePathSegments(&target); err != nil {
+               // ToDo return err
+               return false, nil
+       }
        _, resp, err := c.getResponse("GET", fmt.Sprintf("/user/following/%s", target), nil, nil)
        return err == nil, resp
 }
 
 // IsUserFollowing if the user followed the target
 func (c *Client) IsUserFollowing(user, target string) (bool, *Response) {
+       if err := escapeValidatePathSegments(&user, &target); err != nil {
+               // ToDo return err
+               return false, nil
+       }
        _, resp, err := c.getResponse("GET", fmt.Sprintf("/users/%s/following/%s", user, target), nil, nil)
        return err == nil, resp
 }
 
 // Follow set current user follow the target
 func (c *Client) Follow(target string) (*Response, error) {
+       if err := escapeValidatePathSegments(&target); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/following/%s", target), nil, nil)
        return resp, err
 }
 
 // Unfollow set current user unfollow the target
 func (c *Client) Unfollow(target string) (*Response, error) {
+       if err := escapeValidatePathSegments(&target); err != nil {
+               return nil, err
+       }
        _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/following/%s", target), nil, nil)
        return resp, err
 }
index d3d64b41e4649df4ebaafc1208a07505bd0fafe5..6c1b9d10f345a8277ceddb29e7f47448d9923421 100644 (file)
@@ -40,6 +40,9 @@ type ListGPGKeysOptions struct {
 
 // ListGPGKeys list all the GPG keys of the user
 func (c *Client) ListGPGKeys(user string, opt ListGPGKeysOptions) ([]*GPGKey, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        keys := make([]*GPGKey, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/gpg_keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys)
index d005f00bef588e2df426da89b3acfa10cbda5f1a..02795baefc6567a69d1be2f7bc0527b7784e5140 100644 (file)
@@ -31,6 +31,9 @@ type ListPublicKeysOptions struct {
 
 // ListPublicKeys list all the public keys of the user
 func (c *Client) ListPublicKeys(user string, opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) {
+       if err := escapeValidatePathSegments(&user); err != nil {
+               return nil, nil, err
+       }
        opt.setDefaults()
        keys := make([]*PublicKey, 0, opt.PageSize)
        resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys)
index 747117ccc27230fa93e0cef3e69cbfabe53272fd..ed8a2ae5dd728d334f53db118d0edc71117a1600 100644 (file)
@@ -22,14 +22,8 @@ func (c *Client) ServerVersion() (string, *Response, error) {
 // CheckServerVersionConstraint validates that the login's server satisfies a
 // given version constraint such as ">= 1.11.0+dev"
 func (c *Client) CheckServerVersionConstraint(constraint string) error {
-       c.versionLock.RLock()
-       if c.serverVersion == nil {
-               c.versionLock.RUnlock()
-               if err := c.loadClientServerVersion(); err != nil {
-                       return err
-               }
-       } else {
-               c.versionLock.RUnlock()
+       if err := c.loadServerVersion(); err != nil {
+               return err
        }
 
        check, err := version.NewConstraint(constraint)
@@ -37,48 +31,48 @@ func (c *Client) CheckServerVersionConstraint(constraint string) error {
                return err
        }
        if !check.Check(c.serverVersion) {
-               return fmt.Errorf("gitea server at %s does not satisfy version constraint %s", c.url, constraint)
+               c.mutex.RLock()
+               url := c.url
+               c.mutex.RUnlock()
+               return fmt.Errorf("gitea server at %s does not satisfy version constraint %s", url, constraint)
        }
        return nil
 }
 
 // predefined versions only have to be parsed by library once
 var (
-       version1_10_0, _ = version.NewVersion("1.10.0")
        version1_11_0, _ = version.NewVersion("1.11.0")
        version1_12_0, _ = version.NewVersion("1.12.0")
        version1_13_0, _ = version.NewVersion("1.13.0")
+       version1_14_0, _ = version.NewVersion("1.14.0")
 )
 
 // checkServerVersionGreaterThanOrEqual is internally used to speed up things and ignore issues with prerelease
 func (c *Client) checkServerVersionGreaterThanOrEqual(v *version.Version) error {
-       c.versionLock.RLock()
-       if c.serverVersion == nil {
-               c.versionLock.RUnlock()
-               if err := c.loadClientServerVersion(); err != nil {
-                       return err
-               }
-       } else {
-               c.versionLock.RUnlock()
+       if err := c.loadServerVersion(); err != nil {
+               return err
        }
 
        if !c.serverVersion.GreaterThanOrEqual(v) {
-               return fmt.Errorf("gitea server at %s is older than %s", c.url, v.Original())
+               c.mutex.RLock()
+               url := c.url
+               c.mutex.RUnlock()
+               return fmt.Errorf("gitea server at %s is older than %s", url, v.Original())
        }
        return nil
 }
 
-// loadClientServerVersion init the serverVersion variable
-func (c *Client) loadClientServerVersion() error {
-       c.versionLock.Lock()
-       defer c.versionLock.Unlock()
-
-       raw, _, err := c.ServerVersion()
-       if err != nil {
-               return err
-       }
-       if c.serverVersion, err = version.NewVersion(raw); err != nil {
-               return err
-       }
-       return nil
+// loadServerVersion init the serverVersion variable
+func (c *Client) loadServerVersion() (err error) {
+       c.getVersionOnce.Do(func() {
+               raw, _, err2 := c.ServerVersion()
+               if err2 != nil {
+                       err = err2
+                       return
+               }
+               if c.serverVersion, err = version.NewVersion(raw); err != nil {
+                       return
+               }
+       })
+       return
 }
index 96ac0b77d9a054c261075707e3d58364da8e10a7..8c462983134675fb3a2ce10ef34f8bed2016ae8f 100644 (file)
@@ -5,7 +5,7 @@ cloud.google.com/go/compute/metadata
 ## explicit
 code.gitea.io/gitea-vet
 code.gitea.io/gitea-vet/checks
-# code.gitea.io/sdk/gitea v0.13.2
+# code.gitea.io/sdk/gitea v0.14.0
 ## explicit
 code.gitea.io/sdk/gitea
 # gitea.com/go-chi/binding v0.0.0-20210301195521-1fe1c9a555e7