Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Stash] Go stash pr service #110

Merged
merged 3 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions stash/branch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
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 (
branchesURI = "branches"
defaultBranchURI = "default"
)

// Branches interface defines the methods that can be used to
// retrieve branches of a repository.
type Branches interface {
List(ctx context.Context, projectKey, repositorySlug string, opts *PagingOptions) (*BranchList, error)
Get(ctx context.Context, projectKey, repositorySlug, branchID string) (*Branch, error)
Default(ctx context.Context, projectKey, repositorySlug string) (*Branch, error)
}

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

// Branch represents a branch of a repository.
type Branch struct {
// Session is the session object for the branch.
Session `json:"sessionInfo,omitempty"`
// DisplayID is the branch name e.g. main.
DisplayID string `json:"displayId,omitempty"`
// ID is the branch reference e.g. refs/heads/main.
ID string `json:"id,omitempty"`
// IsDefault is true if this is the default branch.
IsDefault bool `json:"isDefault,omitempty"`
// LatestChangeset is the latest changeset on this branch.
LatestChangeset string `json:"latestChangeset,omitempty"`
// LatestCommit is the latest commit on this branch.
LatestCommit string `json:"latestCommit,omitempty"`
// Type is the type of branch.
Type string `json:"type,omitempty"`
}

// BranchList is a list of branches.
type BranchList struct {
// Paging is the paging information.
Paging
// Branches is the list of branches.
Branches []*Branch `json:"values,omitempty"`
}

// GetBranches returns the list of branches.
func (b *BranchList) GetBranches() []*Branch {
return b.Branches
}

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

if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, ErrNotFound
}

b := &BranchList{}

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

for _, branches := range b.GetBranches() {
branches.Session.set(resp)
}

return b, nil
}

// Get retrieves a stash branch given it's ID i.e a git reference.
// Get uses the endpoint
// "GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/branches?base&details&filterText&orderBy".
// https://docs.atlassian.com/bitbucket-server/rest/5.16.0/bitbucket-rest.html
func (s *BranchesService) Get(ctx context.Context, projectKey, repositorySlug, branchID string) (*Branch, error) {
query := url.Values{
"filterText": []string{branchID},
}

req, err := s.Client.NewRequest(ctx, http.MethodGet, newURI(projectsURI, projectKey, RepositoriesURI, repositorySlug, branchesURI), WithQuery(query))
if err != nil {
return nil, fmt.Errorf("get branch request creation failed: %w", err)
}
res, resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("get branch failed: %w", err)
}

if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, ErrNotFound
}

b := &Branch{}
if err := json.Unmarshal(res, b); err != nil {
return nil, fmt.Errorf("get branch for repository failed, unable to unmarshall repository json: %w", err)
}

b.Session.set(resp)
return b, nil

}

// Default retrieves the default branch of a repository.
// Default uses the endpoint "GET /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/branches/default".
// https://docs.atlassian.com/bitbucket-server/rest/5.16.0/bitbucket-rest.html
func (s *BranchesService) Default(ctx context.Context, projectKey, repositorySlug string) (*Branch, error) {
req, err := s.Client.NewRequest(ctx, http.MethodGet, newURI(projectsURI, projectKey, RepositoriesURI, repositorySlug, branchesURI, defaultBranchURI))
if err != nil {
return nil, fmt.Errorf("get branch request creation failed: %w", err)
}
res, resp, err := s.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("get branch failed: %w", err)
}

if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, ErrNotFound
}

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

b.Session.set(resp)

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

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

func TestGetBranch(t *testing.T) {
tests := []struct {
name string
branchID string
}{
{
name: "test branch does not exist",
branchID: "features",
},
{
name: "test main branch",
branchID: "refs/heads/main",
},
}

validBranchID := []string{"refs/heads/main"}

mux, client := setup(t)

path := fmt.Sprintf("%s/%s/prj1/%s/repo1/%s", stashURIprefix, projectsURI, RepositoriesURI, branchesURI)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
for _, substr := range validBranchID {
if r.URL.Query().Get("filterText") == substr {
w.WriteHeader(http.StatusOK)
u := &Branch{
ID: "refs/heads/main",
DisplayID: "main",
}
json.NewEncoder(w).Encode(u)
return
}
}

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

return

})

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
b, err := client.Branches.Get(ctx, "prj1", "repo1", tt.branchID)
if err != nil {
if err != ErrNotFound {
t.Fatalf("Branches.Get returned error: %v", err)
}
return
}

if b.ID != tt.branchID {
t.Errorf("Branches.Get returned branch:\n%s, want:\n%s", b.ID, tt.branchID)
}

})
}
}

func TestListBranches(t *testing.T) {
bIDs := []*Branch{
{ID: "refs/heads/main"}, {ID: "refs/heads/release"}, {ID: "refs/heads/feature"}, {ID: "refs/heads/hotfix"}}

mux, client := setup(t)

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

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

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

}

func TestDefaultBranch(t *testing.T) {
d := struct {
ID string `json:"id"`
DisplayID string `json:"displayId"`
}{
"refs/heads/main",
"main",
}

mux, client := setup(t)

path := fmt.Sprintf("%s/%s/prj1/%s/repo1/%s/%s", stashURIprefix, projectsURI, RepositoriesURI, branchesURI, defaultBranchURI)
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
u := &Branch{
ID: d.ID,
DisplayID: d.DisplayID,
}
json.NewEncoder(w).Encode(u)

return
})

ctx := context.Background()
b, err := client.Branches.Default(ctx, "prj1", "repo1")
if err != nil {
if err != ErrNotFound {
t.Fatalf("Branches.Default returned error: %v", err)
}
return
}

if b.ID != d.ID {
t.Errorf("Branches.Default returned branch:\n%s, want:\n %s", b.ID, d.ID)
}
}
Loading