From e20d514569520b4e1ec495f85d130a28339bbc91 Mon Sep 17 00:00:00 2001 From: Jose Hernandez <80654874+josegrelnx@users.noreply.github.com> Date: Mon, 17 Nov 2025 19:33:39 -0300 Subject: [PATCH] feat(bitbucket): support glob patterns in repository exclusions (#620) * feat(bitbucket): support glob patterns in repository exclusions Update Bitbucket Cloud and Server exclusion logic to support glob patterns (e.g., "org/repo*") in the exclude.repos configuration, matching the documented behavior and aligning with other providers (GitHub, GitLab, Gitea, Azure DevOps). Changes: - Add micromatch import for pattern matching - Replace Array.includes() with micromatch.isMatch() in cloudShouldExcludeRepo and serverShouldExcludeRepo functions - Add reason logging for exclusion decisions to match GitHub's pattern This enables users to exclude repositories using wildcard patterns as documented in the Bitbucket Cloud connection documentation. * update changelog --------- Co-authored-by: Jose Hernandez Co-authored-by: bkellam --- CHANGELOG.md | 1 + packages/backend/src/bitbucket.ts | 30 ++++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99163978..1f0ea619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed spurious infinite loads with explore panel, file tree, and file search command. [#617](https://github.com/sourcebot-dev/sourcebot/pull/617) - Wipe search context on init if entitlement no longer exists [#618](https://github.com/sourcebot-dev/sourcebot/pull/618) +- Fixed Bitbucket repository exclusions not supporting glob patterns. [#620](https://github.com/sourcebot-dev/sourcebot/pull/620) ## [4.9.2] - 2025-11-13 diff --git a/packages/backend/src/bitbucket.ts b/packages/backend/src/bitbucket.ts index 3f0e18ff..2ba447ce 100644 --- a/packages/backend/src/bitbucket.ts +++ b/packages/backend/src/bitbucket.ts @@ -5,6 +5,7 @@ import type { ClientOptions, ClientPathsWithMethod } from "openapi-fetch"; import { createLogger } from "@sourcebot/shared"; import { measure, fetchWithRetry } from "./utils.js"; import * as Sentry from "@sentry/node"; +import micromatch from "micromatch"; import { SchemaRepository as CloudRepository, } from "@coderabbitai/bitbucket/cloud/openapi"; @@ -346,10 +347,15 @@ async function cloudGetRepos(client: BitbucketClient, repoList: string[]): Promi function cloudShouldExcludeRepo(repo: BitbucketRepository, config: BitbucketConnectionConfig): boolean { const cloudRepo = repo as CloudRepository; + let reason = ''; + const repoName = cloudRepo.full_name!; const shouldExclude = (() => { - if (config.exclude?.repos && config.exclude.repos.includes(cloudRepo.full_name!)) { - return true; + if (config.exclude?.repos) { + if (micromatch.isMatch(repoName, config.exclude.repos)) { + reason = `\`exclude.repos\` contains ${repoName}`; + return true; + } } if (!!config.exclude?.archived) { @@ -357,12 +363,15 @@ function cloudShouldExcludeRepo(repo: BitbucketRepository, config: BitbucketConn } if (!!config.exclude?.forks && cloudRepo.parent !== undefined) { + reason = `\`exclude.forks\` is true`; return true; } + + return false; })(); if (shouldExclude) { - logger.debug(`Excluding repo ${cloudRepo.full_name} because it matches the exclude pattern`); + logger.debug(`Excluding repo ${repoName}. Reason: ${reason}`); return true; } return false; @@ -548,23 +557,32 @@ function serverShouldExcludeRepo(repo: BitbucketRepository, config: BitbucketCon const projectName = serverRepo.project!.key; const repoSlug = serverRepo.slug!; + const repoName = `${projectName}/${repoSlug}`; + let reason = ''; const shouldExclude = (() => { - if (config.exclude?.repos && config.exclude.repos.includes(`${projectName}/${repoSlug}`)) { - return true; + if (config.exclude?.repos) { + if (micromatch.isMatch(repoName, config.exclude.repos)) { + reason = `\`exclude.repos\` contains ${repoName}`; + return true; + } } if (!!config.exclude?.archived && serverRepo.archived) { + reason = `\`exclude.archived\` is true`; return true; } if (!!config.exclude?.forks && serverRepo.origin !== undefined) { + reason = `\`exclude.forks\` is true`; return true; } + + return false; })(); if (shouldExclude) { - logger.debug(`Excluding repo ${projectName}/${repoSlug} because it matches the exclude pattern`); + logger.debug(`Excluding repo ${repoName}. Reason: ${reason}`); return true; } return false;