Menu

#21 feat(bitbucket,repo,ai): Bitbucket Cloud integration, PR/pipelines actions, unified publish, provider-aware AI reviews

closed
nobody
2026-07-05
2026-07-04
Anonymous
No

Originally created by: theBGuy
Originally owned by: theBGuy

This change integrates Bitbucket Cloud as a fully-supported code hosting provider alongside GitHub and GitLab, bringing repository browsing, pull request and Pipelines support (read and write), publish, Insights, and extensive repository management into the desktop app. AI reviews and prompts are now aware of the hosting provider and can incorporate external context. The frontend surfaces for publish, Insights, and notifications are unified to provide consistent interactions across all supported providers.

Bitbucket Cloud integration

  • Adds complete Bitbucket Cloud API support in src-tauri/src/forge/bitbucket.rs, including repo listing, pull requests (read/write), Pipelines, repository settings, and deployments.
  • Implements HTTP authentication and credential management (src-tauri/src/forge/http.rs), and error handling extensions (src-tauri/src/error.rs).
  • Normalizes interface models in src-tauri/src/forge/model.rs, integrating Bitbucket types and mapping API responses.
  • Registers Bitbucket in the forge registry (src-tauri/src/forge/mod.rs) and provider capabilities.
  • Updates frontend status, account handling, and settings to include Bitbucket support in src/features/settings/AccountsSection.tsx, src/features/repository/ForgeNotReady.tsx, and related files.

