mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 08:03:33 +00:00
* Fix Create PR preparation with unavailable lookup * test(source-control): align dirty+unavailable intent expectation Create PR preparation is allowed when review lookup is unavailable; only final create stays fail-closed. Update the local-blocker snapshot test to match. * Keep Create PR intent running when hosted review lookup fails A failed or timed-out hosted-review eligibility lookup no longer aborts Create PR intent mid-run. Local prep (stage/commit/push) continues, the branch-ahead refresh is deferred until after eligibility resolves, and the final create preflight still fails closed to prevent duplicate reviews. Also gate generated PR title/body on eligibility and thread the provider through the intent run token so an unavailable lookup falls back to the inferred remote host. * fix(source-control): align dirty+unavailable intent expectation Local preparation (stage/commit changes) is safe without review-lookup authority; remote actions stay blocked. Prevents dirty trees from dead-ending at sync-first when lookup is unavailable. * fix(source-control): distinguish loading state from unavailable lookup Require head branch presence in shouldAttemptCreateHostedReviewForIntent to separate real unavailable-lookup results from loading placeholders, which share the same outcome/reason pair but lack a branch name. * test(activity): drive portal readiness latch release with explicit rAF Wall-clock setTimeout waits for requestAnimationFrame were flaky under CI load (shard 15/16), leaving status stuck at loading instead of ready. * Distinguish expected absence from git errors in remote removal Why: swallowing all errors silently masks genuine git failures. Check presence explicitly instead, so setup/teardown can still skip when origin is absent while letting real errors surface.
104 lines
3.4 KiB
TypeScript
104 lines
3.4 KiB
TypeScript
import { isBehindOnlyUpstream, shouldForcePushWithLeaseForUpstream } from './git-upstream-status'
|
|
import type { HostedReviewCreationEligibility } from './hosted-review'
|
|
import { supportsHostedReviewCreation } from './hosted-review-creation-providers'
|
|
import type { GitUpstreamStatus } from './git-status-types'
|
|
import type { SourceControlPrimaryActionDecision } from './source-control-primary-action-decision-types'
|
|
|
|
export type CreateReviewIntentKind =
|
|
| 'dirty'
|
|
| 'message_required'
|
|
| 'no_upstream'
|
|
| 'needs_push'
|
|
| 'needs_sync'
|
|
| 'force_push'
|
|
|
|
export type CreateReviewIntentEligibility = {
|
|
eligible: boolean
|
|
kind: CreateReviewIntentKind | null
|
|
}
|
|
|
|
export function resolveCreateReviewIntentEligibility({
|
|
stagedCount,
|
|
hasStageableChanges,
|
|
hasMessage,
|
|
hasUnresolvedConflicts,
|
|
upstreamStatus,
|
|
hostedReviewCreation,
|
|
branchCommitsAhead,
|
|
hasCurrentBranch = true
|
|
}: {
|
|
stagedCount: number
|
|
hasStageableChanges: boolean
|
|
hasMessage: boolean
|
|
hasUnresolvedConflicts: boolean
|
|
upstreamStatus: GitUpstreamStatus | undefined
|
|
hostedReviewCreation?: HostedReviewCreationEligibility | null
|
|
branchCommitsAhead?: number
|
|
hasCurrentBranch?: boolean
|
|
}): CreateReviewIntentEligibility {
|
|
if (
|
|
hasUnresolvedConflicts ||
|
|
!hasCurrentBranch ||
|
|
!hostedReviewCreation ||
|
|
hostedReviewCreation.canCreate ||
|
|
!supportsHostedReviewCreation(hostedReviewCreation.provider)
|
|
) {
|
|
return { eligible: false, kind: null }
|
|
}
|
|
|
|
if (
|
|
hostedReviewCreation.reviewLookupOutcome === 'unavailable' &&
|
|
!hostedReviewCreation.defaultBaseRef?.trim()
|
|
) {
|
|
return { eligible: false, kind: null }
|
|
}
|
|
|
|
// Why: safe branch preparation can continue without lookup authority; the
|
|
// main create preflight still fails closed before creating a duplicate.
|
|
if (hostedReviewCreation.blockedReason === 'dirty') {
|
|
if (stagedCount > 0 && !hasMessage) {
|
|
return { eligible: true, kind: 'message_required' }
|
|
}
|
|
return { eligible: stagedCount > 0 || hasStageableChanges, kind: 'dirty' }
|
|
}
|
|
|
|
if (hostedReviewCreation.blockedReason === 'no_upstream') {
|
|
const hasPublishableCommits = branchCommitsAhead === undefined ? false : branchCommitsAhead > 0
|
|
return {
|
|
eligible: hasPublishableCommits || stagedCount > 0 || hasStageableChanges,
|
|
kind: 'no_upstream'
|
|
}
|
|
}
|
|
|
|
if (hostedReviewCreation.blockedReason === 'needs_push') {
|
|
return { eligible: true, kind: 'needs_push' }
|
|
}
|
|
|
|
if (
|
|
hostedReviewCreation.blockedReason === 'needs_sync' &&
|
|
shouldForcePushWithLeaseForUpstream(upstreamStatus)
|
|
) {
|
|
return { eligible: true, kind: 'force_push' }
|
|
}
|
|
|
|
// Why: a behind-only branch is safe to prepare with `git pull --ff-only`, so
|
|
// the intent flow can handle it in one click. A genuinely diverged branch
|
|
// stays ineligible — auto-merging would reconcile without consent, so the
|
|
// user keeps the explicit sync-first step.
|
|
if (hostedReviewCreation.blockedReason === 'needs_sync' && isBehindOnlyUpstream(upstreamStatus)) {
|
|
return { eligible: true, kind: 'needs_sync' }
|
|
}
|
|
|
|
return { eligible: false, kind: null }
|
|
}
|
|
|
|
export function resolveVisibleCreateReviewHeaderAction({
|
|
createPrHeaderAction
|
|
}: {
|
|
createPrHeaderAction: SourceControlPrimaryActionDecision | null
|
|
}): SourceControlPrimaryActionDecision | null {
|
|
// Why: keep a stable header anchor; disable Create Review when the branch is
|
|
// not ready instead of hiding it and shifting toolbar layout.
|
|
return createPrHeaderAction
|
|
}
|