Repository navigation
feat: Bitbucket Cloud pull requests - #696
Merged
Merged
Conversation
…ions are forge's own
Forgejo and GitLab each kept the same remote resolution, the same commentable
and reanchor reads and Forgejo the diff's index-line revisions; they move to
forge::{checkout, anchors, viewed} so a third host shares them.
…ail or alone HostKind::Bitbucket is Bitbucket Cloud: bitbucket.org alone, never a host whose name has a bitbucket label (Data Center), with no CLI, no stacks and "#" numbers. A saved Bitbucket token is an API token with its Atlassian email (Basic) or an access token (Bearer); SetHostToken carries the optional email and saving one method clears the other. The linking text skips a host without a CLI, so the GitHub-only text is unchanged.
Reads: the pull request with its head's build statuses, comments as threads by parent with resolution and edits, the diff and diffstat by Bitbucket's own next page, file text at a commit, conflicts through the shared conflict rule, default reviewers and workspace members, activity for the watch, viewed marks kept by Tcode. Writes: comment, reply, resolve, edit, title and description, reviewers written whole and checked against Bitbucket's answer, draft, decline, merge, and a review replayed as line comments, a summary and the vote. The credential goes to api.bitbucket.org only, redirects are followed on its origin only, and a 429 pauses until X-RateLimit-Reset.
…se it cannot undo Add host offers Bitbucket with bitbucket.org fixed; the Bitbucket row chooses an API token with the Atlassian email or an access token; the close confirmation says the host cannot reopen it where reopen is absent.
…s, without a viewer
This was referenced Oct 11, 2026
…first, and promises no reopen it cannot see
…around /conflicts The undocumented /conflicts endpoint leaves mergeability unknown when it stops answering. While no credential reads, summaries and branch lookups are kept ten minutes. A rate-limit pause lasts at most Bitbucket's hourly window and belongs to the credential that met it, and a new credential drops what was kept. A fork's merge base names the fork's head in its own repository.
Tryanks
marked this pull request as ready for review
October 11, 2026 04:56
Tryanks
enabled auto-merge (squash)
October 11, 2026 04:56
This was referenced Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Evidence
After:
The profile is a fresh
TCODE_DATA_DIRwith a throwaway HOME andHOSTNAME="Demo Mac", in English and light. It has a synthetic project (sample-app, remotegit@bitbucket.org:atlassian/atlassian-connect-express.git). Its one thread failed to start because no provider is on PATH, and it links public PRatlassian/atlassian-connect-express#547anonymously through the link dialog. That PR shows 8 conversation remarks, reviewers with approvals, 8 files with hunks and per-file counts, and "Viewed marks kept by Tcode". The first open of Files stayed on its skeleton while the window was unfocused and redrew when the window came to the front, so the background frame throttle explains it.The PR-page shots show public content (names, avatars, text) of the public PR atlassian/atlassian-connect-express#547, not the maintainer's data. The conversation shot was taken with the window unfocused (grey traffic lights); it was not retaken because the anonymous budget was spent.
Live reads on bitbucket.org through Tcode's host. These used
forge::connectdriven by a throwaway probe (not committed), anonymously. Counts arebitbucket path=…request log lines.atlassian/atlassian-connect-express#547(merged; inline threads; one build status):d0a06076aec9)bitbucket.org:anonymous; first thread anchored New side at line 237, revisiond0a06076aec9)/merge-base,/diff302 → same-origin/diff/…200; 8 files, complete, base the merge baseb58dfbb…)/src/{sha}/README.md, 830 lines)/workspaces/atlassian/membersanswers 401 anonymously → 5 candidates, incomplete)atlassian/aui#5381(open draft with conflicts and failing builds):/conflicts302 →/file-conflicts/…200; merge state Draft, 5 failing checks)Verdictsrule holds a conflict for 30 s at one head.x-ratelimit-limit: 60, 60;w=3600). Its 429s carry noRetry-After, onlyX-RateLimit-Reset(seconds left in the window), so the host now pauses until then. While paused, Tcode refused the next read before sending it (Paused). The PR page shows "Bitbucket rate limit reached. Status resumes soon."Writes: unverified. This machine has no
BITBUCKET_TOKENand no Bitbucket login, so no scratch repositorytcode-a7-bitbucket-checkwas created and nothing needed deleting. None of these were checked against a live server: comment, reply (parent.id), resolve/reopen (POST/DELETE …/comments/{id}/resolve), edit comment, edit title/description (partialPUT), reviewers (whole-listPUTchecked against the answer), draft/ready (PUT {draft}), decline, merge (merge_commit | squash | rebase_fast_forward,close_source_branchas the PR has it), and review (inline comments withto/fromandstart_*, then the summary, then/approveor/request-changes). Basic and Bearer authentication and/userwith an access token were not checked live either.Deviations from the brief, with evidence:
resolution(seen live:{"type":"comment_resolution","user":…,"created_on":…}on perf(chat): virtualize the timeline by segment, not by turn #547's thread roots), and upstream resolves by creating and deleting…/comments/{id}/resolve(BitbucketPullRequestApi.ts:933). The A7 design table marks Bitbucket resolve ✓./conflicts, not "always Unknown". The endpoint exists, is what upstream uses, and was verified live: it answers with each conflicting path (aui#5381) or an empty list. A permanent Unknown would keep the watch in the 2-minute in-flight cadence, which Source control: other hosts, Forgejo/Gitea, GitLab, Bitbucket, Azure DevOps (A7) #643's Decisions rule out. The summary still makes 2 reads (PR plus statuses) and leaves conflicts to the detail and watch reads.rebase_fast_forward(upstreamBitbucketPullRequestApi.ts:385-395) rather thanfast_forward, which refuses any branch that is not already up to date. This is unverified live./mergetakes amessage, but no read gives the message Bitbucket would write, so there is nothing to clean of agent credits. The credits row is absent, and the Settings description now says Bitbucket always uses the server's message.Left out because the API cannot do it (no substitute built): reactions (none), labels (none; permission
labelfalse), reopen (a decline is final), auto-merge, update branch, behind_by. Bitbucket's merge checks (approvals, tasks) have no documented read, so a PR they block reads Clean and the merge answers with Bitbucket's refusal. Bitbucket inline comments name no comment-time commit, so a thread's anchor uses the revisions itslinks.codecompares andoutdatedstays false. Media is read only forhttps://bitbucket.org/repo/…and/{ws}/{repo}/…URLs named in the conversation. Avatars live on atl-paas.net and are drawn by URL.Tests (all new or extended; none deleted):
the_model_reads_the_hosts_settings_configure: the GitHub-only linking text is still the literal; Bitbucket adds "GitHub or Bitbucket" and no CLI.each_kind_names_its_hosts_by_its_own_rule: bitbucket.org is accepted; other hosts, ports and paths are refused withFixed; abitbucketlabel is not detected as Bitbucket.each_host_goes_to_its_kinds_implementation: bitbucket.org routes by name; a Data Center name stays GitHub's.a_declined_bitbucket_pull_request_is_not_reopened: Reopen →Unsupportedbefore any request.a_credential_is_the_saved_method_then_the_environment: Basic vs Bearer, saving one clears the other, an email alone never turns an access token into an API token, env only for bitbucket.org, header-unsafe token never sent.a_cursor_is_only_a_page_of_the_api: off-origin, http, other-port and non-/2.0cursors are refused.urls_and_remotes_name_the_repository_on_bitbucket_org.build_statuses_read_as_checks: rerun key, STOPPED → failing, none → no state.line_comments_and_their_replies_are_one_thread: deep replies, deleted reply, resolution is not an edit, side and range, the author may only comment.a_reviewer_change_is_what_bitbucket_answers.bitbucket_org_is_listed_once_something_names_it.Review fixes (round 2)
Offer.actionis None), it now reads the action state first, as the merge dialog does. It then says "You can reopen it later." only whencapabilities.reopenis true, and "%{host_name} can't reopen it afterwards." when it is false. When the read fails it says "It may not be reopenable on %{host_name}." (new stringclose_desc_unknown, en and zh-CN). No test was added: lifecycle.rs has no dialog test seam./conflictsis not in Bitbucket's published API, so its NotFound, Refused and Uncertain answers now leave mergeability Unknown instead of failing the action-state or watch read. RateLimited, Paused, Unauthorized and HostDisabled still propagate.Bitbucket::sync_ttl, the one owner, chosen by credential presence; 60 s with a credential). That costs 3 requests per 10 minutes, or 18 an hour, for one PR and its checkout. Opening the detail page still spends its own reads. A write or invalidate drops the kept summary. Saving a token drops the kept summaries and branch lookups, so the new credential applies at once.files()now asks/merge-base/{source ws/repo}:{head}..{destination}, the revspec form Bitbucket's own comment links use. A same-repository PR keeps/merge-base/{head}..{destination}, which was verified live. The fork form is unverified: the anonymous budget was at 0 when checked (429,x-ratelimit-remaining: 0), so no public fork PR could be read.Checks at the PR head
cargo fmt --all --check: cleancargo clippy --workspace --all-targets --locked -- -D warnings: cleancargo nextest run --workspace --locked --no-fail-fast:1132 tests run: 1132 passed, 13 skipped (at c42b5ceb)cargo machete: no unused dependenciesiOS, Android and Web run in CI only. Dark theme and narrow desktop were not inspected; the phone geometry was.
This change alters the wire protocol, so the next release needs a
PROTOCOL_VERSIONbump: a note was added under "Unreleased" above the constant incrates/protocol/src/lib.rs(host kindbitbucket, credential sourcessaved_basic/saved_bearer,SetHostToken.email).Follow-ups (not in this PR)
changed_filesoptional across hosts will be filed by the lead.token_urland theUnverifiednotice for access tokens; the Clear/Set button order; the "Not connected" status line duplicated by the notice.labelscapability, so the Labels row is absent on hosts without labels (Bitbucket shows "No labels" today).Merge Danger
Door: two-way
Blast Radius: additive
Bitbucket is a new kind behind the existing dispatcher. GitHub, Forgejo and GitLab change only where Forgejo and GitLab now call the shared checkout, anchoring and revision helpers instead of their own identical copies. The wire gains a kind, two credential sources and an optional
emailonSetHostToken(noted for the next release). Secrets gain the@bitbucketand@bitbucket-emailgroups, which older builds keep as unknown profile entries.Part of #643