feat(migrations): add migration from Bitbucket Server (Data Center) (#39913)

Add migration from Bitbucket Server (Data Center). It has no issues,
wiki, milestones or releases, so those are not offered.

Migrated: git data with tags and LFS, and pull requests with state,
draft flag, merge commit/base, comments, approvals and inline code
comments including resolved threads. Multi-line ranges become a `Lines
a-b:` text prefix, as Gitea anchors code comments to one line. Auth is
an HTTP access token (personal, project or repository) or
username/password; 429 responses are retried.

Changes outside the downloader:
- `ReviewComment.Resolved`, applied by the uploader.
- The uploader creates a fork PR's head branch from its head commit when
already in the repository, instead of fetching the fork (Bitbucket keeps
it in `refs/pull-requests/<id>/from`; private forks need credentials).

Tested with unit tests, responses recorded from Bitbucket 10.5.1, and an
end-to-end migration. Independent of
https://github.com/go-gitea/gitea/pull/38607 (Bitbucket Cloud); the
service type enum value needs adjusting for whichever lands second.

---------

Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
Lunny Xiaoandbircni authored and GitHub committed 2026-10-23 19:25:29 +00:00
1 parent 8cd7721deb
commit a2da5cd7b5
25 files changed
+1575 -45

No files matched your search

@@ -0,0 +1,13 @@
Cache-Control: no-cache, no-transform
Content-Length: 583
Content-Security-Policy-Report-Only: script-src http://localhost:7990/bitbucket/s/ 'nonce-IWarugy5jB8gRNq1ZARFko1Q1VA='; default-src 'self'; base-uri 'self'; img-src data: *; style-src 'self' 'unsafe-inline'; frame-src 'self' https://www.youtube-nocookie.com/embed/ https://www.loom.com/embed/ https://player.vimeo.com/video/; connect-src 'self'; report-uri /bitbucket/rest/csp/latest/csp-report;
Content-Type: application/json;charset=UTF-8
Vary: X-AUSERNAME
Vary: X-AUSERID
Vary: Cookie
X-Arequestid: @8H2NK9x1265x325x0
X-Auserid: 2
X-Ausername: admin
X-Content-Type-Options: nosniff
{"slug":"demo","id":2,"name":"demo","hierarchyId":"01ee1752a4875aef8503","scmId":"git","state":"AVAILABLE","statusMessage":"Available","forkable":true,"project":{"key":"MIG","id":2,"name":"Migration Test","public":false,"type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG"}]}},"public":false,"archived":false,"links":{"clone":[{"href":"ssh://git@localhost:7999/mig/demo.git","name":"ssh"},{"href":"http://localhost:7990/bitbucket/scm/mig/demo.git","name":"http"}],"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG/repos/demo/browse"}]}}
@@ -0,0 +1,13 @@
Cache-Control: no-cache, no-transform
Content-Length: 59
Content-Security-Policy-Report-Only: script-src http://localhost:7990/bitbucket/s/ 'nonce-PiKZmxDnlrFC7QMnFC1SinlKErE='; default-src 'self'; base-uri 'self'; img-src data: *; style-src 'self' 'unsafe-inline'; frame-src 'self' https://www.youtube-nocookie.com/embed/ https://www.loom.com/embed/ https://player.vimeo.com/video/; connect-src 'self'; report-uri /bitbucket/rest/csp/latest/csp-report;
Content-Type: application/json;charset=UTF-8
Vary: X-AUSERNAME
Vary: X-AUSERID
Vary: Cookie
X-Arequestid: @8H2NK9x1265x326x0
X-Auserid: 2
X-Ausername: admin
X-Content-Type-Options: nosniff
{"id":"refs/heads/main","displayId":"main","type":"BRANCH"}
@@ -0,0 +1,10 @@
Cache-Control: no-cache, no-transform
Content-Security-Policy-Report-Only: script-src http://localhost:7990/bitbucket/s/ 'nonce-0W/hMaj8sXIdhG/WESDaZ6ESgmU='; default-src 'self'; base-uri 'self'; img-src data: *; style-src 'self' 'unsafe-inline'; frame-src 'self' https://www.youtube-nocookie.com/embed/ https://www.loom.com/embed/ https://player.vimeo.com/video/; connect-src 'self'; report-uri /bitbucket/rest/csp/latest/csp-report;
Content-Type: application/json;charset=UTF-8
Vary: x-ausername,x-auserid,cookie,accept-encoding
X-Arequestid: @8H2NK9x1265x330x0
X-Auserid: 2
X-Ausername: admin
X-Content-Type-Options: nosniff
{"size":9,"limit":100,"isLastPage":true,"values":[{"id":61,"createdDate":1792554902572,"user":{"name":"bob","emailAddress":"bob@example.com","active":true,"displayName":"bob","id":52,"slug":"bob","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/bob"}]}},"action":"COMMENTED","commentAction":"ADDED","comment":{"properties":{"repositoryId":2},"id":9,"version":0,"text":"Thread to resolve","author":{"name":"bob","emailAddress":"bob@example.com","active":true,"displayName":"bob","id":52,"slug":"bob","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/bob"}]}},"createdDate":1792554902569,"updatedDate":1792554902569,"comments":[{"properties":{"repositoryId":2},"id":10,"version":0,"text":"reply on resolved thread","author":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"createdDate":1792554906416,"updatedDate":1792554906416,"comments":[],"anchor":{"fromHash":"a41991381bd1e6944b3422cd1dc933b65c74cf67","toHash":"810d0600716f4575691c804b583c1c6a6e6708db","line":6,"lineType":"ADDED","fileType":"TO","path":"a.txt","diffType":"EFFECTIVE","orphaned":false},"threadResolved":true,"severity":"NORMAL","state":"OPEN","permittedOperations":{"editable":true,"transitionable":true,"deletable":true}}],"anchor":{"fromHash":"a41991381bd1e6944b3422cd1dc933b65c74cf67","toHash":"810d0600716f4575691c804b583c1c6a6e6708db","line":6,"lineType":"ADDED","fileType":"TO","path":"a.txt","diffType":"EFFECTIVE","orphaned":false},"threadResolved":true,"severity":"NORMAL","state":"OPEN","permittedOperations":{"editable":false,"transitionable":true,"deletable":false}},"commentAnchor":{"fromHash":"a41991381bd1e6944b3422cd1dc933b65c74cf67","toHash":"810d0600716f4575691c804b583c1c6a6e6708db","line":6,"lineType":"ADDED","fileType":"TO","path":"a.txt","diffType":"EFFECTIVE","orphaned":false},"diff":{"destination":{"components":["a.txt"],"parent":"","name":"a.txt","extension":"txt","toString":"a.txt"},"hunks":[{"sourceLine":1,"sourceSpan":5,"destinationLine":1,"destinationSpan":6,"segments":[{"type":"CONTEXT","lines":[{"destination":1,"source":1,"line":"line1","truncated":false}],"truncated":false},{"type":"REMOVED","lines":[{"destination":2,"source":2,"line":"line2","truncated":false}],"truncated":false},{"type":"ADDED","lines":[{"destination":2,"source":3,"line":"line2 changed","truncated":false}],"truncated":false},{"type":"CONTEXT","lines":[{"destination":3,"source":3,"line":"line3","truncated":false},{"destination":4,"source":4,"line":"line4","truncated":false},{"destination":5,"source":5,"line":"line5","truncated":false}],"truncated":false},{"type":"ADDED","lines":[{"destination":6,"source":6,"line":"line6 new","truncated":false,"commentIds":[9]}],"truncated":false}],"truncated":false}],"truncated":false,"properties":{"toHash":"810d0600716f4575691c804b583c1c6a6e6708db","current":true,"fromHash":"a41991381bd1e6944b3422cd1dc933b65c74cf67"}}},{"id":60,"createdDate":1792554902532,"user":{"name":"bob","emailAddress":"bob@example.com","active":true,"displayName":"bob","id":52,"slug":"bob","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/bob"}]}},"action":"COMMENTED","commentAction":"ADDED","comment":{"properties":{"repositoryId":2},"id":8,"version":0,"text":"Multi line comment","author":{"name":"bob","emailAddress":"bob@example.com","active":true,"displayName":"bob","id":52,"slug":"bob","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/bob"}]}},"createdDate":1792554902528,"updatedDate":1792554902528,"comments":[],"anchor":{"fromHash":"a41991381bd1e6944b3422cd1dc933b65c74cf67","toHash":"810d0600716f4575691c804b583c1c6a6e6708db","line":4,"lineType":"CONTEXT","fileType":"TO","path":"a.txt","multilineMarker":{"startLine":2,"startLineType":"ADDED"},"multilineSpan":{"srcSpanStart":2,"srcSpanEnd":4,"dstSpanStart":2,"dstSpanEnd":4},"diffType":"EFFECTIVE","orphaned":false},"threadResolved":false,"severity":"NORMAL","state":"OPEN","permittedOperations":{"editable":false,"transitionable":true,"deletable":false}},"commentAnchor":{"fromHash":"a41991381bd1e6944b3422cd1dc933b65c74cf67","toHash":"810d0600716f4575691c804b583c1c6a6e6708db","line":4,"lineType":"CONTEXT","fileType":"TO","path":"a.txt","multilineMarker":{"startLine":2,"startLineType":"ADDED"},"multilineSpan":{"srcSpanStart":2,"srcSpanEnd":4,"dstSpanStart":2,"dstSpanEnd":4},"diffType":"EFFECTIVE","orphaned":false},"diff":{"destination":{"components":["a.txt"],"parent":"","name":"a.txt","extension":"txt","toString":"a.txt"},"hunks":[{"sourceLine":3,"sourceSpan":2,"destinationLine":2,"destinationSpan":3,"segments":[{"type":"ADDED","lines":[{"destination":2,"source":3,"line":"line2 changed","truncated":false}],"truncated":false},{"type":"CONTEXT","lines":[{"destination":3,"source":3,"line":"line3","truncated":false},{"destination":4,"sourLine truncated
@@ -0,0 +1,10 @@
Cache-Control: no-cache, no-transform
Content-Security-Policy-Report-Only: script-src http://localhost:7990/bitbucket/s/ 'nonce-3lbjhq7hBJksGbRCuPRjU0dVbfI='; default-src 'self'; base-uri 'self'; img-src data: *; style-src 'self' 'unsafe-inline'; frame-src 'self' https://www.youtube-nocookie.com/embed/ https://www.loom.com/embed/ https://player.vimeo.com/video/; connect-src 'self'; report-uri /bitbucket/rest/csp/latest/csp-report;
Content-Type: application/json;charset=UTF-8
Vary: x-ausername,x-auserid,cookie,accept-encoding
X-Arequestid: @8H2NK9x1265x328x0
X-Auserid: 2
X-Ausername: admin
X-Content-Type-Options: nosniff
{"size":2,"limit":100,"isLastPage":true,"values":[{"id":53,"createdDate":1792553814504,"user":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"action":"MERGED","commit":{"id":"504e78ed1c8fcbc0e55f5f0b6d3c49a0bc1ae861","displayId":"504e78ed1c8","author":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"authorTimestamp":1792553814000,"committer":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"committerTimestamp":1792553814000,"message":"Merge pull request #2 in MIG/demo from merged to main\n\n* commit 'a84b8e075f3071e08052c7d9f8dbfd8d679b11c7':\n merged change","parents":[{"id":"a41991381bd1e6944b3422cd1dc933b65c74cf67","displayId":"a41991381bd","author":{"name":"admin","emailAddress":"admin@example.com"},"authorTimestamp":1792553776000,"committer":{"name":"admin","emailAddress":"admin@example.com"},"committerTimestamp":1792553776000,"message":"initial","parents":[]},{"id":"a84b8e075f3071e08052c7d9f8dbfd8d679b11c7","displayId":"a84b8e075f3","author":{"name":"admin","emailAddress":"admin@example.com"},"authorTimestamp":1792553779000,"committer":{"name":"admin","emailAddress":"admin@example.com"},"committerTimestamp":1792553779000,"message":"merged change","parents":[{"id":"a41991381bd1e6944b3422cd1dc933b65c74cf67","displayId":"a41991381bd"}]}]},"autoMerge":false,"strategyId":"no-ff"},{"id":51,"createdDate":1792553814274,"user":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"action":"OPENED"}],"start":0}
@@ -0,0 +1,13 @@
Cache-Control: no-cache, no-transform
Content-Length: 628
Content-Security-Policy-Report-Only: script-src http://localhost:7990/bitbucket/s/ 'nonce-U5OUdU3Hi1/+nlNGoIe5d424Gfs='; default-src 'self'; base-uri 'self'; img-src data: *; style-src 'self' 'unsafe-inline'; frame-src 'self' https://www.youtube-nocookie.com/embed/ https://www.loom.com/embed/ https://player.vimeo.com/video/; connect-src 'self'; report-uri /bitbucket/rest/csp/latest/csp-report;
Content-Type: application/json;charset=UTF-8
Vary: X-AUSERNAME
Vary: X-AUSERID
Vary: Cookie
X-Arequestid: @8H2NK9x1265x329x0
X-Auserid: 2
X-Ausername: admin
X-Content-Type-Options: nosniff
{"values":[{"id":"a84b8e075f3071e08052c7d9f8dbfd8d679b11c7","displayId":"a84b8e075f3","author":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL"},"authorTimestamp":1792553779000,"committer":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL"},"committerTimestamp":1792553779000,"message":"merged change","parents":[{"id":"a41991381bd1e6944b3422cd1dc933b65c74cf67","displayId":"a41991381bd"}]}],"size":1,"isLastPage":true,"start":0,"limit":100,"nextPageStart":null}
@@ -0,0 +1,13 @@
Cache-Control: no-cache, no-transform
Content-Length: 1414
Content-Security-Policy-Report-Only: script-src http://localhost:7990/bitbucket/s/ 'nonce-e6rCQzX1Ipt1fa5qEaxaFaNwY1Y='; default-src 'self'; base-uri 'self'; img-src data: *; style-src 'self' 'unsafe-inline'; frame-src 'self' https://www.youtube-nocookie.com/embed/ https://www.loom.com/embed/ https://player.vimeo.com/video/; connect-src 'self'; report-uri /bitbucket/rest/csp/latest/csp-report;
Content-Type: application/json;charset=UTF-8
Vary: X-AUSERNAME
Vary: X-AUSERID
Vary: Cookie
X-Arequestid: @8H2NK9x1265x331x0
X-Auserid: 2
X-Ausername: admin
X-Content-Type-Options: nosniff
{"size":3,"limit":100,"isLastPage":true,"values":[{"id":56,"createdDate":1792553814846,"user":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"action":"COMMENTED","commentAction":"ADDED","comment":{"properties":{"repositoryId":2},"id":7,"version":0,"text":"declining this","author":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"createdDate":1792553814843,"updatedDate":1792553814843,"comments":[],"threadResolved":false,"severity":"NORMAL","state":"OPEN","permittedOperations":{"editable":true,"transitionable":true,"deletable":true}}},{"id":55,"createdDate":1792553814750,"user":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"action":"DECLINED"},{"id":52,"createdDate":1792553814412,"user":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"action":"OPENED"}],"start":0}
@@ -0,0 +1,10 @@
Cache-Control: no-cache, no-transform
Content-Security-Policy-Report-Only: script-src http://localhost:7990/bitbucket/s/ 'nonce-Jsnc2VX7MB+aw+YjfC4S+n6OUeg='; default-src 'self'; base-uri 'self'; img-src data: *; style-src 'self' 'unsafe-inline'; frame-src 'self' https://www.youtube-nocookie.com/embed/ https://www.loom.com/embed/ https://player.vimeo.com/video/; connect-src 'self'; report-uri /bitbucket/rest/csp/latest/csp-report;
Content-Type: application/json;charset=UTF-8
Vary: x-ausername,x-auserid,cookie,accept-encoding
X-Arequestid: @8H2NK9x1265x327x0
X-Auserid: 2
X-Ausername: admin
X-Content-Type-Options: nosniff
{"size":4,"limit":50,"isLastPage":true,"values":[{"id":1,"version":1,"title":"Open PR","description":"an open pull request","state":"OPEN","open":true,"closed":false,"draft":false,"createdDate":1792553804655,"updatedDate":1792553804655,"fromRef":{"id":"refs/heads/feature","displayId":"feature","latestCommit":"810d0600716f4575691c804b583c1c6a6e6708db","type":"BRANCH","repository":{"slug":"demo","id":2,"name":"demo","hierarchyId":"01ee1752a4875aef8503","scmId":"git","state":"AVAILABLE","statusMessage":"Available","forkable":true,"project":{"key":"MIG","id":2,"name":"Migration Test","public":false,"type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG"}]}},"public":false,"archived":false,"links":{"clone":[{"href":"ssh://git@localhost:7999/mig/demo.git","name":"ssh"},{"href":"http://localhost:7990/bitbucket/scm/mig/demo.git","name":"http"}],"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG/repos/demo/browse"}]}}},"toRef":{"id":"refs/heads/main","displayId":"main","latestCommit":"504e78ed1c8fcbc0e55f5f0b6d3c49a0bc1ae861","type":"BRANCH","repository":{"slug":"demo","id":2,"name":"demo","hierarchyId":"01ee1752a4875aef8503","scmId":"git","state":"AVAILABLE","statusMessage":"Available","forkable":true,"project":{"key":"MIG","id":2,"name":"Migration Test","public":false,"type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG"}]}},"public":false,"archived":false,"links":{"clone":[{"href":"ssh://git@localhost:7999/mig/demo.git","name":"ssh"},{"href":"http://localhost:7990/bitbucket/scm/mig/demo.git","name":"http"}],"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG/repos/demo/browse"}]}}},"locked":false,"author":{"user":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"role":"AUTHOR","approved":false,"status":"UNAPPROVED"},"creator":{"user":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/admin"}]}},"role":"CREATOR","approved":false,"status":"UNAPPROVED"},"reviewers":[{"user":{"name":"bob","emailAddress":"bob@example.com","active":true,"displayName":"bob","id":52,"slug":"bob","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/bob"}]}},"lastReviewedCommit":"810d0600716f4575691c804b583c1c6a6e6708db","role":"REVIEWER","approved":true,"status":"APPROVED"},{"user":{"name":"carol","emailAddress":"carol@example.com","active":true,"displayName":"carol","id":53,"slug":"carol","type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/users/carol"}]}},"lastReviewedCommit":"810d0600716f4575691c804b583c1c6a6e6708db","role":"REVIEWER","approved":false,"status":"NEEDS_WORK"}],"participants":[],"properties":{"resolvedTaskCount":0,"isQueued":false,"commentCount":9,"openTaskCount":0},"links":{"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG/repos/demo/pull-requests/1"}]}},{"id":2,"version":2,"title":"Merged PR","description":"Merged PR body","state":"MERGED","open":false,"closed":true,"draft":false,"createdDate":1792553814251,"updatedDate":1792553814504,"closedDate":1792553814504,"fromRef":{"id":"refs/heads/merged","displayId":"merged","latestCommit":"a84b8e075f3071e08052c7d9f8dbfd8d679b11c7","type":"BRANCH","repository":{"slug":"demo","id":2,"name":"demo","hierarchyId":"01ee1752a4875aef8503","scmId":"git","state":"AVAILABLE","statusMessage":"Available","forkable":true,"project":{"key":"MIG","id":2,"name":"Migration Test","public":false,"type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG"}]}},"public":false,"archived":false,"links":{"clone":[{"href":"ssh://git@localhost:7999/mig/demo.git","name":"ssh"},{"href":"http://localhost:7990/bitbucket/scm/mig/demo.git","name":"http"}],"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG/repos/demo/browse"}]}}},"toRef":{"id":"refs/heads/main","displayId":"main","latestCommit":"a41991381bd1e6944b3422cd1dc933b65c74cf67","type":"BRANCH","repository":{"slug":"demo","id":2,"name":"demo","hierarchyId":"01ee1752a4875aef8503","scmId":"git","state":"AVAILABLE","statusMessage":"Available","forkable":true,"project":{"key":"MIG","id":2,"name":"Migration Test","public":false,"type":"NORMAL","links":{"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG"}]}},"public":false,"archived":false,"links":{"clone":[{"href":"ssh://git@localhost:7999/mig/demo.git","name":"ssh"},{"href":"http://localhost:7990/bitbucket/scm/mig/demo.git","name":"http"}],"self":[{"href":"http://localhost:7990/bitbucket/projects/MIG/repos/demo/browse"}]}}},"locked":false,"author":{"user":{"name":"admin","emailAddress":"admin@example.com","active":true,"displayName":"Administrator","id":2,"slug":"admin","type":"NORMAL","links":{"self":[{"href":"htLine truncated