Skip to main content
Glama

Atlassian Bitbucket MCP Server

by aashari
atlassian.diff.controller.ts9.85 kB
import { Logger } from '../utils/logger.util.js'; import { handleControllerError } from '../utils/error-handler.util.js'; import { DEFAULT_PAGE_SIZE, applyDefaults } from '../utils/defaults.util.js'; import { ControllerResponse } from '../types/common.types.js'; import { extractPaginationInfo, PaginationType, } from '../utils/pagination.util.js'; import { formatPagination } from '../utils/formatter.util.js'; import * as diffService from '../services/vendor.atlassian.repositories.diff.service.js'; import { formatDiffstat, formatFullDiff } from './atlassian.diff.formatter.js'; import { getDefaultWorkspace } from '../utils/workspace.util.js'; const controllerLogger = Logger.forContext( 'controllers/atlassian.diff.controller.ts', ); controllerLogger.debug('Bitbucket diff controller initialized'); /** * Base interface that extends Record<string, unknown> for error handling compatibility */ interface BaseDiffOptions extends Record<string, unknown> { workspaceSlug?: string; repoSlug: string; includeFullDiff?: boolean; limit?: number; cursor?: number; topic?: boolean; } /** * Interface for branch diff options */ interface BranchDiffOptions extends BaseDiffOptions { sourceBranch: string; destinationBranch?: string; } /** * Interface for commit diff options */ interface CommitDiffOptions extends BaseDiffOptions { sinceCommit: string; untilCommit: string; } /** * Compare two branches and return the differences * * @param options - Options for branch comparison * @returns Promise with formatted diff content and pagination */ async function branchDiff( options: BranchDiffOptions, ): Promise<ControllerResponse> { const methodLogger = controllerLogger.forMethod('branchDiff'); try { methodLogger.debug('Comparing branches', options); // Apply defaults const defaults = { limit: DEFAULT_PAGE_SIZE, includeFullDiff: true, destinationBranch: 'main', // Default to main if not provided topic: false, // Default to topic=false which shows all changes between branches }; // Explicitly cast the result of applyDefaults to preserve the original types const params = applyDefaults(options, defaults) as typeof options & typeof defaults; // Handle optional workspaceSlug if (!params.workspaceSlug) { methodLogger.debug( 'No workspace provided, fetching default workspace', ); const defaultWorkspace = await getDefaultWorkspace(); if (!defaultWorkspace) { throw new Error( 'Could not determine a default workspace. Please provide a workspaceSlug.', ); } params.workspaceSlug = defaultWorkspace; methodLogger.debug( `Using default workspace: ${params.workspaceSlug}`, ); } // Construct the spec (e.g., "main..feature") // NOTE: Bitbucket API expects the destination branch first, then the source branch // This is the opposite of what some Git tools use (e.g., git diff source..destination) // The diff shows changes that would need to be applied to destination to match source // // IMPORTANT: This behavior is counterintuitive in two ways: // 1. The parameter names "sourceBranch" and "destinationBranch" suggest a certain direction, // but the output is displayed as "destinationBranch → sourceBranch" // 2. When comparing branches with newer content in the feature branch (source), full diffs // might only show when using parameters in one order, and only summaries in the other order // // We document this behavior clearly in the CLI and Tool interfaces const spec = `${params.destinationBranch}..${params.sourceBranch}`; methodLogger.debug(`Using diff spec: ${spec}`); try { // Fetch diffstat for the branches const diffstat = await diffService.getDiffstat({ workspace: params.workspaceSlug, repo_slug: params.repoSlug, spec, pagelen: params.limit, cursor: params.cursor, topic: params.topic, }); // Extract pagination info const pagination = extractPaginationInfo( diffstat, PaginationType.PAGE, ); // Fetch full diff if requested let rawDiff: string | null = null; if (params.includeFullDiff) { rawDiff = await diffService.getRawDiff({ workspace: params.workspaceSlug, repo_slug: params.repoSlug, spec, }); } // Format the results let content = params.includeFullDiff && rawDiff ? formatFullDiff( diffstat, rawDiff, params.destinationBranch, params.sourceBranch, ) : formatDiffstat( diffstat, params.destinationBranch, params.sourceBranch, ); // Add pagination information if available if ( pagination && (pagination.hasMore || pagination.count !== undefined) ) { const paginationString = formatPagination(pagination); content += '\n\n' + paginationString; } return { content, }; } catch (error) { // Enhance error handling for common diff-specific errors if ( error instanceof Error && error.message.includes( 'source or destination could not be found', ) ) { // Create a more user-friendly error message throw new Error( `Unable to generate diff between '${params.sourceBranch}' and '${params.destinationBranch}'. ` + `One or both of these branches may not exist in the repository. ` + `Please verify both branch names and ensure you have access to view them.`, ); } // Re-throw other errors to be handled by the outer catch block throw error; } } catch (error) { throw handleControllerError(error, { entityType: 'Branch Diff', operation: 'comparing branches', source: 'controllers/atlassian.diff.controller.ts@branchDiff', additionalInfo: options, }); } } /** * Compare two commits and return the differences * * @param options - Options for commit comparison * @returns Promise with formatted diff content and pagination */ async function commitDiff( options: CommitDiffOptions, ): Promise<ControllerResponse> { const methodLogger = controllerLogger.forMethod('commitDiff'); try { methodLogger.debug('Comparing commits', options); // Apply defaults const defaults = { limit: DEFAULT_PAGE_SIZE, includeFullDiff: true, topic: false, // Default to topic=false which shows all changes between commits }; // Explicitly cast the result of applyDefaults to preserve the original types const params = applyDefaults(options, defaults) as typeof options & typeof defaults; // Handle optional workspaceSlug if (!params.workspaceSlug) { methodLogger.debug( 'No workspace provided, fetching default workspace', ); const defaultWorkspace = await getDefaultWorkspace(); if (!defaultWorkspace) { throw new Error( 'Could not determine a default workspace. Please provide a workspaceSlug.', ); } params.workspaceSlug = defaultWorkspace; methodLogger.debug( `Using default workspace: ${params.workspaceSlug}`, ); } // Construct the spec (e.g., "a1b2c3d..e4f5g6h") // NOTE: Bitbucket API expects the base/since commit first, then the target/until commit // The diff shows changes that would need to be applied to base to match target // // IMPORTANT: The parameter names are counterintuitive to how they must be used: // 1. For proper results with full code changes, sinceCommit should be the NEWER commit, // and untilCommit should be the OLDER commit (reverse chronological order) // 2. If used with chronological order (older → newer), the result may show "No changes detected" // // We document this behavior clearly in the CLI and Tool interfaces const spec = `${params.sinceCommit}..${params.untilCommit}`; methodLogger.debug(`Using diff spec: ${spec}`); try { // Fetch diffstat for the commits const diffstat = await diffService.getDiffstat({ workspace: params.workspaceSlug, repo_slug: params.repoSlug, spec, pagelen: params.limit, cursor: params.cursor, }); // Extract pagination info const pagination = extractPaginationInfo( diffstat, PaginationType.PAGE, ); // Fetch full diff if requested let rawDiff: string | null = null; if (params.includeFullDiff) { rawDiff = await diffService.getRawDiff({ workspace: params.workspaceSlug, repo_slug: params.repoSlug, spec, }); } // Format the results let content = params.includeFullDiff && rawDiff ? formatFullDiff( diffstat, rawDiff, params.sinceCommit, params.untilCommit, ) : formatDiffstat( diffstat, params.sinceCommit, params.untilCommit, ); // Add pagination information if available if ( pagination && (pagination.hasMore || pagination.count !== undefined) ) { const paginationString = formatPagination(pagination); content += '\n\n' + paginationString; } return { content, }; } catch (error) { // Enhance error handling for common diff-specific errors if ( error instanceof Error && error.message.includes( 'source or destination could not be found', ) ) { // Create a more user-friendly error message throw new Error( `Unable to generate diff between commits '${params.sinceCommit}' and '${params.untilCommit}'. ` + `One or both of these commits may not exist in the repository or may be in the wrong order. ` + `Please verify both commit hashes and ensure you have access to view them.`, ); } // Re-throw other errors to be handled by the outer catch block throw error; } } catch (error) { throw handleControllerError(error, { entityType: 'Commit Diff', operation: 'comparing commits', source: 'controllers/atlassian.diff.controller.ts@commitDiff', additionalInfo: options, }); } } export default { branchDiff, commitDiff };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aashari/mcp-server-atlassian-bitbucket'

If you have feedback or need assistance with the MCP directory API, please join our Discord server