Skip to content

Commit

Permalink
adds pull requests service to the stash client
Browse files Browse the repository at this point in the history
PullRequestsService permits retrieving pull requests  and listing them for a given
repository.

Signed-off-by: Soule BA <[email protected]>
  • Loading branch information
souleb committed Sep 23, 2021
1 parent b7839f2 commit 18da9ff
Show file tree
Hide file tree
Showing 3 changed files with 812 additions and 0 deletions.
2 changes: 2 additions & 0 deletions stash/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ type Client struct {
Repositories Repositories
Branches Branches
Commits Commits
PullRequests PullRequests
}

// RateLimiter is the interface that wraps the basic Wait method.
Expand Down Expand Up @@ -208,6 +209,7 @@ func NewClient(httpClient *http.Client, host string, header *http.Header, logger
c.Repositories = &RepositoriesService{Client: c}
c.Branches = &BranchesService{Client: c}
c.Commits = &CommitsService{Client: c}
c.PullRequests = &PullRequestsService{Client: c}

return c, nil
}
Expand Down
318 changes: 318 additions & 0 deletions stash/pull_requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package stash

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
)

const (
pullRequestsURI = "pull-requests"
)

// PullRequests interface defines the methods that can be used to
// retrieve pull requests of a repository.
type PullRequests interface {
Get(ctx context.Context, projectKey, repositorySlug string, prID int) (*PullRequest, error)
List(ctx context.Context, projectKey, repositorySlug string, opts *PagingOptions) (*PullRequestList, error)
Create(ctx context.Context, projectKey, repositorySlug string, pr *CreatePullRequest) (*PullRequest, error)
Update(ctx context.Context, projectKey, repositorySlug string, pr *PullRequest) (*PullRequest, error)
Delete(ctx context.Context, projectKey, repositorySlug string, IDVersion IDVersion) error
}

// PullRequestsService is a client for communicating with stash pull requests endpoint
// bitbucket-server API docs: https://docs.atlassian.com/bitbucket-server/rest/5.16.0/bitbucket-rest.html
type PullRequestsService service

// Participant is a participant of a pull request
type Participant struct {
// Approved indicates if the participant has approved the pull request
Approved bool `json:"approved,omitempty"`
// Role indicates the role of the participant
Role string `json:"role,omitempty"`
// Status indicates the status of the participant
Status string `json:"status,omitempty"`
// User is the participant
User `json:"user,omitempty"`
}

// Ref represents a git reference
type Ref struct {
// DisplayID is the reference name
DisplayID string `json:"displayId,omitempty"`
// ID is the reference id i.e a git reference
ID string `json:"id,omitempty"`
// LatestCommit is the latest commit of the reference
LatestCommit string `json:"latestCommit,omitempty"`
// Repository is the repository of the reference
Repository `json:"repository,omitempty"`
// Type is the type of the reference
Type string `json:"type,omitempty"`
}

// CreatePullRequest creates a pull request from
// a source branch or tag to a target branch.
type CreatePullRequest struct {
// Closed indicates if the pull request is closed
Closed bool `json:"closed,omitempty"`
// Description is the description of the pull request
Description string `json:"description,omitempty"`
// FromRef is the source branch or tag
FromRef Ref `json:"fromRef,omitempty"`
// Locked indicates if the pull request is locked
Locked bool `json:"locked,omitempty"`
// Open indicates if the pull request is open
Open bool `json:"open,omitempty"`
// State is the state of the pull request
State string `json:"state,omitempty"`
// Title is the title of the pull request
Title string `json:"title,omitempty"`
// ToRef is the target branch
ToRef Ref `json:"toRef,omitempty"`
// Reviewers is the list of reviewers
Reviewers []User `json:"reviewers,omitempty"`
}

// IDVersion is a pull request id and version
type IDVersion struct {
// ID is the id of the pull request
ID int `json:"id"`
// Version is the version of the pull request
Version int `json:"version"`
}

