Skip to content

Commit

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

Signed-off-by: Soule BA <[email protected]>
  • Loading branch information
souleb committed Sep 23, 2021
1 parent b32929b commit b7839f2
Show file tree
Hide file tree
Showing 3 changed files with 274 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 @@ -110,6 +110,7 @@ type Client struct {
Git Git
Repositories Repositories
Branches Branches
Commits Commits
}

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

return c, nil
}
Expand Down
147 changes: 147 additions & 0 deletions stash/commits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
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"
)

const (
commitsURI = "commits"
)

// Commits interface defines the methods that can be used to
// retrieve commits of a repository.
type Commits interface {
List(ctx context.Context, projectKey, repositorySlug string, opts *PagingOptions) (*CommitList, error)
Get(ctx context.Context, projectKey, repositorySlug, commitID string) (*CommitObject, error)
}

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

// CommitObject represents a commit in stash
type CommitObject struct {
// Session is the session object for the branch.
Session `json:"sessionInfo,omitempty"`
// Author is the author of the commit.
Author User `json:"author,omitempty"`
// AuthorTimestamp is the timestamp of the author of the commit.
AuthorTimestamp int64 `json:"authorTimestamp,omitempty"`
// Committer is the committer of the commit.
Committer User `json:"committer,omitempty"`
// CommitterTimestamp is the timestamp of the committer of the commit.
CommitterTimestamp int64 `json:"committerTimestamp,omitempty"`
// DisplayID is the display ID of the commit.
DisplayID string `json:"displayId,omitempty"`
// ID is the ID of the commit i.e the SHA1.
ID string `json:"id,omitempty"`
// Message is the message of the commit.
Message string `json:"message,omitempty"`
// Parents is the list of parents of the commit.
Parents []*Parent `json:"parents,omitempty"`
}

// Parent represents a parent of a commit.
type Parent struct {
// DisplayID is the display ID of the commit.
DisplayID string `json:"displayId,omitempty"`
// ID is the ID of the commit i.e the SHA1.
ID string `json:"id,omitempty"`
}

// CommitList represents a list of commits in stash
type CommitList struct {
// Paging is the paging information.
Paging
// Commits is the list of commits.
Commits []*CommitObject `json:"values,omitempty"`
}

// GetCommits returns the list of commits
func (c *CommitList) GetCommits() []*CommitObject {
return c.Commits
}

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

c := &CommitList{}
if err := json.Unmarshal(res, c); err != nil {
return nil, fmt.Errorf("list commits for repository failed, unable to unmarshall repository json: %w", err)
}

for _, commit := range c.GetCommits() {
commit.Session.set(resp)
}
return c, nil
}

// Get retrieves a stash commit given it's ID i.e a SHA1.
// Get uses the endpoint "GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/commits/{commitID}".
// https://docs.atlassian.com/bitbucket-server/rest/5.16.0/bitbucket-rest.html
func (s *CommitsService) Get(ctx context.Context, projectKey, repositorySlug, commitID string) (*CommitObject, error) {
req, err := s.Client.NewRequest(ctx, http.MethodGet, newURI(projectsURI, projectKey, RepositoriesURI, repositorySlug, commitsURI, commitID), nil, nil, nil)
if err != nil {
return nil, fmt.Errorf("get commit request creation failed: %w", err)
}
res, resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("get commit 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
}

c := &CommitObject{}
if err := json.Unmarshal(res, c); err != nil {
return nil, fmt.Errorf("get commit failed, unable to unmarshall json: %w", err)
}

c.Session.set(resp)

return c, nil
}
125 changes: 125 additions & 0 deletions stash/commits_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
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"
"path"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestGetCommit(t *testing.T) {
tests := []struct {
name string
commitID string
}{
{
name: "test a commit",
commitID: "abcdef0123abcdef4567abcdef8987abcdef6543",
},
{
name: "test commit does not exist",
commitID: "*°0#13jbkjfbvsqbùbjùrdfbgzeo'àtu)éuçt&-y",
},
}

validCommitID := []string{"abcdef0123abcdef4567abcdef8987abcdef6543"}

mux, client := setup(t)

fmt.Println("commit")

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := fmt.Sprintf("%s/%s/prj1/%s/repo1/%s/%s", stashURIprefix, projectsURI, RepositoriesURI, commitsURI, tt.commitID)
mux.HandleFunc(p, func(w http.ResponseWriter, r *http.Request) {
for _, substr := range validCommitID {
if path.Base(r.URL.String()) == substr {
w.WriteHeader(http.StatusOK)
c := &CommitObject{
ID: substr,
DisplayID: substr[:10],
}

json.NewEncoder(w).Encode(c)
return
}
}

http.Error(w, "The specified commit does not exist", http.StatusNotFound)

return

})

ctx := context.Background()
c, err := client.Commits.Get(ctx, "prj1", "repo1", tt.commitID)
if err != nil {
if err != ErrNotFound {
t.Fatalf("Commits.Get returned error: %v", err)
}
return
}

if c.ID != tt.commitID {
t.Fatalf("Commits.Get returned commit %s, want %s", c.ID, tt.commitID)
}

})
}
}

func TestListCommits(t *testing.T) {
cIDs := []*CommitObject{
{ID: "abcdef0123abcdef4567abcdef8987abcdef6543"},
{ID: "aerfdef09893abcdef4567abcdef898abcdef652"},
{ID: "abcdef3456abcdef4567abcdef8987abcdef6657"},
{ID: "abcdef9876abcdef4567abcdef8987abcdef4357"}}

mux, client := setup(t)

path := fmt.Sprintf("%s/%s/prj1/%s/repo1/%s", stashURIprefix, projectsURI, RepositoriesURI, commitsURI)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
b := struct {
Commits []*CommitObject `json:"values"`
}{[]*CommitObject{
cIDs[0],
cIDs[1],
cIDs[2],
cIDs[3],
}}
json.NewEncoder(w).Encode(b)
return

})
ctx := context.Background()
list, err := client.Commits.List(ctx, "prj1", "repo1", nil)
if err != nil {
t.Fatalf("Commits.List returned error: %v", err)
}

if diff := cmp.Diff(cIDs, list.Commits); diff != "" {
t.Errorf("Commits.List returned diff (want -> got):\n%s", diff)
}

}

0 comments on commit b7839f2

Please sign in to comment.