github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/gits/bitbucket_server.go (about)

     1  package gits
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/url"
     8  	"os"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/blang/semver"
    14  	"github.com/pkg/errors"
    15  
    16  	"github.com/google/go-github/v32/github"
    17  	"github.com/mitchellh/mapstructure"
    18  
    19  	bitbucket "github.com/gfleury/go-bitbucket-v1"
    20  	"github.com/jenkins-x/jx-logging/pkg/log"
    21  	"github.com/olli-ai/jx/v2/pkg/auth"
    22  	"github.com/olli-ai/jx/v2/pkg/util"
    23  )
    24  
    25  // pageLimit is used for the page size for API responses
    26  const pageLimit = 25
    27  
    28  var (
    29  	// BaseWebHooks are webhooks we enable on all versions
    30  	BaseWebHooks = []string{"repo:refs_changed", "repo:modified", "repo:forked", "repo:comment:added", "repo:comment:edited", "repo:comment:deleted", "pr:opened", "pr:reviewer:approved", "pr:reviewer:unapproved", "pr:reviewer:needs_work", "pr:merged", "pr:declined", "pr:deleted", "pr:comment:added", "pr:comment:edited", "pr:comment:deleted", "pr:modified"}
    31  	// Ver7WebHooks are additional webhooks we enable on server versions >= 7.x
    32  	Ver7WebHooks = []string{"pr:from_ref_updated"}
    33  )
    34  
    35  // BitbucketServerProvider implements GitProvider interface for a bitbucket server
    36  type BitbucketServerProvider struct {
    37  	Client   *bitbucket.APIClient
    38  	Username string
    39  	Context  context.Context
    40  
    41  	Server auth.AuthServer
    42  	User   auth.UserAuth
    43  	Git    Gitter
    44  }
    45  
    46  type projectsPage struct {
    47  	Size          int                 `json:"size"`
    48  	Limit         int                 `json:"limit"`
    49  	Start         int                 `json:"start"`
    50  	NextPageStart int                 `json:"nextPageStart"`
    51  	IsLastPage    bool                `json:"isLastPage"`
    52  	Values        []bitbucket.Project `json:"values"`
    53  }
    54  
    55  type commitsPage struct {
    56  	Size          int                `json:"size"`
    57  	Limit         int                `json:"limit"`
    58  	Start         int                `json:"start"`
    59  	NextPageStart int                `json:"nextPageStart"`
    60  	IsLastPage    bool               `json:"isLastPage"`
    61  	Values        []bitbucket.Commit `json:"values"`
    62  }
    63  
    64  type buildStatusesPage struct {
    65  	Size       int                     `json:"size"`
    66  	Limit      int                     `json:"limit"`
    67  	Start      int                     `json:"start"`
    68  	IsLastPage bool                    `json:"isLastPage"`
    69  	Values     []bitbucket.BuildStatus `json:"values"`
    70  }
    71  
    72  type reposPage struct {
    73  	Size          int                    `json:"size"`
    74  	Limit         int                    `json:"limit"`
    75  	Start         int                    `json:"start"`
    76  	NextPageStart int                    `json:"nextPageStart"`
    77  	IsLastPage    bool                   `json:"isLastPage"`
    78  	Values        []bitbucket.Repository `json:"values"`
    79  }
    80  
    81  type pullRequestPage struct {
    82  	Size          int                     `json:"size"`
    83  	Limit         int                     `json:"limit"`
    84  	Start         int                     `json:"start"`
    85  	NextPageStart int                     `json:"nextPageStart"`
    86  	IsLastPage    bool                    `json:"isLastPage"`
    87  	Values        []bitbucket.PullRequest `json:"values"`
    88  }
    89  
    90  type webHooksPage struct {
    91  	Size          int       `json:"size"`
    92  	Limit         int       `json:"limit"`
    93  	Start         int       `json:"start"`
    94  	NextPageStart int       `json:"nextPageStart"`
    95  	IsLastPage    bool      `json:"isLastPage"`
    96  	Values        []webHook `json:"values"`
    97  }
    98  
    99  type webHook struct {
   100  	ID            int64                  `json:"id"`
   101  	Name          string                 `json:"name"`
   102  	CreatedDate   int64                  `json:"createdDate"`
   103  	UpdatedDate   int64                  `json:"updatedDate"`
   104  	Events        []string               `json:"events"`
   105  	Configuration map[string]interface{} `json:"configuration"`
   106  	URL           string                 `json:"url"`
   107  	Active        bool                   `json:"active"`
   108  }
   109  
   110  func NewBitbucketServerProvider(server *auth.AuthServer, user *auth.UserAuth, git Gitter) (GitProvider, error) {
   111  	ctx := context.Background()
   112  	apiKeyAuthContext := context.WithValue(ctx, bitbucket.ContextAccessToken, user.ApiToken)
   113  
   114  	provider := BitbucketServerProvider{
   115  		Server:   *server,
   116  		User:     *user,
   117  		Username: user.Username,
   118  		Context:  apiKeyAuthContext,
   119  		Git:      git,
   120  	}
   121  
   122  	cfg := bitbucket.NewConfiguration(server.URL + "/rest")
   123  	provider.Client = bitbucket.NewAPIClient(apiKeyAuthContext, cfg)
   124  
   125  	return &provider, nil
   126  }
   127  
   128  func BitbucketServerRepositoryToGitRepository(bRepo bitbucket.Repository) *GitRepository {
   129  	var sshURL string
   130  	var httpCloneURL string
   131  	for _, link := range bRepo.Links.Clone {
   132  		if link.Name == "ssh" {
   133  			sshURL = link.Href
   134  		}
   135  	}
   136  	isFork := false
   137  
   138  	if httpCloneURL == "" {
   139  		cloneLinks := bRepo.Links.Clone
   140  
   141  		for _, link := range cloneLinks {
   142  			if link.Name == "http" {
   143  				httpCloneURL = link.Href
   144  				if !strings.HasSuffix(httpCloneURL, ".git") {
   145  					httpCloneURL += ".git"
   146  				}
   147  			}
   148  		}
   149  	}
   150  	if httpCloneURL == "" {
   151  		httpCloneURL = sshURL
   152  	}
   153  
   154  	htmlURL := bRepo.Links.Self[0].Href
   155  	return &GitRepository{
   156  		Name:         bRepo.Name,
   157  		HTMLURL:      htmlURL,
   158  		CloneURL:     httpCloneURL,
   159  		SSHURL:       sshURL,
   160  		URL:          htmlURL,
   161  		Fork:         isFork,
   162  		Private:      !bRepo.Public,
   163  		Organisation: strings.ToLower(bRepo.Project.Key),
   164  		Project:      bRepo.Project.Key,
   165  	}
   166  }
   167  
   168  func (b *BitbucketServerProvider) GetRepository(org string, name string) (*GitRepository, error) {
   169  	var repo bitbucket.Repository
   170  	apiResponse, err := b.Client.DefaultApi.GetRepository(org, name)
   171  
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	err = mapstructure.Decode(apiResponse.Values, &repo)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	return BitbucketServerRepositoryToGitRepository(repo), nil
   182  }
   183  
   184  func (b *BitbucketServerProvider) ListOrganisations() ([]GitOrganisation, error) {
   185  	var orgsPage projectsPage
   186  	orgsList := []GitOrganisation{}
   187  	paginationOptions := make(map[string]interface{})
   188  
   189  	paginationOptions["start"] = 0
   190  	paginationOptions["limit"] = pageLimit
   191  	for {
   192  		apiResponse, err := b.Client.DefaultApi.GetProjects(paginationOptions)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  
   197  		err = mapstructure.Decode(apiResponse.Values, &orgsPage)
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  
   202  		for _, project := range orgsPage.Values {
   203  			orgsList = append(orgsList, GitOrganisation{Login: project.Key})
   204  		}
   205  
   206  		if orgsPage.IsLastPage {
   207  			break
   208  		}
   209  		if !b.moveToNextPage(paginationOptions, orgsPage.NextPageStart) {
   210  			break
   211  		}
   212  	}
   213  
   214  	return orgsList, nil
   215  }
   216  
   217  func (b *BitbucketServerProvider) ListRepositories(org string) ([]*GitRepository, error) {
   218  	var reposPage reposPage
   219  	repos := []*GitRepository{}
   220  	paginationOptions := make(map[string]interface{})
   221  
   222  	paginationOptions["start"] = 0
   223  	paginationOptions["limit"] = pageLimit
   224  
   225  	for {
   226  		apiResponse, err := b.Client.DefaultApi.GetRepositoriesWithOptions(org, paginationOptions)
   227  		if err != nil {
   228  			return nil, err
   229  		}
   230  
   231  		err = mapstructure.Decode(apiResponse.Values, &reposPage)
   232  		if err != nil {
   233  			return nil, err
   234  		}
   235  
   236  		for _, bRepo := range reposPage.Values {
   237  			repos = append(repos, BitbucketServerRepositoryToGitRepository(bRepo))
   238  		}
   239  
   240  		if reposPage.IsLastPage {
   241  			break
   242  		}
   243  		if !b.moveToNextPage(paginationOptions, reposPage.NextPageStart) {
   244  			break
   245  		}
   246  	}
   247  
   248  	return repos, nil
   249  }
   250  
   251  func (b *BitbucketServerProvider) CreateRepository(org, name string, private bool) (*GitRepository, error) {
   252  	var repo bitbucket.Repository
   253  
   254  	repoRequest := map[string]interface{}{
   255  		"name":   name,
   256  		"public": !private,
   257  	}
   258  
   259  	requestBody, err := json.Marshal(repoRequest)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	apiResponse, err := b.Client.DefaultApi.CreateRepositoryWithOptions(org, requestBody, []string{"application/json"})
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	err = mapstructure.Decode(apiResponse.Values, &repo)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  
   274  	return BitbucketServerRepositoryToGitRepository(repo), nil
   275  }
   276  
   277  func (b *BitbucketServerProvider) DeleteRepository(org, name string) error {
   278  	_, err := b.Client.DefaultApi.DeleteRepository(org, name)
   279  
   280  	return err
   281  }
   282  
   283  func (b *BitbucketServerProvider) RenameRepository(org, name, newName string) (*GitRepository, error) {
   284  	var repo bitbucket.Repository
   285  	var options = map[string]interface{}{
   286  		"name": newName,
   287  	}
   288  
   289  	requestBody, err := json.Marshal(options)
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  
   294  	apiResponse, err := b.Client.DefaultApi.UpdateRepositoryWithOptions(org, name, requestBody, []string{"application/json"})
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  
   299  	err = mapstructure.Decode(apiResponse.Values, &repo)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  
   304  	return BitbucketServerRepositoryToGitRepository(repo), nil
   305  }
   306  
   307  func (b *BitbucketServerProvider) ValidateRepositoryName(org, name string) error {
   308  	apiResponse, err := b.Client.DefaultApi.GetRepository(org, name)
   309  
   310  	if apiResponse != nil && apiResponse.Response.StatusCode == 404 {
   311  		return nil
   312  	}
   313  
   314  	if err == nil {
   315  		return fmt.Errorf("repository %s/%s already exists", b.Username, name)
   316  	}
   317  
   318  	return err
   319  }
   320  
   321  func (b *BitbucketServerProvider) ForkRepository(originalOrg, name, destinationOrg string) (*GitRepository, error) {
   322  	var repo bitbucket.Repository
   323  	var apiResponse *bitbucket.APIResponse
   324  	var options = map[string]interface{}{}
   325  
   326  	if destinationOrg != "" {
   327  		options["project"] = map[string]interface{}{
   328  			"key": destinationOrg,
   329  		}
   330  	}
   331  
   332  	requestBody, err := json.Marshal(options)
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  
   337  	_, err = b.Client.DefaultApi.ForkRepository(originalOrg, name, requestBody, []string{"application/json"})
   338  	if err != nil {
   339  		return nil, err
   340  	}
   341  
   342  	// Wait up to 1 minute for the fork to be ready
   343  	for i := 0; i < 30; i++ {
   344  		time.Sleep(2 * time.Second)
   345  
   346  		if destinationOrg == "" {
   347  			apiResponse, err = b.Client.DefaultApi.GetUserRepository(b.CurrentUsername(), name)
   348  		} else {
   349  			apiResponse, err = b.Client.DefaultApi.GetRepository(destinationOrg, name)
   350  		}
   351  
   352  		if err == nil {
   353  			break
   354  		}
   355  	}
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  
   360  	err = mapstructure.Decode(apiResponse.Values, &repo)
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	return BitbucketServerRepositoryToGitRepository(repo), nil
   366  }
   367  
   368  func (b *BitbucketServerProvider) CreatePullRequest(data *GitPullRequestArguments) (*GitPullRequest, error) {
   369  	if data.GitRepository.Organisation == "" {
   370  		data.GitRepository.Organisation = data.GitRepository.Project
   371  	}
   372  	var bPullRequest, bPR bitbucket.PullRequest
   373  	var options = map[string]interface{}{
   374  		"title":       data.Title,
   375  		"description": data.Body,
   376  		"state":       "OPEN",
   377  		"open":        true,
   378  		"closed":      false,
   379  		"fromRef": map[string]interface{}{
   380  			"id": data.Head,
   381  			"repository": map[string]interface{}{
   382  				"slug": data.GitRepository.Name,
   383  				"project": map[string]interface{}{
   384  					"key": strings.ToUpper(data.GitRepository.Organisation),
   385  				},
   386  			},
   387  		},
   388  		"toRef": map[string]interface{}{
   389  			"id": data.Base,
   390  			"repository": map[string]interface{}{
   391  				"slug": data.GitRepository.Name,
   392  				"project": map[string]interface{}{
   393  					"key": strings.ToUpper(data.GitRepository.Organisation),
   394  				},
   395  			},
   396  		},
   397  	}
   398  
   399  	requestBody, err := json.Marshal(options)
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  
   404  	apiResponse, err := b.Client.DefaultApi.CreatePullRequestWithOptions(strings.ToUpper(data.GitRepository.Organisation), data.GitRepository.Name, requestBody)
   405  	if err != nil {
   406  		return nil, err
   407  	}
   408  
   409  	err = mapstructure.Decode(apiResponse.Values, &bPullRequest)
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  
   414  	// Wait up to 1 minute for the pull request to be ready
   415  	for i := 0; i < 30; i++ {
   416  		time.Sleep(2 * time.Second)
   417  
   418  		apiResponse, err = b.Client.DefaultApi.GetPullRequest(strings.ToUpper(data.GitRepository.Project), data.GitRepository.Name, bPullRequest.ID)
   419  		if err == nil {
   420  			break
   421  		}
   422  	}
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  
   427  	err = mapstructure.Decode(apiResponse.Values, &bPR)
   428  	if err != nil {
   429  		return nil, err
   430  	}
   431  
   432  	return &GitPullRequest{
   433  		URL:    bPR.Links.Self[0].Href,
   434  		Owner:  bPR.Author.User.Name,
   435  		Repo:   bPR.ToRef.Repository.Name,
   436  		Number: &bPR.ID,
   437  		State:  &bPR.State,
   438  		Title:  bPR.Title,
   439  	}, nil
   440  }
   441  
   442  // UpdatePullRequest updates pull request number with data
   443  func (b *BitbucketServerProvider) UpdatePullRequest(data *GitPullRequestArguments, number int) (*GitPullRequest, error) {
   444  	return nil, errors.Errorf("Not yet implemented for bitbucket")
   445  }
   446  
   447  func parseBitBucketServerURL(URL string) (string, string) {
   448  	var projectKey, repoName, subString string
   449  	var projectsIndex, reposIndex, repoEndIndex int
   450  
   451  	if strings.HasSuffix(URL, ".git") {
   452  		subString = strings.TrimSuffix(URL, ".git")
   453  		reposIndex = strings.LastIndex(subString, "/")
   454  		repoName = subString[reposIndex+1:]
   455  
   456  		subString = strings.TrimSuffix(subString, "/"+repoName)
   457  		projectsIndex = strings.LastIndex(subString, "/")
   458  		projectKey = subString[projectsIndex+1:]
   459  
   460  	} else {
   461  		projectsIndex = strings.Index(URL, "projects/")
   462  		subString = URL[projectsIndex+9:]
   463  		projectKey = subString[:strings.Index(subString, "/")]
   464  
   465  		reposIndex = strings.Index(subString, "repos/")
   466  		subString = subString[reposIndex+6:]
   467  
   468  		repoEndIndex = strings.Index(subString, "/")
   469  
   470  		if repoEndIndex == -1 {
   471  			repoName = subString
   472  		} else {
   473  			repoName = subString[:repoEndIndex]
   474  		}
   475  	}
   476  
   477  	return projectKey, repoName
   478  }
   479  
   480  func getMergeCommitSHAFromPRActivity(prActivity map[string]interface{}) *string {
   481  	var activity []map[string]interface{}
   482  	var mergeCommit map[string]interface{}
   483  
   484  	mapstructure.Decode(prActivity["values"], &activity) //nolint:errcheck
   485  	for _, act := range activity {
   486  		if act["action"] == "MERGED" {
   487  			mapstructure.Decode(act["commit"], &mergeCommit) //nolint:errcheck
   488  			break
   489  		}
   490  	}
   491  	commitSHA := ""
   492  	if _, ok := mergeCommit["id"]; ok {
   493  		commitSHA = mergeCommit["id"].(string)
   494  	}
   495  	return &commitSHA
   496  }
   497  
   498  func getLastCommitSHAFromPRCommits(prCommits map[string]interface{}) string {
   499  	return getLastCommitFromPRCommits(prCommits).ID
   500  }
   501  
   502  func getLastCommitFromPRCommits(prCommits map[string]interface{}) *bitbucket.Commit {
   503  	var commits []bitbucket.Commit
   504  	mapstructure.Decode(prCommits["values"], &commits) //nolint:errcheck
   505  	return &commits[0]
   506  }
   507  
   508  func (b *BitbucketServerProvider) UpdatePullRequestStatus(pr *GitPullRequest) error {
   509  	var bitbucketPR bitbucket.PullRequest
   510  
   511  	prID := *pr.Number
   512  	projectKey, repo := parseBitBucketServerURL(pr.URL)
   513  	apiResponse, err := b.Client.DefaultApi.GetPullRequest(projectKey, repo, prID)
   514  	if err != nil {
   515  		return err
   516  	}
   517  
   518  	err = mapstructure.Decode(apiResponse.Values, &bitbucketPR)
   519  	if err != nil {
   520  		return err
   521  	}
   522  
   523  	err = b.populatePullRequest(pr, &bitbucketPR)
   524  	if err != nil {
   525  		return err
   526  	}
   527  	return nil
   528  }
   529  
   530  func (b *BitbucketServerProvider) GetPullRequest(owner string, repo *GitRepository, number int) (*GitPullRequest, error) {
   531  	var bPR *bitbucket.PullRequest
   532  
   533  	apiResponse, err := b.Client.DefaultApi.GetPullRequest(strings.ToUpper(owner), repo.Name, number)
   534  	if err != nil {
   535  		return nil, err
   536  	}
   537  
   538  	err = mapstructure.Decode(apiResponse.Values, &bPR)
   539  	if err != nil {
   540  		return nil, err
   541  	}
   542  
   543  	return b.toPullRequest(bPR)
   544  }
   545  
   546  func (b *BitbucketServerProvider) toPullRequest(bPR *bitbucket.PullRequest) (*GitPullRequest, error) {
   547  	answer := &GitPullRequest{}
   548  
   549  	err := b.populatePullRequest(answer, bPR)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	return answer, nil
   554  }
   555  
   556  func (b *BitbucketServerProvider) populatePullRequest(answer *GitPullRequest, bPR *bitbucket.PullRequest) error {
   557  	var prCommits, prActivity map[string]interface{}
   558  	author := &GitUser{
   559  		URL:   bPR.Author.User.Links.Self[0].Href,
   560  		Login: bPR.Author.User.Slug,
   561  		Name:  bPR.Author.User.Name,
   562  		Email: bPR.Author.User.EmailAddress,
   563  	}
   564  	answer.URL = bPR.Links.Self[0].Href
   565  	answer.Owner = bPR.ToRef.Repository.Project.Key
   566  	answer.Repo = bPR.ToRef.Repository.Name
   567  	answer.Number = &bPR.ID
   568  	answer.State = &bPR.State
   569  	answer.Author = author
   570  	answer.LastCommitSha = bPR.FromRef.LatestCommit
   571  	answer.Title = bPR.Title
   572  	answer.Body = bPR.Description
   573  
   574  	if bPR.State == "MERGED" {
   575  		merged := true
   576  		answer.Merged = &merged
   577  		apiResponse, err := b.Client.DefaultApi.GetPullRequestActivity(answer.Owner, answer.Repo, *answer.Number)
   578  		if err != nil {
   579  			return err
   580  		}
   581  
   582  		err = mapstructure.Decode(apiResponse.Values, &prActivity)
   583  		if err != nil {
   584  			return err
   585  		}
   586  		answer.MergeCommitSHA = getMergeCommitSHAFromPRActivity(prActivity)
   587  	}
   588  	diffURL := bPR.Links.Self[0].Href + "/diff"
   589  	answer.DiffURL = &diffURL
   590  
   591  	apiResponse, err := b.Client.DefaultApi.GetPullRequestCommits(answer.Owner, answer.Repo, *answer.Number)
   592  	if err != nil {
   593  		return err
   594  	}
   595  	err = mapstructure.Decode(apiResponse.Values, &prCommits)
   596  	if err != nil {
   597  		return err
   598  	}
   599  	answer.LastCommitSha = getLastCommitSHAFromPRCommits(prCommits)
   600  	return nil
   601  }
   602  
   603  // ListOpenPullRequests lists the open pull requests
   604  func (b *BitbucketServerProvider) ListOpenPullRequests(owner string, repo string) ([]*GitPullRequest, error) {
   605  	answer := []*GitPullRequest{}
   606  	var pullRequests pullRequestPage
   607  
   608  	paginationOptions := make(map[string]interface{})
   609  
   610  	paginationOptions["start"] = 0
   611  	paginationOptions["limit"] = pageLimit
   612  
   613  	// TODO how to pass in the owner and repo and status? these are total guesses
   614  	paginationOptions["owner"] = owner
   615  	paginationOptions["repo"] = repo
   616  	paginationOptions["state"] = "open"
   617  
   618  	for {
   619  		apiResponse, err := b.Client.DefaultApi.GetPullRequests(paginationOptions)
   620  		if err != nil {
   621  			return nil, err
   622  		}
   623  
   624  		err = mapstructure.Decode(apiResponse.Values, &pullRequests)
   625  		if err != nil {
   626  			return nil, err
   627  		}
   628  
   629  		for _, pr := range pullRequests.Values {
   630  			p := pr
   631  			actualPR, err := b.toPullRequest(&p)
   632  			if err != nil {
   633  				return nil, err
   634  			}
   635  			answer = append(answer, actualPR)
   636  		}
   637  
   638  		if pullRequests.IsLastPage {
   639  			break
   640  		}
   641  		if !b.moveToNextPage(paginationOptions, pullRequests.NextPageStart) {
   642  			break
   643  		}
   644  	}
   645  	return answer, nil
   646  }
   647  
   648  // moveToNextPage returns true if we should move to the next page
   649  func (b *BitbucketServerProvider) moveToNextPage(paginationOptions map[string]interface{}, nextPage int) bool {
   650  	lastStartValue := paginationOptions["start"]
   651  	lastStart, _ := lastStartValue.(int)
   652  	if lastStart < 0 {
   653  		lastStart = 0
   654  	}
   655  	if nextPage < 0 {
   656  		return false
   657  	}
   658  	if nextPage <= lastStart {
   659  		return false
   660  	}
   661  	paginationOptions["start"] = nextPage
   662  	return true
   663  }
   664  
   665  func convertBitBucketCommitToGitCommit(bCommit *bitbucket.Commit, repo *GitRepository) *GitCommit {
   666  	return &GitCommit{
   667  		SHA:     bCommit.ID,
   668  		Message: bCommit.Message,
   669  		Author: &GitUser{
   670  			Login: bCommit.Author.Name,
   671  			Name:  bCommit.Author.DisplayName,
   672  			Email: bCommit.Author.EmailAddress,
   673  		},
   674  		URL: repo.URL + "/commits/" + bCommit.ID,
   675  		Committer: &GitUser{
   676  			Login: bCommit.Committer.Name,
   677  			Name:  bCommit.Committer.DisplayName,
   678  			Email: bCommit.Committer.EmailAddress,
   679  		},
   680  	}
   681  }
   682  
   683  func (b *BitbucketServerProvider) GetPullRequestCommits(owner string, repository *GitRepository, number int) ([]*GitCommit, error) {
   684  	var commitsPage commitsPage
   685  	commits := []*GitCommit{}
   686  	paginationOptions := make(map[string]interface{})
   687  
   688  	paginationOptions["start"] = 0
   689  	paginationOptions["limit"] = pageLimit
   690  	for {
   691  		apiResponse, err := b.Client.DefaultApi.GetPullRequestCommitsWithOptions(strings.ToUpper(owner), repository.Name, number, paginationOptions)
   692  		if err != nil {
   693  			return nil, err
   694  		}
   695  
   696  		err = mapstructure.Decode(apiResponse.Values, &commitsPage)
   697  		if err != nil {
   698  			return nil, err
   699  		}
   700  
   701  		for _, commit := range commitsPage.Values {
   702  			c := commit
   703  			commits = append(commits, convertBitBucketCommitToGitCommit(&c, repository))
   704  		}
   705  
   706  		if commitsPage.IsLastPage {
   707  			break
   708  		}
   709  		if !b.moveToNextPage(paginationOptions, commitsPage.NextPageStart) {
   710  			break
   711  		}
   712  	}
   713  
   714  	return commits, nil
   715  }
   716  
   717  func (b *BitbucketServerProvider) PullRequestLastCommitStatus(pr *GitPullRequest) (string, error) {
   718  	var prCommits map[string]interface{}
   719  	var buildStatusesPage buildStatusesPage
   720  
   721  	projectKey, repo := parseBitBucketServerURL(pr.URL)
   722  	apiResponse, err := b.Client.DefaultApi.GetPullRequestCommits(projectKey, repo, *pr.Number)
   723  	if err != nil {
   724  		return "", err
   725  	}
   726  	err = mapstructure.Decode(apiResponse.Values, &prCommits)
   727  	if err != nil {
   728  		return "", err
   729  	}
   730  	lastCommit := getLastCommitFromPRCommits(prCommits)
   731  	lastCommitSha := lastCommit.ID
   732  
   733  	apiResponse, err = b.Client.DefaultApi.GetCommitBuildStatuses(lastCommitSha)
   734  	if err != nil {
   735  		return "", err
   736  	}
   737  
   738  	err = mapstructure.Decode(apiResponse.Values, &buildStatusesPage)
   739  	if err != nil {
   740  		return "", err
   741  	}
   742  	if buildStatusesPage.Size == 0 {
   743  		return "success", nil
   744  	}
   745  
   746  	for _, buildStatus := range buildStatusesPage.Values {
   747  		if time.Unix(buildStatus.DateAdded, 0).After(time.Unix(lastCommit.CommitterTimestamp, 0)) {
   748  			// var from BitBucketCloudProvider
   749  			return stateMap[buildStatus.State], nil
   750  		}
   751  	}
   752  
   753  	return "success", nil
   754  }
   755  
   756  func (b *BitbucketServerProvider) ListCommitStatus(org, repo, sha string) ([]*GitRepoStatus, error) {
   757  	var buildStatusesPage buildStatusesPage
   758  	statuses := []*GitRepoStatus{}
   759  
   760  	for {
   761  		apiResponse, err := b.Client.DefaultApi.GetCommitBuildStatuses(sha)
   762  		if err != nil {
   763  			return nil, err
   764  		}
   765  
   766  		err = mapstructure.Decode(apiResponse.Values, &buildStatusesPage)
   767  		if err != nil {
   768  			return nil, err
   769  		}
   770  
   771  		for _, buildStatus := range buildStatusesPage.Values {
   772  			b := buildStatus
   773  			statuses = append(statuses, convertBitBucketBuildStatusToGitStatus(&b))
   774  		}
   775  
   776  		if buildStatusesPage.IsLastPage {
   777  			break
   778  		}
   779  	}
   780  
   781  	return statuses, nil
   782  }
   783  
   784  func (b *BitbucketServerProvider) UpdateCommitStatus(org string, repo string, sha string, status *GitRepoStatus) (*GitRepoStatus, error) {
   785  	return &GitRepoStatus{}, errors.New("TODO")
   786  }
   787  
   788  func convertBitBucketBuildStatusToGitStatus(buildStatus *bitbucket.BuildStatus) *GitRepoStatus {
   789  	return &GitRepoStatus{
   790  		ID:  buildStatus.Key,
   791  		URL: buildStatus.Url,
   792  		// var from BitBucketCloudProvider
   793  		State:       stateMap[buildStatus.State],
   794  		TargetURL:   buildStatus.Url,
   795  		Description: buildStatus.Description,
   796  		Context:     buildStatus.Name,
   797  	}
   798  }
   799  
   800  func (b *BitbucketServerProvider) MergePullRequest(pr *GitPullRequest, message string) error {
   801  	var currentPR bitbucket.PullRequest
   802  	projectKey, repo := parseBitBucketServerURL(pr.URL)
   803  	queryParams := map[string]interface{}{}
   804  
   805  	apiResponse, err := b.Client.DefaultApi.GetPullRequest(projectKey, repo, *pr.Number)
   806  	if err != nil {
   807  		return err
   808  	}
   809  
   810  	err = mapstructure.Decode(apiResponse.Values, &currentPR)
   811  	if err != nil {
   812  		return err
   813  	}
   814  	queryParams["version"] = currentPR.Version
   815  
   816  	var options = map[string]interface{}{
   817  		"message": message,
   818  	}
   819  
   820  	requestBody, err := json.Marshal(options)
   821  	if err != nil {
   822  		return err
   823  	}
   824  
   825  	apiResponse, err = b.Client.DefaultApi.Merge(projectKey, repo, *pr.Number, queryParams, requestBody, []string{"application/json"})
   826  	if err != nil {
   827  		return err
   828  	}
   829  
   830  	return nil
   831  }
   832  
   833  func (b *BitbucketServerProvider) parseWebHookURL(data *GitWebHookArguments) (string, string, error) {
   834  	repoURL := data.Repo.URL
   835  	owner := data.Repo.Organisation
   836  	repoName := data.Repo.Name
   837  	if repoURL == "" {
   838  		repository, err := b.GetRepository(owner, repoName)
   839  		if err != nil {
   840  			return "", "", errors.Wrapf(err, "failed to find repository %s/%s in server %s", owner, repoName, b.Server.URL)
   841  		}
   842  		repoURL = repository.URL
   843  		if repoURL == "" {
   844  			repoURL = repository.HTMLURL
   845  		}
   846  		if repoURL == "" {
   847  			return "", "", errors.Wrapf(err, "repository %s/%s on server %s has no URL", owner, repoName, b.Server.URL)
   848  		}
   849  	}
   850  	projectKey, repo := parseBitBucketServerURL(repoURL)
   851  	return projectKey, repo, nil
   852  }
   853  
   854  type appProps struct {
   855  	Version     string `json:"version"`
   856  	BuildNumber string `json:"buildNumber"`
   857  	BuildDate   string `json:"buildDate"`
   858  	DisplayName string `json:"displayName"`
   859  }
   860  
   861  func (b *BitbucketServerProvider) getServerVersion() (*semver.Version, error) {
   862  	apiResponse, err := b.Client.DefaultApi.GetApplicationProperties()
   863  	if err != nil {
   864  		return nil, errors.Wrapf(err, "failed to get BitBucket Server version")
   865  	}
   866  
   867  	var props *appProps
   868  	err = mapstructure.Decode(apiResponse.Values, &props)
   869  	if err != nil {
   870  		return nil, errors.Wrap(err, "failed to decode response from application properties")
   871  	}
   872  
   873  	rawVersion := props.Version
   874  	if rawVersion == "" {
   875  		return semver.New("0.0.0")
   876  	}
   877  
   878  	return semver.New(rawVersion)
   879  }
   880  
   881  func (b *BitbucketServerProvider) webHooksForServer() ([]string, error) {
   882  	v, err := b.getServerVersion()
   883  	if err != nil {
   884  		return nil, err
   885  	}
   886  	versionSeven, _ := semver.New("7.0.0")
   887  
   888  	var webhooks []string
   889  	webhooks = append(webhooks, BaseWebHooks...)
   890  	if v.GTE(*versionSeven) {
   891  		webhooks = append(webhooks, Ver7WebHooks...)
   892  	}
   893  	return webhooks, nil
   894  }
   895  
   896  // CreateWebHook adds a new webhook to a git repository
   897  func (b *BitbucketServerProvider) CreateWebHook(data *GitWebHookArguments) error {
   898  	projectKey, repo, err := b.parseWebHookURL(data)
   899  	if err != nil {
   900  		return err
   901  	}
   902  
   903  	if data.URL == "" {
   904  		return errors.New("missing property URL")
   905  	}
   906  
   907  	hooks, err := b.ListWebHooks(projectKey, repo)
   908  	if err != nil {
   909  		return errors.Wrapf(err, "error querying webhooks on %s/%s\n", projectKey, repo)
   910  	}
   911  	for _, hook := range hooks {
   912  		if data.URL == hook.URL {
   913  			log.Logger().Warnf("Already has a webhook registered for %s", data.URL)
   914  			return nil
   915  		}
   916  	}
   917  
   918  	webhooks, err := b.webHooksForServer()
   919  	if err != nil {
   920  		return errors.Wrapf(err, "failed to determine webhooks for server version")
   921  	}
   922  	var options = map[string]interface{}{
   923  		"url":    data.URL,
   924  		"name":   "Jenkins X Web Hook",
   925  		"active": true,
   926  		"events": webhooks,
   927  	}
   928  
   929  	if data.Secret != "" {
   930  		options["configuration"] = map[string]interface{}{
   931  			"secret": data.Secret,
   932  		}
   933  	}
   934  
   935  	requestBody, err := json.Marshal(options)
   936  	if err != nil {
   937  		return errors.Wrap(err, "failed to JSON encode webhook request body for creation")
   938  	}
   939  
   940  	_, err = b.Client.DefaultApi.CreateWebhook(projectKey, repo, requestBody, []string{"application/json"})
   941  
   942  	if err != nil {
   943  		return errors.Wrapf(err, "create webhook request failed on %s/%s", projectKey, repo)
   944  	}
   945  	return nil
   946  }
   947  
   948  // ListWebHooks lists all of the webhooks on a given git repository
   949  func (b *BitbucketServerProvider) ListWebHooks(owner string, repo string) ([]*GitWebHookArguments, error) {
   950  	var webHooksPage webHooksPage
   951  	var webHooks []*GitWebHookArguments
   952  
   953  	paginationOptions := make(map[string]interface{})
   954  	paginationOptions["start"] = 0
   955  	paginationOptions["limit"] = pageLimit
   956  
   957  	for {
   958  		apiResponse, err := b.Client.DefaultApi.FindWebhooks(owner, repo, paginationOptions)
   959  		if err != nil {
   960  			return nil, errors.Wrapf(err, "failed to list webhooks on repository %s/%s", owner, repo)
   961  		}
   962  
   963  		err = mapstructure.Decode(apiResponse.Values, &webHooksPage)
   964  		if err != nil {
   965  			return nil, errors.Wrap(err, "failed to decode response from list webhooks")
   966  		}
   967  
   968  		for _, wh := range webHooksPage.Values {
   969  			secret := ""
   970  			if cfg, ok := wh.Configuration["secret"].(string); ok {
   971  				secret = cfg
   972  			}
   973  
   974  			webHooks = append(webHooks, &GitWebHookArguments{
   975  				ID:     wh.ID,
   976  				Owner:  owner,
   977  				Repo:   nil,
   978  				URL:    wh.URL,
   979  				Secret: secret,
   980  			})
   981  		}
   982  
   983  		if webHooksPage.IsLastPage {
   984  			break
   985  		}
   986  		if !b.moveToNextPage(paginationOptions, webHooksPage.NextPageStart) {
   987  			break
   988  		}
   989  	}
   990  
   991  	return webHooks, nil
   992  }
   993  
   994  // UpdateWebHook is used to update a webhook on a git repository.  It is best to pass in the webhook ID.
   995  func (b *BitbucketServerProvider) UpdateWebHook(data *GitWebHookArguments) error {
   996  	projectKey, repo, err := b.parseWebHookURL(data)
   997  	if err != nil {
   998  		return err
   999  	}
  1000  
  1001  	if data.URL == "" {
  1002  		return errors.New("missing property URL")
  1003  	}
  1004  
  1005  	dataID := data.ID
  1006  	if dataID == 0 && data.ExistingURL != "" {
  1007  		hooks, err := b.ListWebHooks(projectKey, repo)
  1008  		if err != nil {
  1009  			log.Logger().Errorf("Error querying webhooks on %s/%s: %s", projectKey, repo, err)
  1010  		}
  1011  		for _, hook := range hooks {
  1012  			if data.ExistingURL == hook.URL {
  1013  				log.Logger().Warnf("Found existing webhook for url %s", data.ExistingURL)
  1014  				dataID = hook.ID
  1015  			}
  1016  		}
  1017  	}
  1018  	if dataID == 0 {
  1019  		log.Logger().Warn("No webhooks found to update")
  1020  		return nil
  1021  	}
  1022  	id := int32(dataID)
  1023  	if int64(id) != dataID {
  1024  		return errors.Errorf("Failed to update webhook with ID = %d due to int32 conversion failure", dataID)
  1025  	}
  1026  
  1027  	webhooks, err := b.webHooksForServer()
  1028  	if err != nil {
  1029  		return errors.Wrapf(err, "failed to determine webhooks for server version")
  1030  	}
  1031  	var options = map[string]interface{}{
  1032  		"url":    data.URL,
  1033  		"name":   "Jenkins X Web Hook",
  1034  		"active": true,
  1035  		"events": webhooks,
  1036  	}
  1037  
  1038  	if data.Secret != "" {
  1039  		options["configuration"] = map[string]interface{}{
  1040  			"secret": data.Secret,
  1041  		}
  1042  	}
  1043  
  1044  	requestBody, err := json.Marshal(options)
  1045  	if err != nil {
  1046  		return errors.Wrap(err, "failed to JSON encode webhook request body for update")
  1047  	}
  1048  
  1049  	log.Logger().Infof("Updating Bitbucket server webhook for %s/%s for url %s", util.ColorInfo(projectKey), util.ColorInfo(repo), util.ColorInfo(data.URL))
  1050  	_, err = b.Client.DefaultApi.UpdateWebhook(projectKey, repo, id, requestBody, []string{"application/json"})
  1051  
  1052  	if err != nil {
  1053  		return errors.Wrapf(err, "failed to update webhook on %s/%s", projectKey, repo)
  1054  	}
  1055  	return nil
  1056  }
  1057  
  1058  func (b *BitbucketServerProvider) SearchIssues(org string, name string, query string) ([]*GitIssue, error) {
  1059  
  1060  	gitIssues := []*GitIssue{}
  1061  
  1062  	log.Logger().Warn("Searching issues on bitbucket server is not supported at this moment")
  1063  
  1064  	return gitIssues, nil
  1065  }
  1066  
  1067  func (b *BitbucketServerProvider) SearchIssuesClosedSince(org string, name string, t time.Time) ([]*GitIssue, error) {
  1068  	issues, err := b.SearchIssues(org, name, "")
  1069  	if err != nil {
  1070  		return issues, err
  1071  	}
  1072  	return FilterIssuesClosedSince(issues, t), nil
  1073  }
  1074  
  1075  func (b *BitbucketServerProvider) GetIssue(org string, name string, number int) (*GitIssue, error) {
  1076  
  1077  	log.Logger().Warn("Finding an issue on bitbucket server is not supported at this moment")
  1078  	return &GitIssue{}, nil
  1079  }
  1080  
  1081  func (b *BitbucketServerProvider) IssueURL(org string, name string, number int, isPull bool) string {
  1082  	serverPrefix := b.Server.URL
  1083  	if strings.Index(serverPrefix, "://") < 0 {
  1084  		serverPrefix = "https://" + serverPrefix
  1085  	}
  1086  	path := "issues"
  1087  	if isPull {
  1088  		path = "pull"
  1089  	}
  1090  	url := util.UrlJoin(serverPrefix, org, name, path, strconv.Itoa(number))
  1091  	return url
  1092  }
  1093  
  1094  func (b *BitbucketServerProvider) CreateIssue(owner string, repo string, issue *GitIssue) (*GitIssue, error) {
  1095  
  1096  	log.Logger().Warn("Creating an issue on bitbucket server is not suuported at this moment")
  1097  	return &GitIssue{}, nil
  1098  }
  1099  
  1100  func (b *BitbucketServerProvider) AddPRComment(pr *GitPullRequest, comment string) error {
  1101  
  1102  	if pr.Number == nil {
  1103  		return fmt.Errorf("Missing Number for GitPullRequest %#v", pr)
  1104  	}
  1105  	n := *pr.Number
  1106  
  1107  	prComment := `{
  1108  		"text": "` + comment + `"
  1109  	}`
  1110  	_, err := b.Client.DefaultApi.CreateComment_1(pr.Owner, pr.Repo, n, prComment, []string{"application/json"})
  1111  	return err
  1112  }
  1113  
  1114  func (b *BitbucketServerProvider) CreateIssueComment(owner string, repo string, number int, comment string) error {
  1115  	log.Logger().Warn("Bitbucket Server doesn't support adding issue comments via the REST API")
  1116  	return nil
  1117  }
  1118  
  1119  func (b *BitbucketServerProvider) HasIssues() bool {
  1120  	return true
  1121  }
  1122  
  1123  func (b *BitbucketServerProvider) IsGitHub() bool {
  1124  	return false
  1125  }
  1126  
  1127  func (b *BitbucketServerProvider) IsGitea() bool {
  1128  	return false
  1129  }
  1130  
  1131  func (b *BitbucketServerProvider) IsBitbucketCloud() bool {
  1132  	return false
  1133  }
  1134  
  1135  func (b *BitbucketServerProvider) IsBitbucketServer() bool {
  1136  	return true
  1137  }
  1138  
  1139  func (b *BitbucketServerProvider) IsGerrit() bool {
  1140  	return false
  1141  }
  1142  
  1143  func (b *BitbucketServerProvider) Kind() string {
  1144  	return "bitbucketserver"
  1145  }
  1146  
  1147  // Exposed by Jenkins plugin; this one is for https://wiki.jenkins.io/display/JENKINS/BitBucket+Plugin
  1148  func (b *BitbucketServerProvider) JenkinsWebHookPath(gitURL string, secret string) string {
  1149  	return "/bitbucket-scmsource-hook/notify?server_url=" + url.QueryEscape(b.Server.URL)
  1150  }
  1151  
  1152  func (b *BitbucketServerProvider) Label() string {
  1153  	return b.Server.Label()
  1154  }
  1155  
  1156  func (b *BitbucketServerProvider) ServerURL() string {
  1157  	return b.Server.URL
  1158  }
  1159  
  1160  func (b *BitbucketServerProvider) BranchArchiveURL(org string, name string, branch string) string {
  1161  	return util.UrlJoin(b.ServerURL(), "rest/api/1.0/projects", org, "repos", name, "archive?format=zip&at="+branch)
  1162  }
  1163  
  1164  func (b *BitbucketServerProvider) CurrentUsername() string {
  1165  	return b.Username
  1166  }
  1167  
  1168  func (b *BitbucketServerProvider) UserAuth() auth.UserAuth {
  1169  	return b.User
  1170  }
  1171  
  1172  func (b *BitbucketServerProvider) UserInfo(username string) *GitUser {
  1173  	var user bitbucket.UserWithLinks
  1174  	apiResponse, err := b.Client.DefaultApi.GetUser(username)
  1175  	if err != nil {
  1176  		log.Logger().Error("Unable to fetch user info for " + username + " due to " + err.Error())
  1177  		return nil
  1178  	}
  1179  	err = mapstructure.Decode(apiResponse.Values, &user)
  1180  
  1181  	return &GitUser{
  1182  		Login: username,
  1183  		Name:  user.DisplayName,
  1184  		Email: user.EmailAddress,
  1185  		URL:   user.Links.Self[0].Href,
  1186  	}
  1187  }
  1188  
  1189  func (b *BitbucketServerProvider) UpdateRelease(owner string, repo string, tag string, releaseInfo *GitRelease) error {
  1190  	log.Logger().Warn("Bitbucket Server doesn't support releases")
  1191  	return nil
  1192  }
  1193  
  1194  // UpdateReleaseStatus is not supported for this git provider
  1195  func (b *BitbucketServerProvider) UpdateReleaseStatus(owner string, repo string, tag string, releaseInfo *GitRelease) error {
  1196  	log.Logger().Warn("Bitbucket Server doesn't support releases")
  1197  	return nil
  1198  }
  1199  
  1200  func (b *BitbucketServerProvider) ListReleases(org string, name string) ([]*GitRelease, error) {
  1201  	answer := []*GitRelease{}
  1202  	log.Logger().Warn("Bitbucket Server doesn't support releases")
  1203  	return answer, nil
  1204  }
  1205  
  1206  // GetRelease is unsupported on bitbucket as releases are not supported
  1207  func (b *BitbucketServerProvider) GetRelease(org string, name string, tag string) (*GitRelease, error) {
  1208  	log.Logger().Warn("Bitbucket Cloud doesn't support releases")
  1209  	return nil, nil
  1210  }
  1211  
  1212  func (b *BitbucketServerProvider) AddCollaborator(user string, organisation string, repo string) error {
  1213  	options := make(map[string]interface{})
  1214  	options["name"] = user
  1215  	options["permission"] = "REPO_WRITE"
  1216  	_, err := b.Client.DefaultApi.SetPermissionForUser(organisation, repo, options)
  1217  	return err
  1218  }
  1219  
  1220  func (b *BitbucketServerProvider) ListInvitations() ([]*github.RepositoryInvitation, *github.Response, error) {
  1221  	log.Logger().Infof("Automatically adding the pipeline user as a collaborator is currently not implemented for bitbucket.")
  1222  	return []*github.RepositoryInvitation{}, &github.Response{}, nil
  1223  }
  1224  
  1225  func (b *BitbucketServerProvider) AcceptInvitation(ID int64) (*github.Response, error) {
  1226  	log.Logger().Infof("Automatically adding the pipeline user as a collaborator is currently not implemented for bitbucket.")
  1227  	return &github.Response{}, nil
  1228  }
  1229  
  1230  func (b *BitbucketServerProvider) GetContent(org string, name string, path string, ref string) (*GitFileContent, error) {
  1231  	return nil, fmt.Errorf("Getting content not supported on bitbucket")
  1232  }
  1233  
  1234  // ShouldForkForPullReques treturns true if we should create a personal fork of this repository
  1235  // before creating a pull request
  1236  func (b *BitbucketServerProvider) ShouldForkForPullRequest(originalOwner string, repoName string, username string) bool {
  1237  	// return originalOwner != username
  1238  	// TODO assuming forking doesn't work yet?
  1239  	return false
  1240  }
  1241  
  1242  func BitBucketServerAccessTokenURL(url string) string {
  1243  	// TODO with github we can default the scopes/flags we need on a token via adding
  1244  	// ?scopes=repo,read:user,user:email,write:repo_hook
  1245  	//
  1246  	// is there a way to do that for bitbucket?
  1247  	return util.UrlJoin(url, "/plugins/servlet/access-tokens/manage")
  1248  }
  1249  
  1250  // ListCommits lists the commits for the specified repo and owner
  1251  func (b *BitbucketServerProvider) ListCommits(owner, repoName string, opt *ListCommitsArguments) ([]*GitCommit, error) {
  1252  	options := make(map[string]interface{})
  1253  	options["limit"] = pageLimit
  1254  	options["start"] = 0
  1255  	options["until"] = opt.SHA
  1256  	var commitsPage commitsPage
  1257  	commits := []*GitCommit{}
  1258  
  1259  	repo, err := b.GetRepository(owner, repoName)
  1260  	if err != nil {
  1261  		return nil, err
  1262  	}
  1263  	apiResponse, err := b.Client.DefaultApi.GetCommits(strings.ToUpper(owner), repo.Name, options)
  1264  	if err != nil {
  1265  		return nil, err
  1266  	}
  1267  
  1268  	err = mapstructure.Decode(apiResponse.Values, &commitsPage)
  1269  	if err != nil {
  1270  		return nil, err
  1271  	}
  1272  
  1273  	for _, commit := range commitsPage.Values {
  1274  		c := commit
  1275  		commits = append(commits, convertBitBucketCommitToGitCommit(&c, repo))
  1276  	}
  1277  
  1278  	return commits, nil
  1279  }
  1280  
  1281  // AddLabelsToIssue adds labels to issues or pullrequests
  1282  func (b *BitbucketServerProvider) AddLabelsToIssue(owner, repo string, number int, labels []string) error {
  1283  	log.Logger().Warnf("Adding labels not supported on bitbucket server yet for repo %s/%s issue %d labels %v", owner, repo, number, labels)
  1284  	return nil
  1285  }
  1286  
  1287  // GetLatestRelease fetches the latest release from the git provider for org and name
  1288  func (b *BitbucketServerProvider) GetLatestRelease(org string, name string) (*GitRelease, error) {
  1289  	return nil, nil
  1290  }
  1291  
  1292  // UploadReleaseAsset will upload an asset to org/repo to a release with id, giving it a name, it will return the release asset from the git provider
  1293  func (b *BitbucketServerProvider) UploadReleaseAsset(org string, repo string, id int64, name string, asset *os.File) (*GitReleaseAsset, error) {
  1294  	return nil, nil
  1295  }
  1296  
  1297  // GetBranch returns the branch information for an owner/repo, including the commit at the tip
  1298  func (b *BitbucketServerProvider) GetBranch(owner string, repo string, branch string) (*GitBranch, error) {
  1299  	return nil, nil
  1300  }
  1301  
  1302  // GetProjects returns all the git projects in owner/repo
  1303  func (b *BitbucketServerProvider) GetProjects(owner string, repo string) ([]GitProject, error) {
  1304  	return nil, nil
  1305  }
  1306  
  1307  //ConfigureFeatures sets specific features as enabled or disabled for owner/repo
  1308  func (b *BitbucketServerProvider) ConfigureFeatures(owner string, repo string, issues *bool, projects *bool, wikis *bool) (*GitRepository, error) {
  1309  	return nil, nil
  1310  }
  1311  
  1312  // IsWikiEnabled returns true if a wiki is enabled for owner/repo
  1313  func (b *BitbucketServerProvider) IsWikiEnabled(owner string, repo string) (bool, error) {
  1314  	return false, nil
  1315  }