// PullRequest is a pull request
type PullRequest struct {
// Session is the session of the pull request
Session `json:"sessionInfo,omitempty"`
// Author is the author of the pull request
Author Participant `json:"author,omitempty"`
// Closed indicates if the pull request is closed
Closed bool `json:"closed,omitempty"`
// CreatedDate is the creation date of the pull request
CreatedDate int64 `json:"createdDate,omitempty"`
// Description is the description of the pull request
Description string `json:"description,omitempty"`
// FromRef is the source branch or tag
FromRef Ref `json:"fromRef,omitempty"`
IDVersion
// Links is a set of hyperlinks that link to other related resources.
Links `json:"links,omitempty"`
// Locked indicates if the pull request is locked
Locked bool `json:"locked,omitempty"`
// Open indicates if the pull request is open
Open bool `json:"open,omitempty"`
// Participants are the participants of the pull request
Participants []Participant `json:"participants,omitempty"`
// Properties are the properties of the pull request
Properties Properties `json:"properties,omitempty"`
// Reviewers are the reviewers of the pull request
Reviewers []Participant `json:"reviewers,omitempty"`
// State is the state of the pull request
State string `json:"state,omitempty"`
// Title is the title of the pull request
Title string `json:"title,omitempty"`
// ToRef is the target branch
ToRef Ref `json:"toRef,omitempty"`
// UpdatedDate is the update date of the pull request
UpdatedDate int64 `json:"updatedDate,omitempty"`
}

// Properties are the properties of a pull request
type Properties struct {
// MergeResult is the merge result of the pull request
MergeResult MergeResult `json:"mergeResult,omitempty"`
// OpenTaskCount is the number of open tasks
OpenTaskCount float64 `json:"openTaskCount,omitempty"`
// ResolvedTaskCount is the number of resolved tasks
ResolvedTaskCount float64 `json:"resolvedTaskCount,omitempty"`
}

// MergeResult is the merge result of a pull request
type MergeResult struct {
// Current is the current merge result
Current bool `json:"current,omitempty"`
// Outcome is the outcome of the merge
Outcome string `json:"outcome,omitempty"`
}

// PullRequestList is a list of pull requests
type PullRequestList struct {
// Paging is the paging information
Paging
// PullRequests are the pull requests
PullRequests []*PullRequest `json:"values,omitempty"`
}

// GetPullRequests returns a list of pull requests
func (p *PullRequestList) GetPullRequests() []*PullRequest {
return p.PullRequests
}

// List returns the list of pull requests.
// Paging is optional and is enabled by providing a PagingOptions struct.
// A pointer to a PullRequestsList struct is returned to retrieve the next page of results.
// List uses the endpoint "GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests".
// https://docs.atlassian.com/bitbucket-server/rest/5.16.0/bitbucket-rest.html
func (s *PullRequestsService) List(ctx context.Context, projectKey, repositorySlug string, opts *PagingOptions) (*PullRequestList, error) {
query := addPaging(&url.Values{}, opts)
req, err := s.Client.NewRequest(ctx, http.MethodGet, newURI(projectsURI, projectKey, RepositoriesURI, repositorySlug, pullRequestsURI), *query, nil, nil)
if err != nil {
return nil, fmt.Errorf("list pull requests request creation failed: %w", err)
}
res, resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("list pull requests failed: %w", err)
}

// As nothing is done with the response body, it is safe to close here
// to avoid leaking connections
defer resp.Body.Close()
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, ErrNotFound
}

p := &PullRequestList{}
if err := json.Unmarshal(res, p); err != nil {
return nil, fmt.Errorf("list pull requests failed, unable to unmarshal repository list json: %w", err)
}

for _, r := range p.GetPullRequests() {
r.Session.set(resp)
}

return p, nil
}

