Files
orca/src/shared/source-control-base-ref-resolution.ts
T
Kelvin Amoaba 9d2e441947 fix(mobile): compare branches and show diffs with desktop's base-ref and whitespace settings (#34038)
* fix(mobile): compare branches and show diffs with desktop's base-ref and whitespace settings

Base-ref resolution moves to src/shared and the host publishes the compare-against-upstream
and show-whitespace settings, so the phone's branch compare and diffs match desktop.

* fix(mobile): use the upstream compare policy on the review screen and rebuild diffs when whitespace changes

* fix(mobile): reread Source Control settings on Refresh and restart diffs loading under the old whitespace setting

* test(mobile): cover diffs dropped when desktop's whitespace setting arrives

* refactor(mobile): share one Source Control settings read per host across screens
2026-10-27 05:29:46 +00:00

130 lines
4.1 KiB
TypeScript

import type { GitUpstreamStatus } from './git-status-types'
export function resolveSourceControlBaseRef(input: {
worktreeBaseRef?: string | null
reviewBaseRefName?: string | null
repoBaseRef?: string | null
defaultBaseRef?: string | null
}): string | null {
const worktreeBaseRef = input.worktreeBaseRef?.trim() || null
const hasReviewBaseRefName = Boolean(input.reviewBaseRefName?.trim())
const reviewBaseRef = resolveHostedReviewCompareBaseRef(input.reviewBaseRefName, [
input.repoBaseRef,
input.defaultBaseRef
])
if (worktreeBaseRef && isFullGitCommitOid(worktreeBaseRef) && hasReviewBaseRefName) {
return reviewBaseRef
}
return worktreeBaseRef || input.repoBaseRef?.trim() || input.defaultBaseRef?.trim() || null
}
// Upstream comparison can differ from the PR/rebase target; fall back to effectiveBaseRef when unavailable.
export function resolveSourceControlCompareBaseRef(input: {
enabled: boolean
worktreeBaseRef?: string | null
repoBaseRef?: string | null
upstreamName?: string | null
fallbackBaseRef?: string | null
}): string | null {
if (!input.enabled) {
return input.fallbackBaseRef?.trim() || null
}
const pinned = input.worktreeBaseRef?.trim() || input.repoBaseRef?.trim()
if (pinned) {
return pinned
}
return input.upstreamName?.trim() || input.fallbackBaseRef?.trim() || null
}
// Wait for upstream resolution before clearing a missing base to avoid flicker.
export function shouldClearBranchCompareForMissingBase(input: {
isFolder: boolean
compareBaseRef: string | null
remoteStatus: GitUpstreamStatus | undefined
}): boolean {
if (input.isFolder || input.compareBaseRef) {
return false
}
return input.remoteStatus !== undefined
}
export function resolveSourceControlPickerBaseRef(input: {
pinnedBaseRef?: string | null
effectiveBaseRef?: string | null
}): string | undefined {
const pinnedBaseRef = input.pinnedBaseRef?.trim()
if (!pinnedBaseRef) {
return undefined
}
return input.effectiveBaseRef?.trim() || pinnedBaseRef
}
function isFullGitCommitOid(value: string): boolean {
return /^[0-9a-f]{40}$/i.test(value)
}
function resolveHostedReviewCompareBaseRef(
baseRefName: string | null | undefined,
candidates: (string | null | undefined)[]
): string | null {
const branch = baseRefName?.trim()
if (!branch) {
return null
}
for (const candidate of candidates) {
const trimmed = candidate?.trim()
if (!trimmed) {
continue
}
if (getCompareBaseCandidateBranchName(trimmed) === branch) {
return trimmed
}
}
for (const candidate of candidates) {
const rewritten = rewriteCompareBaseBranchFromCandidate(candidate, branch)
if (rewritten) {
return rewritten
}
}
return null
}
function getCompareBaseCandidateBranchName(candidate: string): string {
const remoteRefPrefix = 'refs/remotes/'
if (candidate.startsWith(remoteRefPrefix)) {
const remoteAndBranch = candidate.slice(remoteRefPrefix.length)
const slashIndex = remoteAndBranch.indexOf('/')
return slashIndex > 0 ? remoteAndBranch.slice(slashIndex + 1) : remoteAndBranch
}
const headsRefPrefix = 'refs/heads/'
if (candidate.startsWith(headsRefPrefix)) {
return candidate.slice(headsRefPrefix.length)
}
const slashIndex = candidate.indexOf('/')
return slashIndex > 0 ? candidate.slice(slashIndex + 1) : candidate
}
function rewriteCompareBaseBranchFromCandidate(
candidate: string | null | undefined,
branch: string
): string | null {
const trimmed = candidate?.trim()
if (!trimmed) {
return null
}
const remoteRefPrefix = 'refs/remotes/'
if (trimmed.startsWith(remoteRefPrefix)) {
const remoteAndBranch = trimmed.slice(remoteRefPrefix.length)
const slashIndex = remoteAndBranch.indexOf('/')
return slashIndex > 0
? `${remoteRefPrefix}${remoteAndBranch.slice(0, slashIndex)}/${branch}`
: null
}
const headsRefPrefix = 'refs/heads/'
if (trimmed.startsWith(headsRefPrefix)) {
return `${headsRefPrefix}${branch}`
}
const slashIndex = trimmed.indexOf('/')
return slashIndex > 0 ? `${trimmed.slice(0, slashIndex)}/${branch}` : null
}