Pull Request & Pipelines actions (Bitbucket)

  • Enables reading and managing Bitbucket Cloud PRs: list, view, diff, comments, approve/unapprove, request changes (toggleable), reviewers picker, draft/ready toggle, edit, and merge with all strategies.
  • Adds PR Tasks checklist component (src/features/pulls/PrTasksSection.tsx), with add/edit/resolve/delete, progress bar, and closed-state handling.
  • Supports custom PR creation/edits with reviewers selection in src/features/pulls/CreatePrDialog.tsx, src/features/pulls/ReviewersPopover.tsx, and related inputs.
  • Supports Pipelines: rerun, trigger (custom pipeline selection, variables), stop, read deployment environments (src/features/actions/RunWorkflowDialog.tsx, src/features/repo-settings/BitbucketEnvironmentsSection.tsx).
  • Presents branch restrictions, default reviewers, variables (secured), schedules, webhooks, and full Danger Zone management in src/features/repo-settings/* components.

Repository publishing, Insights, and cross-provider UX

  • Unifies publish controls across the sync bar, not-ready panels, and new-repo onboarding, with complete Bitbucket publish flow (src/features/repository/PublishRepoControl.tsx, src/features/repository/PublishDialog.tsx, plus UI references).
  • Brings Insights (local git stats, CI, links out to Bitbucket resources) to Bitbucket repos (src/features/repository/insights/InsightsBoard.tsx, src/features/repository/insights/LinkOutsCard.tsx).
  • Adds Bitbucket notifications for PRs and remote PR sync to background polling (src/features/repository/usePrNotifications.ts).
  • Updates and cleans up repository menus, list, and settings dialogs for cross-provider clarity and Bitbucket features.

AI prompts and review context

  • Makes AI review and summary prompts provider-aware, passing relevant provider and repository info (src/lib/ai/prompt.ts, src/lib/ai/types.ts, src/features/pulls/useGeneratePrDescription.ts, src/features/tags/useGenerateReleaseNotes.ts).
  • Supports context-aware AI responses on Bitbucket, including external review inputs (src/lib/ai/external-context.ts).

Backend and model updates

  • Extends neutral forge models and APIs to support Bitbucket types and capabilities (src-tauri/src/forge/model.rs).
  • Adapts git queries and types to recognize Bitbucket remotes (src/lib/git/api.ts, src/lib/git/queries.ts, src/lib/git/types.ts).
  • Miscellaneous cross-provider bugfixes and UI/UX improvements to support the new workflows (src/features/repository/SyncControls.tsx, src/features/repo-settings/DangerZone.tsx, src/lib/pulls/queries.ts).

Documentation & meta

  • Updates documentation and marketing copy with Bitbucket support in README.md, CHANGELOG.md, and main site (site/src/pages/index.astro).
  • Adds new .gitignore entries as needed.

These changes collectively deliver first-class Bitbucket Cloud integration and enable a unified workflow for repositories and pull requests across all supported forges, both in the desktop experience and via AI-driven features.

Related

Tickets: #142

Discussion

  • Anonymous

    Anonymous - 2026-07-04

    Originally posted by: theBGuy

    AI review (sonnet) · automated

    This is a very large first-class provider integration (~5500 lines of Rust + significant frontend changes) that follows the established GitLab patterns closely: OS-keyring credentials, neutral model mapping, pre-mutation guards, paginated reads, post-mutation disclosure on partial failures, and a strong unit-test suite covering the mapping layer. The architecture is sound and the overall implementation is high-quality. Two genuine issues worth fixing before shipping.


    Correctness

    should-fix · bitbucket.rs · publish_repo / git credential approve seeding

    The credential seed at ~line 3679 uses a hardcoded username=x-bitbucket-api-token-auth:

    let approve_input = format!(
        "protocol=https\nhost=bitbucket.org\nusername=x-bitbucket-api-token-auth\npassword={}\n\n",
        creds.token
    );
    

    But the module-level doc and the BbCredentials type both document that Bitbucket authenticates over HTTP Basic as {atlassian_account_email}:{api_token} — the email is the username, not a special sentinel. git credential approve seeds whatever username git will subsequently emit in the Authorization: Basic ... header, so a mismatch means the seeded credential is never matched and the immediate git push -c credential.interactive=false will fail for any user who does not already have a separate GCM OAuth session for bitbucket.org.

    The error path discloses "The Bitbucket repository was created at X, but pushing failed", so it's not silent, but the publish flow is unusable in the normal onboarding case (user connects a token, then immediately tries to publish a local repo with no pre-existing GCM session).

    Fix: use creds.email as the username:

    let approve_input = format!(
        "protocol=https\nhost=bitbucket.org\nusername={}\npassword={}\n\n",
        creds.email, creds.token
    );
    

    Edge cases

    should-fix · bitbucket.rs · rewritten_origin_url

    The HTTPS branch rewrites any http:// / https:// origin URL unconditionally:

    if trimmed.starts_with("http://") || trimmed.starts_with("https://") {
        ...
        Some(format!("{scheme}://bitbucket.org/{ws}/{new_slug}.git"))
    }
    

    A user who cloned through a corporate git proxy (https://git-proxy.corp.example.com/ws/slug.git) would have their proxy URL silently replaced with the canonical bitbucket.org URL after a rename, breaking future fetches/pushes through the proxy. The function returns None when the URL isn't recognized, so the fix is to guard on the host:

    if trimmed.starts_with("http://") || trimmed.starts_with("https://") {
        let host = remote_host(trimmed)?;
        if host != "bitbucket.org" { return None; }
        ...
    }
    

    Nits

    nit · bitbucket.rs · pr_tasks initial URL

    Every other paginated list endpoint in the file explicitly requests pagelen=100. The pr_tasks first-page URL is {base}/pullrequests/{number}/tasks with no pagelen, so it uses Bitbucket's server default (typically 10–20). Subsequent pages follow next, so correctness is preserved, but the first page usually requires an immediate pagination round-trip for any PR with more than ~10 tasks. Should be {base}/pullrequests/{number}/tasks?pagelen=100.

    nit · bitbucket.rs · resolve_pipeline fallback catches auth errors

    The fallback from the primary pipeline GET to the query-list endpoint catches AppError::Bitbucket(_) broadly:

    Err(AppError::Bitbucket(_)) => {
        // query by build_number...
    }
    

    Because http_error(401, …) and http_error(429, …) both produce AppError::Bitbucket(…), an expired token triggers two consecutive API calls to the same expired endpoint. The error message from the second call is correct, so this is only a performance nit, but it's worth tightening the match to only a genuine "not found" status to avoid the double request on auth failures.

     
  • Anonymous

    Anonymous - 2026-07-04

    Originally posted by: theBGuy

    AI security audit (sonnet) · automated

    Let me examine the key security-relevant files in this diff.Now let me look at the SSRF-relevant parts of resolve_url with the pagination next handling, and the Tauri command registration.Let me examine more of the credential handling and the AccountsSection.tsx changes to check if the token/email are exposed over IPC.No genuine security vulnerabilities in these changes meet the reporting thresholds.


    Triage of Copilot flags:

    • Copilot flagged ForgeNotReady.tsx:35 (stale "not yet implemented" comment) and PullRequestsPanel.tsx:82 (misleading disabled-reason text): Documentation/UX issues, not security.

    • Copilot flagged prompt.ts:183 (mixed PR/MR terminology for GitLab): Cosmetic correctness issue, not security.

    • Copilot flagged sync.ts:30 (sameSha docstring vs. implementation divergence): A correctness concern about SHA comparison, but not exploitable as a security vulnerability — the worst case is a false-negative "SHAs match" on a very short string, not an attacker-controlled outcome.

    • Copilot flagged BitbucketWebhooksSection.tsx:261 ("Skip certificate verification" lacks a security warning in the UI): The setting tells Bitbucket's servers to skip TLS verification on outbound webhook delivery — it doesn't affect the app's own TLS. While a missing warning is a UX concern, there is no attack path an external adversary can exercise without the user first deliberately enabling the toggle; it does not clear the Low-severity confidence threshold (9/10).


    Areas I investigated that turned out clean:

    • Credential handling over IPC: BbAccountInfo never includes the token; forge secrets are pub(crate) only and never returned by any #[tauri::command].

    • Pagination next URL following workspace_members / pr_tasks: The next URL comes from Bitbucket's own HTTPS API response and is sent directly to resolve_url, which passes absolute URLs through to bb_get_json with Basic auth. There is no host-pinning check. However, the next URL is server-generated (not user-controlled), its origin is protected by TLS (api.bitbucket.org), and compromising it requires breaking Bitbucket's TLS or server — which is outside the threat model here. Similarly for the Location header followed in poll_merge_task.

    • BBQL injection in prs_for_branch: The " and \ block combined with encode_query_value percent-encoding of the complete query parameter is adequate for the BBQL context.

    • encode_uuid path segments: All percent-encodes outside the unreserved set; path traversal sequences like ../ are safely encoded before use in API paths.

    • External review content in AI prompts: This is the product working as designed (PR comment content → AI review context). The model has no tool access or security decision capability in the review flow, so this is not a cross-privilege-boundary XPIA scenario.

     
  • Anonymous

    Anonymous - 2026-07-05

    Originally posted by: theBGuy

    AI review (sonnet) · automated

    Resolved since last review

    • publish_repo credential sentinel — The module-level doc and function comment now explicitly state (with a "probe-validated" note) that x-bitbucket-api-token-auth is the correct sentinel for git-over-HTTPS, distinct from the email:token Basic pair used by the REST API. The previous finding was a false positive; the code was already correct.
    • rewritten_origin_url proxy URL clobbering — Fixed: the HTTPS branch now extracts the authority, strips userinfo/port, and bails with None unless the host is bitbucket.org (case-insensitive). The SCP branch similarly guards on the host after @, so git@github.com:… URLs are left alone. New unit tests cover both cases.
    • pr_tasks missing pagelen — Fixed: first-page URL is now …/tasks?pagelen=100.
    • resolve_pipeline broad error catch — Fixed: uses bb_get_text_status to inspect the raw HTTP status, only falling through to the query-list fallback on a genuine 404; auth errors (401, 429, …) surface immediately without a doomed second request.

    Copilot's findings are all confirmed resolved: the ForgeNotReady doc comment is updated, the "isn't supported yet" copy in PullRequestsPanel was replaced with the connect-account prompt, prompt.ts now replaces "issue or PR numbers" correctly, sync.ts's sameSha docstring correctly documents the ≥7-char guard as prefix-path-only, and the TLS warning appears when skipCertVerification is toggled on.


    The corrected PR is in good shape. All four previous findings are cleanly addressed and the Copilot findings are genuinely resolved in the current diff. No new issues are visible in the changed code.

     
  • Anonymous

    Anonymous - 2026-07-05
     
  • Anonymous

    Anonymous - 2026-07-05

    Originally posted by: theBGuy

    AI review (sonnet) · automated

    Now let me look at some of the frontend pieces, particularly the new external reviews gating and the forgeJobLogs change.Let me look at the external-context changes and some more truncated files to check key concerns.This is a large, well-structured Bitbucket Cloud integration touching 60+ files (5000+ lines in the Rust provider, extensive frontend additions, docs). The implementation follows the established GitLab pattern faithfully and includes solid unit-test coverage for all pure logic. No blockers — the change is ready.

    Resolved since last review

    The following prior findings are confirmed fixed in the current diff:

    • display_name: None in account()KEY_DISPLAY_NAME is now persisted at set_account time, read in account(), and deleted in clear_account(). ✓
    • All-workspace-fail returning empty Oklist_repos now tracks workspace_count/any_ok/last_err and surfaces the error when every workspace fetch fails. ✓
    • PrTasksSection UI state leaking across PR switchesuseEffect keyed on number resets all edit/add state. ✓
    • Redundant onChange fires in ReviewersPopover / AssigneesPopover — both now compare draft vs. value set before calling onChange. ✓
    • DebugJobDialog calling forgeCiJobLogs for Bitbucket jobs — now routes through forgeJobLogs, which dispatches on job.logRef. ✓
    • External reviews query running before forge status resolvesuseExternalReviews now gates on forge.isSuccess && provider != null. ✓
    • Provider not threaded to buildReviewPrompt in automationsrunner.ts resolves the provider once and threads it to both resolveExternalContext and the buildReviewPrompt call. ✓
    • GitLab is_bot: true bypass allowing human inline comments to pose as AI findingsisReviewerFinding now branches on provider === "gitlab" and requires the REVIEWER_BOTS allowlist for every kind (not just comment), and the interactive review path (reviews.ts:357) now threads context.provider. ✓
    • tasksKey / bbVariablesKey local duplicates — both promoted to exports from queries.ts; component-local copies removed. ✓

    Performance

    should-fixrun_failed_logs re-resolves credentials and workspace/slug for every failed step.

    bitbucket.rs, run_failed_logs (~line 1449):

    let log = match step_logs(repo_path, &log_ref).await {
    

    step_logs starts with http::load_credentials().await? and workspace_slug(repo_path).await? — the latter spawns a git remote get-url origin subprocess. The outer run_failed_logs already has creds, ws, and slug in scope. For a pipeline with N failed steps this is N redundant keyring reads and N redundant git subprocess invocations on top of the initial ones (on Windows, subprocess spawn cost is non-trivial).

    Suggested fix: extract a step_log_raw(creds: &BbCredentials, ws: &str, slug: &str, pipeline_uuid: &str, step_uuid: &str) private helper that contains the inner HTTP logic; have the public step_logs command resolve credentials/workspace and delegate to it; call that helper directly from the loop in run_failed_logs.

    Correctness

    nitviewer_did_author: false creates a one-way comment surface for Bitbucket.

    bitbucket.rs, from_bb_comment (~line 991):

    viewer_did_author: false,
    

    Since comment_pr is implemented, users can post comments but the UI will never show Edit/Delete on their own comments (those controls gate on viewer_did_author). The comment author UUID is available via c.user.as_ref().and_then(|u| u.uuid.as_deref()) and could be compared against the viewer UUID resolved by a GET /2.0/user call (the same call pr_approvals already does). This is likely a deliberate v1 limitation (Bitbucket comment-delete may not yet be wired at the forge_pr_delete_comment dispatch level, so showing the button would just produce an error), but it's worth documenting explicitly in a // TODO or the help guide if it's intentional.

    Minor

    nitencode_query_value is named for query values but used for path segments too.

    forge/mod.rs and forge/bitbucket.rs (e.g. repo_base):

    encode_query_value(&ws),
    encode_query_value(&slug),
    

    These are path segments, not query values; the function happens to over-encode safely (RFC-3986 unreserved encoding is stricter than path-segment encoding), but a name like encode_uri_component would better signal dual use. Low priority, but the mismatch could confuse future readers.

     
  • Anonymous

    Anonymous - 2026-07-05

    Ticket changed by: theBGuy

    • status: open --> closed
     

Log in to post a comment.