Skip to content

forge: Add Bitbucket Data Center support to the bitbucket forge#1230

Merged
abhinav merged 23 commits into
abhinav:mainfrom
kanchev1:feat/bitbucket-server-forge
Jul 8, 2026
Merged

forge: Add Bitbucket Data Center support to the bitbucket forge#1230
abhinav merged 23 commits into
abhinav:mainfrom
kanchev1:feat/bitbucket-server-forge

Conversation

@kanchev1

@kanchev1 kanchev1 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Resolves #1229. Reworked per the design feedback: one bitbucket forge, two API clients — and now a single repository implementation over a gateway interface, replacing the previous revision's two parallel implementations.

What's added

  • The bitbucket forge now serves both Bitbucket Cloud (REST 2.0) and Bitbucket Data Center / Server (REST 1.0), selecting the client per repository when opening it. No separate forge is registered.
  • Product inference: no URL or bitbucket.org selects Cloud; any custom URL selects Data Center. spice.forge.bitbucket.kind (cloud/datacenter, alias server) overrides the inference, e.g. to stay on the Cloud API behind a custom URL.
  • With spice.forge.kind=bitbucket, the instance URL is derived from the Git remote URL (context paths included), so a single configuration key suffices for self-hosted setups; spice.forge.bitbucket.url always wins when set. Implemented as a small optional forge.RemoteURLConfigurer interface used by the remote resolver and gs auth login — separable if you'd rather require the explicit URL.
  • Both products now fill new pull request descriptions from PULL_REQUEST_TEMPLATE-style files on the repository's default branch.

