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).
**⚠️ REMOVAL DATE: February 27, 2026**
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](https://developer.atlassian.com/cloud/bitbucket/changelog/#CHANGE-2770)
- [New Public APIs to retrieve list of accessible workspaces for a user](https://developer.atlassian.com/cloud/bitbucket/changelog/#CHANGE-3022)
- [Bitbucket Cloud REST API - Repositories](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-repositories/)
- [Bitbucket Cloud REST API - Workspaces](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-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` | ✅ **USED** in `lib/bitbucket/client.rb:117` |
| `GET /2.0/workspaces` | `GET /2.0/user/workspaces` | ❌ Not used |
**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}/members` endpoint (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_repos` method (lines 154-158)
**Current implementation:**
```ruby
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
<details>
<summary>Click to expand</summary>
Create `lib/bitbucket/representation/workspace.rb`:
```ruby
# 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`:
```ruby
# 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`:
```ruby
# 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
```
</details>
### Testing
1. **Add unit tests:**
- `spec/lib/bitbucket/representation/workspace_spec.rb`
- `spec/lib/bitbucket/multi_workspace_collection_spec.rb`
- Update `spec/lib/bitbucket/client_spec.rb`
2. **Add integration tests:**
- Test multi-workspace repository fetching
- Test filtering across workspaces
- Test pagination behavior
3. **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
- [x] `GET /2.0/repositories` endpoint is no longer used
- [x] `GET /2.0/user/workspaces` endpoint is implemented
- [x] `GET /2.0/repositories/{workspace}` endpoint is implemented
- [x] Repository listing in import UI works with multiple workspaces
- [x] Filtering works across all workspaces
- [x] Pagination maintains existing UI behavior
- [x] All existing tests pass
- [x] New tests added for workspace functionality
- [x] 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`
issue