Skip to content

feat: Add Bitbucket Cloud plugin support#129

Merged
ewega merged 7 commits intomainfrom
copilot/add-bitbucket-cloud-support
Mar 6, 2026
Merged

feat: Add Bitbucket Cloud plugin support#129
ewega merged 7 commits intomainfrom
copilot/add-bitbucket-cloud-support

Conversation

Copy link
Contributor

Copilot AI commented Mar 5, 2026

  • Understand codebase structure and patterns
  • Add BitbucketRepoScope type to internal/devlake/types.go
  • Add bitbucket ConnectionDef to connectionRegistry in cmd/connection_types.go
  • Implement scopeBitbucketHandler (and helpers) in cmd/configure_scopes.go
  • Add r.BitbucketID == slug to exact-match condition in searchBitbucketReposBySlugs
  • Add TestConnectionRegistry_Bitbucket test in cmd/connection_types_test.go
  • Wrap resolveBitbucketRepos error with context: fmt.Errorf("resolving Bitbucket repositories: %w", err)
  • Add repos and repos-file ScopeFlags verification to TestConnectionRegistry_Bitbucket
  • Add Bitbucket Cloud row to README Supported Plugins table
  • Add pagination to browseBitbucketReposInteractively (workspace list + repo list) using NextPageToken loop, matching Jira handler pattern
  • go build ./..., go vet ./..., go test ./... all pass

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Bitbucket Cloud plugin support to CLI feat: Add Bitbucket Cloud plugin support Mar 5, 2026
Copilot finished work on behalf of ewega March 5, 2026 15:24
@ewega ewega marked this pull request as ready for review March 5, 2026 15:31
Copilot AI review requested due to automatic review settings March 5, 2026 15:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Bitbucket Cloud as a fully supported plugin in the gh-devlake CLI, following the same patterns established for GitLab, Jenkins, and Jira. It enables connection creation with BasicAuth (username + app password), workspace→repository interactive scope browsing, flag-driven scope addition, and blueprint integration.

Changes:

  • Added BitbucketRepoScope struct to internal/devlake/types.go for the DevLake Bitbucket plugin scope payload
  • Added bitbucket entry to connectionRegistry in cmd/connection_types.go with BasicAuth, env var resolution, and scope flags
  • Added scopeBitbucketHandler and supporting functions (resolveBitbucketRepos, searchBitbucketReposBySlugs, browseBitbucketReposInteractively, parseBitbucketRepo, putBitbucketScopes) in cmd/configure_scopes.go

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
internal/devlake/types.go Adds BitbucketRepoScope type with bitbucketId, connectionId, name, fullName, and optional URL fields
cmd/connection_types.go Adds the Bitbucket ConnectionDef registry entry with BasicAuth, credential env vars, endpoint, and scope flags
cmd/configure_scopes.go Implements the full Bitbucket scope handler: flag-driven slug lookup, interactive workspace→repo picker, parseBitbucketRepo helper, and putBitbucketScopes batch upsert