Design notes

  • The gateway seam is laid out as ports and adapters under internal/gateway/bitbucket: the package itself defines the product-neutral Gateway contract (interface + request/response types), and one adapter package per product — cloud/ (REST 2.0) and server/ (REST 1.0) — implements it, each containing its thin REST client alongside the translator. Supporting a future API version means adding an adapter package; the forge is closed against new products.
  • internal/forge/bitbucket contains one forge.Repository implementation, written against the Gateway contract. The two adapters hide every product difference: pagination models, reviewer identity (account UUIDs vs usernames), Data Center's optimistic-locking versions with one refetch-and-retry on conflict, comment reads via the activity feed, merge vetoes, and draft-support probing. All shared workflow and policy — warnings, checks aggregation, comment counting and filtering, limits, template lookup, metadata — lives once in the repository implementation.
  • Optional capabilities use an embedded UnsupportedGateway base supplying "unsupported" defaults (the gRPC UnimplementedXServer / Gitea NullDownloader pattern); the shared repository warns and continues. Today that covers toggling draft status after creation, which Data Center cannot do.
  • Two deliberate micro-unifications from merging the implementations, both practically unreachable edge cases: a failed CI check now wins over a pending one on Cloud (previously the first in-progress status short-circuited the scan), and deleting a tracked comment whose persisted metadata lacks the PR ID now errors on both products (previously Data Center silently no-op'd against PR #0).

Compatibility

  • Cloud's forge ID, configuration keys, environment variables, stored credentials, and persisted PR metadata are unchanged. The metadata JSON shape is byte-pinned in tests, and the recorded Cloud integration fixtures replay unchanged.
  • Behavior change for a released option: a custom spice.forge.bitbucket.url now selects the Data Center API (previously it was parsed as Cloud). Noted in a Changed changelog entry; spice.forge.bitbucket.kind=cloud restores the old behavior.
  • The previous revision's bitbucket-server forge ID and BITBUCKET_SERVER_* variables never shipped and are gone.

Testing

  • A shared repository policy suite runs against a mocked gateway: warnings, checks aggregation, comment counting/filtering, guards, limit defaults, template lookup, metadata construction.
  • Per-adapter tests, living in each adapter package, cover the wire mechanics: optimistic-lock conflict retries, activity-feed version recovery, reviewer UUID resolution, default-reviewer merging, draft probing, veto formatting, and both pagination models.
  • A cross-product conformance suite next to the contract asserts both adapters produce identical neutral results for equivalent scenarios (states, finds, comments, templates, checks), and pins the documented capability gaps.
  • The recorded Cloud integration fixtures replay unchanged, with kind inference, URL derivation (HTTPS, context paths, SSH), and resolver/login wiring covered by unit tests for both products. Data Center behavior is covered by unit tests only; merge and CI-check behavior are not exercised against a live instance.

@kanchev1 kanchev1 force-pushed the feat/bitbucket-server-forge branch 7 times, most recently from 119a73d to 20e2220 Compare June 10, 2026 19:13
@kanchev1 kanchev1 changed the title feat(forge): Add self-hosted Bitbucket Data Center forge forge: Add Bitbucket Data Center support Jun 10, 2026
@abhinav

abhinav commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Thanks @kanchev1, this makes sense to support but I'm reluctant to register a separate forge for this.
Unless I'm misinterpreting how this fits, I think what we'd want is two separate gateway implementations (just clients for the 1.0 and 2.0 REST APIs 1) with similar APIs and unifying them in the bitbucket forge implementation, e.g.

We could then do: default is: bitbucket.org with API 2.0, but you can provide a custom URL and we'll switch to API 1.0 (with a configuration option to stay on 2.0 even with a custom URL).

I haven't thought the design through fully, but that's my initial gut reaction. There's a backlog of LLM-assisted PRs so I'm going to come back to this as time permits.

Footnotes

  1. They're really clients. I should've called them clients. I'll fix that later maybe.

@kanchev1 kanchev1 force-pushed the feat/bitbucket-server-forge branch from 20e2220 to 1ef15dd Compare June 11, 2026 11:56
@kanchev1 kanchev1 changed the title forge: Add Bitbucket Data Center support forge: Add Bitbucket Data Center support to the bitbucket forge Jun 11, 2026
@kanchev1

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback, it pointed me in the right direction. I've reworked the PR along those lines. There's no longer a separate forge. It's now just the single bitbucket forge backed by two thin REST clients, one for Cloud (2.0) and one for Data Center (1.0), and it picks the right one per repository when the repo is opened.

The configuration is fairly flexible while still doing the sensible thing by default. With no URL set, or bitbucket.org, it uses Cloud, and any custom URL switches to Data Center. You can override that inference with spice.forge.bitbucket.kind (cloud or datacenter) if you ever need to. When you set spice.forge.kind=bitbucket, it derives the instance URL from the Git remote, including context paths and SSH remotes, so a single config key is enough for self-hosted setups. An explicit spice.forge.bitbucket.url still takes precedence when it's set.

I've been running this live against our Data Center instance for day-to-day work, so I can confirm the submit, edit, merge, and comment paths and the URL auto-detection all work well.

I'm also using it together with #1237 (merge-based restack, not merged yet). The two of them together have made git-spice a really useful and helpful tool for me, which is why I wanted to share both upstream in case they're useful to others as well.

Thanks for the great tool. Let me know if you have any remarks on the implementation and I'll adjust or fix anything.

@kanchev1 kanchev1 marked this pull request as draft June 11, 2026 20:43
@kanchev1 kanchev1 force-pushed the feat/bitbucket-server-forge branch 3 times, most recently from 550c414 to 3b8309a Compare June 12, 2026 22:10
Support self-hosted Atlassian Bitbucket Data Center / Server
instances. Unlike Bitbucket Cloud (REST 2.0), Data Center speaks the
REST 1.0 API, so the bitbucket forge now selects one of two API
gateways per repository, instead of registering a separate forge.

The product is inferred from the instance URL: no URL or
bitbucket.org selects Cloud, and any other URL selects Data Center.
spice.forge.bitbucket.kind (cloud or datacenter) overrides the
inference, e.g. to keep the Cloud API behind a custom URL. With
spice.forge.kind=bitbucket, the instance URL itself is derived from
the Git remote URL (including context paths for HTTP(S) remotes), so
a single configuration key suffices for self-hosted instances.
Authentication uses an HTTP access token sent as a Bearer token,
entered via `gs auth login --forge bitbucket` or provided in
BITBUCKET_TOKEN; tokens are stored per instance URL.

A single forge.Repository implementation serves both products
through a gateway contract (ports and adapters): the
product-neutral gateway interface and its DTOs live in their own
package, and one adapter package per product implements the
contract over its wire API, hiding the differences: pagination
models, reviewer identity (account UUIDs vs usernames), Data
Center's optimistic-locking versions with one refetch-and-retry on
conflict, comment reads from the pull request activity feed (Data
Center has no comment-listing endpoint), merge vetoes, and
draft-support probing. Pull request updates on Data Center carry
the current title, description, and reviewers because the update
endpoint replaces those fields wholesale. Optional capabilities
default to a no-op "unsupported" implementation that the forge
reports and ignores, e.g. toggling draft status after creation.
Adding support for another API version means adding an adapter
package; the forge itself is closed against new products. Both
products fill new pull request descriptions from pull request
template files on the repository's default branch.

Bitbucket Cloud behavior, configuration keys, stored credentials,
and persisted PR metadata are unchanged. Platform limitations match
the documented Bitbucket Cloud ones where applicable: no PR labels,
no assignees, draft state cannot be toggled after creation (and
needs Data Center 8.18+), and no cross-repository (fork) pull
requests.

  - internal/gateway/bitbucket:        gateway contract shared by both products
  - internal/gateway/bitbucket/cloud:  Bitbucket Cloud adapter (REST 2.0)
  - internal/gateway/bitbucket/server: Bitbucket Data Center adapter (REST 1.0)
  - internal/forge/bitbucket:          one forge over the gateway contract

Implements abhinav#1229

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kanchev1 kanchev1 force-pushed the feat/bitbucket-server-forge branch from 3b8309a to 9090881 Compare June 13, 2026 08:12
@kanchev1 kanchev1 marked this pull request as ready for review June 13, 2026 11:32
@abhinav

abhinav commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Thanks, @kanchev1, apologies for the delay. I'll follow up on this as soon as I'm able.

abhinav and others added 6 commits July 3, 2026 11:35
Merge main into feat/bitbucket-server-forge after resolving the
Bitbucket gateway conflicts. The resolution keeps a single Bitbucket
forge backed by neutral Cloud and Data Center gateways.

This keeps main's named change checks and mergeability model while
preserving the shared Bitbucket forge boundary.
Bitbucket Data Center repository IDs carry the instance URL derived from the remote. Open the server gateway from that repository URL so API and browser targets stay tied to the parsed repository identity.

Keep spice.forge.bitbucket.api-url as an explicit override for callers that need a separate REST root.
Bitbucket Data Center personal repositories use a tilde-prefixed
project key in project-centric REST paths. Keep browser URLs on
the bare user slug, but add the tilde when building REST requests
so personal repositories do not call /projects/{user}/repos.

The regression opens a personal repository through the forge and
exercises template lookup, protecting the parsed remote-to-gateway
path. Import giturl in the forge package so that contract builds.
@abhinav abhinav self-assigned this Jul 5, 2026
Bitbucket Cloud and Data Center expose mergeability through different REST shapes. Model mergeability as a gateway operation so the forge asks for that decision directly instead of reading it from pull request metadata.

Data Center now uses the documented merge probe to report ready, blocked, and conflict states. Veto text remains display-only, so blocked non-conflict responses keep an unknown reason.
abhinav added 13 commits July 5, 2026 16:06
Register forge definitions instead of using registry-held forge instances as command execution state. Definitions own the option storage populated by CLI and environment parsing, and command paths construct fresh Forge values before auth, remote parsing, or metadata handling.

Implicit remote matching now goes through InferFromRemoteURL. Explicit selection uses Registry.New with optional remote context, so auth can construct a selected forge without parsing a repository path.
Merge the neutral forge construction refactor into the Bitbucket feature branch. The integration keeps Bitbucket Data Center support on the feature branch while replacing remote URL mutation with definition-based forge construction.

Bitbucket remote-derived instance URLs are now applied to fresh forge instances from New, leaving the registry definition options unchanged.
Bring the merged forge definition construction work into the Bitbucket Server branch.

Keep the generic registry, auth, remote, and existing forge definitions from main. Adapt the Bitbucket forge so Definition owns CLI options and constructs a resolved Forge from the remote URL while preserving Data Center URL derivation and gateway selection.
Resolve Bitbucket Cloud versus Data Center once in Definition.New.
The constructed Forge now stores the resolved web URL, API URL,
product kind, and product behavior object instead of recalculating
those values from Options on each operation.

Use one concrete RepositoryID for both Bitbucket products.
The selected product parses repository paths into that shared identity
and opens repositories through the matching gateway implementation.

Move ChangeTemplatePaths next to ListChangeTemplates so template path
knowledge stays with template lookup.
Clean up the Bitbucket construction refactor so it passes the repository lint rules.
Use the staticcheck-preferred switch form, keep the construction error string
lowercase, and remove the duplicate REST project-key helper that became unused
after RepositoryID was unified.
Bitbucket forge construction now resolves the selected product during
Definition.New and stores only the fields the Forge needs at runtime.
The Cloud and Data Center product objects own their gateway inputs,
so repository opening no longer sends the whole Forge back through the
product abstraction.

The Forge keeps the resolved base URL, API URL, kind, token, logger,
and product instead of retaining Options after construction.
Repository path parsing for Data Center lives with the Data Center
product because the path form is product-specific.

giturl.URL now exposes the normalized parsed scheme so Bitbucket URL
derivation can preserve HTTP-style clone ports and derive git+http and
git+https remotes without re-reading the raw remote string.
Bitbucket gateway conformance tests now declare the product names and wire
state spellings before the scenarios that use them.
Per-scenario pull request IDs, comment IDs, commit hashes, and template paths
live in the tests that own those values, so the shared harness only keeps the
product and repository coordinates it actually needs.

Bitbucket Data Center now implements SetChangeDraft directly by returning
bitbucket.ErrUnsupported.
The shared UnsupportedGateway type is removed so optional capability refusals
stay on the product gateway that owns them.

The Bitbucket integration template skip now documents the current blocker:
the harness can exercise fixed template files, but the Bitbucket template
fixtures have not been recorded.
Bitbucket forge construction now resolves the prefixed logger locally when it
builds the selected product object.
The Forge logger accessor keeps the same nil-safe behavior inline,
matching the shape used by the neighboring forge implementations.

This removes the extra bitbucketLogger helper without changing the logger
contract for repositories or gateways.
The Bitbucket forge now imports the Bitbucket gateway package with its
natural package name instead of the gw alias.

This keeps the import consistent with the Go guide: the current file package
name does not require an alias for another imported package selector.
Bitbucket gateway conformance tests now keep single-use fake-server setup
inside the scenario that depends on it.
The suite still shares gateway construction and pull-request wire rendering,
because those boundaries hide repeated product setup rather than simple path
or assertion plumbing.

This removes the path-builder helpers, the one-off template and comment
stubs, and the helper that stripped product-specific fields from pull request
assertions.
Bitbucket conformance pull-request fixtures now render product wire payloads
through methods on conformancePR.
Both renderers return JSON-shaped maps, so the Cloud and Data Center fake
responses have the same test-local shape.

The conformance gateway constructor now builds the selected product gateway
itself instead of delegating to product-specific helpers.
The FindChangesByBranch assertion keeps its changeSummary type local to the
scenario that uses it.
Bitbucket Cloud and Data Center clients now keep HTTP transport
separate from JSON decoding. The private do method applies shared
request headers and returns an open response body to callers that own
the lifecycle.

doJSON handles JSON response checking and streaming decode. getRaw
remains the only success path that reads a full response body because
raw template and file endpoints return bytes instead of JSON.

Bitbucket Cloud pagination still flows through Response.NextURL by
copying the decoded list response Next field after JSON decode.
@abhinav abhinav enabled auto-merge (squash) July 8, 2026 11:57
@abhinav abhinav merged commit eb81cf2 into abhinav:main Jul 8, 2026
22 checks passed
@abhinav

abhinav commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Thanks! Will try to make time for #1237 soon as well.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat Request: Support self-hosted Bitbucket Data Center / Server

2 participants