Skip to content

feat(migrations): add repository migration from bitbucket.org - #38607

Open
lunny wants to merge 2 commits into
go-gitea:mainfrom
lunny:lunny/bitbucket
Open

feat(migrations): add repository migration from bitbucket.org#38607
lunny wants to merge 2 commits into
go-gitea:mainfrom
lunny:lunny/bitbucket

Conversation

@lunny

@lunny lunny commented Jul 24, 2026

Copy link
Copy Markdown
Member

What this PR does

Adds support for migrating repositories and their metadata from bitbucket.org (Bitbucket Cloud), following Gitea's existing migration framework. Both the web UI and the REST API (POST /repos/migrate with service=bitbucket) are supported.

Migrated content

Implemented via the Bitbucket Cloud REST API v2:

  • Repository info (owner, name, description, website, default branch, clone URL, private flag)
  • Issues (with reporter, assignee, state, timestamps)
  • Issue & pull request comments
  • Pull requests (source/target branch, SHAs, merge commit, patch URL, state)
  • Labels — synthesized from Bitbucket issue metadata (kind/*, priority/*, component/*, version/*)
  • Milestones — derived from the issue tracker

Bitbucket has no "release" concept, so releases are not supported and the option is hidden in the migration UI. Bitbucket also uses separate numbering for issues and pull requests, so PR numbers are offset past the highest issue ID to avoid index collisions.

Rate limiting

Bitbucket Cloud enforces per-hour rate limits and returns HTTP 429 with an X-RateLimit-Reset header expressed as seconds-remaining (not a Unix epoch) and no Retry-After. The downloader transparently waits-and-retries on 429 (and transient 5xx) for idempotent GET requests:

  • Honors Retry-After, then X-RateLimit-Reset (delta-seconds, with epoch/RFC3339 fallbacks), then exponential backoff with jitter
  • All waits are capped, bounded by a per-call total budget, and interruptible via context
  • On exhaustion, the error advises authenticating to raise the limit (anonymous access is capped at 60 requests/hour)

Auth / clone

Supports username + app password (basic auth) and access tokens. FormatCloneURL uses Bitbucket's required x-token-auth username when cloning with a token.

UI

  • New per-service migration form (bitbucket.tmpl); Bitbucket appears after Gitea and before Gogs.
  • Reworked the migration source picker into a responsive auto-fill grid (minmax(200px, 1fr)) with smaller icons, and fixed a long-standing bug where the card divider followed the text width instead of spanning the full card.

Tests

  • Unit tests for the downloader (repo/issues/comments/PRs/labels/milestones) against a mock server
  • Unit tests for rate-limit wait computation (Retry-After, delta reset, epoch, RFC3339, backoff)
  • A 429 retry test
  • TestToGitServiceType extended for bitbucket
  • TestRepoMigrationUI integration test verifying the migration forms render

Notes

  • Swagger/OpenAPI specs regenerated (service enum now includes bitbucket).
  • Only the en-US locale was updated; other locales go through Crowdin.

ScreenShot

image

@lunny lunny added this to the 28.0.0 milestone Jul 24, 2026
@lunny lunny added the type/feature Completely new functionality. Can only be merged if feature freeze is not active. label Jul 24, 2026
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Jul 24, 2026
@lunny
lunny marked this pull request as ready for review July 24, 2026 03:54
@silverwind

silverwind commented Jul 24, 2026

Copy link
Copy Markdown
Member

+1.294 for a new migration source seems quite bad, we should probably rewrite and dedupe a lot of this migration code eventually.

@wxiaoguang

Copy link
Copy Markdown
Contributor

At least, the "owner list" should be extracted into a shared template first. #37970

I am not a fan of keeping flooding (copy&paste) such fragile code everywhere, very difficult to maintain (e.g.: Refactor dropdown ellipsis (#34123))

@silverwind

Copy link
Copy Markdown
Member

Yeah, maybe later, but I guess we should take this now as-is.

@wxiaoguang

Copy link
Copy Markdown
Contributor

Yeah, maybe later, but I guess we should take this now as-is.

Take this copied&pasted code as-is? Oh no ......

@lunny

lunny commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

At least, the "owner list" should be extracted into a shared template first. #37970

I am not a fan of keeping flooding (copy&paste) such fragile code everywhere, very difficult to maintain (e.g.: Refactor dropdown ellipsis (#34123))

owner list extracted into a shared template.

@wxiaoguang

Copy link
Copy Markdown
Contributor

"github.com/stretchr/testify/require"
)

func TestBitbucketDownloadRepo(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take it this has no reference repo on bitbucket to test against and that is why the reply capturing and replaying framework @silverwind made isn't used here?

@silverwind silverwind Jul 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could make a temp reference repo on bitbucket.org and capture for the migration tests, highly recommended.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But buibucket.org has a rate limit which is very slow.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you need a API token to get around public rate limits, this is the same already for the GitLab capture.

workspace := fields[0]
repoSlug := strings.TrimSuffix(fields[1], ".git")
webBaseURL := u.Scheme + "://" + u.Host
apiBaseURL := "https://api.bitbucket.org/2.0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hardcoded despite the web url being taken from url? What about self-hosted instances?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I know, bitbucket server has a different API from the cloud.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For those who are using bitbucket server, https://github.com/appleboy/BitbucketServer2Gitea is a better choice.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so why is webBaseURL being made from the request? I wouldn't expect host to be anything but https://bitbucket.org ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. type/feature Completely new functionality. Can only be merged if feature freeze is not active.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants