You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by kl...@apache.org on 2026/07/26 12:35:13 UTC

(devlake) branch main updated: fix(bitbucket): paginate remote-scopes API to return all repositories (#8999) (#9004)

This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new c4a15bf89 fix(bitbucket): paginate remote-scopes API to return all repositories (#8999) (#9004)
c4a15bf89 is described below

commit c4a15bf894a7108c61df49798ff13d43329fd7e2
Author: Bramhanand Lingala <br...@gmail.com>
AuthorDate: Sun Jul 26 08:35:08 2026 -0400

    fix(bitbucket): paginate remote-scopes API to return all repositories (#8999) (#9004)
---
 backend/plugins/bitbucket/api/remote_api.go      | 14 +++-
 backend/plugins/bitbucket/api/remote_api_test.go | 83 ++++++++++++++++++++++++
 backend/plugins/bitbucket/models/repo.go         |  2 +
 3 files changed, 98 insertions(+), 1 deletion(-)

diff --git a/backend/plugins/bitbucket/api/remote_api.go b/backend/plugins/bitbucket/api/remote_api.go
index cf60b1bd5..36fa45a0d 100644
--- a/backend/plugins/bitbucket/api/remote_api.go
+++ b/backend/plugins/bitbucket/api/remote_api.go
@@ -107,6 +107,12 @@ func listBitbucketWorkspaces(
 			FullName: r.GroupName(),
 		})
 	}
+	if resBody.Next != "" {
+		nextPage = &BitbucketRemotePagination{
+			Page:    page.Page + 1,
+			PageLen: page.PageLen,
+		}
+	}
 	return
 }
 
@@ -123,7 +129,7 @@ func listBitbucketRepos(
 	var res *http.Response
 	// list projects part
 	res, err = apiClient.Get(fmt.Sprintf("/repositories/%s", workspace), url.Values{
-		"fields":  {"values.name,values.full_name,values.language,values.description,values.owner.display_name,values.created_on,values.updated_on,values.links.clone,values.links.html,pagelen,page,size"},
+		"fields":  {"values.name,values.full_name,values.language,values.description,values.owner.display_name,values.created_on,values.updated_on,values.links.clone,values.links.html,pagelen,page,size,next"},
 		"page":    {fmt.Sprintf("%v", page.Page)},
 		"pagelen": {fmt.Sprintf("%v", page.PageLen)},
 	}, nil)
@@ -153,6 +159,12 @@ func listBitbucketRepos(
 			Data:     r.ConvertApiScope(),
 		})
 	}
+	if resBody.Next != "" {
+		nextPage = &BitbucketRemotePagination{
+			Page:    page.Page + 1,
+			PageLen: page.PageLen,
+		}
+	}
 	return
 }
 
diff --git a/backend/plugins/bitbucket/api/remote_api_test.go b/backend/plugins/bitbucket/api/remote_api_test.go
new file mode 100644
index 000000000..26bdbc4a2
--- /dev/null
+++ b/backend/plugins/bitbucket/api/remote_api_test.go
@@ -0,0 +1,83 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 api
+
+import (
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+)
+
+type fakeApiClient struct{ body string }
+
+func (f *fakeApiClient) SetData(name string, data interface{})                    {}
+func (f *fakeApiClient) GetData(name string) interface{}                          { return nil }
+func (f *fakeApiClient) SetHeaders(headers map[string]string)                     {}
+func (f *fakeApiClient) SetBeforeFunction(callback plugin.ApiClientBeforeRequest) {}
+func (f *fakeApiClient) GetBeforeFunction() plugin.ApiClientBeforeRequest         { return nil }
+func (f *fakeApiClient) SetAfterFunction(callback plugin.ApiClientAfterResponse)  {}
+func (f *fakeApiClient) GetAfterFunction() plugin.ApiClientAfterResponse          { return nil }
+
+func (f *fakeApiClient) Get(path string, query url.Values, headers http.Header) (*http.Response, errors.Error) {
+	return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(f.body))}, nil
+}
+func (f *fakeApiClient) Post(path string, query url.Values, body interface{}, headers http.Header) (*http.Response, errors.Error) {
+	return nil, nil
+}
+
+func TestListBitbucketRepos_ReturnsNextPage(t *testing.T) {
+	client := &fakeApiClient{body: `{"pagelen":2,"page":1,"size":4,
+		"next":"https://api.bitbucket.org/2.0/repositories/myworkspace?page=2",
+		"values":[{"name":"repo-a","full_name":"myworkspace/repo-a"},
+		{"name":"repo-b","full_name":"myworkspace/repo-b"}]}`}
+	children, nextPage, err := listBitbucketRepos(client, "myworkspace", BitbucketRemotePagination{Page: 1, PageLen: 2})
+	assert.Nil(t, err)
+	assert.Len(t, children, 2)
+	if assert.NotNil(t, nextPage) {
+		assert.Equal(t, 2, nextPage.Page)
+	}
+}
+
+func TestListBitbucketRepos_LastPageHasNoNextPage(t *testing.T) {
+	client := &fakeApiClient{body: `{"pagelen":2,"page":2,"size":4,
+		"values":[{"name":"repo-c","full_name":"myworkspace/repo-c"},
+		{"name":"repo-d","full_name":"myworkspace/repo-d"}]}`}
+	children, nextPage, err := listBitbucketRepos(client, "myworkspace", BitbucketRemotePagination{Page: 2, PageLen: 2})
+	assert.Nil(t, err)
+	assert.Len(t, children, 2)
+	assert.Nil(t, nextPage)
+}
+
+func TestListBitbucketWorkspaces_ReturnsNextPage(t *testing.T) {
+	client := &fakeApiClient{body: `{"pagelen":1,"page":1,"size":2,
+		"next":"https://api.bitbucket.org/2.0/user/workspaces?page=2",
+		"values":[{"workspace":{"slug":"ws-a","name":"Workspace A"}}]}`}
+	children, nextPage, err := listBitbucketWorkspaces(client, BitbucketRemotePagination{Page: 1, PageLen: 1})
+	assert.Nil(t, err)
+	assert.Len(t, children, 1)
+	if assert.NotNil(t, nextPage) {
+		assert.Equal(t, 2, nextPage.Page)
+	}
+}
diff --git a/backend/plugins/bitbucket/models/repo.go b/backend/plugins/bitbucket/models/repo.go
index b33df3615..126990a94 100644
--- a/backend/plugins/bitbucket/models/repo.go
+++ b/backend/plugins/bitbucket/models/repo.go
@@ -114,6 +114,7 @@ type WorkspaceResponse struct {
 	Pagelen int             `json:"pagelen"`
 	Page    int             `json:"page"`
 	Size    int             `json:"size"`
+	Next    string          `json:"next"`
 	Values  []GroupResponse `json:"values"`
 }
 
@@ -142,6 +143,7 @@ type ReposResponse struct {
 	Pagelen int                `json:"pagelen"`
 	Page    int                `json:"page"`
 	Size    int                `json:"size"`
+	Next    string             `json:"next"`
 	Values  []BitbucketApiRepo `json:"values"`
 }