import type { GitPullRequest, GitRepository, GitStatusContext, } from 'azure-devops-node-api/interfaces/GitInterfaces.js'; import { GitPullRequestMergeStrategy, PullRequestStatus, } from 'azure-devops-node-api/interfaces/GitInterfaces.js'; import type { MergeStrategy } from '../../../config/types.ts'; import { logger } from '../../../logger/index.ts'; import type { GitOptions } from '../../../types/git.ts'; import type { HostRule, PrState } from '../../../types/index.ts'; import { isProbablyJwt } from '../../../util/http/jwt.ts'; import { addSecretForSanitizing } from '../../../util/sanitize.ts'; import { toBase64 } from '../../../util/string.ts'; import { getPrBodyStruct } from '../pr-body.ts'; import type { AzurePr } from './types.ts'; export function getGitStatusContextCombinedName( context: GitStatusContext | null | undefined, ): string | undefined { if (!context) { return undefined; } const combinedName = `${context.genre ? `${context.genre}/` : ''}${ // TODO: types (#22198) context.name! }`; logger.trace(`Got combined context name of ${combinedName}`); return combinedName; } export function getGitStatusContextFromCombinedName( context: string | undefined | null, ): GitStatusContext | undefined { if (!context) { return undefined; } let name = context; let genre: string | undefined; const lastSlash = context.lastIndexOf('/'); if (lastSlash > 0) { name = context.substring(lastSlash + 1); genre = context.substring(0, lastSlash); } return { genre, name, }; } export function getBranchNameWithoutRefsheadsPrefix( branchPath: string | undefined, ): string | undefined { if (!branchPath) { logger.error(`getBranchNameWithoutRefsheadsPrefix(undefined)`); return undefined; } if (!branchPath.startsWith('refs/heads/')) { logger.trace( `The refs/heads/ name should have started with 'refs/heads/' but it didn't. (${branchPath})`, ); return branchPath; } return branchPath.substring(11, branchPath.length); } export function getBranchNameWithoutRefsPrefix( branchPath?: string, ): string | undefined { if (!branchPath) { logger.error(`getBranchNameWithoutRefsPrefix(undefined)`); return undefined; } if (!branchPath.startsWith('refs/')) { logger.trace( `The ref name should have started with 'refs/' but it didn't. (${branchPath})`, ); return branchPath; } return branchPath.substring(5, branchPath.length); } const stateMap = { [PullRequestStatus.Abandoned]: 'closed', [PullRequestStatus.Completed]: 'merged', } as Record; export function getRenovatePRFormat(azurePr: GitPullRequest): AzurePr { const number = azurePr.pullRequestId; const sourceBranch = getBranchNameWithoutRefsheadsPrefix( azurePr.sourceRefName, ); const targetBranch = getBranchNameWithoutRefsheadsPrefix( azurePr.targetRefName, ); const bodyStruct = getPrBodyStruct(azurePr.description); const createdAt = azurePr.creationDate?.toISOString(); // TODO #22198 const state = stateMap[azurePr.status!] ?? 'open'; const sourceRefName = azurePr.sourceRefName; return { ...azurePr, sourceBranch, state, number, bodyStruct, sourceRefName, targetBranch, createdAt, } as AzurePr; } export function getStorageExtraCloneOpts(config: HostRule): GitOptions { let authType: string; let authValue: string; if (!config.token && config.username && config.password) { authType = 'basic'; authValue = toBase64(`${config.username}:${config.password}`); } else if (config.token && isProbablyJwt(config.token)) { authType = 'bearer'; authValue = config.token; } else { authType = 'basic'; authValue = toBase64(`:${config.token}`); } addSecretForSanitizing(authValue, 'global'); return { '-c': `http.extraHeader=AUTHORIZATION: ${authType} ${authValue}`, }; } export function max4000Chars(str: string): string { if (str && str.length >= 4000) { return str.substring(0, 3999); } return str; } export function getProjectAndRepo(str: string): { project: string; repo: string; } { logger.trace(`getProjectAndRepo(${str})`); const strSplit = str.split(`/`); if (strSplit.length === 1) { return { project: str, repo: str, }; } if (strSplit.length === 2) { return { project: strSplit[0], repo: strSplit[1], }; } const msg = `Azure repository can be only structured this way : 'repository' or 'projectName/repository'!`; logger.warn({ repository: str }, msg); throw new Error(msg); } export function getRepoByName( name: string, repos: (GitRepository | null | undefined)[] | undefined | null, ): GitRepository | null { logger.trace(`getRepoByName(${name})`); let { project, repo } = getProjectAndRepo(name); project = project.toLowerCase(); repo = repo.toLowerCase(); const foundRepo = repos?.find( (r) => project === r?.project?.name?.toLowerCase() && repo === r?.name?.toLowerCase(), ); if (!foundRepo) { logger.debug(`Repo not found: ${name}`); } return foundRepo ?? null; } export function mapMergeStrategy( mergeStrategy?: MergeStrategy, ): GitPullRequestMergeStrategy { switch (mergeStrategy) { case 'rebase': case 'fast-forward': return GitPullRequestMergeStrategy.Rebase; case 'merge-commit': return GitPullRequestMergeStrategy.NoFastForward; case 'rebase-merge': return GitPullRequestMergeStrategy.RebaseMerge; case 'squash': return GitPullRequestMergeStrategy.Squash; default: return GitPullRequestMergeStrategy.NoFastForward; } } export function getWorkItemTitle(rawTitle: string, repository: string): string { const repoName = repository.split('/').pop(); const isDependencyDashboard = rawTitle.includes('Dependency Dashboard'); if (isDependencyDashboard && !rawTitle.includes(`[${repoName}]`)) { return `[${repoName}] ${rawTitle}`; } return rawTitle; }