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