// Get retrieves a pull request given it's ID.
// Get uses the endpoint "GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}".
// https://docs.atlassian.com/bitbucket-server/rest/5.16.0/bitbucket-rest.html
func (s *PullRequestsService) Get(ctx context.Context, projectKey, repositorySlug string, prID int) (*PullRequest, error) {
req, err := s.Client.NewRequest(ctx, http.MethodGet, newURI(projectsURI, projectKey, RepositoriesURI, repositorySlug, pullRequestsURI, strconv.Itoa(prID)), nil, nil, nil)
if err != nil {
return nil, fmt.Errorf("get pull request request creation failed: %w", err)
}
res, resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("get pull request failed: %w", err)
}

// As nothing is done with the response body, it is safe to close here
// to avoid leaking connections
defer resp.Body.Close()
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, ErrNotFound
}

p := &PullRequest{}
if err := json.Unmarshal(res, p); err != nil {
return nil, fmt.Errorf("get pull request failed, unable to unmarshal repository list json: %w", err)
}

p.Session.set(resp)

return p, nil
}

// Create creates a pull request.
// Create uses the endpoint "POST /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests".
func (s *PullRequestsService) Create(ctx context.Context, projectKey, repositorySlug string, pr *CreatePullRequest) (*PullRequest, error) {
header := http.Header{"Content-Type": []string{"application/json"}}
req, err := s.Client.NewRequest(ctx, http.MethodPost, newURI(projectsURI, projectKey, RepositoriesURI, repositorySlug, pullRequestsURI), nil, pr, header)
if err != nil {
return nil, fmt.Errorf("create pull request request creation failed: %w", err)
}
res, resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("create pull request failed: %w", err)
}

p := &PullRequest{}
if err := json.Unmarshal(res, p); err != nil {
return nil, fmt.Errorf("create pull request failed, unable to unmarshal repository list json: %w", err)
}

p.Session.set(resp)

return p, nil
}

// Update updates the pull request with the given ID
// Update uses the endpoint "PUT /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}".
func (s *PullRequestsService) Update(ctx context.Context, projectKey, repositorySlug string, pr *PullRequest) (*PullRequest, error) {
header := http.Header{"Content-Type": []string{"application/json"}}
req, err := s.Client.NewRequest(ctx, http.MethodPut, newURI(projectsURI, projectKey, RepositoriesURI, repositorySlug, pullRequestsURI, strconv.Itoa(pr.ID)), nil, pr, header)
if err != nil {
return nil, fmt.Errorf("update pull request creation failed: %w", err)
}
res, resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("update pull failed: %w", err)
}

// As nothing is done with the response body, it is safe to close here
// to avoid leaking connections
defer resp.Body.Close()
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, ErrNotFound
}

p := &PullRequest{}
if err := json.Unmarshal(res, p); err != nil {
return nil, fmt.Errorf("create pull request failed, unable to unmarshal repository list json: %w", err)
}

p.Session.set(resp)

return p, nil
}

// Delete deletes the pull request with the given ID
// Delete uses the endpoint "DELETE /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}".
// To call this resource, users must:
// - be the pull request author, if the system is configured to allow authors to delete their own pull requests (this is the default) OR
// - have repository administrator permission for the repository the pull request is targeting
// A body containing the ID and version of the pull request must be provided with this request.
// {
// "id": 1,
// "version": 1
// }
func (s *PullRequestsService) Delete(ctx context.Context, projectKey, repositorySlug string, IDVersion IDVersion) error {
header := http.Header{"Content-Type": []string{"application/json"}}
req, err := s.Client.NewRequest(ctx, http.MethodDelete, newURI(projectsURI, projectKey, RepositoriesURI, repositorySlug, pullRequestsURI, strconv.Itoa(IDVersion.ID)), nil, IDVersion.Version, header)
if err != nil {
return fmt.Errorf("delete pull request frequest creation failed: %w", err)
}
_, resp, err := s.Client.Do(req)
if err != nil {
return fmt.Errorf("delete pull request for repository failed: %w", err)
}

// As nothing is done with the response body, it is safe to close here
// to avoid leaking connections
defer resp.Body.Close()
if resp != nil && resp.StatusCode == http.StatusNotFound {
return ErrNotFound
}

return nil
}
Loading

0 comments on commit 18da9ff

Please sign in to comment.