if r == nil {
continue
}
if r.FullName == slug || r.Name == slug {
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The searchBitbucketReposBySlugs exact-match check compares r.FullName == slug || r.Name == slug (line 879), but never checks r.BitbucketID == slug. Users pass slugs in workspace/repo-slug format, which is the canonical bitbucketId. When the DevLake Bitbucket plugin returns a scope where bitbucketId is already set to workspace/repo-slug but fullName is populated with a display name (e.g., the repo's human-readable title rather than its slug path), the lookup will silently fall back to the first result instead of finding the exact match. The match condition should include r.BitbucketID == slug so that the canonical scope identifier is always tried as an exact-match criterion.

Suggested change
if r.FullName == slug || r.Name == slug {
if r.BitbucketID == slug || r.FullName == slug || r.Name == slug {

Copilot uses AI. Check for mistakes.
Comment on lines +276 to +299
{
Plugin: "bitbucket",
DisplayName: "Bitbucket Cloud",
Available: true,
Endpoint: "https://api.bitbucket.org/2.0/",
SupportsTest: true,
AuthMethod: "BasicAuth",
NeedsUsername: true,
UsernamePrompt: "Bitbucket username",
UsernameEnvVars: []string{"BITBUCKET_USER", "BITBUCKET_USERNAME"},
UsernameEnvFileKeys: []string{"BITBUCKET_USER", "BITBUCKET_USERNAME"},
TokenPrompt: "Bitbucket app password",
EnvVarNames: []string{"BITBUCKET_TOKEN", "BITBUCKET_APP_PASSWORD"},
EnvFileKeys: []string{"BITBUCKET_TOKEN", "BITBUCKET_APP_PASSWORD"},
RequiredScopes: []string{},
ScopeHint: "",
ScopeFunc: scopeBitbucketHandler,
ScopeIDField: "bitbucketId",
HasRepoScopes: true,
ScopeFlags: []FlagDef{
{Name: "repos", Description: "Comma-separated Bitbucket repos (workspace/repo-slug)"},
{Name: "repos-file", Description: "Path to file with repo slugs (one per line)"},
},
},
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no test for the Bitbucket plugin registry entry in connection_types_test.go. Every other plugin that has been added — GitHub (TestGitLabRegistryEntry), Jenkins (TestConnectionRegistry_Jenkins), Jira (TestJiraConnectionDef) — has a corresponding registry-entry test that verifies Plugin, AuthMethod, NeedsUsername, ScopeIDField, HasRepoScopes, ScopeFunc, EnvVarNames, EnvFileKeys, etc. A TestConnectionRegistry_Bitbucket test covering these fields should be added to match the project's testing conventions.

Copilot generated this review using guidance from repository custom instructions.
@ewega
Copy link
Contributor

ewega commented Mar 5, 2026

@copilot Please fix:

  1. In searchBitbucketReposBySlugs, add r.BitbucketID == slug to the exact-match condition so the canonical scope ID is always tried.
  2. Add a TestConnectionRegistry_Bitbucket test in cmd/connection_types_test.go verifying registry fields (Plugin, AuthMethod, NeedsUsername, ScopeIDField, HasRepoScopes, ScopeFunc, EnvVarNames). Follow the existing test patterns.

…cket

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 5, 2026

@copilot Please fix:

  1. In searchBitbucketReposBySlugs, add r.BitbucketID == slug to the exact-match condition so the canonical scope ID is always tried.
  2. Add a `TestConnectionRegistry_Bitbucket...

Both fixes applied in 36523ce:

  1. searchBitbucketReposBySlugs now checks r.BitbucketID == slug || r.FullName == slug || r.Name == slug so the canonical bitbucketId (workspace/repo-slug) is always tried first.
  2. Added TestConnectionRegistry_Bitbucket to cmd/connection_types_test.go covering Plugin, AuthMethod, NeedsUsername, ScopeIDField, HasRepoScopes, ScopeFunc, EnvVarNames, UsernameEnvVars, and the BasicAuth credential mapping in BuildCreateRequest.

Copilot finished work on behalf of ewega March 5, 2026 15:43
@ewega ewega requested a review from Copilot March 5, 2026 17:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment on lines +859 to +865
// searchBitbucketReposBySlugs looks up Bitbucket repositories by their full name
// (workspace/repo-slug) using the DevLake search-remote-scopes API.
func searchBitbucketReposBySlugs(client *devlake.Client, connID int, slugs []string) ([]*devlake.BitbucketRepoScope, error) {
var repos []*devlake.BitbucketRepoScope
for _, slug := range slugs {
fmt.Printf("\n🔍 Searching for Bitbucket repository %q...\n", slug)
resp, err := client.SearchRemoteScopes("bitbucket", connID, slug, 1, 20)
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin name "bitbucket" is hardcoded in API calls (and also appears elsewhere in this new handler, e.g. ListRemoteScopes, PutScopes, and BlueprintConnection.PluginName). This duplicates a key routing string in multiple places and makes future refactors/error prevention harder. Consider defining a single local constant (e.g. const plugin = "bitbucket") and using it throughout these helper functions (including the returned BlueprintConnection) so the plugin identifier is sourced from one place.

Suggested change
// searchBitbucketReposBySlugs looks up Bitbucket repositories by their full name
// (workspace/repo-slug) using the DevLake search-remote-scopes API.
func searchBitbucketReposBySlugs(client *devlake.Client, connID int, slugs []string) ([]*devlake.BitbucketRepoScope, error) {
var repos []*devlake.BitbucketRepoScope
for _, slug := range slugs {
fmt.Printf("\n🔍 Searching for Bitbucket repository %q...\n", slug)
resp, err := client.SearchRemoteScopes("bitbucket", connID, slug, 1, 20)
const bitbucketPlugin = "bitbucket"
// searchBitbucketReposBySlugs looks up Bitbucket repositories by their full name
// (workspace/repo-slug) using the DevLake search-remote-scopes API.
func searchBitbucketReposBySlugs(client *devlake.Client, connID int, slugs []string) ([]*devlake.BitbucketRepoScope, error) {
var repos []*devlake.BitbucketRepoScope
for _, slug := range slugs {
fmt.Printf("\n🔍 Searching for Bitbucket repository %q...\n", slug)
resp, err := client.SearchRemoteScopes(bitbucketPlugin, connID, slug, 1, 20)

Copilot uses AI. Check for mistakes.
Comment on lines +879 to +886
if r.BitbucketID == slug || r.FullName == slug || r.Name == slug {
found = r
break
}
if found == nil {
found = r // use first match if no exact match
}
}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Falling back to “first match if no exact match” can silently select the wrong repository when the search query is ambiguous (e.g., a plain repo slug that exists in multiple workspaces). A safer approach is to (a) require workspace/repo-slug for non-interactive selection, or (b) if only a slug is provided and a workspace is known, normalize it into workspace/slug, and (c) if multiple matches remain, return an error listing candidates instead of picking the first.

Copilot uses AI. Check for mistakes.
func scopeBitbucketHandler(client *devlake.Client, connID int, org, enterprise string, opts *ScopeOpts) (*devlake.BlueprintConnection, error) {
repos, err := resolveBitbucketRepos(client, connID, org, opts)
if err != nil {
return nil, err
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error returned from resolveBitbucketRepos is propagated without additional context at this top-level handler boundary. Wrapping it here (e.g., "resolving Bitbucket repositories: %w") would make failures easier to diagnose when surfaced from higher-level calls.

Suggested change
return nil, err
return nil, fmt.Errorf("resolving Bitbucket repositories: %w", err)

Copilot uses AI. Check for mistakes.
@ewega
Copy link
Contributor

ewega commented Mar 6, 2026

@copilot Please fix: Wrap the error from resolveBitbucketRepos with context at the handler boundary: return nil, fmt.Errorf("resolving Bitbucket repositories: %w", err)

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 6, 2026

@copilot Please fix: Wrap the error from resolveBitbucketRepos with context at the handler boundary: return nil, fmt.Errorf("resolving Bitbucket repositories: %w", err)

Fixed in acdd607 — the error from resolveBitbucketRepos is now wrapped with context: return nil, fmt.Errorf("resolving Bitbucket repositories: %w", err).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment on lines +559 to +618
// TestConnectionRegistry_Bitbucket verifies the Bitbucket Cloud plugin registry entry.
func TestConnectionRegistry_Bitbucket(t *testing.T) {
def := FindConnectionDef("bitbucket")
if def == nil {
t.Fatal("bitbucket connection def not found")
}
if !def.Available {
t.Errorf("bitbucket should be available")
}
if def.AuthMethod != "BasicAuth" {
t.Errorf("bitbucket AuthMethod = %q, want BasicAuth", def.AuthMethod)
}
if !def.NeedsUsername {
t.Errorf("bitbucket NeedsUsername should be true")
}
if def.ScopeIDField != "bitbucketId" {
t.Errorf("bitbucket ScopeIDField = %q, want %q", def.ScopeIDField, "bitbucketId")
}
if !def.HasRepoScopes {
t.Errorf("bitbucket HasRepoScopes should be true")
}
if def.ScopeFunc == nil {
t.Errorf("bitbucket ScopeFunc should be set")
}
wantEnvVars := []string{"BITBUCKET_TOKEN", "BITBUCKET_APP_PASSWORD"}
if len(def.EnvVarNames) != len(wantEnvVars) {
t.Errorf("bitbucket EnvVarNames length: got %d, want %d", len(def.EnvVarNames), len(wantEnvVars))
} else {
for i, v := range wantEnvVars {
if def.EnvVarNames[i] != v {
t.Errorf("bitbucket EnvVarNames[%d]: got %q, want %q", i, def.EnvVarNames[i], v)
}
}
}
wantUserEnvVars := []string{"BITBUCKET_USER", "BITBUCKET_USERNAME"}
if len(def.UsernameEnvVars) != len(wantUserEnvVars) {
t.Errorf("bitbucket UsernameEnvVars length: got %d, want %d", len(def.UsernameEnvVars), len(wantUserEnvVars))
} else {
for i, v := range wantUserEnvVars {
if def.UsernameEnvVars[i] != v {
t.Errorf("bitbucket UsernameEnvVars[%d]: got %q, want %q", i, def.UsernameEnvVars[i], v)
}
}
}

// BasicAuth: BuildCreateRequest puts credentials into username/password, not token
req := def.BuildCreateRequest("test-conn", ConnectionParams{
Token: "app-password",
Username: "myuser",
})
if req.Username != "myuser" {
t.Errorf("bitbucket create request Username = %q, want %q", req.Username, "myuser")
}
if req.Password != "app-password" {
t.Errorf("bitbucket create request Password = %q, want %q", req.Password, "app-password")
}
if req.Token != "" {
t.Errorf("bitbucket create request Token should be empty for BasicAuth, got %q", req.Token)
}
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TestConnectionRegistry_Bitbucket test does not verify the ScopeFlags field. The comparable TestConnectionRegistry_Jenkins test (starting at line 620 in the same file) checks that the jobs scope flag is registered. For consistency, the Bitbucket test should verify that at least repos and repos-file scope flags are present, since these are the primary mechanism for flag-mode scoping and could silently be omitted or misspelled in the registry entry.

Copilot generated this review using guidance from repository custom instructions.
@ewega
Copy link
Contributor

ewega commented Mar 6, 2026

@copilot Please fix: Add ScopeFlags verification to TestConnectionRegistry_Bitbucket — check that repos and repos-file scope flags are present, following the same pattern as TestConnectionRegistry_Jenkins which verifies the jobs flag.

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 6, 2026

@copilot Please fix: Add ScopeFlags verification to TestConnectionRegistry_Bitbucket — check that repos and repos-file scope flags are present, following the same pattern as `TestConnectionReg...

Added in fa10e5aTestConnectionRegistry_Bitbucket now checks that both repos and repos-file are present in ScopeFlags, following the same pattern as the Jenkins test.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment on lines +276 to +299
{
Plugin: "bitbucket",
DisplayName: "Bitbucket Cloud",
Available: true,
Endpoint: "https://api.bitbucket.org/2.0/",
SupportsTest: true,
AuthMethod: "BasicAuth",
NeedsUsername: true,
UsernamePrompt: "Bitbucket username",
UsernameEnvVars: []string{"BITBUCKET_USER", "BITBUCKET_USERNAME"},
UsernameEnvFileKeys: []string{"BITBUCKET_USER", "BITBUCKET_USERNAME"},
TokenPrompt: "Bitbucket app password",
EnvVarNames: []string{"BITBUCKET_TOKEN", "BITBUCKET_APP_PASSWORD"},
EnvFileKeys: []string{"BITBUCKET_TOKEN", "BITBUCKET_APP_PASSWORD"},
RequiredScopes: []string{},
ScopeHint: "",
ScopeFunc: scopeBitbucketHandler,
ScopeIDField: "bitbucketId",
HasRepoScopes: true,
ScopeFlags: []FlagDef{
{Name: "repos", Description: "Comma-separated Bitbucket repos (workspace/repo-slug)"},
{Name: "repos-file", Description: "Path to file with repo slugs (one per line)"},
},
},
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Supported Plugins" table in README.md (around line 191-198) lists GitHub, GitHub Copilot, Jenkins, Jira, Azure DevOps, and GitLab — but does not include Bitbucket Cloud, which is newly added as an available plugin in this PR. The table should include a row for Bitbucket Cloud showing its status (✅ Available), what it collects, and the required credentials (Bitbucket username + app password).

Copilot uses AI. Check for mistakes.
@ewega
Copy link
Contributor

ewega commented Mar 6, 2026

@copilot Please fix: Add a Bitbucket Cloud row to the README Supported Plugins table. It should show status as available, note that it collects repository/PR data, and requires Bitbucket username + app password.

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 6, 2026

@copilot Please fix: Add a Bitbucket Cloud row to the README Supported Plugins table. It should show status as available, note that it collects repository/PR data, and requires Bitbucket username + ap...

Added in 1b776eb — Bitbucket Cloud now appears in the Supported Plugins table as ✅ Available, collecting repos/PRs/commits, with Bitbucket username + app password as the required credentials.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@ewega
Copy link
Contributor

ewega commented Mar 6, 2026

@copilot Please fix: Add pagination to browseBitbucketReposInteractively. Currently it calls client.ListRemoteScopes once without looping on NextPageToken. For workspaces with many repos, results will be silently truncated. Add a loop like the SonarQube and Azure DevOps handlers do:
go allChildren := remoteScopes.Children nextToken := remoteScopes.NextPageToken for nextToken != "" { page, err := client.ListRemoteScopes("bitbucket", connID, workspaceID, nextToken) if err != nil { break } allChildren = append(allChildren, page.Children...) nextToken = page.NextPageToken }

…stings

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 6, 2026

@copilot Please fix: Add pagination to browseBitbucketReposInteractively. Currently it calls client.ListRemoteScopes once without looping on NextPageToken. For workspaces with many repos, result...

Fixed in 46a395bbrowseBitbucketReposInteractively now paginates both the workspace listing and the repository listing using a NextPageToken loop, matching the same pattern as the Jira handler.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

cmd/configure_scopes.go:914

  • The pagination loops in browseBitbucketReposInteractively silently swallow errors by using break when ListRemoteScopes fails during paginated fetches. This means the user may be shown only a partial list of workspaces or repositories without any indication that data is missing. The established codebase pattern (used in scopeJiraHandler, lines 726–731) is to propagate the pagination error with a formatted message (e.g., return nil, fmt.Errorf("failed to list Bitbucket workspaces (page token %s): %w", nextToken, err)). The same applies to the repository pagination loop at lines 953–960.

This divergence from the Jira handler pattern means a transient or persistent API failure during pagination would silently truncate the list, potentially causing the user to inadvertently skip repositories or workspaces they wanted to select.

		for nextToken != "" {
			page, err := client.ListRemoteScopes("bitbucket", connID, "", nextToken)
			if err != nil {
				break
			}

Comment on lines +953 to +957
for nextToken != "" {
page, err := client.ListRemoteScopes("bitbucket", connID, workspaceID, nextToken)
if err != nil {
break
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same silent-break pagination issue applies to the repository listing loop: when ListRemoteScopes fails while fetching subsequent pages of repositories in a workspace, the error is silently discarded and the user sees only a truncated list. Following the scopeJiraHandler pattern (lines 726–731), this should return an error rather than break silently.

This issue also appears on line 910 of the same file.

Copilot uses AI. Check for mistakes.
Comment on lines +837 to +856
func resolveBitbucketRepos(client *devlake.Client, connID int, workspace string, opts *ScopeOpts) ([]*devlake.BitbucketRepoScope, error) {
fmt.Println("\n📦 Resolving Bitbucket repositories...")
if opts != nil && opts.Repos != "" {
var slugs []string
for _, s := range strings.Split(opts.Repos, ",") {
if s = strings.TrimSpace(s); s != "" {
slugs = append(slugs, s)
}
}
return searchBitbucketReposBySlugs(client, connID, slugs)
}
if opts != nil && opts.ReposFile != "" {
slugs, err := repofile.Parse(opts.ReposFile)
if err != nil {
return nil, fmt.Errorf("failed to read repos file: %w", err)
}
fmt.Printf(" Loaded %d repository slug(s) from file\n", len(slugs))
return searchBitbucketReposBySlugs(client, connID, slugs)
}
return browseBitbucketReposInteractively(client, connID, workspace)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resolveBitbucketRepos function's flag-based and file-based paths (lines 839–854) are not covered by any tests, even though the analogous GitHub (TestResolveRepos_WithReposFlag, TestResolveRepos_WithReposFile) and Jenkins (TestResolveJenkinsJobs_WithJobsFlag) resolver functions are thoroughly tested in configure_scopes_test.go. Adding tests for the Repos comma-separated flag path and the ReposFile path would be consistent with the established codebase convention.

Copilot uses AI. Check for mistakes.
Comment on lines +199 to 201
| Bitbucket Cloud | ✅ Available | Repos, PRs, commits | Bitbucket username + app password |

See [Token Handling](docs/token-handling.md) for env key names and multi-plugin `.devlake.env` examples.
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README at line 201 directs users to docs/token-handling.md for env key names and multi-plugin .devlake.env examples, but that file does not include Bitbucket Cloud env key information (BITBUCKET_TOKEN, BITBUCKET_APP_PASSWORD, BITBUCKET_USER, BITBUCKET_USERNAME). The token-handling doc still labels GitLab as "coming soon" and has no entry for Bitbucket or Jenkins, even though all three are now available. Users following the README's pointer to that doc will not find the Bitbucket env key names they need.

Suggested change
| Bitbucket Cloud | ✅ Available | Repos, PRs, commits | Bitbucket username + app password |
See [Token Handling](docs/token-handling.md) for env key names and multi-plugin `.devlake.env` examples.
| Bitbucket Cloud | ✅ Available | Repos, PRs, commits | Bitbucket username (`BITBUCKET_USERNAME`/`BITBUCKET_USER`) + app password (`BITBUCKET_APP_PASSWORD`/`BITBUCKET_TOKEN`) |
See [Token Handling](docs/token-handling.md) for common token patterns, env key names for most plugins, and multi-plugin `.devlake.env` examples. Bitbucket Cloud and Jenkins env variable names are documented in the table above.

Copilot uses AI. Check for mistakes.
@ewega ewega merged commit 63147b7 into main Mar 6, 2026
11 checks passed
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.

3 participants