Files
openchamber/packages/ui/src/components/views/git/baseBranch.ts
T

80 lines
2.5 KiB
TypeScript

/**
* Derives the base ("target") branch a feature branch should compare and
* merge against. Shared by GitView and the standalone pull-request surface so
* both resolve the same base for the same repository state.
*/
export const deriveBaseBranch = (options: {
remoteNames: ReadonlySet<string>;
localBranches: readonly string[];
worktreeCreatedFromBranch?: string | null;
rootBranchHint?: string | null;
}): string => {
const { remoteNames, localBranches, worktreeCreatedFromBranch, rootBranchHint } = options;
const normalizeBaseCandidate = (value: string): string => {
if (!value) {
return '';
}
let normalized = value.trim();
if (!normalized || normalized === 'HEAD') {
return '';
}
if (localBranches.includes(normalized)) {
return normalized;
}
if (normalized.startsWith('refs/heads/')) {
normalized = normalized.slice('refs/heads/'.length);
}
if (normalized.startsWith('heads/')) {
normalized = normalized.slice('heads/'.length);
}
if (normalized.startsWith('remotes/')) {
normalized = normalized.slice('remotes/'.length);
}
const slashIndex = normalized.indexOf('/');
if (slashIndex > 0) {
const maybeRemote = normalized.slice(0, slashIndex);
if (remoteNames.has(maybeRemote)) {
const withoutRemote = normalized.slice(slashIndex + 1).trim();
if (withoutRemote) {
normalized = withoutRemote;
}
}
}
return normalized;
};
const fromMeta = normalizeBaseCandidate(
typeof worktreeCreatedFromBranch === 'string' ? worktreeCreatedFromBranch : ''
);
if (fromMeta) return fromMeta;
const fromHint = normalizeBaseCandidate(typeof rootBranchHint === 'string' ? rootBranchHint : '');
if (fromHint) return fromHint;
if (localBranches.includes('main')) return 'main';
if (localBranches.includes('master')) return 'master';
if (localBranches.includes('develop')) return 'develop';
return 'main';
};
/**
* Whether a base branch can be resolved locally or through one of the active
* remote-tracking refs. Callers must not offer comparisons against the `main`
* fallback when that ref does not actually exist in the repository.
*/
export const hasResolvableBaseBranch = (options: {
baseBranch: string;
localBranches: readonly string[];
remoteBranches: readonly string[];
}): boolean => {
const { baseBranch, localBranches, remoteBranches } = options;
return localBranches.includes(baseBranch)
|| remoteBranches.some((branch) => branch.endsWith(`/${baseBranch}`));
};