Documentation
¶
Overview ¶
Package bitbucket provides a forge.Provider implementation for Bitbucket Cloud using the Downloads API. Bitbucket has no native "Releases" concept; version information is inferred from asset filenames using a configurable regular expression. Provider construction uses package-owned Settings; GTB config integration lives in SettingsFromConfig.
Alongside the release contract it implements the optional gitlab.com/phpboyscout/go/forge.KeyManager capability (SSH-key upload via the account SSH-keys API). It deliberately implements no gitlab.com/phpboyscout/go/forge.Authenticator: Bitbucket authenticates with a username and app password, so there is no interactive login to offer and setup falls back to manual credential entry.
The connection ladder stops one rung short here ¶
Spec 0008's ladder offers a native-client rung (D5) — a constructor taking the platform SDK's own client — for the adapters that have one. This module has no platform SDK: it speaks the Bitbucket API over plain net/http, so its native unit IS the net/http.Client, and a rung taking one would duplicate gitlab.com/phpboyscout/go/forge.WithHTTPClient rather than add anything.
The absence is therefore deliberate, and the ladder here starts at the transport and client rungs, which this module does honour. See connection.go.
Index ¶
- type BitbucketReleaseProvider
- func (p *BitbucketReleaseProvider) Close(ctx context.Context, owner, repo string, number int) error
- func (p *BitbucketReleaseProvider) Create(ctx context.Context, owner, repo string, draft forge.PullRequestDraft) (forge.PullRequest, error)
- func (p *BitbucketReleaseProvider) DownloadChecksumManifest(ctx context.Context, rel forge.Release, maxBytes int64) ([]byte, error)
- func (p *BitbucketReleaseProvider) DownloadReleaseAsset(ctx context.Context, _, _ string, asset forge.ReleaseAsset) (io.ReadCloser, string, error)
- func (p *BitbucketReleaseProvider) DownloadSignature(ctx context.Context, rel forge.Release, maxBytes int64) ([]byte, error)
- func (p *BitbucketReleaseProvider) Find(ctx context.Context, owner, repo, sourceBranch string) (forge.PullRequest, error)
- func (p *BitbucketReleaseProvider) FindLastMerged(ctx context.Context, owner, repo, sourceBranch string) (forge.PullRequest, error)
- func (p *BitbucketReleaseProvider) GetFile(ctx context.Context, owner, repo, path, ref string, maxBytes int64) ([]byte, error)
- func (p *BitbucketReleaseProvider) GetLatestRelease(ctx context.Context, owner, repo string) (forge.Release, error)
- func (p *BitbucketReleaseProvider) GetReleaseByTag(_ context.Context, _, _, _ string) (forge.Release, error)
- func (p *BitbucketReleaseProvider) ListReleases(_ context.Context, _, _ string, _ int) ([]forge.Release, error)
- func (p *BitbucketReleaseProvider) ListRepositories(ctx context.Context, namespace string, opts forge.RepositoryListOptions, ...) error
- func (p *BitbucketReleaseProvider) ResolveMergedCommit(ctx context.Context, owner, repo string, number int) (string, error)
- func (p *BitbucketReleaseProvider) SetAPIBase(base string)
- func (p *BitbucketReleaseProvider) Update(ctx context.Context, owner, repo string, number int, title, body string) error
- func (p *BitbucketReleaseProvider) UploadKey(ctx context.Context, name string, publicKey []byte) error
- type Settings
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BitbucketReleaseProvider ¶
type BitbucketReleaseProvider struct {
// contains filtered or unexported fields
}
BitbucketReleaseProvider implements forge.Provider for Bitbucket Cloud.
func NewReleaseProvider ¶
func NewReleaseProvider(ctx context.Context, settings Settings) (*BitbucketReleaseProvider, error)
NewReleaseProvider constructs a BitbucketReleaseProvider from explicit typed settings.
Both credential halves are resolved here, bounded by ctx: a source the caller supplied may reach a keychain or a remote secret store. An error means every source for that field was broken rather than merely ABSENT — absence is not a failure, and construction succeeds without a credential.
It has to: one connection serves public and private repositories alike, so whether a credential is needed is knowable at the operation and not before. A refusal with none resolved carries guidance instead; see refuse.
func (*BitbucketReleaseProvider) Close ¶ added in v0.10.0
func (p *BitbucketReleaseProvider) Close( ctx context.Context, owner, repo string, number int, ) error
Close declines a pull request without merging it.
Bitbucket spells this as its own endpoint rather than a state change, which is why there is no PUT of state="DECLINED" here.
func (*BitbucketReleaseProvider) Create ¶ added in v0.10.0
func (p *BitbucketReleaseProvider) Create( ctx context.Context, owner, repo string, draft forge.PullRequestDraft, ) (forge.PullRequest, error)
Create opens a pull request.
func (*BitbucketReleaseProvider) DownloadChecksumManifest ¶
func (p *BitbucketReleaseProvider) DownloadChecksumManifest(ctx context.Context, rel forge.Release, maxBytes int64) ([]byte, error)
DownloadChecksumManifest implements forge.ChecksumProvider by locating an uploaded `checksums.txt` by exact filename in the repository's downloads list. The filename regex used by [matchAssets] is intentionally tight around the binary pattern, so the manifest is not picked up there; this method bypasses the regex for the well-known manifest name.
Returns forge.ErrNotSupported when the downloads list contains no `checksums.txt`, so the caller treats it the same as "provider has no manifest support" and respects require_checksum policy.
func (*BitbucketReleaseProvider) DownloadReleaseAsset ¶
func (p *BitbucketReleaseProvider) DownloadReleaseAsset(ctx context.Context, _, _ string, asset forge.ReleaseAsset) (io.ReadCloser, string, error)
DownloadReleaseAsset streams the asset at its BrowserDownloadURL.
func (*BitbucketReleaseProvider) DownloadSignature ¶
func (p *BitbucketReleaseProvider) DownloadSignature(ctx context.Context, rel forge.Release, maxBytes int64) ([]byte, error)
DownloadSignature implements forge.SignatureProvider by locating an uploaded `checksums.txt.sig` by exact filename in the downloads list. Returns forge.ErrNotSupported when no signature file was uploaded, so the caller respects the require_signature policy.
func (*BitbucketReleaseProvider) Find ¶ added in v0.10.0
func (p *BitbucketReleaseProvider) Find( ctx context.Context, owner, repo, sourceBranch string, ) (forge.PullRequest, error)
Find returns the OPEN pull request opened from sourceBranch.
func (*BitbucketReleaseProvider) FindLastMerged ¶ added in v0.10.0
func (p *BitbucketReleaseProvider) FindLastMerged( ctx context.Context, owner, repo, sourceBranch string, ) (forge.PullRequest, error)
FindLastMerged returns the most recently merged pull request from sourceBranch, ordered by merge time.
Bitbucket records no merge time ¶
The pull request carries created_on and updated_on and nothing that names when it merged. So updated_on is what orders here, and that is a WEAKER guarantee than the other two adapters give: a comment posted after a merge moves it.
It is still the best available, and it is right far more often than it is wrong — a merged pull request is usually not touched again. The contract's MergedAt is populated from it so a caller can see what it was ordered by rather than being told a merge time that does not exist.
func (*BitbucketReleaseProvider) GetFile ¶ added in v0.3.0
func (p *BitbucketReleaseProvider) GetFile( ctx context.Context, owner, repo, path, ref string, maxBytes int64, ) ([]byte, error)
GetFile reads one file at a ref without cloning.
The bound is enforced twice, and both are load-bearing. The size is checked from format=meta BEFORE the body is requested, so an oversized file costs a metadata request rather than a download; and the body itself is read through an io.LimitReader, so a server that under-reports its size still cannot stream past the bound. Bitbucket is the only adapter that gets both, because it is the only one not going through an SDK that buffers on its behalf.
func (*BitbucketReleaseProvider) GetLatestRelease ¶
func (p *BitbucketReleaseProvider) GetLatestRelease(ctx context.Context, owner, repo string) (forge.Release, error)
GetLatestRelease returns a synthetic release built from the most recently uploaded Downloads that match the filename pattern.
func (*BitbucketReleaseProvider) GetReleaseByTag ¶
func (p *BitbucketReleaseProvider) GetReleaseByTag(_ context.Context, _, _, _ string) (forge.Release, error)
GetReleaseByTag is not supported for Bitbucket Downloads.
func (*BitbucketReleaseProvider) ListReleases ¶
func (p *BitbucketReleaseProvider) ListReleases(_ context.Context, _, _ string, _ int) ([]forge.Release, error)
ListReleases is not supported for Bitbucket Downloads.
func (*BitbucketReleaseProvider) ListRepositories ¶ added in v0.3.0
func (p *BitbucketReleaseProvider) ListRepositories( ctx context.Context, namespace string, opts forge.RepositoryListOptions, yield func(forge.Repository) bool, ) error
ListRepositories enumerates the repositories in a Bitbucket workspace.
Bitbucket needs no organisation/user resolution: one endpoint serves a workspace whether it belongs to a team or a person, so the namespace is used directly. That is a genuine simplification rather than an omission.
A Bitbucket "project" — a grouping of repositories inside a workspace — is NOT an enumerable namespace here. A repository's canonical path is workspace/repo, so a project-scoped namespace could never satisfy the contract's containment guarantee, and synthesising workspace/project/repo to make it look like it did would be a lie.
func (*BitbucketReleaseProvider) ResolveMergedCommit ¶ added in v0.10.0
func (p *BitbucketReleaseProvider) ResolveMergedCommit( ctx context.Context, owner, repo string, number int, ) (string, error)
ResolveMergedCommit returns the commit ON THE TARGET BRANCH that this pull request produced.
Candidates cheapest first: the recorded merge commit, then a reverse-lookup walk of the target branch. EVERY candidate goes through confirmOnBranch before it is returned.
The fallback may be unavailable here, and that is not a bug ¶
Bitbucket's reverse lookup depends on an app a HUMAN installed — see commitClaimedBy. Where the recorded merge commit does not confirm and that endpoint is absent, this adapter has no second route and returns ErrNotFound. That is the contract working: a refusal rather than a guess is exactly what this method promises when it cannot establish an answer. Bitbucket simply reaches that refusal more often than the others.
func (*BitbucketReleaseProvider) SetAPIBase ¶
func (p *BitbucketReleaseProvider) SetAPIBase(base string)
SetAPIBase overrides the Bitbucket API base URL. Intended for testing only.
func (*BitbucketReleaseProvider) Update ¶ added in v0.10.0
func (p *BitbucketReleaseProvider) Update( ctx context.Context, owner, repo string, number int, title, body string, ) error
Update replaces the title and body.
Both are sent unconditionally, as the contract requires: a forge distinguishes "field omitted, leave it" from "field sent empty, clear it", and sending both every time is what makes the caller's intent unambiguous.
func (*BitbucketReleaseProvider) UploadKey ¶ added in v0.2.0
func (p *BitbucketReleaseProvider) UploadKey(ctx context.Context, name string, publicKey []byte) error
UploadKey implements the optional forge.KeyManager capability: it registers an OpenSSH-format public key on the authenticated Bitbucket account via the account SSH-keys API (`POST /2.0/users/{username}/ssh-keys`). The configured username and app password authorise the call; name becomes the key's label in the account's key list.
Bitbucket has no unauthenticated key API and no interactive login, so an unconfigured provider returns an error wrapping forge.ErrNotSupported — the caller treats it exactly like "provider does not implement this capability" and falls back to manual app-password-authenticated entry.
type Settings ¶
type Settings struct {
// Endpoint addresses this instance. Type is [forge.SourceTypeBitbucket] and
// also selects the configuration subtree read by [SettingsFromConfig]; Name
// selects which configured source this is.
//
// Host is unused: the API base is hard-pinned to api.bitbucket.org, because
// Bitbucket Cloud is the only instance this provider speaks to.
Endpoint forge.Endpoint
// UsernameSource and AppPasswordSource yield the two halves of a Bitbucket
// credential. Nil means absent, which is only an error for a private
// repository. To supply them directly:
//
// UsernameSource: forge.StaticCredential(user),
// AppPasswordSource: forge.StaticCredential(appPassword),
UsernameSource forge.CredentialSource
AppPasswordSource forge.CredentialSource
FilenamePattern string
// Logger receives this provider's diagnostics. Nil discards them, and is
// never [slog.Default]. See [forge.WithLogger] for the registry route.
Logger *slog.Logger
// ConfigWarning describes a configuration problem detected while building
// these settings. It is NOT an error: the settings are usable and the
// provider will build. The constructor logs it at WARN.
//
// [SettingsFromConfig] sets it when the configuration it was handed looks
// like a pre-scoped subtree rather than the root — a mistake that resolves
// no credential and reports nothing, so a diagnostic is the only way a
// caller learns of it. See [forge.PreScopedConfig] and spec 0011.
ConfigWarning error
// HTTPTransport is the transport this provider builds its clients on, so
// several providers share one connection pool and TLS session cache. Nil
// means it builds its own, which is the default and always valid.
//
// This provider still builds the client, and so keeps its own redirect
// policy. It is the rung to prefer. Set through the registry with
// [forge.WithHTTPTransport].
HTTPTransport http.RoundTripper
// HTTPClient replaces the client this provider would have built for its own
// API requests — redirect policy included, and the obligation with it.
//
// It is NOT used for asset downloads; see connection.go for why. Set
// through the registry with [forge.WithHTTPClient].
HTTPClient *http.Client
}
Settings contains the typed configuration needed to construct a Bitbucket release provider without binding the provider to any config container.
Bitbucket needs TWO credentials rather than one, so it holds two sources. They are separate because partial configuration is legitimate and occasionally useful during rotation — a username from the environment while the app password still comes from the keychain.
func SettingsFromConfig ¶
SettingsFromConfig adapts the bitbucket config subtree into typed provider settings.
The keychain blob is read ONCE here, not once per field: it holds both halves of the credential in a single entry, so two reads would mean two unlock prompts for one secret. The decoded fields are then closed over by the two composed sources.
cfg is the ROOT configuration, not a pre-scoped subtree: the endpoint resolves its own section, because which subtree a source reads is part of what the endpoint means.