Replace deprecated API in Bitbucket Cloud importer
Overview
Atlassian has announced the deprecation of cross-workspace Bitbucket Cloud REST API endpoints, with removal scheduled for 2026-02-27 (19 days from now).
GitLab's Bitbucket Cloud importer currently uses one of the deprecated endpoints and must be updated to use the new workspace-scoped alternatives before the deadline.
Atlassian Announcements
- Removal of Public REST APIs to retrieve various Bitbucket artifacts for a user
- New Public APIs to retrieve list of accessible workspaces for a user
- Bitbucket Cloud REST API - Repositories
- Bitbucket Cloud REST API - Workspaces
Impact Assessment
Deprecated Endpoints Being Removed
| Endpoint | Replacement | GitLab Usage |
|---|---|---|
GET /2.0/repositories |
GET /2.0/repositories/{workspace} + GET /2.0/user/workspaces
|
lib/bitbucket/client.rb:117
|
GET /2.0/workspaces |
GET /2.0/user/workspaces |
|
What will break:
- Users will be unable to see their Bitbucket Cloud repositories in the GitLab import UI
- The repository listing step during Bitbucket Cloud import will fail
What will continue working:
-
/2.0/workspaces/{workspace}/membersendpoint (used for user mapping) is NOT deprecated - All other import functionality (issues, pull requests, etc.) uses repository-scoped endpoints that are unaffected
Affected Code
Primary file:
-
lib/bitbucket/client.rb-repos()method (lines 117-122)
Caller:
-
app/controllers/import/bitbucket_controller.rb-bitbucket_reposmethod (lines 154-158)
Current implementation:
def repos(filter: nil, limit: nil, after_cursor: nil)
path = "/repositories?role=member&sort=created_on"
path += "&q=name~\"#{filter}\"" if filter
get_collection(path, :repo, page_number: nil, limit: limit, after_cursor: after_cursor)
end
Suggested Implementation Plan
Core Changes
1. Add Workspace Representation
Click to expand
Create lib/bitbucket/representation/workspace.rb:
# frozen_string_literal: true
module Bitbucket
module Representation
class Workspace < Representation::Base
def slug
raw['slug']
end
def name
raw['name']
end
def uuid
raw['uuid']
end
end
end
end
2. Create Multi-Workspace Collection Wrapper
Create lib/bitbucket/multi_workspace_collection.rb:
# frozen_string_literal: true
module Bitbucket
class MultiWorkspaceCollection
attr_reader :items
def initialize(client, filter:, limit:, after_cursor:)
@items = fetch_all_repos(client, filter, limit, after_cursor)
@has_more = false # Simplified: no cross-workspace pagination initially
end
def page_info
{
has_next_page: @has_more,
start_cursor: nil,
end_cursor: nil
}
end
def to_a
@items
end
private
def fetch_all_repos(client, filter, limit, after_cursor)
workspaces = client.send(:fetch_workspaces).to_a
all_repos = []
workspaces.each do |workspace|
repos = client.send(:fetch_workspace_repos, workspace.slug, filter, limit, after_cursor).to_a
all_repos.concat(repos)
break if limit && all_repos.size >= limit
end
all_repos.first(limit || all_repos.size)
end
end
end
3. Update Bitbucket Client
Modify lib/bitbucket/client.rb:
# Update existing method
def repos(filter: nil, limit: nil, after_cursor: nil)
MultiWorkspaceCollection.new(self, filter: filter, limit: limit, after_cursor: after_cursor)
end
# Add new private methods
private
def fetch_workspaces
path = "/user/workspaces"
get_collection(path, :workspace, page_number: nil, limit: nil)
end
def fetch_workspace_repos(workspace_slug, filter, limit, after_cursor)
path = "/repositories/#{workspace_slug}?sort=created_on"
path += "&q=name~\"#{filter}\"" if filter
get_collection(path, :repo, page_number: nil, limit: limit, after_cursor: after_cursor)
end
Testing
-
Add unit tests:
spec/lib/bitbucket/representation/workspace_spec.rbspec/lib/bitbucket/multi_workspace_collection_spec.rb- Update
spec/lib/bitbucket/client_spec.rb
-
Add integration tests:
- Test multi-workspace repository fetching
- Test filtering across workspaces
- Test pagination behavior
-
Manual testing:
- Test import flow with multiple workspaces
- Test with single workspace
- Test with no accessible workspaces
Known Limitations & Trade-offs
Pagination Complexity
The new implementation aggregates repositories from multiple workspaces, which changes pagination behavior:
- Before: Single API call with cursor-based pagination
- After: Multiple API calls (1 for workspaces + N for each workspace's repos)
Initial implementation: Simplified pagination (fetch all, return subset) Future enhancement: Implement proper cross-workspace cursor pagination
Performance Considerations
- Increased API calls: N+1 pattern (1 workspace call + N repository calls)
- Rate limiting: Higher chance of hitting Bitbucket API rate limits
- Latency: Slower response time when users have many workspaces
Mitigation: Consider caching workspace list, implementing parallel requests in future iterations
Critical deadline: Changes must be in production by 2026-02-25 to allow for rollback if needed.
Acceptance Criteria
-
GET /2.0/repositoriesendpoint is no longer used -
GET /2.0/user/workspacesendpoint is implemented -
GET /2.0/repositories/{workspace}endpoint is implemented - Repository listing in import UI works with multiple workspaces
- Filtering works across all workspaces
- Pagination maintains existing UI behavior
- All existing tests pass
- New tests added for workspace functionality
- Documentation updated if needed
- Changes deployed to production before 2026-02-27
- Backport for GitLab 18.9 merged in time for 18.9.1
- Assessment of whether we need to backport further
Related
- Bitbucket Cloud importer:
lib/bitbucket/ - Import controller:
app/controllers/import/bitbucket_controller.rb - User importer (uses non-deprecated workspace endpoint):
lib/gitlab/bitbucket_import/importers/users_importer.rb